From 4d16cb84fec3188dd16aeb7003eee7b3c509cff3 Mon Sep 17 00:00:00 2001 From: Kabir Oberai Date: Mon, 14 Sep 2026 19:20:35 -0400 Subject: [PATCH] anisette: use XADI on macOS --- Package.swift | 2 +- .../GrandSlam/Anisette/ADIDataProvider.swift | 44 ++++++++++++++++--- .../Anisette/AnisetteDataProvider.swift | 9 ++++ Sources/XToolSupport/AuthCommand.swift | 5 ++- Sources/XToolSupport/AuthToken.swift | 14 +++++- macOS/Support/XToolMac.entitlements | 2 + 6 files changed, 66 insertions(+), 10 deletions(-) diff --git a/Package.swift b/Package.swift index cdacc743..47615c6e 100644 --- a/Package.swift +++ b/Package.swift @@ -120,7 +120,7 @@ let package = Package( "DeveloperAPI", "CXKit", "XUtils", - .product(name: "XADI", package: "xadi", condition: .when(platforms: [.linux])), + .product(name: "XADI", package: "xadi", condition: .when(platforms: [.linux, .macOS])), .product(name: "ConcurrencyExtras", package: "swift-concurrency-extras"), .product(name: "Dependencies", package: "swift-dependencies"), .product(name: "SwiftyMobileDevice", package: "SwiftyMobileDevice"), diff --git a/Sources/XKit/GrandSlam/Anisette/ADIDataProvider.swift b/Sources/XKit/GrandSlam/Anisette/ADIDataProvider.swift index 085730c4..5d6bd678 100644 --- a/Sources/XKit/GrandSlam/Anisette/ADIDataProvider.swift +++ b/Sources/XKit/GrandSlam/Anisette/ADIDataProvider.swift @@ -92,13 +92,10 @@ public struct ADIDataProvider: AnisetteDataProvider { private let _clientInfo = LockIsolated(nil) - public init(provisioningData: ProvisioningData? = nil) { + public init() { @Dependency(\.keyValueStorage) var storage - if let provisioningData { - self.localUserUID = provisioningData.localUserUID - try? storage.setData(provisioningData.adiPb, forKey: Self.provisioningKey) - try? storage.setString("\(provisioningData.routingInfo)", forKey: Self.routingInfoKey) - } else if let localUserUIDString = try? storage.string(forKey: Self.localUserUIDKey), + + if let localUserUIDString = try? storage.string(forKey: Self.localUserUIDKey), let localUserUID = UUID(uuidString: localUserUIDString) { self.localUserUID = localUserUID } else { @@ -110,9 +107,15 @@ public struct ADIDataProvider: AnisetteDataProvider { self.localUserID = SHA256.hash(data: Data(localUserUID.uuidString.utf8)) .map { String(format: "%02X", $0) } .joined() + + if (try? storage.string(forKey: Self.providerIDKey)) != Self.providerID { + self.resetProvisioning() + try? storage.setString(Self.providerID, forKey: Self.providerIDKey) + } } private static let localUserUIDKey = "XTLLocalUserUID" + private static let providerIDKey = "XTLADIProviderID" private static let provisioningKey = "XTLProvisioningInfo" private static let routingInfoKey = "XTLRoutingInfo" @@ -245,6 +248,16 @@ public struct ADIDataProvider: AnisetteDataProvider { try? storage.setString(nil, forKey: Self.routingInfoKey) } + private static var providerID: String? { + #if os(macOS) + return "1" + #else + return nil + #endif + } + + public var providerID: String? { Self.providerID } + public func provisioningData() -> ProvisioningData? { guard let provisioningInfo = try? storage.data(forKey: Self.provisioningKey), let routingInfoString = try? storage.string(forKey: Self.routingInfoKey), @@ -280,6 +293,23 @@ public struct ADIDataProvider: AnisetteDataProvider { } -public struct ADIError: Error { +public struct ADIError: Error, CustomStringConvertible { public var code: Int + + public var description: String { + switch code { + case -45061: + return """ + You were logged out. Please log in again with `xtool auth`. + + If you see this repeatedly, please file an issue at https://github.com/xtool-org/xtool/issues/new/choose + """ + default: + return """ + Apple Anisette library returned error \(code). Please try logging in again with `xtool auth`. + + If this problem persists, file an issue at https://github.com/xtool-org/xtool/issues/new/choose + """ + } + } } diff --git a/Sources/XKit/GrandSlam/Anisette/AnisetteDataProvider.swift b/Sources/XKit/GrandSlam/Anisette/AnisetteDataProvider.swift index 474bbb06..554eec8a 100644 --- a/Sources/XKit/GrandSlam/Anisette/AnisetteDataProvider.swift +++ b/Sources/XKit/GrandSlam/Anisette/AnisetteDataProvider.swift @@ -10,6 +10,14 @@ import Foundation import Dependencies public protocol AnisetteDataProvider: Sendable { + /// Stored alongside login data. If the provider reports a new ID, we log out. + /// + /// This is useful when switching Anisette provider implementations if the old + /// implementation's provisioning data won't work with the new implementation. + /// + /// The default implementation returns `nil`. + var providerID: String? { get } + // This is a suggestion and not a requirement. func resetProvisioning() async func provisioningData() -> ProvisioningData? @@ -24,6 +32,7 @@ public struct ProvisioningData: Hashable, Codable, Sendable { } extension AnisetteDataProvider { + public var providerID: String? { nil } public func provisioningData() -> ProvisioningData? { nil } public func resetProvisioning() async {} } diff --git a/Sources/XToolSupport/AuthCommand.swift b/Sources/XToolSupport/AuthCommand.swift index 40f86576..c0d028cd 100644 --- a/Sources/XToolSupport/AuthCommand.swift +++ b/Sources/XToolSupport/AuthCommand.swift @@ -111,12 +111,15 @@ struct AuthOperation { } ) + @Dependency(\.anisetteDataProvider) var anisetteProvider + return AuthToken.xcode(.init( appleID: username, adsid: token.adsid, token: token.token, expiry: token.expiry, - teamID: team.id.rawValue + teamID: team.id.rawValue, + anisetteProviderID: anisetteProvider.providerID )) } } diff --git a/Sources/XToolSupport/AuthToken.swift b/Sources/XToolSupport/AuthToken.swift index eef02e8e..0319a7bf 100644 --- a/Sources/XToolSupport/AuthToken.swift +++ b/Sources/XToolSupport/AuthToken.swift @@ -9,6 +9,7 @@ enum AuthToken: Codable, CustomStringConvertible { var token: String var expiry: Date var teamID: String + var anisetteProviderID: String? } struct AppStoreConnect: Codable { @@ -58,7 +59,18 @@ extension AuthToken { guard let data = try storage.data(forKey: "XTLAuthToken") else { return nil } - return try decoder.decode(AuthToken.self, from: data) + let decoded = try decoder.decode(AuthToken.self, from: data) + switch decoded { + case .xcode(let xcode): + @Dependency(\.anisetteDataProvider) var anisetteProvider + guard xcode.anisetteProviderID == anisetteProvider.providerID else { + try? clear() + return nil + } + case .appStoreConnect: + break + } + return decoded } static func clear() throws { diff --git a/macOS/Support/XToolMac.entitlements b/macOS/Support/XToolMac.entitlements index 55d91602..5e84acac 100644 --- a/macOS/Support/XToolMac.entitlements +++ b/macOS/Support/XToolMac.entitlements @@ -2,6 +2,8 @@ + com.apple.security.cs.allow-unsigned-executable-memory + keychain-access-groups $(AppIdentifierPrefix)sh.xtool.keychain