diff --git a/packages-exp/auth-exp/demo/src/index.js b/packages-exp/auth-exp/demo/src/index.js index d0bb974091d..1a559e47257 100644 --- a/packages-exp/auth-exp/demo/src/index.js +++ b/packages-exp/auth-exp/demo/src/index.js @@ -40,9 +40,11 @@ import { signInWithEmailAndPassword, sendSignInLinkToEmail, sendPasswordResetEmail, + EmailAuthProvider, verifyPasswordResetCode, confirmPasswordReset, linkWithCredential, + reauthenticateWithCredential, unlink } from '@firebase/auth-exp'; @@ -373,17 +375,14 @@ function onLinkWithEmailLink() { * Re-authenticate a user with email link credential. */ function onReauthenticateWithEmailLink() { - alertNotImplemented(); - // var email = $('#link-with-email-link-email').val(); - // var link = $('#link-with-email-link-link').val() || undefined; - // var credential = firebase.auth.EmailAuthProvider - // .credentialWithLink(email, link); - // activeUser().reauthenticateWithCredential(credential) - // .then(function(result) { - // logAdditionalUserInfo(result); - // refreshUserData(); - // alertSuccess('User reauthenticated!'); - // }, onAuthError); + var email = $('#link-with-email-link-email').val(); + var link = $('#link-with-email-link-link').val() || undefined; + var credential = EmailAuthProvider.credentialWithLink(email, link); + reauthenticateWithCredential(activeUser(), credential).then(function(result) { + logAdditionalUserInfo(result); + refreshUserData(); + alertSuccess('User reauthenticated!'); + }, onAuthError); } /** @@ -558,13 +557,11 @@ function onReauthConfirmPhoneVerification() { verificationId, verificationCode ); - activeUser() - .reauthenticateWithCredential(credential) - .then(function(result) { - logAdditionalUserInfo(result); - refreshUserData(); - alertSuccess('User reauthenticated!'); - }, onAuthError); + reauthenticateWithCredential(activeUser(), credential).then(function(result) { + logAdditionalUserInfo(result); + refreshUserData(); + alertSuccess('User reauthenticated!'); + }, onAuthError); } /** diff --git a/packages-exp/auth-exp/src/core/credentials/anonymous.test.ts b/packages-exp/auth-exp/src/core/credentials/anonymous.test.ts index f3700b4cc2f..70be29bdf7c 100644 --- a/packages-exp/auth-exp/src/core/credentials/anonymous.test.ts +++ b/packages-exp/auth-exp/src/core/credentials/anonymous.test.ts @@ -15,16 +15,18 @@ * limitations under the License. */ -import { ProviderId, SignInMethod } from '@firebase/auth-types-exp'; -import * as mockFetch from '../../../test/mock_fetch'; import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import { testAuth } from '../../../test/mock_auth'; -import { Auth } from '../../model/auth'; -import { AnonymousCredential } from './anonymous'; + +import { ProviderId, SignInMethod } from '@firebase/auth-types-exp'; + import { mockEndpoint } from '../../../test/api/helper'; +import { testAuth } from '../../../test/mock_auth'; +import * as mockFetch from '../../../test/mock_fetch'; import { Endpoint } from '../../api'; import { APIUserInfo } from '../../api/account_management/account'; +import { Auth } from '../../model/auth'; +import { AnonymousCredential } from './anonymous'; use(chaiAsPromised); @@ -84,9 +86,9 @@ describe('core/credentials/anonymous', () => { }); }); - describe('#_matchIdTokenWithUid', () => { + describe('#_getReauthenticationResolver', () => { it('throws', () => { - expect(() => credential._matchIdTokenWithUid(auth, 'other-uid')).to.throw( + expect(() => credential._getReauthenticationResolver(auth)).to.throw( Error ); }); diff --git a/packages-exp/auth-exp/src/core/credentials/anonymous.ts b/packages-exp/auth-exp/src/core/credentials/anonymous.ts index 3a37f841268..40681d3bdb2 100644 --- a/packages-exp/auth-exp/src/core/credentials/anonymous.ts +++ b/packages-exp/auth-exp/src/core/credentials/anonymous.ts @@ -16,11 +16,12 @@ */ import { ProviderId, SignInMethod } from '@firebase/auth-types-exp'; + import { signUp } from '../../api/authentication/sign_up'; import { Auth } from '../../model/auth'; import { IdTokenResponse } from '../../model/id_token'; import { debugFail } from '../util/assert'; -import { AuthCredential } from '.'; +import { AuthCredential } from './'; export class AnonymousCredential implements AuthCredential { providerId = ProviderId.ANONYMOUS; @@ -44,7 +45,7 @@ export class AnonymousCredential implements AuthCredential { debugFail("Can't link to an anonymous credential"); } - _matchIdTokenWithUid(_auth: Auth, _uid: string): Promise { + _getReauthenticationResolver(_auth: Auth): Promise { debugFail('Method not implemented.'); } } diff --git a/packages-exp/auth-exp/src/core/credentials/email.test.ts b/packages-exp/auth-exp/src/core/credentials/email.test.ts index acf2dd5d08a..323532d2fce 100644 --- a/packages-exp/auth-exp/src/core/credentials/email.test.ts +++ b/packages-exp/auth-exp/src/core/credentials/email.test.ts @@ -15,17 +15,19 @@ * limitations under the License. */ -import { ProviderId, SignInMethod } from '@firebase/auth-types-exp'; import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; + +import { ProviderId, SignInMethod } from '@firebase/auth-types-exp'; + +import { mockEndpoint } from '../../../test/api/helper'; import { testAuth } from '../../../test/mock_auth'; -import { Auth } from '../../model/auth'; -import { EmailAuthProvider } from '../providers/email'; -import { EmailAuthCredential } from './email'; import * as mockFetch from '../../../test/mock_fetch'; -import { mockEndpoint } from '../../../test/api/helper'; import { Endpoint } from '../../api'; import { APIUserInfo } from '../../api/account_management/account'; +import { Auth } from '../../model/auth'; +import { EmailAuthProvider } from '../providers/email'; +import { EmailAuthCredential } from './email'; use(chaiAsPromised); @@ -95,11 +97,11 @@ describe('core/credentials/email', () => { }); }); - describe('#_matchIdTokenWithUid', () => { + describe('#_getReauthenticationResolver', () => { it('throws', () => { - expect(() => - credential._matchIdTokenWithUid(auth, 'other-uid') - ).to.throw(Error); + expect(() => credential._getReauthenticationResolver(auth)).to.throw( + Error + ); }); }); }); @@ -160,9 +162,9 @@ describe('core/credentials/email', () => { describe('#_matchIdTokenWithUid', () => { it('throws', () => { - expect(() => - credential._matchIdTokenWithUid(auth, 'other-uid') - ).to.throw(Error); + expect(() => credential._getReauthenticationResolver(auth)).to.throw( + Error + ); }); }); }); diff --git a/packages-exp/auth-exp/src/core/credentials/email.ts b/packages-exp/auth-exp/src/core/credentials/email.ts index 2f6266ca88a..741e460b68d 100644 --- a/packages-exp/auth-exp/src/core/credentials/email.ts +++ b/packages-exp/auth-exp/src/core/credentials/email.ts @@ -16,14 +16,15 @@ */ import * as externs from '@firebase/auth-types-exp'; + import { signInWithPassword } from '../../api/authentication/email_and_password'; import { signInWithEmailLink } from '../../api/authentication/email_link'; import { Auth } from '../../model/auth'; import { IdTokenResponse } from '../../model/id_token'; -import { AuthErrorCode, AUTH_ERROR_FACTORY } from '../errors'; +import { AUTH_ERROR_FACTORY, AuthErrorCode } from '../errors'; import { EmailAuthProvider } from '../providers/email'; import { debugFail } from '../util/assert'; -import { AuthCredential } from '.'; +import { AuthCredential } from './'; export class EmailAuthCredential implements AuthCredential { readonly providerId = EmailAuthProvider.PROVIDER_ID; @@ -66,7 +67,7 @@ export class EmailAuthCredential implements AuthCredential { debugFail('Method not implemented.'); } - _matchIdTokenWithUid(_auth: Auth, _uid: string): Promise { + _getReauthenticationResolver(_auth: Auth): Promise { debugFail('Method not implemented.'); } } diff --git a/packages-exp/auth-exp/src/core/credentials/index.d.ts b/packages-exp/auth-exp/src/core/credentials/index.d.ts index d9215b56b64..3ffa8da5ea0 100644 --- a/packages-exp/auth-exp/src/core/credentials/index.d.ts +++ b/packages-exp/auth-exp/src/core/credentials/index.d.ts @@ -16,14 +16,15 @@ */ import * as externs from '@firebase/auth-types-exp'; + import { PhoneOrOauthTokenResponse } from '../../api/authentication/mfa'; -import { IdTokenResponse } from '../../model/id_token'; import { Auth } from '../../model/auth'; +import { IdTokenResponse } from '../../model/id_token'; export abstract class AuthCredential extends externs.AuthCredential { static fromJSON(json: object | string): AuthCredential | null; _getIdTokenResponse(auth: Auth): Promise; _linkToIdToken(auth: Auth, idToken: string): Promise; - _matchIdTokenWithUid(auth: Auth, uid: string): Promise; + _getReauthenticationResolver(auth: Auth): Promise; } diff --git a/packages-exp/auth-exp/src/core/credentials/phone.ts b/packages-exp/auth-exp/src/core/credentials/phone.ts index bbd8d9e048a..768ce056dc5 100644 --- a/packages-exp/auth-exp/src/core/credentials/phone.ts +++ b/packages-exp/auth-exp/src/core/credentials/phone.ts @@ -21,12 +21,12 @@ import { PhoneOrOauthTokenResponse } from '../../api/authentication/mfa'; import { linkWithPhoneNumber, signInWithPhoneNumber, - SignInWithPhoneNumberRequest + SignInWithPhoneNumberRequest, + verifyPhoneNumberForExisting } from '../../api/authentication/sms'; import { Auth } from '../../model/auth'; import { IdTokenResponse } from '../../model/id_token'; -import { debugFail } from '../util/assert'; -import { AuthCredential } from '.'; +import { AuthCredential } from './'; export interface PhoneAuthCredentialParameters { verificationId?: string; @@ -53,10 +53,8 @@ export class PhoneAuthCredential }); } - _matchIdTokenWithUid(auth: Auth, uid: string): Promise { - void auth; - void uid; - return debugFail('not implemented'); + _getReauthenticationResolver(auth: Auth): Promise { + return verifyPhoneNumberForExisting(auth, this.makeVerificationRequest()); } private makeVerificationRequest(): SignInWithPhoneNumberRequest { diff --git a/packages-exp/auth-exp/src/core/strategies/credential.test.ts b/packages-exp/auth-exp/src/core/strategies/credential.test.ts index 607859eb0ab..5d173aded57 100644 --- a/packages-exp/auth-exp/src/core/strategies/credential.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/credential.test.ts @@ -26,6 +26,7 @@ import { import { FirebaseError } from '@firebase/util'; import { mockEndpoint } from '../../../test/api/helper'; +import { makeJWT } from '../../../test/jwt'; import { testAuth, testUser } from '../../../test/mock_auth'; import { MockAuthCredential } from '../../../test/mock_auth_credential'; import * as mockFetch from '../../../test/mock_fetch'; @@ -37,6 +38,7 @@ import { User } from '../../model/user'; import { _assertLinkedStatus, linkWithCredential, + reauthenticateWithCredential, signInWithCredential } from './credential'; @@ -105,6 +107,38 @@ describe('core/strategies/credential', () => { }); }); + describe('reauthenticateWithCredential', () => { + it('should throw an error if the uid is mismatched', async () => { + authCredential._setIdTokenResponse({ + ...idTokenResponse, + idToken: makeJWT({ sub: 'not-my-uid' }) + }); + + await expect( + reauthenticateWithCredential(user, authCredential) + ).to.be.rejectedWith( + FirebaseError, + 'Firebase: The supplied credentials do not correspond to the previously signed in user. (auth/user-mismatch).' + ); + }); + + it('sould return the expected user credential', async () => { + authCredential._setIdTokenResponse({ + ...idTokenResponse, + idToken: makeJWT({ sub: 'uid' }) + }); + + const { + credential, + user: newUser, + operationType + } = await reauthenticateWithCredential(user, authCredential); + expect(operationType).to.eq(OperationType.REAUTHENTICATE); + expect(newUser).to.eq(user); + expect(credential).to.be.null; + }); + }); + describe('linkWithCredential', () => { it('should throw an error if the provider is already linked', async () => { getAccountInfoEndpoint.response = { diff --git a/packages-exp/auth-exp/src/core/strategies/credential.ts b/packages-exp/auth-exp/src/core/strategies/credential.ts index c2fe6ff18bb..df6eb66f44a 100644 --- a/packages-exp/auth-exp/src/core/strategies/credential.ts +++ b/packages-exp/auth-exp/src/core/strategies/credential.ts @@ -21,13 +21,15 @@ import { OperationType, UserCredential } from '@firebase/auth-types-exp'; import { PhoneOrOauthTokenResponse } from '../../api/authentication/mfa'; import { SignInWithPhoneNumberResponse } from '../../api/authentication/sms'; import { Auth } from '../../model/auth'; +import { IdTokenResponse } from '../../model/id_token'; import { User } from '../../model/user'; import { AuthCredential } from '../credentials'; import { PhoneAuthCredential } from '../credentials/phone'; import { AuthErrorCode } from '../errors'; +import { _parseToken } from '../user/id_token_result'; import { _reloadWithoutSaving } from '../user/reload'; import { UserCredentialImpl } from '../user/user_credential_impl'; -import { assert } from '../util/assert'; +import { assert, fail } from '../util/assert'; import { providerDataAsNames } from '../util/providers'; export async function signInWithCredential( @@ -61,9 +63,24 @@ export async function linkWithCredential( await user.getIdToken() ); - const newCred = _authCredentialFromTokenResponse(response); - await user._updateTokensIfNecessary(response, /* reload */ true); - return new UserCredentialImpl(user, newCred, OperationType.LINK); + return userCredForOperation(user, OperationType.LINK, response); +} + +export async function reauthenticateWithCredential( + userExtern: externs.User, + credentialExtern: externs.AuthCredential +): Promise { + const credential = credentialExtern as AuthCredential; + const user = userExtern as User; + + const { auth, uid } = user; + const response = await verifyTokenResponseUid( + credential._getReauthenticationResolver(auth), + uid, + auth.name + ); + + return userCredForOperation(user, OperationType.REAUTHENTICATE, response); } export function _authCredentialFromTokenResponse( @@ -95,3 +112,37 @@ export async function _assertLinkedStatus( : AuthErrorCode.NO_SUCH_PROVIDER; assert(providerIds.has(provider) === expected, user.auth.name, code); } + +async function verifyTokenResponseUid( + idTokenResolver: Promise, + uid: string, + appName: string +): Promise { + try { + const response = await idTokenResolver; + assert(response.idToken, appName, AuthErrorCode.INTERNAL_ERROR); + const parsed = _parseToken(response.idToken); + assert(parsed, appName, AuthErrorCode.INTERNAL_ERROR); + + const { sub: localId } = parsed; + assert(uid === localId, appName, AuthErrorCode.USER_MISMATCH); + + return response; + } catch (e) { + // Convert user deleted error into user mismatch + if (e?.code === `auth/${AuthErrorCode.USER_DELETED}`) { + fail(appName, AuthErrorCode.USER_MISMATCH); + } + throw e; + } +} + +async function userCredForOperation( + user: User, + opType: OperationType, + response: IdTokenResponse +): Promise { + const newCred = _authCredentialFromTokenResponse(response); + await user._updateTokensIfNecessary(response, /* reload */ true); + return new UserCredentialImpl(user, newCred, opType); +} diff --git a/packages-exp/auth-exp/src/core/strategies/phone.test.ts b/packages-exp/auth-exp/src/core/strategies/phone.test.ts index cd9f03e675a..4c94087ee08 100644 --- a/packages-exp/auth-exp/src/core/strategies/phone.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/phone.test.ts @@ -24,9 +24,11 @@ import { OperationType, ProviderId } from '@firebase/auth-types-exp'; import { FirebaseError } from '@firebase/util'; import { mockEndpoint } from '../../../test/api/helper'; +import { makeJWT } from '../../../test/jwt'; import { testAuth, testUser } from '../../../test/mock_auth'; import * as fetch from '../../../test/mock_fetch'; import { Endpoint } from '../../api'; +import { ApplicationVerifier } from '../../model/application_verifier'; import { Auth } from '../../model/auth'; import { IdTokenResponse } from '../../model/id_token'; import { User } from '../../model/user'; @@ -34,9 +36,9 @@ import { RecaptchaVerifier } from '../../platform_browser/recaptcha/recaptcha_ve import { _verifyPhoneNumber, linkWithPhoneNumber, + reauthenticateWithPhoneNumber, signInWithPhoneNumber } from './phone'; -import { ApplicationVerifier } from '../../model/application_verifier'; use(chaiAsPromised); use(sinonChai); @@ -189,6 +191,95 @@ describe('core/strategies/phone', () => { }); }); + describe('reauthenticateWithPhoneNumber', () => { + let user: User; + + beforeEach(() => { + mockEndpoint(Endpoint.GET_ACCOUNT_INFO, { + users: [{ uid: 'uid' }] + }); + + user = testUser(auth, 'uid', 'email', true); + }); + + it('calls verify phone number', async () => { + await reauthenticateWithPhoneNumber(user, '+15105550000', verifier); + + expect(sendCodeEndpoint.calls[0].request).to.eql({ + recaptchaToken: 'recaptcha-token', + phoneNumber: '+15105550000' + }); + }); + + context('ConfirmationResult', () => { + it('result contains verification id baked in', async () => { + const result = await reauthenticateWithPhoneNumber( + user, + 'number', + verifier + ); + expect(result.verificationId).to.eq('session-info'); + }); + + it('calling #confirm finishes the sign in flow', async () => { + const idTokenResponse: IdTokenResponse = { + idToken: makeJWT({ 'sub': 'uid' }), + refreshToken: 'my-refresh-token', + expiresIn: '1234', + localId: 'uid', + kind: 'my-kind' + }; + + // This endpoint is called from within the callback, in + // signInWithCredential + const signInEndpoint = mockEndpoint( + Endpoint.SIGN_IN_WITH_PHONE_NUMBER, + idTokenResponse + ); + mockEndpoint(Endpoint.GET_ACCOUNT_INFO, { + users: [{ localId: 'uid' }] + }); + + const result = await reauthenticateWithPhoneNumber( + user, + 'number', + verifier + ); + const userCred = await result.confirm('6789'); + expect(userCred.user.uid).to.eq('uid'); + expect(userCred.operationType).to.eq(OperationType.REAUTHENTICATE); + expect(signInEndpoint.calls[0].request).to.eql({ + sessionInfo: 'session-info', + code: '6789', + operation: 'REAUTH' + }); + }); + + it('rejects if the uid mismatches', async () => { + const idTokenResponse: IdTokenResponse = { + idToken: makeJWT({ 'sub': 'different-uid' }), + refreshToken: 'my-refresh-token', + expiresIn: '1234', + localId: 'uid', + kind: 'my-kind' + }; + // This endpoint is called from within the callback, in + // signInWithCredential + mockEndpoint(Endpoint.SIGN_IN_WITH_PHONE_NUMBER, idTokenResponse); + + const result = await reauthenticateWithPhoneNumber( + user, + 'number', + verifier + ); + await expect(result.confirm('code')).to.be.rejectedWith( + FirebaseError, + 'Firebase: The supplied credentials do not correspond to the previously signed in user. (auth/user-mismatch)' + ); + }); + }); + }); + describe('_verifyPhoneNumber', () => { it('works with a string phone number', async () => { await _verifyPhoneNumber(auth, 'number', verifier); diff --git a/packages-exp/auth-exp/src/core/strategies/phone.ts b/packages-exp/auth-exp/src/core/strategies/phone.ts index 28d101a41ca..68c6ed4b060 100644 --- a/packages-exp/auth-exp/src/core/strategies/phone.ts +++ b/packages-exp/auth-exp/src/core/strategies/phone.ts @@ -18,6 +18,7 @@ import * as externs from '@firebase/auth-types-exp'; import { sendPhoneVerificationCode } from '../../api/authentication/sms'; +import { ApplicationVerifier } from '../../model/application_verifier'; import { Auth } from '../../model/auth'; import { User } from '../../model/user'; import { RECAPTCHA_VERIFIER_TYPE } from '../../platform_browser/recaptcha/recaptcha_verifier'; @@ -27,9 +28,9 @@ import { assert } from '../util/assert'; import { _assertLinkedStatus, linkWithCredential, + reauthenticateWithCredential, signInWithCredential } from './credential'; -import { ApplicationVerifier } from '../../model/application_verifier'; interface OnConfirmationCallback { (credential: PhoneAuthCredential): Promise; @@ -82,6 +83,22 @@ export async function linkWithPhoneNumber( ); } +export async function reauthenticateWithPhoneNumber( + userExtern: externs.User, + phoneNumber: string, + appVerifier: externs.ApplicationVerifier +): Promise { + const user = userExtern as User; + const verificationId = await _verifyPhoneNumber( + user.auth, + phoneNumber, + appVerifier as ApplicationVerifier + ); + return new ConfirmationResult(verificationId, cred => + reauthenticateWithCredential(user, cred) + ); +} + /** * Returns a verification ID to be used in conjunction with the SMS code that * is sent. diff --git a/packages-exp/auth-exp/src/core/user/id_token_result.ts b/packages-exp/auth-exp/src/core/user/id_token_result.ts index 11343e0441d..6312178bd92 100644 --- a/packages-exp/auth-exp/src/core/user/id_token_result.ts +++ b/packages-exp/auth-exp/src/core/user/id_token_result.ts @@ -35,7 +35,7 @@ export async function getIdTokenResult( ): Promise { const user = externUser as User; const token = await user.getIdToken(forceRefresh); - const claims = parseToken(token); + const claims = _parseToken(token); assert( claims && claims.exp && claims.auth_time && claims.iat, @@ -83,7 +83,7 @@ function utcTimestampToDateString(timestamp: string | number): string { return ''; // TODO(avolkovi): is this the right fallback? } -function parseToken(token: string): externs.ParsedToken | null { +export function _parseToken(token: string): externs.ParsedToken | null { const [algorithm, payload, signature] = token.split('.'); if ( algorithm === undefined || diff --git a/packages-exp/auth-exp/src/index.ts b/packages-exp/auth-exp/src/index.ts index 5aba137bad3..5a8f415ea3e 100644 --- a/packages-exp/auth-exp/src/index.ts +++ b/packages-exp/auth-exp/src/index.ts @@ -67,7 +67,8 @@ export { PhoneAuthProvider } from './core/providers/phone'; export { signInAnonymously } from './core/strategies/anonymous'; export { signInWithCredential, - linkWithCredential + linkWithCredential, + reauthenticateWithCredential } from './core/strategies/credential'; export { signInWithCustomToken } from './core/strategies/custom_token'; export { @@ -90,7 +91,8 @@ export { } from './core/strategies/email'; export { signInWithPhoneNumber, - linkWithPhoneNumber + linkWithPhoneNumber, + reauthenticateWithPhoneNumber } from './core/strategies/phone'; // core diff --git a/packages-exp/auth-exp/test/mock_auth_credential.ts b/packages-exp/auth-exp/test/mock_auth_credential.ts index 12ec8e1ae16..44b505d80d0 100644 --- a/packages-exp/auth-exp/test/mock_auth_credential.ts +++ b/packages-exp/auth-exp/test/mock_auth_credential.ts @@ -18,9 +18,9 @@ import { ProviderId, SignInMethod } from '@firebase/auth-types-exp'; import { PhoneOrOauthTokenResponse } from '../src/api/authentication/mfa'; +import { AuthCredential } from '../src/core/credentials'; import { Auth } from '../src/model/auth'; import { IdTokenResponse } from '../src/model/id_token'; -import { AuthCredential } from '../src/core/credentials'; export class MockAuthCredential implements AuthCredential { response?: PhoneOrOauthTokenResponse; @@ -57,7 +57,7 @@ export class MockAuthCredential implements AuthCredential { return this.response!; } - _matchIdTokenWithUid(_auth: Auth, _uid: string): Promise { - throw new Error('Method not implemented.'); + async _getReauthenticationResolver(_auth: Auth): Promise { + return this.response!; } } diff --git a/packages-exp/auth-types-exp/index.d.ts b/packages-exp/auth-types-exp/index.d.ts index 604a9d8306e..5caebb212b4 100644 --- a/packages-exp/auth-types-exp/index.d.ts +++ b/packages-exp/auth-types-exp/index.d.ts @@ -80,6 +80,7 @@ export interface Config { */ export interface ParsedToken { 'exp'?: string; + 'sub'?: string; 'auth_time'?: string; 'iat'?: string; 'firebase'?: {