@@ -58,6 +58,7 @@ function createExpoBiometrics({ withHash = true }: { withHash?: boolean } = {})
5858 canEvaluateBiometrics : true ,
5959 canEvaluateDeviceOwner : true ,
6060 errorCode : null ,
61+ secureKeyStorageAvailable : true as boolean | undefined ,
6162 } ) ) ,
6263 createKey : asyncFn ( ( ) => {
6364 store . keys . add ( 'key_new' ) ;
@@ -220,6 +221,42 @@ describe('getAvailability', () => {
220221 expect ( biometrics . listRecords ) . not . toHaveBeenCalled ( ) ;
221222 } ) ;
222223
224+ test ( 'reports biometric authentication unavailable without secure key storage, before reading local records' , async ( ) => {
225+ addLocalCredential ( ) ;
226+ biometrics . getAvailability . mockResolvedValue ( {
227+ biometryType : 'faceID' ,
228+ canEvaluateBiometrics : true ,
229+ canEvaluateDeviceOwner : true ,
230+ errorCode : null ,
231+ secureKeyStorageAvailable : false ,
232+ } ) ;
233+
234+ await expect ( renderBiometricCredentials ( ) . getAvailability ( ) ) . resolves . toEqual ( {
235+ isAvailable : false ,
236+ unavailableReason : 'biometric_authentication_unavailable' ,
237+ } ) ;
238+ expect ( biometrics . listRecords ) . not . toHaveBeenCalled ( ) ;
239+ expect ( biometrics . hasKey ) . not . toHaveBeenCalled ( ) ;
240+ expect ( biometrics . deleteRecord ) . not . toHaveBeenCalled ( ) ;
241+ expect ( biometrics . store . records ) . toHaveLength ( 1 ) ;
242+ } ) ;
243+
244+ test ( 'treats a module that does not report secure key storage as having it' , async ( ) => {
245+ addLocalCredential ( ) ;
246+ biometrics . getAvailability . mockResolvedValue ( {
247+ biometryType : 'faceID' ,
248+ canEvaluateBiometrics : true ,
249+ canEvaluateDeviceOwner : true ,
250+ errorCode : null ,
251+ secureKeyStorageAvailable : undefined ,
252+ } ) ;
253+
254+ await expect ( renderBiometricCredentials ( ) . getAvailability ( ) ) . resolves . toEqual ( {
255+ isAvailable : true ,
256+ unavailableReason : null ,
257+ } ) ;
258+ } ) ;
259+
223260 test ( 'reports no local credential when the device has none for this app' , async ( ) => {
224261 addLocalCredential ( { appIdentifier : 'com.example.other' } ) ;
225262
@@ -581,6 +618,18 @@ describe('enroll', () => {
581618 expect ( clerk . user . __experimental_prepareBiometricCredential ) . not . toHaveBeenCalled ( ) ;
582619 } ) ;
583620
621+ test ( 'fails fast when the device has no secure key storage' , async ( ) => {
622+ biometrics . createKey . mockRejectedValue ( moduleError ( 'secure_key_storage_unavailable' , 'No Secure Enclave' ) ) ;
623+
624+ await expect ( renderBiometricCredentials ( ) . enroll ( ) ) . rejects . toMatchObject ( {
625+ code : 'biometric_authentication_unavailable' ,
626+ message : 'No Secure Enclave' ,
627+ } ) ;
628+ expect ( clerk . user . __experimental_prepareBiometricCredential ) . not . toHaveBeenCalled ( ) ;
629+ expect ( biometrics . sign ) . not . toHaveBeenCalled ( ) ;
630+ expect ( biometrics . saveRecord ) . not . toHaveBeenCalled ( ) ;
631+ } ) ;
632+
584633 test ( 'surfaces reverification errors from prepare unchanged and deletes the key' , async ( ) => {
585634 const error = apiError ( 'session_reverification_required' ) ;
586635 clerk . user . __experimental_prepareBiometricCredential . mockRejectedValue ( error ) ;
@@ -740,6 +789,23 @@ describe('signIn', () => {
740789 expect ( clerk . clientSignIn . create ) . not . toHaveBeenCalled ( ) ;
741790 } ) ;
742791
792+ test ( 'rejects without contacting the server when the device has no secure key storage' , async ( ) => {
793+ addLocalCredential ( ) ;
794+ biometrics . getAvailability . mockResolvedValue ( {
795+ biometryType : 'faceID' ,
796+ canEvaluateBiometrics : true ,
797+ canEvaluateDeviceOwner : true ,
798+ errorCode : null ,
799+ secureKeyStorageAvailable : false ,
800+ } ) ;
801+
802+ const operation = renderBiometricCredentials ( ) . signIn ( ) ;
803+
804+ await expect ( operation ) . rejects . toThrow ( 'biometric_authentication_unavailable' ) ;
805+ expect ( clerk . clientSignIn . create ) . not . toHaveBeenCalled ( ) ;
806+ expect ( biometrics . sign ) . not . toHaveBeenCalled ( ) ;
807+ } ) ;
808+
743809 test ( 'rejects when the Clerk client is unavailable' , async ( ) => {
744810 addLocalCredential ( ) ;
745811 clerk . instance . client = undefined ;
0 commit comments