From 9d6be7c9f27925de6a34c9cd6237f3f535059a99 Mon Sep 17 00:00:00 2001 From: Sam Olsen Date: Tue, 16 Jun 2020 14:32:26 -0700 Subject: [PATCH 1/7] Add reauthenticateWithCredential and reauthenticateWithPhoneNumber --- .../src/core/credentials/anonymous.test.ts | 16 ++-- .../src/core/credentials/anonymous.ts | 5 +- .../src/core/credentials/email.test.ts | 18 ++-- .../auth-exp/src/core/credentials/email.ts | 7 +- .../auth-exp/src/core/credentials/index.d.ts | 5 +- .../auth-exp/src/core/credentials/phone.ts | 14 ++- .../src/core/strategies/credential.test.ts | 41 +++++++-- .../src/core/strategies/credential.ts | 49 +++++++++- .../src/core/strategies/phone.test.ts | 89 ++++++++++++++++++- .../auth-exp/src/core/strategies/phone.ts | 22 ++++- .../auth-exp/src/core/user/id_token_result.ts | 6 +- .../auth-exp/test/mock_auth_credential.ts | 6 +- packages-exp/auth-types-exp/index.d.ts | 9 +- 13 files changed, 226 insertions(+), 61 deletions(-) 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..105a4c60851 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,10 +97,10 @@ describe('core/credentials/email', () => { }); }); - describe('#_matchIdTokenWithUid', () => { + describe('#_getReauthenticationResolver', () => { it('throws', () => { expect(() => - credential._matchIdTokenWithUid(auth, 'other-uid') + credential._getReauthenticationResolver(auth) ).to.throw(Error); }); }); @@ -161,7 +163,7 @@ describe('core/credentials/email', () => { describe('#_matchIdTokenWithUid', () => { it('throws', () => { expect(() => - credential._matchIdTokenWithUid(auth, 'other-uid') + 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..f8b2b1f7603 100644 --- a/packages-exp/auth-exp/src/core/credentials/phone.ts +++ b/packages-exp/auth-exp/src/core/credentials/phone.ts @@ -19,14 +19,12 @@ import * as externs from '@firebase/auth-types-exp'; import { PhoneOrOauthTokenResponse } from '../../api/authentication/mfa'; import { - linkWithPhoneNumber, - signInWithPhoneNumber, - SignInWithPhoneNumberRequest + linkWithPhoneNumber, signInWithPhoneNumber, 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 +51,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..97e5056f180 100644 --- a/packages-exp/auth-exp/src/core/strategies/credential.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/credential.test.ts @@ -18,14 +18,11 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import { - OperationType, - ProviderId, - SignInMethod -} from '@firebase/auth-types-exp'; +import { OperationType, ProviderId, SignInMethod } 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 { MockAuthCredential } from '../../../test/mock_auth_credential'; import * as mockFetch from '../../../test/mock_fetch'; @@ -35,9 +32,7 @@ import { Auth } from '../../model/auth'; import { IdTokenResponse } from '../../model/id_token'; import { User } from '../../model/user'; import { - _assertLinkedStatus, - linkWithCredential, - signInWithCredential + _assertLinkedStatus, linkWithCredential, reauthenticateWithCredential, signInWithCredential } from './credential'; use(chaiAsPromised); @@ -105,6 +100,36 @@ 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..7cc950c4564 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( @@ -66,6 +68,25 @@ export async function linkWithCredential( return new UserCredentialImpl(user, newCred, OperationType.LINK); } +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); + const newCred = _authCredentialFromTokenResponse(response); + + await user._updateTokensIfNecessary(response, /* reload */ true); + return new UserCredentialImpl( + user, + newCred, + OperationType.REAUTHENTICATE + ); +} + export function _authCredentialFromTokenResponse( response: PhoneOrOauthTokenResponse ): AuthCredential | null { @@ -95,3 +116,29 @@ 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 { + const code = AuthErrorCode.USER_MISMATCH; + try { + const response = await idTokenResolver; + assert(response.idToken, appName, code); + const parsed = _parseToken(response.idToken); + assert(parsed, appName, AuthErrorCode.INTERNAL_ERROR); + + const { sub: localId } = parsed; + assert(uid === localId, appName, code); + + return response; + } catch (e) { + // Convert user deleted error into user mismatch + if (e?.code === `auth/${AuthErrorCode.USER_DELETED}`) { + fail(appName, code); + } + throw e; + } +} \ No newline at end of file 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..109728aa163 100644 --- a/packages-exp/auth-exp/src/core/strategies/phone.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/phone.test.ts @@ -24,19 +24,18 @@ 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'; import { RecaptchaVerifier } from '../../platform_browser/recaptcha/recaptcha_verifier'; import { - _verifyPhoneNumber, - linkWithPhoneNumber, - signInWithPhoneNumber + _verifyPhoneNumber, linkWithPhoneNumber, reauthenticateWithPhoneNumber, signInWithPhoneNumber } from './phone'; -import { ApplicationVerifier } from '../../model/application_verifier'; use(chaiAsPromised); use(sinonChai); @@ -189,6 +188,88 @@ 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..b8f49d57ca4 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'; @@ -25,11 +26,8 @@ import { PhoneAuthCredential } from '../credentials/phone'; import { AuthErrorCode } from '../errors'; import { assert } from '../util/assert'; import { - _assertLinkedStatus, - linkWithCredential, - signInWithCredential + _assertLinkedStatus, linkWithCredential, reauthenticateWithCredential, signInWithCredential } from './credential'; -import { ApplicationVerifier } from '../../model/application_verifier'; interface OnConfirmationCallback { (credential: PhoneAuthCredential): Promise; @@ -82,6 +80,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..014f69627d6 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 || @@ -105,4 +105,4 @@ function parseToken(token: string): externs.ParsedToken | null { _logError('Caught error parsing JWT payload as JSON', e); return null; } -} +} \ No newline at end of file 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..1d32ba0a3bb 100644 --- a/packages-exp/auth-types-exp/index.d.ts +++ b/packages-exp/auth-types-exp/index.d.ts @@ -15,13 +15,7 @@ * limitations under the License. */ -import { - CompleteFn, - ErrorFn, - NextFn, - Observer, - Unsubscribe -} from '@firebase/util'; +import { CompleteFn, ErrorFn, NextFn, Observer, Unsubscribe } from '@firebase/util'; /** * Supported providers @@ -80,6 +74,7 @@ export interface Config { */ export interface ParsedToken { 'exp'?: string; + 'sub'?: string; 'auth_time'?: string; 'iat'?: string; 'firebase'?: { From 42a291b4806f533c4f9419c4453ba2402c948897 Mon Sep 17 00:00:00 2001 From: Sam Olsen Date: Tue, 16 Jun 2020 14:40:16 -0700 Subject: [PATCH 2/7] Add reauth methods to index.ts --- packages-exp/auth-exp/src/index.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages-exp/auth-exp/src/index.ts b/packages-exp/auth-exp/src/index.ts index 5aba137bad3..4725f24fc10 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 From faefe2e83695aa905996cdc8fd5b3b9bc2625cb8 Mon Sep 17 00:00:00 2001 From: Sam Olsen Date: Tue, 16 Jun 2020 14:40:43 -0700 Subject: [PATCH 3/7] Formatting --- .../src/core/credentials/email.test.ts | 12 +++--- .../auth-exp/src/core/credentials/phone.ts | 6 ++- .../src/core/strategies/credential.test.ts | 19 +++++++--- .../src/core/strategies/credential.ts | 17 ++++----- .../src/core/strategies/phone.test.ts | 38 ++++++++++++------- .../auth-exp/src/core/strategies/phone.ts | 5 ++- .../auth-exp/src/core/user/id_token_result.ts | 2 +- packages-exp/auth-exp/src/index.ts | 2 +- packages-exp/auth-types-exp/index.d.ts | 8 +++- 9 files changed, 69 insertions(+), 40 deletions(-) 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 105a4c60851..323532d2fce 100644 --- a/packages-exp/auth-exp/src/core/credentials/email.test.ts +++ b/packages-exp/auth-exp/src/core/credentials/email.test.ts @@ -99,9 +99,9 @@ describe('core/credentials/email', () => { describe('#_getReauthenticationResolver', () => { it('throws', () => { - expect(() => - credential._getReauthenticationResolver(auth) - ).to.throw(Error); + expect(() => credential._getReauthenticationResolver(auth)).to.throw( + Error + ); }); }); }); @@ -162,9 +162,9 @@ describe('core/credentials/email', () => { describe('#_matchIdTokenWithUid', () => { it('throws', () => { - expect(() => - credential._getReauthenticationResolver(auth) - ).to.throw(Error); + expect(() => credential._getReauthenticationResolver(auth)).to.throw( + Error + ); }); }); }); diff --git a/packages-exp/auth-exp/src/core/credentials/phone.ts b/packages-exp/auth-exp/src/core/credentials/phone.ts index f8b2b1f7603..768ce056dc5 100644 --- a/packages-exp/auth-exp/src/core/credentials/phone.ts +++ b/packages-exp/auth-exp/src/core/credentials/phone.ts @@ -19,8 +19,10 @@ import * as externs from '@firebase/auth-types-exp'; import { PhoneOrOauthTokenResponse } from '../../api/authentication/mfa'; import { - linkWithPhoneNumber, signInWithPhoneNumber, SignInWithPhoneNumberRequest, - verifyPhoneNumberForExisting + linkWithPhoneNumber, + signInWithPhoneNumber, + SignInWithPhoneNumberRequest, + verifyPhoneNumberForExisting } from '../../api/authentication/sms'; import { Auth } from '../../model/auth'; import { IdTokenResponse } from '../../model/id_token'; 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 97e5056f180..5d173aded57 100644 --- a/packages-exp/auth-exp/src/core/strategies/credential.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/credential.test.ts @@ -18,7 +18,11 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import { OperationType, ProviderId, SignInMethod } from '@firebase/auth-types-exp'; +import { + OperationType, + ProviderId, + SignInMethod +} from '@firebase/auth-types-exp'; import { FirebaseError } from '@firebase/util'; import { mockEndpoint } from '../../../test/api/helper'; @@ -32,7 +36,10 @@ import { Auth } from '../../model/auth'; import { IdTokenResponse } from '../../model/id_token'; import { User } from '../../model/user'; import { - _assertLinkedStatus, linkWithCredential, reauthenticateWithCredential, signInWithCredential + _assertLinkedStatus, + linkWithCredential, + reauthenticateWithCredential, + signInWithCredential } from './credential'; use(chaiAsPromised); @@ -104,10 +111,12 @@ describe('core/strategies/credential', () => { it('should throw an error if the uid is mismatched', async () => { authCredential._setIdTokenResponse({ ...idTokenResponse, - idToken: makeJWT({sub: 'not-my-uid'}), + idToken: makeJWT({ sub: 'not-my-uid' }) }); - await expect(reauthenticateWithCredential(user, authCredential)).to.be.rejectedWith( + 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).' ); @@ -116,7 +125,7 @@ describe('core/strategies/credential', () => { it('sould return the expected user credential', async () => { authCredential._setIdTokenResponse({ ...idTokenResponse, - idToken: makeJWT({sub: 'uid'}), + idToken: makeJWT({ sub: 'uid' }) }); const { diff --git a/packages-exp/auth-exp/src/core/strategies/credential.ts b/packages-exp/auth-exp/src/core/strategies/credential.ts index 7cc950c4564..23eb6d6ca90 100644 --- a/packages-exp/auth-exp/src/core/strategies/credential.ts +++ b/packages-exp/auth-exp/src/core/strategies/credential.ts @@ -75,16 +75,16 @@ export async function reauthenticateWithCredential( const credential = credentialExtern as AuthCredential; const user = userExtern as User; - const {auth, uid} = user; - const response = await verifyTokenResponseUid(credential._getReauthenticationResolver(auth), uid, auth.name); + const { auth, uid } = user; + const response = await verifyTokenResponseUid( + credential._getReauthenticationResolver(auth), + uid, + auth.name + ); const newCred = _authCredentialFromTokenResponse(response); await user._updateTokensIfNecessary(response, /* reload */ true); - return new UserCredentialImpl( - user, - newCred, - OperationType.REAUTHENTICATE - ); + return new UserCredentialImpl(user, newCred, OperationType.REAUTHENTICATE); } export function _authCredentialFromTokenResponse( @@ -117,7 +117,6 @@ export async function _assertLinkedStatus( assert(providerIds.has(provider) === expected, user.auth.name, code); } - async function verifyTokenResponseUid( idTokenResolver: Promise, uid: string, @@ -141,4 +140,4 @@ async function verifyTokenResponseUid( } throw e; } -} \ No newline at end of file +} 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 109728aa163..4c94087ee08 100644 --- a/packages-exp/auth-exp/src/core/strategies/phone.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/phone.test.ts @@ -34,7 +34,10 @@ import { IdTokenResponse } from '../../model/id_token'; import { User } from '../../model/user'; import { RecaptchaVerifier } from '../../platform_browser/recaptcha/recaptcha_verifier'; import { - _verifyPhoneNumber, linkWithPhoneNumber, reauthenticateWithPhoneNumber, signInWithPhoneNumber + _verifyPhoneNumber, + linkWithPhoneNumber, + reauthenticateWithPhoneNumber, + signInWithPhoneNumber } from './phone'; use(chaiAsPromised); @@ -210,13 +213,17 @@ describe('core/strategies/phone', () => { context('ConfirmationResult', () => { it('result contains verification id baked in', async () => { - const result = await reauthenticateWithPhoneNumber(user, 'number', verifier); + 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'}), + idToken: makeJWT({ 'sub': 'uid' }), refreshToken: 'my-refresh-token', expiresIn: '1234', localId: 'uid', @@ -233,20 +240,24 @@ describe('core/strategies/phone', () => { users: [{ localId: 'uid' }] }); - const result = await reauthenticateWithPhoneNumber(user, 'number', verifier); + 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', + operation: 'REAUTH' }); }); it('rejects if the uid mismatches', async () => { const idTokenResponse: IdTokenResponse = { - idToken: makeJWT({'sub': 'different-uid'}), + idToken: makeJWT({ 'sub': 'different-uid' }), refreshToken: 'my-refresh-token', expiresIn: '1234', localId: 'uid', @@ -254,15 +265,14 @@ describe('core/strategies/phone', () => { }; // This endpoint is called from within the callback, in // signInWithCredential - mockEndpoint( - Endpoint.SIGN_IN_WITH_PHONE_NUMBER, - idTokenResponse + mockEndpoint(Endpoint.SIGN_IN_WITH_PHONE_NUMBER, idTokenResponse); + + const result = await reauthenticateWithPhoneNumber( + user, + 'number', + verifier ); - - const result = await reauthenticateWithPhoneNumber(user, 'number', verifier); - await expect( - result.confirm('code') - ).to.be.rejectedWith( + 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)' ); diff --git a/packages-exp/auth-exp/src/core/strategies/phone.ts b/packages-exp/auth-exp/src/core/strategies/phone.ts index b8f49d57ca4..68c6ed4b060 100644 --- a/packages-exp/auth-exp/src/core/strategies/phone.ts +++ b/packages-exp/auth-exp/src/core/strategies/phone.ts @@ -26,7 +26,10 @@ import { PhoneAuthCredential } from '../credentials/phone'; import { AuthErrorCode } from '../errors'; import { assert } from '../util/assert'; import { - _assertLinkedStatus, linkWithCredential, reauthenticateWithCredential, signInWithCredential + _assertLinkedStatus, + linkWithCredential, + reauthenticateWithCredential, + signInWithCredential } from './credential'; interface OnConfirmationCallback { 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 014f69627d6..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 @@ -105,4 +105,4 @@ export function _parseToken(token: string): externs.ParsedToken | null { _logError('Caught error parsing JWT payload as JSON', e); return null; } -} \ No newline at end of file +} diff --git a/packages-exp/auth-exp/src/index.ts b/packages-exp/auth-exp/src/index.ts index 4725f24fc10..5a8f415ea3e 100644 --- a/packages-exp/auth-exp/src/index.ts +++ b/packages-exp/auth-exp/src/index.ts @@ -92,7 +92,7 @@ export { export { signInWithPhoneNumber, linkWithPhoneNumber, - reauthenticateWithPhoneNumber, + reauthenticateWithPhoneNumber } from './core/strategies/phone'; // core diff --git a/packages-exp/auth-types-exp/index.d.ts b/packages-exp/auth-types-exp/index.d.ts index 1d32ba0a3bb..5caebb212b4 100644 --- a/packages-exp/auth-types-exp/index.d.ts +++ b/packages-exp/auth-types-exp/index.d.ts @@ -15,7 +15,13 @@ * limitations under the License. */ -import { CompleteFn, ErrorFn, NextFn, Observer, Unsubscribe } from '@firebase/util'; +import { + CompleteFn, + ErrorFn, + NextFn, + Observer, + Unsubscribe +} from '@firebase/util'; /** * Supported providers From 3bdb1fcb7b0e3f9ee1b73a3c880d1fb031c4eb1a Mon Sep 17 00:00:00 2001 From: Sam Olsen Date: Tue, 16 Jun 2020 14:55:58 -0700 Subject: [PATCH 4/7] Add new methods to the demo --- packages-exp/auth-exp/demo/src/index.js | 26 ++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/packages-exp/auth-exp/demo/src/index.js b/packages-exp/auth-exp/demo/src/index.js index d0bb974091d..d320634f9e1 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,16 @@ 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,8 +559,7 @@ function onReauthConfirmPhoneVerification() { verificationId, verificationCode ); - activeUser() - .reauthenticateWithCredential(credential) + reauthenticateWithCredential(activeUser(), credential) .then(function(result) { logAdditionalUserInfo(result); refreshUserData(); From 051fe3f4646e79e203d98d64de3acdaf78380f69 Mon Sep 17 00:00:00 2001 From: Sam Olsen Date: Tue, 16 Jun 2020 14:56:26 -0700 Subject: [PATCH 5/7] Formatting --- packages-exp/auth-exp/demo/src/index.js | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/packages-exp/auth-exp/demo/src/index.js b/packages-exp/auth-exp/demo/src/index.js index d320634f9e1..1a559e47257 100644 --- a/packages-exp/auth-exp/demo/src/index.js +++ b/packages-exp/auth-exp/demo/src/index.js @@ -377,14 +377,12 @@ function onLinkWithEmailLink() { function onReauthenticateWithEmailLink() { 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); + var credential = EmailAuthProvider.credentialWithLink(email, link); + reauthenticateWithCredential(activeUser(), credential).then(function(result) { + logAdditionalUserInfo(result); + refreshUserData(); + alertSuccess('User reauthenticated!'); + }, onAuthError); } /** @@ -559,12 +557,11 @@ function onReauthConfirmPhoneVerification() { verificationId, verificationCode ); - reauthenticateWithCredential(activeUser(), credential) - .then(function(result) { - logAdditionalUserInfo(result); - refreshUserData(); - alertSuccess('User reauthenticated!'); - }, onAuthError); + reauthenticateWithCredential(activeUser(), credential).then(function(result) { + logAdditionalUserInfo(result); + refreshUserData(); + alertSuccess('User reauthenticated!'); + }, onAuthError); } /** From 7c369617c8a3d0a4f0f71b083ac2efae4625392a Mon Sep 17 00:00:00 2001 From: Sam Olsen Date: Tue, 16 Jun 2020 15:08:49 -0700 Subject: [PATCH 6/7] PR feedback --- .../src/core/strategies/credential.ts | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/packages-exp/auth-exp/src/core/strategies/credential.ts b/packages-exp/auth-exp/src/core/strategies/credential.ts index 23eb6d6ca90..34e20baad67 100644 --- a/packages-exp/auth-exp/src/core/strategies/credential.ts +++ b/packages-exp/auth-exp/src/core/strategies/credential.ts @@ -63,9 +63,7 @@ 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( @@ -81,10 +79,8 @@ export async function reauthenticateWithCredential( uid, auth.name ); - const newCred = _authCredentialFromTokenResponse(response); - await user._updateTokensIfNecessary(response, /* reload */ true); - return new UserCredentialImpl(user, newCred, OperationType.REAUTHENTICATE); + return userCredForOperation(user, OperationType.REAUTHENTICATE, response); } export function _authCredentialFromTokenResponse( @@ -122,22 +118,27 @@ async function verifyTokenResponseUid( uid: string, appName: string ): Promise { - const code = AuthErrorCode.USER_MISMATCH; try { const response = await idTokenResolver; - assert(response.idToken, appName, code); + 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, code); + 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, code); + 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); +} From bec15b4f9977b22b2bebd110138d0d0849ebfc88 Mon Sep 17 00:00:00 2001 From: Sam Olsen Date: Tue, 16 Jun 2020 15:09:21 -0700 Subject: [PATCH 7/7] Formatting --- packages-exp/auth-exp/src/core/strategies/credential.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages-exp/auth-exp/src/core/strategies/credential.ts b/packages-exp/auth-exp/src/core/strategies/credential.ts index 34e20baad67..df6eb66f44a 100644 --- a/packages-exp/auth-exp/src/core/strategies/credential.ts +++ b/packages-exp/auth-exp/src/core/strategies/credential.ts @@ -137,7 +137,11 @@ async function verifyTokenResponseUid( } } -async function userCredForOperation(user: User, opType: OperationType, response: IdTokenResponse): Promise { +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);