@@ -29,39 +29,23 @@ 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
35- #if !os(tvOS) && !os(watchOS)
36- /// Indicates if biometrics are available
37- public var availability : Availability {
38- let context = LAContext ( )
39- var error : NSError ?
40- switch ( context. canEvaluatePolicy ( . deviceOwnerAuthenticationWithBiometrics, error: & error) , registrationAvailable) {
41- case ( false , _) :
42- return . systemUnavailable( ( error as? LAError ) ? . code)
43- case ( true , false ) :
44- return . availableNoRegistration
45- case ( true , true ) :
46- return . availableRegistered
47- }
48- }
49- #endif
50-
5135 // sourcery: AsyncVariants, (NOTE: - must use /// doc comment styling)
5236 /// Removes the current device's existing biometric registration from both the device itself and from the server.
5337 public func removeRegistration( ) async throws {
54- guard let queryResult: KeychainClient . QueryResult = try ? keychainClient. get ( . privateKeyRegistration ) . first else {
38+ guard let queryResult = try ? keychainClient. get ( . biometricKeyRegistration ) . first else {
5539 return
5640 }
5741
5842 // 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 ) )
43+ if let biometricRegistrationId = queryResult. stringValue {
44+ _ = try await StytchClient . user. deleteFactor ( . biometricRegistration( id: User . BiometricRegistration . ID ( stringLiteral : biometricRegistrationId ) ) )
6145 }
6246
63- // Remove local registration
64- try keychainClient . removeItem ( . privateKeyRegistration )
47+ // Remove local registrations
48+ try clearBiometricRegistrations ( )
6549 }
6650
6751 // sourcery: AsyncVariants, (NOTE: - must use /// doc comment styling)
@@ -104,23 +88,29 @@ public extension StytchClient {
10488 registrationId: finishResponse. biometricRegistrationId
10589 )
10690
91+ // Set the .privateKeyRegistration
10792 try keychainClient. set (
10893 key: privateKey,
10994 registration: registration,
11095 accessPolicy: parameters. accessPolicy. keychainValue
11196 )
11297
98+ // Set the .biometricKeyRegistration
99+ try keychainClient. set ( registration. registrationId. rawValue, for: . biometricKeyRegistration)
100+
113101 return finishResponse
114102 }
115103
116104 // sourcery: AsyncVariants, (NOTE: - must use /// doc comment styling)
117105 /// 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.
118106 public func authenticate( parameters: AuthenticateParameters ) async throws -> AuthenticateResponse {
119- guard let queryResult : KeychainClient . QueryResult = try keychainClient. get ( . privateKeyRegistration) . first else {
107+ guard let privateKeyRegistrationQueryResult : KeychainClient . QueryResult = try keychainClient. get ( . privateKeyRegistration) . first else {
120108 throw StytchSDKError . noBiometricRegistration
121109 }
122110
123- let privateKey = queryResult. data
111+ try copyBiometricRegistrationIDToKeychainIfNeeded ( privateKeyRegistrationQueryResult)
112+
113+ let privateKey = privateKeyRegistrationQueryResult. data
124114 let publicKey = try cryptoClient. publicKeyForPrivateKey ( privateKey)
125115
126116 let startResponse : AuthenticateStartResponse = try await router. post (
@@ -143,33 +133,48 @@ public extension StytchClient {
143133 return authenticateResponse
144134 }
145135
136+ // Clear both the .privateKeyRegistration and the .biometricKeyRegistration
137+ func clearBiometricRegistrations( ) throws {
138+ try keychainClient. removeItem ( . privateKeyRegistration)
139+ try keychainClient. removeItem ( . biometricKeyRegistration)
140+ }
141+
142+ // if we have a local biometric registration that doesn't exist on the user object, delete the local
146143 func cleanupPotentiallyOrphanedBiometricRegistrations( ) {
147144 guard let user = StytchClient . user. getSync ( ) else {
148145 return
149146 }
150147
151- // if we have a local biometric registration that doesn't exist on the user object, delete the local
152148 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)
149+ try ? clearBiometricRegistrations ( )
150+ } else {
151+ let queryResult = try ? keychainClient. get ( . biometricKeyRegistration) . first
152+
153+ // Check if the user's biometric registrations contain the ID
154+ var userBiometricRegistrationIds = [ String] ( )
155+ for biometricRegistration in user. biometricRegistrations {
156+ userBiometricRegistrationIds. append ( biometricRegistration. biometricRegistrationId. rawValue)
157+ }
158+
159+ if let biometricRegistrationId = queryResult? . stringValue, userBiometricRegistrationIds. contains ( biometricRegistrationId) == false {
160+ // Remove the orphaned biometric registration
161+ try ? clearBiometricRegistrations ( )
162+ }
157163 }
158164 }
159165
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)
166+ /*
167+ After introducing the .biometricKeyRegistration keychain item in version 0.54.0, we needed a way for versions prior to 0.54.0
168+ to copy the value stored in the biometrically protected .privateKeyRegistration keychain item into the non-biometric
169+ .biometricKeyRegistration keychain item without triggering unnecessary Face ID prompts. Since a Face ID prompt is already
170+ being shown here for authentication, we decided to use this as an opportunity to perform the migration by copying the
171+ registration ID into the .biometricKeyRegistration keychain item. For versions after 0.54.0, this action occurs during
172+ registration, and it should only happen here if the .biometricKeyRegistration keychain item is empty.
173+ */
174+ func copyBiometricRegistrationIDToKeychainIfNeeded( _ queryResult: KeychainClient . QueryResult ) throws {
175+ let biometricKeyRegistration = try ? keychainClient. get ( . biometricKeyRegistration) . first
176+ if biometricKeyRegistration == nil , let registration = try queryResult. generic. map ( { try jsonDecoder. decode ( KeychainClient . KeyRegistration. self, from: $0) } ) {
177+ try keychainClient. set ( registration. registrationId. rawValue, for: . biometricKeyRegistration)
173178 }
174179 }
175180 }
0 commit comments