@@ -29,7 +29,7 @@ public extension StytchClient {
2929
3030 /// Indicates if there is an existing biometric registration on device.
3131 public var registrationAvailable : Bool {
32- keychainClient. valueExistsForItem ( . privateKeyRegistration )
32+ keychainClient. valueExistsForItem ( . biometricKeyRegistration )
3333 }
3434
3535 #if !os(tvOS) && !os(watchOS)
@@ -51,17 +51,17 @@ public extension StytchClient {
5151 // sourcery: AsyncVariants, (NOTE: - must use /// doc comment styling)
5252 /// Removes the current device's existing biometric registration from both the device itself and from the server.
5353 public func removeRegistration( ) async throws {
54- guard let queryResult: KeychainClient . QueryResult = try ? keychainClient. get ( . privateKeyRegistration ) . first else {
54+ guard let queryResult = try ? keychainClient. get ( . biometricKeyRegistration ) . first else {
5555 return
5656 }
5757
5858 // Delete registration from backend
59- if let registration = try queryResult. generic . map ( { try jsonDecoder . decode ( KeychainClient . KeyRegistration . self , from : $0 ) } ) {
60- _ = try await StytchClient . user. deleteFactor ( . biometricRegistration( id: registration . registrationId ) )
59+ if let biometricRegistrationId = queryResult. stringValue {
60+ _ = try await StytchClient . user. deleteFactor ( . biometricRegistration( id: User . BiometricRegistration . ID ( stringLiteral : biometricRegistrationId ) ) )
6161 }
6262
63- // Remove local registration
64- try keychainClient . removeItem ( . privateKeyRegistration )
63+ // Remove local registrations
64+ try clearBiometricRegistrations ( )
6565 }
6666
6767 // sourcery: AsyncVariants, (NOTE: - must use /// doc comment styling)
@@ -104,23 +104,29 @@ public extension StytchClient {
104104 registrationId: finishResponse. biometricRegistrationId
105105 )
106106
107+ // Set the .privateKeyRegistration
107108 try keychainClient. set (
108109 key: privateKey,
109110 registration: registration,
110111 accessPolicy: parameters. accessPolicy. keychainValue
111112 )
112113
114+ // Set the .biometricKeyRegistration
115+ try keychainClient. set ( registration. registrationId. rawValue, for: . biometricKeyRegistration)
116+
113117 return finishResponse
114118 }
115119
116120 // sourcery: AsyncVariants, (NOTE: - must use /// doc comment styling)
117121 /// If a valid biometric registration exists, this method confirms the current device owner via the device's built-in biometric reader and returns an updated session object by either starting a new session or adding a the biometric factor to an existing session.
118122 public func authenticate( parameters: AuthenticateParameters ) async throws -> AuthenticateResponse {
119- guard let queryResult : KeychainClient . QueryResult = try keychainClient. get ( . privateKeyRegistration) . first else {
123+ guard let privateKeyRegistrationQueryResult : KeychainClient . QueryResult = try keychainClient. get ( . privateKeyRegistration) . first else {
120124 throw StytchSDKError . noBiometricRegistration
121125 }
122126
123- let privateKey = queryResult. data
127+ try copyBiometricRegistrationIDToKeychainIfNeeded ( privateKeyRegistrationQueryResult)
128+
129+ let privateKey = privateKeyRegistrationQueryResult. data
124130 let publicKey = try cryptoClient. publicKeyForPrivateKey ( privateKey)
125131
126132 let startResponse : AuthenticateStartResponse = try await router. post (
@@ -143,33 +149,44 @@ public extension StytchClient {
143149 return authenticateResponse
144150 }
145151
152+ // Clear both the .privateKeyRegistration and the .biometricKeyRegistration
153+ func clearBiometricRegistrations( ) throws {
154+ try keychainClient. removeItem ( . privateKeyRegistration)
155+ try keychainClient. removeItem ( . biometricKeyRegistration)
156+ }
157+
158+ // if we have a local biometric registration that doesn't exist on the user object, delete the local
146159 func cleanupPotentiallyOrphanedBiometricRegistrations( ) {
147160 guard let user = StytchClient . user. getSync ( ) else {
148161 return
149162 }
150163
151- // if we have a local biometric registration that doesn't exist on the user object, delete the local
152164 if user. biometricRegistrations. isEmpty {
153- try ? keychainClient. removeItem ( . privateKeyRegistration)
154- } else if !user. biometricRegistrations. isEmpty {
155- let queryResult = try ? keychainClient. get ( . privateKeyRegistration) . first
156- cleanupBiometricRegistrationIfOrphaned ( queryResult: queryResult, user: user)
165+ try ? clearBiometricRegistrations ( )
166+ } else {
167+ let queryResult = try ? keychainClient. get ( . biometricKeyRegistration) . first
168+
169+ // Check if the user's biometric registrations contain the ID
170+ let userBiometricRegistrationIds = user. biometricRegistrations. compactMap ( \. biometricRegistrationId. rawValue)
171+ if let biometricRegistrationId = queryResult? . stringValue, userBiometricRegistrationIds. contains ( biometricRegistrationId) == false {
172+ // Remove the orphaned biometric registration
173+ try ? clearBiometricRegistrations ( )
174+ }
157175 }
158176 }
159177
160- private func cleanupBiometricRegistrationIfOrphaned(
161- queryResult: KeychainClient . QueryResult ? ,
162- user: User
163- ) {
164- // Decode the biometric registration ID from the query result
165- let biometricRegistrationId = try ? queryResult? . generic. map {
166- try jsonDecoder. decode ( KeychainClient . KeyRegistration. self, from: $0)
167- }
168-
169- // Check if the user's biometric registrations contain the ID
170- if user. biometricRegistrations. map ( \. id) . contains ( biometricRegistrationId? . registrationId) == false {
171- // Remove the orphaned biometric registration
172- try ? keychainClient. removeItem ( . privateKeyRegistration)
178+ /*
179+ After introducing the .biometricKeyRegistration keychain item in version 0.54.0, we needed a way for versions prior to 0.54.0
180+ to copy the value stored in the biometrically protected .privateKeyRegistration keychain item into the non-biometric
181+ .biometricKeyRegistration keychain item without triggering unnecessary Face ID prompts. Since a Face ID prompt is already
182+ being shown here for authentication, we decided to use this as an opportunity to perform the migration by copying the
183+ registration ID into the .biometricKeyRegistration keychain item. For versions after 0.54.0, this action occurs during
184+ registration, and it should only happen here if the .biometricKeyRegistration keychain item is empty.
185+ */
186+ func copyBiometricRegistrationIDToKeychainIfNeeded( _ queryResult: KeychainClient . QueryResult ) throws {
187+ let biometricKeyRegistration = try ? keychainClient. get ( . biometricKeyRegistration) . first
188+ if biometricKeyRegistration == nil , let registration = try queryResult. generic. map ( { try jsonDecoder. decode ( KeychainClient . KeyRegistration. self, from: $0) } ) {
189+ try keychainClient. set ( registration. registrationId. rawValue, for: . biometricKeyRegistration)
173190 }
174191 }
175192 }
0 commit comments