@@ -47,9 +47,11 @@ import { mockEndpointWithParams } from '../../../test/helpers/api/helper';
47
47
import { Endpoint , RecaptchaClientType , RecaptchaVersion } from '../../api' ;
48
48
import * as mockFetch from '../../../test/helpers/mock_fetch' ;
49
49
import { AuthErrorCode } from '../errors' ;
50
+ import * as exchangeTokenModule from '../strategies/exhange_token' ;
50
51
import {
51
52
FirebaseToken ,
52
- PasswordValidationStatus
53
+ PasswordValidationStatus ,
54
+ TokenRefreshHandler
53
55
} from '../../model/public_types' ;
54
56
import { PasswordPolicyImpl } from './password_policy_impl' ;
55
57
import { PersistenceUserManager } from '../persistence/persistence_user_manager' ;
@@ -302,6 +304,116 @@ describe('core/auth/auth_impl', () => {
302
304
} ) ;
303
305
} ) ;
304
306
307
+ describe ( '#setTokenRefreshHandler' , ( ) => {
308
+ it ( 'sets the tokenRefreshHandler on the auth object' , ( ) => {
309
+ const handler : TokenRefreshHandler = {
310
+ refreshIdpToken : async ( ) => ( { idToken : 'a' , idpConfigId : 'b' } )
311
+ } ;
312
+ auth . setTokenRefreshHandler ( handler ) ;
313
+ expect ( ( auth as any ) . tokenRefreshHandler ) . to . eq ( handler ) ;
314
+ } ) ;
315
+
316
+ describe ( '#getFirebaseAccessToken' , ( ) => {
317
+ let exchangeTokenStub : sinon . SinonStub ;
318
+ let mockToken : FirebaseToken ;
319
+ let expiredMockToken : FirebaseToken ;
320
+ let tokenRefreshHandler : TokenRefreshHandler ;
321
+ const tokenKey = `firebase:persistence-token:${ FAKE_APP . options . apiKey ! } :${
322
+ FAKE_APP . name
323
+ } `;
324
+
325
+ beforeEach ( ( ) => {
326
+ exchangeTokenStub = sinon . stub ( exchangeTokenModule , 'exchangeToken' ) . resolves ( ) ;
327
+
328
+ mockToken = {
329
+ token : 'test-token' ,
330
+ expirationTime : Date . now ( ) + 300000 // 5 minutes from now
331
+ } ;
332
+ expiredMockToken = {
333
+ token : 'expired-test-token' ,
334
+ expirationTime : Date . now ( ) - 1000 // 1 second ago
335
+ } ;
336
+ tokenRefreshHandler = {
337
+ refreshIdpToken : sinon . stub ( ) . resolves ( {
338
+ idToken : 'new-id-token' ,
339
+ idpConfigId : 'test-idp'
340
+ } )
341
+ } ;
342
+ // Reset cached token and persistence before each test
343
+ ( auth as any ) . firebaseToken = null ;
344
+ persistenceStub . _get . withArgs ( tokenKey ) . resolves ( null ) ;
345
+ } ) ;
346
+
347
+ afterEach ( ( ) => {
348
+ sinon . restore ( ) ;
349
+ } ) ;
350
+
351
+ it ( 'should return the existing token if it is valid' , async ( ) => {
352
+ persistenceStub . _get . withArgs ( tokenKey ) . resolves ( mockToken as any ) ;
353
+ const token = await auth . getFirebaseAccessToken ( ) ;
354
+ expect ( token ) . to . eql ( mockToken ) ;
355
+ expect ( exchangeTokenStub ) . not . to . have . been . called ;
356
+ } ) ;
357
+
358
+ it ( 'should return null if the token is expired and no token refresh handler is set' , async ( ) => {
359
+ persistenceStub . _get . withArgs ( tokenKey ) . resolves ( expiredMockToken as any ) ;
360
+ const token = await auth . getFirebaseAccessToken ( ) ;
361
+ expect ( token ) . to . be . null ;
362
+ expect ( exchangeTokenStub ) . not . to . have . been . called ;
363
+ } ) ;
364
+
365
+ it ( 'should refresh the token if it is expired and a token refresh handler is set' , async ( ) => {
366
+ persistenceStub . _get . withArgs ( tokenKey ) . resolves ( expiredMockToken as any ) ;
367
+ auth . setTokenRefreshHandler ( tokenRefreshHandler ) ;
368
+
369
+ exchangeTokenStub . callsFake ( async ( ) => {
370
+ // When exchangeToken is called, simulate that the new token is persisted.
371
+ persistenceStub . _get . withArgs ( tokenKey ) . resolves ( mockToken as any ) ;
372
+ } ) ;
373
+
374
+ const token = await auth . getFirebaseAccessToken ( ) ;
375
+
376
+ expect ( tokenRefreshHandler . refreshIdpToken ) . to . have . been . calledOnce ;
377
+ expect ( exchangeTokenStub ) . to . have . been . calledWith ( auth , 'test-idp' , 'new-id-token' ) ;
378
+ expect ( token ) . to . eql ( mockToken ) ;
379
+ } ) ;
380
+
381
+ it ( 'should force refresh the token when forceRefresh is true' , async ( ) => {
382
+ persistenceStub . _get . withArgs ( tokenKey ) . resolves ( mockToken as any ) ;
383
+ auth . setTokenRefreshHandler ( tokenRefreshHandler ) ;
384
+
385
+ exchangeTokenStub . callsFake ( async ( ) => {
386
+ persistenceStub . _get . withArgs ( tokenKey ) . resolves ( mockToken as any ) ;
387
+ } ) ;
388
+
389
+ await auth . getFirebaseAccessToken ( true ) ;
390
+
391
+ expect ( tokenRefreshHandler . refreshIdpToken ) . to . have . been . calledOnce ;
392
+ expect ( exchangeTokenStub ) . to . have . been . calledWith ( auth , 'test-idp' , 'new-id-token' ) ;
393
+ } ) ;
394
+
395
+ it ( 'should return null and log an error if token refresh fails' , async ( ) => {
396
+ const consoleErrorStub = sinon . stub ( console , 'error' ) ;
397
+ persistenceStub . _get . withArgs ( tokenKey ) . resolves ( expiredMockToken as any ) ;
398
+ ( tokenRefreshHandler . refreshIdpToken as sinon . SinonStub ) . rejects ( new Error ( 'refresh failed' ) ) ;
399
+ auth . setTokenRefreshHandler ( tokenRefreshHandler ) ;
400
+ const token = await auth . getFirebaseAccessToken ( ) ;
401
+ expect ( token ) . to . be . null ;
402
+ expect ( consoleErrorStub ) . to . have . been . calledWith ( 'Token refresh failed:' , sinon . match . instanceOf ( Error ) ) ;
403
+ } ) ;
404
+
405
+ it ( 'should return null and log an error if the refreshed token is invalid' , async ( ) => {
406
+ const consoleErrorStub = sinon . stub ( console , 'error' ) ;
407
+ persistenceStub . _get . withArgs ( tokenKey ) . resolves ( expiredMockToken as any ) ;
408
+ ( tokenRefreshHandler . refreshIdpToken as sinon . SinonStub ) . resolves ( { idToken : 'new-id-token' } ) ; // Missing idpConfigId
409
+ auth . setTokenRefreshHandler ( tokenRefreshHandler ) ;
410
+ const token = await auth . getFirebaseAccessToken ( ) ;
411
+ expect ( token ) . to . be . null ;
412
+ expect ( consoleErrorStub ) . to . have . been . calledWith ( 'Token refresh failed:' , sinon . match . instanceOf ( FirebaseError ) ) ;
413
+ } ) ;
414
+ } ) ;
415
+
416
+
305
417
describe ( '#signOut' , ( ) => {
306
418
it ( 'sets currentUser to null, calls remove' , async ( ) => {
307
419
await auth . _updateCurrentUser ( testUser ( auth , 'test' ) ) ;
0 commit comments