From 33d55cb0d98fc051bee6237d2056566d19d3e96a Mon Sep 17 00:00:00 2001 From: Lucas Fischer Date: Fri, 19 Jun 2026 08:14:24 +0800 Subject: [PATCH 1/4] feat(licensing): replace HardwareIdentifier with injectable DeviceIdentity --- Sources/AmoreLicensing/AmoreLicensing.swift | 50 +++++++++++++++---- .../DeviceIdentity/DeviceIdentity.swift | 16 ++++++ .../MacDeviceIdentity.swift} | 12 ++++- .../HardwareIdentifier.swift | 3 -- .../AmoreClientTests.swift | 2 +- .../DeviceIdentity/MockDeviceIdentity.swift | 6 +++ .../MockHardwareIdentifier.swift | 5 -- .../LicenseClient/MockLicenseClient.swift | 10 ++-- .../LicenseMigrationTests.swift | 2 +- .../ValidationFrequencyTests.swift | 2 +- 10 files changed, 83 insertions(+), 25 deletions(-) create mode 100644 Sources/AmoreLicensing/DeviceIdentity/DeviceIdentity.swift rename Sources/AmoreLicensing/{HardwareIdentifier/MacHardwareIdentifier.swift => DeviceIdentity/MacDeviceIdentity.swift} (68%) delete mode 100644 Sources/AmoreLicensing/HardwareIdentifier/HardwareIdentifier.swift create mode 100644 Tests/AmoreLicensingTests/DeviceIdentity/MockDeviceIdentity.swift delete mode 100644 Tests/AmoreLicensingTests/HardwareIdentifier/MockHardwareIdentifier.swift diff --git a/Sources/AmoreLicensing/AmoreLicensing.swift b/Sources/AmoreLicensing/AmoreLicensing.swift index 74b8435..9983710 100644 --- a/Sources/AmoreLicensing/AmoreLicensing.swift +++ b/Sources/AmoreLicensing/AmoreLicensing.swift @@ -1,5 +1,6 @@ import Foundation import JWTKit +import Observation /// Manages license activation, deactivation, and validation against an Amore licensing server. /// @@ -11,7 +12,7 @@ public final class AmoreLicensing: Licensing { private let bundleIdentifier: String private let configuration: LicensingConfiguration - private let hardwareIdentifier: HardwareIdentifier + private let deviceIdentity: DeviceIdentity private let jwtCollection = JWTKeyCollection() private let licenseClient: LicenseClient private let publicKey: EdDSA.PublicKey @@ -25,39 +26,70 @@ public final class AmoreLicensing: Licensing { /// - bundleIdentifier: The app's bundle identifier. Defaults to `Bundle.main.bundleIdentifier`. /// - configuration: The licensing configuration. Defaults to ``LicensingConfiguration/default``. /// - server: The license server to use. Defaults to the Amore server. + /// - deviceIdentity: How this device is identified when binding a license. On macOS, use the initializer without this parameter to default to the built-in identifier. /// - tokenStore: A custom store for persisting the license token. Defaults to a ``FileTokenStore`` in Application Support. Provide a custom ``TokenStore`` to store the token elsewhere. public init( publicKey: String, bundleIdentifier: String? = nil, configuration: LicensingConfiguration = .default, server: LicenseServer? = nil, - tokenStore: (any TokenStore)? = nil + deviceIdentity: (any DeviceIdentity), + tokenStore: (any TokenStore)? = nil, ) throws { let bundleIdentifier = bundleIdentifier ?? Bundle.main.bundleIdentifier ?? publicKey self.configuration = configuration self.publicKey = try EdDSA.PublicKey(x: publicKey, curve: .ed25519) self.bundleIdentifier = bundleIdentifier self.tokenStore = tokenStore ?? FileTokenStore(bundleIdentifier: bundleIdentifier) - self.hardwareIdentifier = MacHardwareIdentifier() + self.deviceIdentity = deviceIdentity self.licenseClient = HTTPLicenseClient(server: server ?? .amore(for: bundleIdentifier)) if configuration.validationFrequency.shouldValidateAtLaunch { Task { [self] in try? await validate() } } } +#if os(macOS) + /// Creates a new licensing instance using the built-in macOS device identity. + /// + /// This is the recommended initializer on macOS. To control how the device is + /// identified, use the initializer that takes a `deviceIdentity` instead. + /// - Parameters: + /// - publicKey: The Ed25519 public key used to verify server responses. + /// - bundleIdentifier: The app's bundle identifier. Defaults to `Bundle.main.bundleIdentifier`. + /// - configuration: The licensing configuration. Defaults to ``LicensingConfiguration/default``. + /// - server: The license server to use. Defaults to the Amore server. + /// - tokenStore: A custom store for persisting the license token. Defaults to a ``FileTokenStore`` in Application Support. Provide a custom ``TokenStore`` to store the token elsewhere. + public convenience init( + publicKey: String, + bundleIdentifier: String? = nil, + configuration: LicensingConfiguration = .default, + server: LicenseServer? = nil, + tokenStore: (any TokenStore)? = nil, + ) throws { + try self.init( + publicKey: publicKey, + bundleIdentifier: bundleIdentifier, + configuration: configuration, + server: server, + deviceIdentity: MacDeviceIdentity(), + tokenStore: tokenStore + ) + } +#endif + internal init( publicKey: EdDSA.PublicKey, bundleIdentifier: String, configuration: LicensingConfiguration = .default, tokenStore: TokenStore, - hardwareIdentifier: HardwareIdentifier, + deviceIdentity: DeviceIdentity, licenseClient: LicenseClient ) { self.configuration = configuration self.publicKey = publicKey self.bundleIdentifier = bundleIdentifier self.tokenStore = tokenStore - self.hardwareIdentifier = hardwareIdentifier + self.deviceIdentity = deviceIdentity self.licenseClient = licenseClient } @@ -68,8 +100,8 @@ public final class AmoreLicensing: Licensing { let nonce = UUID().uuidString let token = try await mapClientErrors { try await self.licenseClient.activate( - licenseKey: licenseKey, hardwareId: self.hardwareIdentifier.identifier, nonce: nonce, - name: Host.current().localizedName + licenseKey: licenseKey, hardwareId: self.deviceIdentity.identifier, nonce: nonce, + name: self.deviceIdentity.deviceName ) } let payload = try await verifyToken(token, expectedNonce: nonce) @@ -122,7 +154,7 @@ public final class AmoreLicensing: Licensing { let result: ValidationStatus do { let payload = try await jwtCollection.verify(token, as: LicensePayload.self) - guard payload.hardwareId == hardwareIdentifier.identifier else { + guard payload.hardwareId == deviceIdentity.identifier else { status = .invalid throw AmoreError.hardwareIdMismatch } @@ -220,7 +252,7 @@ public final class AmoreLicensing: Licensing { throw .invalidSignature } guard payload.nonce == expectedNonce else { throw .nonceMismatch } - guard payload.hardwareId == hardwareIdentifier.identifier else { throw .hardwareIdMismatch } + guard payload.hardwareId == deviceIdentity.identifier else { throw .hardwareIdMismatch } return payload } diff --git a/Sources/AmoreLicensing/DeviceIdentity/DeviceIdentity.swift b/Sources/AmoreLicensing/DeviceIdentity/DeviceIdentity.swift new file mode 100644 index 0000000..fc0176d --- /dev/null +++ b/Sources/AmoreLicensing/DeviceIdentity/DeviceIdentity.swift @@ -0,0 +1,16 @@ +/// Identifies the device a license is bound to. +/// +/// AmoreLicensing ships a built-in implementation for macOS. On every other +/// platform, provide your own conformance and inject it when creating an +/// ``AmoreLicensing`` instance. +public protocol DeviceIdentity: Sendable { + /// A human-readable name for this device, sent to the server on activation so + /// the device can be recognised in the licensing dashboard. + var deviceName: String { get } + + /// A stable, machine-unique identifier used to bind a license to this device. + /// + /// This value must stay constant for the lifetime of the install: if it + /// changes, the bound license stops validating and a re-activation is required. + var identifier: String { get } +} diff --git a/Sources/AmoreLicensing/HardwareIdentifier/MacHardwareIdentifier.swift b/Sources/AmoreLicensing/DeviceIdentity/MacDeviceIdentity.swift similarity index 68% rename from Sources/AmoreLicensing/HardwareIdentifier/MacHardwareIdentifier.swift rename to Sources/AmoreLicensing/DeviceIdentity/MacDeviceIdentity.swift index 3105347..34d1f4b 100644 --- a/Sources/AmoreLicensing/HardwareIdentifier/MacHardwareIdentifier.swift +++ b/Sources/AmoreLicensing/DeviceIdentity/MacDeviceIdentity.swift @@ -1,14 +1,21 @@ +#if os(macOS) import Foundation import IOKit +import SystemConfiguration -struct MacHardwareIdentifier: HardwareIdentifier { +struct MacDeviceIdentity: DeviceIdentity { + var deviceName: String { + (SCDynamicStoreCopyComputerName(nil, nil) as String?) + ?? ProcessInfo.processInfo.hostName + } + var identifier: String { let service = IOServiceGetMatchingService( kIOMainPortDefault, IOServiceMatching("IOPlatformExpertDevice") ) defer { IOObjectRelease(service) } - + guard let data = IORegistryEntryCreateCFProperty( service, "IOPlatformSerialNumber" as CFString, @@ -20,3 +27,4 @@ struct MacHardwareIdentifier: HardwareIdentifier { return data } } +#endif diff --git a/Sources/AmoreLicensing/HardwareIdentifier/HardwareIdentifier.swift b/Sources/AmoreLicensing/HardwareIdentifier/HardwareIdentifier.swift deleted file mode 100644 index 4978734..0000000 --- a/Sources/AmoreLicensing/HardwareIdentifier/HardwareIdentifier.swift +++ /dev/null @@ -1,3 +0,0 @@ -protocol HardwareIdentifier: Sendable { - var identifier: String { get } -} diff --git a/Tests/AmoreLicensingTests/AmoreClientTests.swift b/Tests/AmoreLicensingTests/AmoreClientTests.swift index 4ce723b..2c86106 100644 --- a/Tests/AmoreLicensingTests/AmoreClientTests.swift +++ b/Tests/AmoreLicensingTests/AmoreClientTests.swift @@ -43,7 +43,7 @@ import Testing publicKey: publicKey, bundleIdentifier: bundleId, tokenStore: tokenStore, - hardwareIdentifier: MockHardwareIdentifier(identifier: hardwareId), + deviceIdentity: MockDeviceIdentity(identifier: hardwareId), licenseClient: licenseClient ) return (client, tokenStore, licenseClient) diff --git a/Tests/AmoreLicensingTests/DeviceIdentity/MockDeviceIdentity.swift b/Tests/AmoreLicensingTests/DeviceIdentity/MockDeviceIdentity.swift new file mode 100644 index 0000000..102f85e --- /dev/null +++ b/Tests/AmoreLicensingTests/DeviceIdentity/MockDeviceIdentity.swift @@ -0,0 +1,6 @@ +@testable import AmoreLicensing + +struct MockDeviceIdentity: DeviceIdentity { + var deviceName: String = "Test Device" + let identifier: String +} diff --git a/Tests/AmoreLicensingTests/HardwareIdentifier/MockHardwareIdentifier.swift b/Tests/AmoreLicensingTests/HardwareIdentifier/MockHardwareIdentifier.swift deleted file mode 100644 index 9aeb25b..0000000 --- a/Tests/AmoreLicensingTests/HardwareIdentifier/MockHardwareIdentifier.swift +++ /dev/null @@ -1,5 +0,0 @@ -@testable import AmoreLicensing - -struct MockHardwareIdentifier: HardwareIdentifier { - let identifier: String -} diff --git a/Tests/AmoreLicensingTests/LicenseClient/MockLicenseClient.swift b/Tests/AmoreLicensingTests/LicenseClient/MockLicenseClient.swift index 8b05372..d56727f 100644 --- a/Tests/AmoreLicensingTests/LicenseClient/MockLicenseClient.swift +++ b/Tests/AmoreLicensingTests/LicenseClient/MockLicenseClient.swift @@ -4,21 +4,25 @@ final class MockLicenseClient: LicenseClient, @unchecked Sendable { var onActivate: ((String, String, String) async throws -> String)? var onDeactivate: ((String) async throws -> Void)? var onValidate: ((String, String) async throws -> String)? - + + /// The `name` passed to the most recent `activate` call. + private(set) var lastActivateName: String? + func activate(licenseKey: String, hardwareId: String, nonce: String, name: String?) async throws -> String { + lastActivateName = name guard let handler = onActivate else { throw AmoreError.client(.licensingNotConfigured) } return try await handler(licenseKey, hardwareId, nonce) } - + func deactivate(token: String) async throws { guard let handler = onDeactivate else { throw AmoreError.client(.licensingNotConfigured) } try await handler(token) } - + func validate(token: String, nonce: String) async throws -> String { guard let handler = onValidate else { throw AmoreError.client(.licensingNotConfigured) diff --git a/Tests/AmoreLicensingTests/LicenseMigrationTests.swift b/Tests/AmoreLicensingTests/LicenseMigrationTests.swift index 138a95f..c918a46 100644 --- a/Tests/AmoreLicensingTests/LicenseMigrationTests.swift +++ b/Tests/AmoreLicensingTests/LicenseMigrationTests.swift @@ -23,7 +23,7 @@ import Testing publicKey: publicKey, bundleIdentifier: bundleId, tokenStore: tokenStore, - hardwareIdentifier: MockHardwareIdentifier(identifier: hardwareId), + deviceIdentity: MockDeviceIdentity(identifier: hardwareId), licenseClient: licenseClient ) } diff --git a/Tests/AmoreLicensingTests/ValidationFrequencyTests.swift b/Tests/AmoreLicensingTests/ValidationFrequencyTests.swift index 06d2879..06951e7 100644 --- a/Tests/AmoreLicensingTests/ValidationFrequencyTests.swift +++ b/Tests/AmoreLicensingTests/ValidationFrequencyTests.swift @@ -43,7 +43,7 @@ import Testing bundleIdentifier: bundleId, configuration: configuration, tokenStore: tokenStore, - hardwareIdentifier: MockHardwareIdentifier(identifier: hardwareId), + deviceIdentity: MockDeviceIdentity(identifier: hardwareId), licenseClient: licenseClient ) return (client, tokenStore, licenseClient) From 323fbc8517f93c64067e6570c6a6beab5e689ec0 Mon Sep 17 00:00:00 2001 From: Lucas Fischer Date: Fri, 19 Jun 2026 08:14:24 +0800 Subject: [PATCH 2/4] build(licensing): support building on Linux --- .github/workflows/swift.yml | 12 ++- Package.swift | 76 +++++++++++-------- .../LicenseClient/HTTPLicenseClient.swift | 3 + 3 files changed, 58 insertions(+), 33 deletions(-) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index 5cdb38c..0605fd5 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -10,10 +10,18 @@ on: branches: [ "main" ] jobs: - build: - + macos: runs-on: macos-26 + steps: + - uses: actions/checkout@v4 + - name: Build + run: swift build -v + - name: Run tests + run: swift test -v + linux: + runs-on: ubuntu-latest + container: swift:6.2 steps: - uses: actions/checkout@v4 - name: Build diff --git a/Package.swift b/Package.swift index 0b84769..924dd5c 100644 --- a/Package.swift +++ b/Package.swift @@ -3,43 +3,57 @@ import PackageDescription +// AmoreStore depends on Foundation currency metadata (NumberFormatter) that is +// only reliable on Apple platforms, so it is built only there. +var products: [Product] = [ + .library( + name: "AmoreLicensing", + targets: ["AmoreLicensing"] + ), +] + +var targets: [Target] = [ + .target( + name: "AmoreLicensing", + dependencies: [ + .product(name: "JWTKit", package: "jwt-kit"), + ] + ), + .testTarget( + name: "AmoreLicensingTests", + dependencies: [ + "AmoreLicensing", + .product(name: "JWTKit", package: "jwt-kit"), + ] + ), +] + +#if canImport(Darwin) +products.append( + .library( + name: "AmoreStore", + targets: ["AmoreStore"] + ) +) +targets.append(.target(name: "AmoreStore")) +targets.append( + .testTarget( + name: "AmoreStoreTests", + dependencies: ["AmoreStore"] + ) +) +#endif + let package = Package( name: "AmoreKit", - platforms: [.macOS(.v14)], - products: [ - // Products define the executables and libraries a package produces, making them visible to other packages. - .library( - name: "AmoreLicensing", - targets: ["AmoreLicensing"] - ), - .library( - name: "AmoreStore", - targets: ["AmoreStore"] - ), + platforms: [ + .macOS(.v14), ], + products: products, dependencies: [ .package(url: "https://github.com/vapor/jwt-kit.git", from: "5.0.0"), .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.1.0"), ], - targets: [ - .target( - name: "AmoreLicensing", - dependencies: [ - .product(name: "JWTKit", package: "jwt-kit"), - ], - ), - .target(name: "AmoreStore"), - .testTarget( - name: "AmoreLicensingTests", - dependencies: [ - "AmoreLicensing", - .product(name: "JWTKit", package: "jwt-kit"), - ] - ), - .testTarget( - name: "AmoreStoreTests", - dependencies: ["AmoreStore"] - ), - ], + targets: targets, swiftLanguageModes: [.v6] ) diff --git a/Sources/AmoreLicensing/LicenseClient/HTTPLicenseClient.swift b/Sources/AmoreLicensing/LicenseClient/HTTPLicenseClient.swift index 68bfa27..ca93584 100644 --- a/Sources/AmoreLicensing/LicenseClient/HTTPLicenseClient.swift +++ b/Sources/AmoreLicensing/LicenseClient/HTTPLicenseClient.swift @@ -1,4 +1,7 @@ import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif struct HTTPLicenseClient: LicenseClient { private let server: LicenseServer From 44337eca091885183ed79fe577a6e3caed45676a Mon Sep 17 00:00:00 2001 From: Lucas Fischer Date: Fri, 19 Jun 2026 08:14:24 +0800 Subject: [PATCH 3/4] docs(licensing): document custom device identity and drop macOS-only framing --- .../Architecture & Security.md | 4 +- .../Custom Device Identity.md | 67 +++++++++++++++++++ .../Documentation.docc/Getting Started.md | 2 + .../Documentation.docc/Index.md | 7 +- readme.md | 5 +- 5 files changed, 80 insertions(+), 5 deletions(-) create mode 100644 Sources/AmoreLicensing/Documentation.docc/Custom Device Identity.md diff --git a/Sources/AmoreLicensing/Documentation.docc/Architecture & Security.md b/Sources/AmoreLicensing/Documentation.docc/Architecture & Security.md index 1e04758..577364c 100644 --- a/Sources/AmoreLicensing/Documentation.docc/Architecture & Security.md +++ b/Sources/AmoreLicensing/Documentation.docc/Architecture & Security.md @@ -14,7 +14,7 @@ Server stores per-app private keys for signing JWTs. Clients verify signatures a 1. User enters license key in app 2. Client generates: - - Hardware ID (IOPlatformSerialNumber or similar) + - Device ID from ``DeviceIdentity`` (the built-in macOS identity uses IOPlatformSerialNumber) - Random nonce (UUID) 3. Client sends HTTPS POST to server: { license_key, hardware_id, nonce } @@ -30,7 +30,7 @@ Server stores per-app private keys for signing JWTs. Clients verify signatures a - JWT signature valid (using server's public key) - Nonce matches what client sent - JWT not expired -1. Client stores JWT in macOS file system +1. Client stores JWT in the file system ### Ongoing Validation (offline-first) diff --git a/Sources/AmoreLicensing/Documentation.docc/Custom Device Identity.md b/Sources/AmoreLicensing/Documentation.docc/Custom Device Identity.md new file mode 100644 index 0000000..74faf3b --- /dev/null +++ b/Sources/AmoreLicensing/Documentation.docc/Custom Device Identity.md @@ -0,0 +1,67 @@ +# Custom Device Identity + +Bind a license to a device on platforms without a built-in identity. + +## Overview + +AmoreLicensing binds each license to the device it was activated on, identifying that device through a ``DeviceIdentity``. On macOS this is automatic: the ``AmoreLicensing`` initializer that takes no `deviceIdentity` uses a built-in implementation backed by the hardware serial number. + +On every other platform there is no built-in identity, so you provide your own: conform to ``DeviceIdentity`` and pass it when creating ``AmoreLicensing``. + +## Conforming to DeviceIdentity + +``DeviceIdentity`` has two requirements: + +- ``DeviceIdentity/identifier``: a stable, machine-unique string the license binds to. +- ``DeviceIdentity/deviceName``: a human-readable name shown in the licensing dashboard. + +```swift +import AmoreLicensing + +struct MyDeviceIdentity: DeviceIdentity { + var identifier: String { + // A stable, machine-unique id for this install. + } + + var deviceName: String { + // A human-readable name, for example the host name. + } +} +``` + +## Injecting your identity + +Pass your conformance through the `deviceIdentity` parameter. This initializer is available on every platform: + +```swift +let licensing = try AmoreLicensing( + publicKey: "sa92JNtsaYefYp0MIWQbKu1hpS9bSN89ta7b8mlPbI8=", + deviceIdentity: MyDeviceIdentity() +) +``` + +On macOS you can use this initializer too, to override the built-in identity. + +## Choosing an identifier + +``DeviceIdentity/identifier`` is the value the license is bound to, so it must be: + +- **Stable**: constant for the lifetime of the install. If it changes, the bound license stops validating and the user has to re-activate. +- **Unique**: distinct per device, so a license cannot be shared across machines. + +On Linux, for example, you might read `/etc/machine-id`: + +```swift +import Foundation + +struct LinuxDeviceIdentity: DeviceIdentity { + var deviceName: String { ProcessInfo.processInfo.hostName } + + var identifier: String { + (try? String(contentsOfFile: "/etc/machine-id", encoding: .utf8))? + .trimmingCharacters(in: .whitespacesAndNewlines) ?? "unknown" + } +} +``` + +> Important: Avoid values that change across reboots, OS updates, or network changes (such as IP addresses or dynamic host names), or licenses will repeatedly invalidate. diff --git a/Sources/AmoreLicensing/Documentation.docc/Getting Started.md b/Sources/AmoreLicensing/Documentation.docc/Getting Started.md index a0a1c1f..fdde756 100644 --- a/Sources/AmoreLicensing/Documentation.docc/Getting Started.md +++ b/Sources/AmoreLicensing/Documentation.docc/Getting Started.md @@ -28,6 +28,8 @@ let licensing = try AmoreLicensing( > Note: All methods on ``AmoreLicensing`` throw ``AmoreError`` with detailed information about what went wrong. +> Tip: The initializer above uses a built-in device identity that ships with macOS. On other platforms you must provide your own. See . + ## Activation To activate your user's license, call ``AmoreLicensing/activate(licenseKey:)`` with a valid license key. diff --git a/Sources/AmoreLicensing/Documentation.docc/Index.md b/Sources/AmoreLicensing/Documentation.docc/Index.md index 0a6e74f..847e292 100644 --- a/Sources/AmoreLicensing/Documentation.docc/Index.md +++ b/Sources/AmoreLicensing/Documentation.docc/Index.md @@ -1,6 +1,6 @@ # ``AmoreLicensing`` -A macOS licensing SDK for license activation, validation, and deactivation. +A licensing SDK for license activation, validation, and deactivation. ## Overview @@ -15,6 +15,7 @@ AmoreLicensing provides an `@Observable` class that manages the full license lif ### Articles - +- - ### Essentials @@ -37,6 +38,10 @@ AmoreLicensing provides an `@Observable` class that manages the full license lif - ``TokenStore`` - ``FileTokenStore`` +### Device Identity + +- ``DeviceIdentity`` + ### Errors - ``AmoreError`` diff --git a/readme.md b/readme.md index 51491c6..d563923 100644 --- a/readme.md +++ b/readme.md @@ -1,10 +1,11 @@ # AmoreKit -A macOS JWT-based licensing SDK with offline-first validation and hardware ID binding for [amore.computer](https://amore.computer) +A JWT-based licensing SDK with offline-first validation and device binding for [amore.computer](https://amore.computer) ## Requirements -- macOS 14+ +- macOS 14+: uses the built-in device identity +- Other platforms: provide a custom `DeviceIdentity` ## Installation From 549645335ebee4e5cbbd0caebc38d978bfdd56a7 Mon Sep 17 00:00:00 2001 From: Lucas Fischer Date: Sun, 21 Jun 2026 12:54:56 +0800 Subject: [PATCH 4/4] test(licensing): build licensing tests on Linux --- Tests/AmoreLicensingTests/AmoreClientTests.swift | 6 +++--- Tests/AmoreLicensingTests/PublicKeyIngestionTests.swift | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Tests/AmoreLicensingTests/AmoreClientTests.swift b/Tests/AmoreLicensingTests/AmoreClientTests.swift index dd23afa..22dc839 100644 --- a/Tests/AmoreLicensingTests/AmoreClientTests.swift +++ b/Tests/AmoreLicensingTests/AmoreClientTests.swift @@ -314,7 +314,6 @@ import Testing /// rely on at startup to gate access without awaiting the server. @Test func launchInitializerSurfacesValidStoredTokenSynchronously() throws { let (privateKey, publicKey) = makeKeys() - let hardwareId = MacDeviceIdentity().identifier let store = MockTokenStore() let token = try signToken(privateKey: privateKey, hardwareId: hardwareId, nonce: "stored") try store.store(token) @@ -323,6 +322,7 @@ import Testing publicKey: publicKey.rawRepresentation.base64URLEncodedString(), bundleIdentifier: bundleId, server: unreachableServer(), + deviceIdentity: MockDeviceIdentity(identifier: hardwareId), tokenStore: store ) @@ -339,7 +339,6 @@ import Testing /// refresh fails and applies grace. @Test func launchInitializerSurfacesGracePeriodForTokenWithinGraceSynchronously() throws { let (privateKey, publicKey) = makeKeys() - let hardwareId = MacDeviceIdentity().identifier let store = MockTokenStore() let expDate = Date().addingTimeInterval(-2 * 24 * 3600) // expired 2 days ago let token = try signToken( @@ -351,6 +350,7 @@ import Testing publicKey: publicKey.rawRepresentation.base64URLEncodedString(), bundleIdentifier: bundleId, server: unreachableServer(), + deviceIdentity: MockDeviceIdentity(identifier: hardwareId), tokenStore: store ) @@ -367,7 +367,6 @@ import Testing /// async `validate()` makes that call. @Test func launchInitializerStaysUnknownForTokenBeyondGrace() throws { let (privateKey, publicKey) = makeKeys() - let hardwareId = MacDeviceIdentity().identifier let store = MockTokenStore() let token = try signToken( privateKey: privateKey, hardwareId: hardwareId, nonce: "stored", @@ -379,6 +378,7 @@ import Testing publicKey: publicKey.rawRepresentation.base64URLEncodedString(), bundleIdentifier: bundleId, server: unreachableServer(), + deviceIdentity: MockDeviceIdentity(identifier: hardwareId), tokenStore: store ) diff --git a/Tests/AmoreLicensingTests/PublicKeyIngestionTests.swift b/Tests/AmoreLicensingTests/PublicKeyIngestionTests.swift index a34e80e..c126da4 100644 --- a/Tests/AmoreLicensingTests/PublicKeyIngestionTests.swift +++ b/Tests/AmoreLicensingTests/PublicKeyIngestionTests.swift @@ -5,7 +5,7 @@ import Testing @testable import AmoreLicensing -/// Exercises the public ``AmoreLicensing/init(publicKey:bundleIdentifier:configuration:server:tokenStore:)`` +/// Exercises the public ``AmoreLicensing/init(publicKey:bundleIdentifier:configuration:server:deviceIdentity:tokenStore:)`` /// string-to-key path: the one place a deployed app's hardcoded key string is /// ingested. ``ValidationFrequency/manual`` keeps the initializer side-effect /// free (no launch validation, no network). @@ -21,6 +21,7 @@ struct PublicKeyIngestionTests { publicKey: keyString, bundleIdentifier: "com.test.amorekit", configuration: manual, + deviceIdentity: MockDeviceIdentity(identifier: "TEST-DEVICE"), tokenStore: MockTokenStore() ) } @@ -31,6 +32,7 @@ struct PublicKeyIngestionTests { publicKey: "not base64 !!!", bundleIdentifier: "com.test.amorekit", configuration: manual, + deviceIdentity: MockDeviceIdentity(identifier: "TEST-DEVICE"), tokenStore: MockTokenStore() ) } @@ -43,6 +45,7 @@ struct PublicKeyIngestionTests { publicKey: tooShort, bundleIdentifier: "com.test.amorekit", configuration: manual, + deviceIdentity: MockDeviceIdentity(identifier: "TEST-DEVICE"), tokenStore: MockTokenStore() ) }