|
14 | 14 |
|
15 | 15 | import Foundation
|
16 | 16 |
|
| 17 | +#if os(iOS) || os(tvOS) || os(macOS) || targetEnvironment(macCatalyst) |
| 18 | + import AuthenticationServices |
| 19 | +#endif |
| 20 | + |
17 | 21 | @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
|
18 | 22 | extension User: NSSecureCoding {}
|
19 | 23 |
|
@@ -1047,6 +1051,59 @@ extension User: NSSecureCoding {}
|
1047 | 1051 | }
|
1048 | 1052 | }
|
1049 | 1053 |
|
| 1054 | + // MARK: Passkey Implementation |
| 1055 | + |
| 1056 | + #if os(iOS) || os(tvOS) || os(macOS) || targetEnvironment(macCatalyst) |
| 1057 | + |
| 1058 | + /// A cached passkey name being passed from startPasskeyEnrollment(withName:) call and consumed |
| 1059 | + /// at finalizePasskeyEnrollment(withPlatformCredential:) call |
| 1060 | + private var passkeyName: String? |
| 1061 | + |
| 1062 | + /// Start the passkey enrollment creating a plaform public key creation request with the |
| 1063 | + /// challenge from GCIP backend. |
| 1064 | + /// - Parameter name: The name for the passkey to be created. |
| 1065 | + @available(iOS 15.0, macOS 12.0, tvOS 16.0, *) |
| 1066 | + public func startPasskeyEnrollment(withName name: String?) async throws |
| 1067 | + -> ASAuthorizationPlatformPublicKeyCredentialRegistrationRequest { |
| 1068 | + guard auth != nil else { |
| 1069 | + /// If auth is nil, this User object is in an invalid state for this operation. |
| 1070 | + fatalError( |
| 1071 | + "Firebase Auth Internal Error: Set user's auth property with non-nil instance. Cannot start passkey enrollment." |
| 1072 | + ) |
| 1073 | + } |
| 1074 | + let enrollmentIdToken = rawAccessToken() |
| 1075 | + let request = StartPasskeyEnrollmentRequest( |
| 1076 | + idToken: enrollmentIdToken, |
| 1077 | + requestConfiguration: requestConfiguration |
| 1078 | + ) |
| 1079 | + let response = try await backend.call(with: request) |
| 1080 | + passkeyName = (name?.isEmpty ?? true) ? "Unnamed account (Apple)" : name! |
| 1081 | + guard let challengeInData = Data(base64Encoded: response.challenge) else { |
| 1082 | + throw NSError( |
| 1083 | + domain: AuthErrorDomain, |
| 1084 | + code: AuthInternalErrorCode.RPCResponseDecodingError.rawValue, |
| 1085 | + userInfo: [NSLocalizedDescriptionKey: "Failed to decode base64 challenge from response."] |
| 1086 | + ) |
| 1087 | + } |
| 1088 | + guard let userIdInData = Data(base64Encoded: response.userID) else { |
| 1089 | + throw NSError( |
| 1090 | + domain: AuthErrorDomain, |
| 1091 | + code: AuthInternalErrorCode.RPCResponseDecodingError.rawValue, |
| 1092 | + userInfo: [NSLocalizedDescriptionKey: "Failed to decode base64 userId from response."] |
| 1093 | + ) |
| 1094 | + } |
| 1095 | + let provider = ASAuthorizationPlatformPublicKeyCredentialProvider( |
| 1096 | + relyingPartyIdentifier: response.rpID |
| 1097 | + ) |
| 1098 | + let registrationRequest = provider.createCredentialRegistrationRequest( |
| 1099 | + challenge: challengeInData, |
| 1100 | + name: passkeyName ?? "Unnamed account (Apple)", |
| 1101 | + userID: userIdInData |
| 1102 | + ) |
| 1103 | + return registrationRequest |
| 1104 | + } |
| 1105 | + #endif |
| 1106 | + |
1050 | 1107 | // MARK: Internal implementations below
|
1051 | 1108 |
|
1052 | 1109 | func rawAccessToken() -> String {
|
|
0 commit comments