diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3fa9797..e3d1c23 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,13 +15,13 @@ jobs: os: # - ubuntu-latest - macos-latest - swift: - - "5.10" + # swift: + # - "5.10" runs-on: ${{ matrix.os }} steps: - - uses: swift-actions/setup-swift@e1dca7c4a36344146bbc2803f0d538462477bb37 # 2.0.0 - with: - swift-version: ${{ matrix.swift }} + # - uses: swift-actions/setup-swift@e1dca7c4a36344146bbc2803f0d538462477bb37 # 2.0.0 + # with: + # swift-version: ${{ matrix.swift }} - uses: actions/checkout@v4 - name: Build run: swift build diff --git a/Package.swift b/Package.swift index aa5694a..f3fa273 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.10 +// swift-tools-version: 6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -24,7 +24,8 @@ let package = Package( .target( name: "SnapAuth", swiftSettings: [ - .define("HARDWARE_KEY_SUPPORT", .when(platforms: [.iOS, .macOS])) + .define("HARDWARE_KEY_SUPPORT", .when(platforms: [.iOS, .macOS])), + .swiftLanguageVersion(.v6), ]), .testTarget( name: "SnapAuthTests", diff --git a/Sources/SnapAuth/Errors.swift b/Sources/SnapAuth/Errors.swift index 584dbe6..6d5e0d6 100644 --- a/Sources/SnapAuth/Errors.swift +++ b/Sources/SnapAuth/Errors.swift @@ -72,16 +72,13 @@ extension ASAuthorizationError.Code { case .invalidResponse: return .invalidResponse case .notHandled: return .notHandled case .notInteractive: return .notInteractive - @unknown default: - /* This is (AFAICT) correct, but doesn't seem to work on the Github - Actions runner version. + default: // This case only exists on new OS platforms if #available(iOS 18, visionOS 2, macOS 15, tvOS 18, *) { if case .matchedExcludedCredential = self { return .matchedExcludedCredential } } - */ return .unknown } } diff --git a/Sources/SnapAuth/PresentationAnchor.swift b/Sources/SnapAuth/PresentationAnchor.swift index 6ddc55d..52fb7bd 100644 --- a/Sources/SnapAuth/PresentationAnchor.swift +++ b/Sources/SnapAuth/PresentationAnchor.swift @@ -1,14 +1,20 @@ import AuthenticationServices +extension ASPresentationAnchor { + /// A platform-specific anchor, intended to be used by ASAuthorizationController + static var `default`: ASPresentationAnchor { #if os(macOS) -// FIXME: Figure out better fallback mechanisms here. -// This will cause a new window to open _and remain open_ -fileprivate let defaultPresentationAnchor: ASPresentationAnchor = NSApplication.shared.mainWindow ?? ASPresentationAnchor() + // FIXME: Figure out better fallback mechanisms here. + // This will cause a new window to open _and remain open_ + return NSApplication.shared.mainWindow ?? ASPresentationAnchor() #else -fileprivate let defaultPresentationAnchor: ASPresentationAnchor = (UIApplication.shared.connectedScenes.first as? UIWindowScene)?.windows.first?.rootViewController?.view.window ?? ASPresentationAnchor() + return (UIApplication.shared.connectedScenes.first as? UIWindowScene)? + .windows + .first? + .rootViewController? + .view + .window + ?? ASPresentationAnchor() #endif - -extension ASPresentationAnchor { - /// A platform-specific anchor, intended to be used by ASAuthorizationController - static let `default` = defaultPresentationAnchor + } } diff --git a/Sources/SnapAuth/SnapAuth+ASACD.swift b/Sources/SnapAuth/SnapAuth+ASACD.swift index 0b2e8b6..25a614a 100644 --- a/Sources/SnapAuth/SnapAuth+ASACD.swift +++ b/Sources/SnapAuth/SnapAuth+ASACD.swift @@ -4,44 +4,45 @@ import AuthenticationServices @available(macOS 12.0, iOS 15.0, visionOS 1.0, tvOS 16.0, *) extension SnapAuth: ASAuthorizationControllerDelegate { - public func authorizationController( + nonisolated public func authorizationController( controller: ASAuthorizationController, didCompleteWithError error: Error ) { logger.debug("ASACD error") - guard let asError = error as? ASAuthorizationError else { - logger.error("authorizationController didCompleteWithError error was not an ASAuthorizationError") - sendError(.unknown) - return - } + Task { @MainActor in + guard let asError = error as? ASAuthorizationError else { + logger.error("authorizationController didCompleteWithError error was not an ASAuthorizationError") + sendError(.unknown) + return + } - sendError(asError.code.snapAuthError) - // The start call can SILENTLY produce this error which never makes it into this handler - // ASAuthorizationController credential request failed with error: Error Domain=com.apple.AuthenticationServices.AuthorizationError Code=1004 "(null)" + sendError(asError.code.snapAuthError) + // The start call can SILENTLY produce this error which never makes it into this handler + // ASAuthorizationController credential request failed with error: Error Domain=com.apple.AuthenticationServices.AuthorizationError Code=1004 "(null)" + } } - public func authorizationController( + nonisolated public func authorizationController( controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization ) { logger.debug("ASACD did complete") - - switch authorization.credential { - case is ASAuthorizationPublicKeyCredentialAssertion: - handleAssertion(authorization.credential as! ASAuthorizationPublicKeyCredentialAssertion) - case is ASAuthorizationPublicKeyCredentialRegistration: - handleRegistration(authorization.credential as! ASAuthorizationPublicKeyCredentialRegistration) - default: - logger.error("Unexpected credential type \(String(describing: type(of: authorization.credential)))") - sendError(.unexpectedAuthorizationType) + Task { @MainActor in + switch authorization.credential { + case is ASAuthorizationPublicKeyCredentialAssertion: + handleAssertion(authorization.credential as! ASAuthorizationPublicKeyCredentialAssertion) + case is ASAuthorizationPublicKeyCredentialRegistration: + handleRegistration(authorization.credential as! ASAuthorizationPublicKeyCredentialRegistration) + default: + logger.error("Unexpected credential type \(String(describing: type(of: authorization.credential)))") + sendError(.unexpectedAuthorizationType) + } } } /// Sends the error to the appropriate delegate method and resets the internal state back to idle private func sendError(_ error: SnapAuthError) { - // One or the other should eb set, but not both - assert(continuation != nil) continuation?.resume(returning: .failure(error)) continuation = nil } @@ -157,4 +158,3 @@ extension SnapAuth: ASAuthorizationControllerDelegate { // } // } } - diff --git a/Sources/SnapAuth/SnapAuth.swift b/Sources/SnapAuth/SnapAuth.swift index 0a5b9c0..16b3e24 100644 --- a/Sources/SnapAuth/SnapAuth.swift +++ b/Sources/SnapAuth/SnapAuth.swift @@ -7,6 +7,7 @@ import os /// This is used to start the passkey registration and authentication processes, /// typically in the `action` of a `Button` @available(macOS 12.0, iOS 15.0, tvOS 16.0, *) +@MainActor public class SnapAuth: NSObject { // NSObject for ASAuthorizationControllerDelegate internal let api: SnapAuthClient @@ -39,7 +40,7 @@ public class SnapAuth: NSObject { // NSObject for ASAuthorizationControllerDeleg } /// Permitted authenticator types - public enum Authenticator: CaseIterable { + public enum Authenticator: CaseIterable, Sendable { /// Allow all available authenticator types to be used public static let all = Set(Authenticator.allCases) @@ -223,7 +224,7 @@ public class SnapAuth: NSObject { // NSObject for ASAuthorizationControllerDeleg } } -public enum AuthenticatingUser { +public enum AuthenticatingUser: Sendable { /// Your application's internal identifier for the user (usually a primary key) case id(String) /// The user's handle, such as a username or email address