From d718ca46a4002504456751cbf96f05098134f625 Mon Sep 17 00:00:00 2001 From: Sam Olsen Date: Mon, 8 Jun 2020 15:43:15 -0700 Subject: [PATCH 1/4] Add signInWithPhoneNumber flow --- packages-exp/auth-exp/src/api/index.ts | 7 +- .../auth-exp/src/core/providers/phone.test.ts | 73 +++++++++ .../auth-exp/src/core/providers/phone.ts | 51 ++++++ .../src/core/strategies/phone.test.ts | 154 ++++++++++++++++++ .../auth-exp/src/core/strategies/phone.ts | 76 +++++++++ .../core/strategies/phone_credential.test.ts | 141 ++++++++++++++++ .../src/core/strategies/phone_credential.ts | 92 +++++++++++ packages-exp/auth-exp/src/index.ts | 4 + packages-exp/auth-exp/test/mock_fetch.ts | 1 + packages-exp/auth-types-exp/index.d.ts | 19 ++- 10 files changed, 609 insertions(+), 9 deletions(-) create mode 100644 packages-exp/auth-exp/src/core/providers/phone.test.ts create mode 100644 packages-exp/auth-exp/src/core/providers/phone.ts create mode 100644 packages-exp/auth-exp/src/core/strategies/phone.test.ts create mode 100644 packages-exp/auth-exp/src/core/strategies/phone.ts create mode 100644 packages-exp/auth-exp/src/core/strategies/phone_credential.test.ts create mode 100644 packages-exp/auth-exp/src/core/strategies/phone_credential.ts diff --git a/packages-exp/auth-exp/src/api/index.ts b/packages-exp/auth-exp/src/api/index.ts index 952bfcc41ff..f24b8a56d01 100644 --- a/packages-exp/auth-exp/src/api/index.ts +++ b/packages-exp/auth-exp/src/api/index.ts @@ -21,12 +21,7 @@ import { AUTH_ERROR_FACTORY, AuthErrorCode } from '../core/errors'; import { Delay } from '../core/util/delay'; import { Auth } from '../model/auth'; import { IdTokenResponse } from '../model/id_token'; -import { - JsonError, - SERVER_ERROR_MAP, - ServerError, - ServerErrorMap -} from './errors'; +import { JsonError, SERVER_ERROR_MAP, ServerError, ServerErrorMap } from './errors'; export enum HttpMethod { POST = 'POST', diff --git a/packages-exp/auth-exp/src/core/providers/phone.test.ts b/packages-exp/auth-exp/src/core/providers/phone.test.ts new file mode 100644 index 00000000000..d5430be097a --- /dev/null +++ b/packages-exp/auth-exp/src/core/providers/phone.test.ts @@ -0,0 +1,73 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from 'chai'; +import * as sinon from 'sinon'; + +import { mockEndpoint } from '../../../test/api/helper'; +import { testAuth } from '../../../test/mock_auth'; +import * as fetch from '../../../test/mock_fetch'; +import { Endpoint } from '../../api'; +import { Auth } from '../../model/auth'; +import { RecaptchaVerifier } from '../../platform_browser/recaptcha/recaptcha_verifier'; +import { PhoneAuthProvider } from './phone'; + +describe('core/providers/phone', () => { + let auth: Auth; + + beforeEach(async () => { + fetch.setUp(); + auth = await testAuth(); + }); + + afterEach(() => { + fetch.tearDown(); + sinon.restore(); + }); + + context('#verifyPhoneNumber', () => { + it('calls verify on the appVerifier and then calls the server', async () => { + const route = mockEndpoint(Endpoint.SEND_VERIFICATION_CODE, { + sessionInfo: 'verification-id', + }); + + auth.settings.appVerificationDisabledForTesting = true; + const verifier = new RecaptchaVerifier(document.createElement('div'), {}, auth); + sinon.stub(verifier, 'verify').returns(Promise.resolve('verification-code')); + + const provider = new PhoneAuthProvider(auth); + const result = await provider.verifyPhoneNumber('+15105550000', verifier); + expect(result).to.eq('verification-id'); + expect(route.calls[0].request).to.eql({ + phoneNumber: '+15105550000', + recaptchaToken: 'verification-code', + }); + }); + }); + + context('.credential', () => { + it('creates a phone auth credential', () => { + const credential = PhoneAuthProvider.credential('id', 'code'); + + // Allows us to inspect the object + const blob = credential.toJSON() as {[key: string]: string}; + + expect(blob.verificationId).to.eq('id'); + expect(blob.verificationCode).to.eq('code'); + }); + }); +}); \ No newline at end of file diff --git a/packages-exp/auth-exp/src/core/providers/phone.ts b/packages-exp/auth-exp/src/core/providers/phone.ts new file mode 100644 index 00000000000..84159d67284 --- /dev/null +++ b/packages-exp/auth-exp/src/core/providers/phone.ts @@ -0,0 +1,51 @@ +import * as externs from '@firebase/auth-types-exp'; +import { FirebaseError } from '@firebase/util'; + +import { Auth } from '../../model/auth'; +import { initializeAuth } from '../auth/auth_impl'; +import { _verifyPhoneNumber } from '../strategies/phone'; +import { PhoneAuthCredential } from '../strategies/phone_credential'; +import { debugFail } from '../util/assert'; + +export class PhoneAuthProvider implements externs.AuthProvider { + static readonly PROVIDER_ID = externs.ProviderId.PHONE; + static readonly PHONE_SIGN_IN_METHOD = externs.SignInMethod.PHONE; + + private readonly auth: Auth; + readonly providerId = PhoneAuthProvider.PROVIDER_ID; + + constructor(auth?: externs.Auth | null) { + this.auth = (auth || initializeAuth()) as Auth; + } + + verifyPhoneNumber( + phoneNumber: string, + applicationVerifier: externs.ApplicationVerifier, + /* multiFactorSession?: MultiFactorSession, */ + ): Promise { + return _verifyPhoneNumber(this.auth, phoneNumber, applicationVerifier); + } + + static credential( + verificationId: string, + verificationCode: string + ): PhoneAuthCredential { + return new PhoneAuthCredential({verificationId, verificationCode}); + } + + static credentialFromResult( + userCredential: externs.UserCredential): externs.AuthCredential | null { + void userCredential; + return debugFail('not implemented'); + } + + static credentialFromError(error: FirebaseError): externs.AuthCredential | null { + void error; + return debugFail('not implemented'); + } + + static credentialFromJSON(json: string|object): externs.AuthCredential { + void json; + return debugFail('not implemented'); + } +} \ 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 new file mode 100644 index 00000000000..b6d75043cf4 --- /dev/null +++ b/packages-exp/auth-exp/src/core/strategies/phone.test.ts @@ -0,0 +1,154 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect, use } from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import * as sinon from 'sinon'; +import * as sinonChai from 'sinon-chai'; + +import { ApplicationVerifier, OperationType } from '@firebase/auth-types-exp'; +import { FirebaseError } from '@firebase/util'; + +import { mockEndpoint } from '../../../test/api/helper'; +import { testAuth } from '../../../test/mock_auth'; +import * as fetch from '../../../test/mock_fetch'; +import { Endpoint } from '../../api'; +import { Auth } from '../../model/auth'; +import { IdTokenResponse } from '../../model/id_token'; +import { RecaptchaVerifier } from '../../platform_browser/recaptcha/recaptcha_verifier'; +import { _verifyPhoneNumber, signInWithPhoneNumber } from './phone'; + +use(chaiAsPromised); +use(sinonChai); + + describe('core/strategies/phone', () => { + let auth: Auth; + let verifier: ApplicationVerifier; + let sendCodeEndpoint: fetch.Route; + + beforeEach(async () => { + auth = await testAuth(); + auth.settings.appVerificationDisabledForTesting = true; + fetch.setUp(); + + sendCodeEndpoint = mockEndpoint(Endpoint.SEND_VERIFICATION_CODE, { + sessionInfo: 'session-info', + }); + + verifier = new RecaptchaVerifier(document.createElement('div'), {}, auth); + sinon.stub(verifier, 'verify').returns(Promise.resolve('recaptcha-token')); + }); + + afterEach(() => { + fetch.tearDown(); + sinon.restore(); + }); + + describe('signInWithPhoneNumber', () => { + it('calls verify phone number', async () => { + await signInWithPhoneNumber(auth, '+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 signInWithPhoneNumber(auth, 'number', verifier); + expect(result.verificationId).to.eq('session-info'); + }); + + it('calling #confirm finishes the sign in flow', async () => { + const idTokenResponse: IdTokenResponse = { + idToken: 'my-id-token', + refreshToken: 'my-refresh-token', + expiresIn: '1234', + localId: 'uid', + kind: 'my-kind' + }; + const signInEndpoint = mockEndpoint(Endpoint.SIGN_IN_WITH_PHONE_NUMBER, idTokenResponse); + mockEndpoint(Endpoint.GET_ACCOUNT_INFO, { + users: [ + {localId: 'uid'} + ] + }); + + const result = await signInWithPhoneNumber(auth, 'number', verifier); + const userCred = await result.confirm('6789'); + expect(userCred.user.uid).to.eq('uid'); + expect(userCred.operationType).to.eq(OperationType.SIGN_IN); + expect(signInEndpoint.calls[0].request).to.eql({ + sessionInfo: 'session-info', + code: '6789', + }); + }); + }); + }); + + describe('_verifyPhoneNumber', () => { + it('works with a string phone number', async () => { + await _verifyPhoneNumber(auth, 'number', verifier); + expect(sendCodeEndpoint.calls[0].request).to.eql({ + recaptchaToken: 'recaptcha-token', + phoneNumber: 'number', + }); + }); + + it('works with an options object', async () => { + await _verifyPhoneNumber(auth, { + phoneNumber: 'number', + }, verifier); + expect(sendCodeEndpoint.calls[0].request).to.eql({ + recaptchaToken: 'recaptcha-token', + phoneNumber: 'number' + }); + }); + + it('throws if the verifier does not return a string', async () => { + (verifier.verify as sinon.SinonStub).returns(Promise.resolve(123)); + await expect(_verifyPhoneNumber(auth, 'number', verifier)).to.be.rejectedWith( + FirebaseError, + 'Firebase: Error (auth/argument-error)', + ); + }); + + it('throws if the verifier type is not recaptcha', async () => { + const mutVerifier: {-readonly [K in keyof ApplicationVerifier]: ApplicationVerifier[K] } = verifier; + mutVerifier.type = 'not-recaptcha-thats-for-sure'; + await expect(_verifyPhoneNumber(auth, 'number', mutVerifier)).to.be.rejectedWith( + FirebaseError, + 'Firebase: Error (auth/argument-error)', + ); + }); + + it('resets the verifer after successful verification', async () => { + sinon.spy(verifier, 'reset'); + expect(await _verifyPhoneNumber(auth, 'number', verifier)).to.eq('session-info'); + expect(verifier.reset).to.have.been.called; + }); + + it('resets the verifer after a failed verification', async () => { + sinon.spy(verifier, 'reset'); + (verifier.verify as sinon.SinonStub).returns(Promise.resolve(123)); + + await expect(_verifyPhoneNumber(auth, 'number', verifier)).to.be.rejected; + expect(verifier.reset).to.have.been.called; + }); + }); +}); \ No newline at end of file diff --git a/packages-exp/auth-exp/src/core/strategies/phone.ts b/packages-exp/auth-exp/src/core/strategies/phone.ts new file mode 100644 index 00000000000..a2b8fe7f834 --- /dev/null +++ b/packages-exp/auth-exp/src/core/strategies/phone.ts @@ -0,0 +1,76 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as externs from '@firebase/auth-types-exp'; + +import { sendPhoneVerificationCode } from '../../api/authentication/sms'; +import { Auth } from '../../model/auth'; +import { RECAPTCHA_VERIFIER_TYPE } from '../../platform_browser/recaptcha/recaptcha_verifier'; +import { AuthErrorCode } from '../errors'; +import { PhoneAuthProvider } from '../providers/phone'; +import { assert } from '../util/assert'; +import { signInWithCredential } from './credential'; +import { PhoneAuthCredential } from './phone_credential'; + +interface OnConfirmationCallback { + (credential: PhoneAuthCredential): Promise; +} + +class ConfirmationResult implements externs.ConfirmationResult { + constructor(readonly verificationId: string, private readonly onConfirmation: OnConfirmationCallback) {} + + confirm(verificationCode: string): Promise { + const authCredential = PhoneAuthProvider.credential(this.verificationId, verificationCode); + return this.onConfirmation(authCredential); + } +} + +export async function signInWithPhoneNumber(auth: externs.Auth, phoneNumber: string, appVerifier: externs.ApplicationVerifier): Promise { + const verificationId = await _verifyPhoneNumber(auth as Auth, phoneNumber, appVerifier); + return new ConfirmationResult(verificationId, cred => signInWithCredential(auth, cred)); +} + +/** + * Returns a verification ID to be used in conjunction with the SMS code that + * is sent. + */ +export async function _verifyPhoneNumber(auth: Auth, options: externs.PhoneInfoOptions | string, verifier: externs.ApplicationVerifier): Promise { + const recaptchaToken = await verifier.verify(); + + try { + assert(typeof recaptchaToken === 'string', auth.name, AuthErrorCode.ARGUMENT_ERROR); + assert(verifier.type === RECAPTCHA_VERIFIER_TYPE, auth.name, AuthErrorCode.ARGUMENT_ERROR); + + let phoneNumber: string; + if (typeof options === 'string') { + phoneNumber = options; + } else { + phoneNumber = options.phoneNumber; + } + + // MFA steps should happen here, before this next block + const {sessionInfo} = await sendPhoneVerificationCode(auth, { + phoneNumber, + recaptchaToken, + }); + + return sessionInfo; + } finally { + verifier.reset(); + } +} + diff --git a/packages-exp/auth-exp/src/core/strategies/phone_credential.test.ts b/packages-exp/auth-exp/src/core/strategies/phone_credential.test.ts new file mode 100644 index 00000000000..bfa8d3f2531 --- /dev/null +++ b/packages-exp/auth-exp/src/core/strategies/phone_credential.test.ts @@ -0,0 +1,141 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from 'chai'; + +import { mockEndpoint } from '../../../test/api/helper'; +import { testAuth } from '../../../test/mock_auth'; +import * as fetch from '../../../test/mock_fetch'; +import { Endpoint } from '../../api'; +import { Auth } from '../../model/auth'; +import { IdTokenResponse } from '../../model/id_token'; +import { PhoneAuthCredential } from './phone_credential'; + +describe('core/strategies/phone_credential', () => { + let auth: Auth; + + beforeEach(async () => { + auth = await testAuth(); + fetch.setUp(); + }); + + afterEach(() => { + fetch.tearDown(); + }); + + context('#_getIdTokenResponse', () => { + const response: IdTokenResponse = { + idToken: '', + refreshToken: '', + kind: '', + expiresIn: '10', + localId: '', + }; + + it('calls the endpoint with session and code', async () => { + const cred = new PhoneAuthCredential({ + verificationId: 'session-info', + verificationCode: 'code', + }); + + const route = mockEndpoint(Endpoint.SIGN_IN_WITH_PHONE_NUMBER, response); + + expect(await cred._getIdTokenResponse(auth)).to.eql(response); + expect(route.calls[0].request).to.eql({ + sessionInfo: 'session-info', + code: 'code', + }); + }); + + it('calls the endpoint with proof and number', async () => { + const cred = new PhoneAuthCredential({ + temporaryProof: 'temp-proof', + phoneNumber: 'number', + }); + + const route = mockEndpoint(Endpoint.SIGN_IN_WITH_PHONE_NUMBER, response); + + expect(await cred._getIdTokenResponse(auth)).to.eql(response); + expect(route.calls[0].request).to.eql({ + temporaryProof: 'temp-proof', + phoneNumber: 'number', + }); + }); + }); + + context('#toJSON', () => { + it('fills out the object with everything that is set', () => { + const cred = new PhoneAuthCredential({ + temporaryProof: 'proof', + phoneNumber: 'number', + verificationId: 'id', + verificationCode: 'code', + }); + + expect(cred.toJSON()).to.eql({ + providerId: 'phone', + temporaryProof: 'proof', + phoneNumber: 'number', + verificationId: 'id', + verificationCode: 'code', + }); + }); + + it('omits missing fields', () => { + const cred = new PhoneAuthCredential({ + temporaryProof: 'proof', + phoneNumber: 'number', + }); + + expect(cred.toJSON()).to.eql({ + providerId: 'phone', + temporaryProof: 'proof', + phoneNumber: 'number', + }); + }); + }); + + context('.fromJSON', () => { + it('works if passed a string', () => { + const cred = PhoneAuthCredential.fromJSON('{"phoneNumber": "number"}'); + expect(cred?.toJSON()).to.eql({ + providerId: 'phone', + phoneNumber: 'number', + }); + }); + + it('works if passed an object', () => { + const cred = PhoneAuthCredential.fromJSON({ + temporaryProof: 'proof', + phoneNumber: 'number', + verificationId: 'id', + verificationCode: 'code' + }); + expect(cred?.toJSON()).to.eql({ + providerId: 'phone', + temporaryProof: 'proof', + phoneNumber: 'number', + verificationId: 'id', + verificationCode: 'code' + }); + }); + + it('returns null if object contains no matching fields', () => { + expect(PhoneAuthCredential.fromJSON({})).to.be.null; + }); + }); +}); \ No newline at end of file diff --git a/packages-exp/auth-exp/src/core/strategies/phone_credential.ts b/packages-exp/auth-exp/src/core/strategies/phone_credential.ts new file mode 100644 index 00000000000..5a5e6d62583 --- /dev/null +++ b/packages-exp/auth-exp/src/core/strategies/phone_credential.ts @@ -0,0 +1,92 @@ +import * as externs from '@firebase/auth-types-exp'; + +import { PhoneOrOauthTokenResponse } from '../../api/authentication/mfa'; +import { signInWithPhoneNumber, SignInWithPhoneNumberRequest } from '../../api/authentication/sms'; +import { Auth } from '../../model/auth'; +import { IdTokenResponse } from '../../model/id_token'; +import { debugFail } from '../util/assert'; + +export interface PhoneAuthCredentialParameters { + verificationId?: string; + verificationCode?: string; + phoneNumber?: string; + temporaryProof?: string; +} + +export class PhoneAuthCredential implements externs.AuthCredential { + readonly providerId = externs.ProviderId.PHONE; + readonly signInMethod = externs.SignInMethod.PHONE; + + constructor(private readonly params: PhoneAuthCredentialParameters) {} + + _getIdTokenResponse(auth: Auth): Promise { + return signInWithPhoneNumber(auth, this.makeVerificationRequest()); + } + + _linkToIdToken(auth: Auth, idToken: string): Promise { + void auth; + void idToken; + return debugFail('not implemented'); + } + + _matchIdTokenWithUid(auth: Auth, uid: string): Promise { + void auth; + void uid; + return debugFail('not implemented'); + } + + private makeVerificationRequest(): SignInWithPhoneNumberRequest { + const { + temporaryProof, + phoneNumber, + verificationId, + verificationCode + } = this.params; + if (temporaryProof && phoneNumber) { + return { temporaryProof, phoneNumber }; + } + + return { + sessionInfo: verificationId, + code: verificationCode + }; + } + + toJSON(): object { + const obj: { [key: string]: string } = { + providerId: this.providerId + }; + if (this.params.phoneNumber) { + obj.phoneNumber = this.params.phoneNumber; + } + if (this.params.temporaryProof) { + obj.temporaryProof = this.params.temporaryProof; + } + if (this.params.verificationCode) { + obj.verificationCode = this.params.verificationCode; + } + if (this.params.verificationId) { + obj.verificationId = this.params.verificationId; + } + + return obj; + } + + static fromJSON(json: string | object): externs.AuthCredential | null { + if (typeof json === 'string') { + json = JSON.parse(json); + } + + const {verificationId, verificationCode, phoneNumber, temporaryProof} = json as {[key: string]: string}; + if (!verificationCode && !verificationId && !phoneNumber && !temporaryProof) { + return null; + } + + return new PhoneAuthCredential({ + verificationId, + verificationCode, + phoneNumber, + temporaryProof, + }); + } +} \ 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 79717233949..04052de8485 100644 --- a/packages-exp/auth-exp/src/index.ts +++ b/packages-exp/auth-exp/src/index.ts @@ -26,6 +26,9 @@ export { export { inMemoryPersistence } from './core/persistence/in_memory'; export { indexedDBLocalPersistence } from './core/persistence/indexed_db'; +// core/providers +export { PhoneAuthProvider } from './core/providers/phone'; + // core/strategies export { signInWithCredential } from './core/strategies/credential'; export { @@ -42,6 +45,7 @@ export { fetchSignInMethodsForEmail, sendEmailVerification } from './core/strategies/email'; +export { signInWithPhoneNumber } from './core/strategies/phone'; // core/user export { diff --git a/packages-exp/auth-exp/test/mock_fetch.ts b/packages-exp/auth-exp/test/mock_fetch.ts index a4d35ebf01d..a3f8a50c3bc 100644 --- a/packages-exp/auth-exp/test/mock_fetch.ts +++ b/packages-exp/auth-exp/test/mock_fetch.ts @@ -39,6 +39,7 @@ const fakeFetch: typeof fetch = (input: RequestInfo, request?: RequestInit) => { } if (!routes.has(input)) { + console.error(`Unknown route being requested: ${input}`); throw new Error(`Unknown route being requested: ${input}`); } diff --git a/packages-exp/auth-types-exp/index.d.ts b/packages-exp/auth-types-exp/index.d.ts index 0727c5dd0ef..bd7d42bd70f 100644 --- a/packages-exp/auth-types-exp/index.d.ts +++ b/packages-exp/auth-types-exp/index.d.ts @@ -183,11 +183,14 @@ export const enum SignInMethod { TWITTER = 'twitter.com' } -export interface AuthCredential { +export interface AuthProvider { + readonly providerId: string; +} + +export abstract class AuthCredential { readonly providerId: ProviderId; readonly signInMethod: SignInMethod; toJSON(): object; - fromJSON(json: object | string): AuthCredential | null; } export abstract class OAuthCredential implements AuthCredential { @@ -200,7 +203,6 @@ export abstract class OAuthCredential implements AuthCredential { constructor(); toJSON(): object; - fromJSON(json: object | string): OAuthCredential | null; } export const enum OperationType { @@ -220,3 +222,14 @@ export interface ApplicationVerifier { verify(): Promise; reset(): void; } + +export interface ConfirmationResult { + readonly verificationId: string; + confirm(verificationCode: string): Promise; +} + +export interface PhoneInfoOptions { + phoneNumber: string; + // session?: MultiFactorSession; + // multiFactorHint?: MultiFactorInfo; +} From d771fdc997a8fde947faece361ee4f841c36d472 Mon Sep 17 00:00:00 2001 From: Sam Olsen Date: Mon, 8 Jun 2020 15:43:49 -0700 Subject: [PATCH 2/4] Formatting --- packages-exp/auth-exp/src/api/index.ts | 7 ++- .../auth-exp/src/core/providers/phone.test.ts | 18 ++++--- .../auth-exp/src/core/providers/phone.ts | 42 +++++++++++---- .../src/core/strategies/phone.test.ts | 51 ++++++++++++------- .../auth-exp/src/core/strategies/phone.ts | 51 ++++++++++++++----- .../core/strategies/phone_credential.test.ts | 22 ++++---- .../src/core/strategies/phone_credential.ts | 42 ++++++++++++--- 7 files changed, 167 insertions(+), 66 deletions(-) diff --git a/packages-exp/auth-exp/src/api/index.ts b/packages-exp/auth-exp/src/api/index.ts index f24b8a56d01..952bfcc41ff 100644 --- a/packages-exp/auth-exp/src/api/index.ts +++ b/packages-exp/auth-exp/src/api/index.ts @@ -21,7 +21,12 @@ import { AUTH_ERROR_FACTORY, AuthErrorCode } from '../core/errors'; import { Delay } from '../core/util/delay'; import { Auth } from '../model/auth'; import { IdTokenResponse } from '../model/id_token'; -import { JsonError, SERVER_ERROR_MAP, ServerError, ServerErrorMap } from './errors'; +import { + JsonError, + SERVER_ERROR_MAP, + ServerError, + ServerErrorMap +} from './errors'; export enum HttpMethod { POST = 'POST', diff --git a/packages-exp/auth-exp/src/core/providers/phone.test.ts b/packages-exp/auth-exp/src/core/providers/phone.test.ts index d5430be097a..77a805bc9aa 100644 --- a/packages-exp/auth-exp/src/core/providers/phone.test.ts +++ b/packages-exp/auth-exp/src/core/providers/phone.test.ts @@ -42,19 +42,25 @@ describe('core/providers/phone', () => { context('#verifyPhoneNumber', () => { it('calls verify on the appVerifier and then calls the server', async () => { const route = mockEndpoint(Endpoint.SEND_VERIFICATION_CODE, { - sessionInfo: 'verification-id', + sessionInfo: 'verification-id' }); auth.settings.appVerificationDisabledForTesting = true; - const verifier = new RecaptchaVerifier(document.createElement('div'), {}, auth); - sinon.stub(verifier, 'verify').returns(Promise.resolve('verification-code')); + const verifier = new RecaptchaVerifier( + document.createElement('div'), + {}, + auth + ); + sinon + .stub(verifier, 'verify') + .returns(Promise.resolve('verification-code')); const provider = new PhoneAuthProvider(auth); const result = await provider.verifyPhoneNumber('+15105550000', verifier); expect(result).to.eq('verification-id'); expect(route.calls[0].request).to.eql({ phoneNumber: '+15105550000', - recaptchaToken: 'verification-code', + recaptchaToken: 'verification-code' }); }); }); @@ -64,10 +70,10 @@ describe('core/providers/phone', () => { const credential = PhoneAuthProvider.credential('id', 'code'); // Allows us to inspect the object - const blob = credential.toJSON() as {[key: string]: string}; + const blob = credential.toJSON() as { [key: string]: string }; expect(blob.verificationId).to.eq('id'); expect(blob.verificationCode).to.eq('code'); }); }); -}); \ No newline at end of file +}); diff --git a/packages-exp/auth-exp/src/core/providers/phone.ts b/packages-exp/auth-exp/src/core/providers/phone.ts index 84159d67284..ab3fd623e3a 100644 --- a/packages-exp/auth-exp/src/core/providers/phone.ts +++ b/packages-exp/auth-exp/src/core/providers/phone.ts @@ -1,3 +1,20 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import * as externs from '@firebase/auth-types-exp'; import { FirebaseError } from '@firebase/util'; @@ -20,32 +37,35 @@ export class PhoneAuthProvider implements externs.AuthProvider { verifyPhoneNumber( phoneNumber: string, - applicationVerifier: externs.ApplicationVerifier, + applicationVerifier: externs.ApplicationVerifier /* multiFactorSession?: MultiFactorSession, */ - ): Promise { - return _verifyPhoneNumber(this.auth, phoneNumber, applicationVerifier); - } + ): Promise { + return _verifyPhoneNumber(this.auth, phoneNumber, applicationVerifier); + } static credential( - verificationId: string, - verificationCode: string + verificationId: string, + verificationCode: string ): PhoneAuthCredential { - return new PhoneAuthCredential({verificationId, verificationCode}); + return new PhoneAuthCredential({ verificationId, verificationCode }); } static credentialFromResult( - userCredential: externs.UserCredential): externs.AuthCredential | null { + userCredential: externs.UserCredential + ): externs.AuthCredential | null { void userCredential; return debugFail('not implemented'); } - static credentialFromError(error: FirebaseError): externs.AuthCredential | null { + static credentialFromError( + error: FirebaseError + ): externs.AuthCredential | null { void error; return debugFail('not implemented'); } - static credentialFromJSON(json: string|object): externs.AuthCredential { + static credentialFromJSON(json: string | object): externs.AuthCredential { void json; return debugFail('not implemented'); } -} \ 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 b6d75043cf4..921c49df108 100644 --- a/packages-exp/auth-exp/src/core/strategies/phone.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/phone.test.ts @@ -35,7 +35,7 @@ import { _verifyPhoneNumber, signInWithPhoneNumber } from './phone'; use(chaiAsPromised); use(sinonChai); - describe('core/strategies/phone', () => { +describe('core/strategies/phone', () => { let auth: Auth; let verifier: ApplicationVerifier; let sendCodeEndpoint: fetch.Route; @@ -46,7 +46,7 @@ use(sinonChai); fetch.setUp(); sendCodeEndpoint = mockEndpoint(Endpoint.SEND_VERIFICATION_CODE, { - sessionInfo: 'session-info', + sessionInfo: 'session-info' }); verifier = new RecaptchaVerifier(document.createElement('div'), {}, auth); @@ -64,7 +64,7 @@ use(sinonChai); expect(sendCodeEndpoint.calls[0].request).to.eql({ recaptchaToken: 'recaptcha-token', - phoneNumber: '+15105550000', + phoneNumber: '+15105550000' }); }); @@ -82,11 +82,12 @@ use(sinonChai); localId: 'uid', kind: 'my-kind' }; - const signInEndpoint = mockEndpoint(Endpoint.SIGN_IN_WITH_PHONE_NUMBER, idTokenResponse); + const signInEndpoint = mockEndpoint( + Endpoint.SIGN_IN_WITH_PHONE_NUMBER, + idTokenResponse + ); mockEndpoint(Endpoint.GET_ACCOUNT_INFO, { - users: [ - {localId: 'uid'} - ] + users: [{ localId: 'uid' }] }); const result = await signInWithPhoneNumber(auth, 'number', verifier); @@ -95,7 +96,7 @@ use(sinonChai); expect(userCred.operationType).to.eq(OperationType.SIGN_IN); expect(signInEndpoint.calls[0].request).to.eql({ sessionInfo: 'session-info', - code: '6789', + code: '6789' }); }); }); @@ -106,14 +107,18 @@ use(sinonChai); await _verifyPhoneNumber(auth, 'number', verifier); expect(sendCodeEndpoint.calls[0].request).to.eql({ recaptchaToken: 'recaptcha-token', - phoneNumber: 'number', + phoneNumber: 'number' }); }); it('works with an options object', async () => { - await _verifyPhoneNumber(auth, { - phoneNumber: 'number', - }, verifier); + await _verifyPhoneNumber( + auth, + { + phoneNumber: 'number' + }, + verifier + ); expect(sendCodeEndpoint.calls[0].request).to.eql({ recaptchaToken: 'recaptcha-token', phoneNumber: 'number' @@ -122,24 +127,32 @@ use(sinonChai); it('throws if the verifier does not return a string', async () => { (verifier.verify as sinon.SinonStub).returns(Promise.resolve(123)); - await expect(_verifyPhoneNumber(auth, 'number', verifier)).to.be.rejectedWith( + await expect( + _verifyPhoneNumber(auth, 'number', verifier) + ).to.be.rejectedWith( FirebaseError, - 'Firebase: Error (auth/argument-error)', + 'Firebase: Error (auth/argument-error)' ); }); it('throws if the verifier type is not recaptcha', async () => { - const mutVerifier: {-readonly [K in keyof ApplicationVerifier]: ApplicationVerifier[K] } = verifier; + const mutVerifier: { + -readonly [K in keyof ApplicationVerifier]: ApplicationVerifier[K]; + } = verifier; mutVerifier.type = 'not-recaptcha-thats-for-sure'; - await expect(_verifyPhoneNumber(auth, 'number', mutVerifier)).to.be.rejectedWith( + await expect( + _verifyPhoneNumber(auth, 'number', mutVerifier) + ).to.be.rejectedWith( FirebaseError, - 'Firebase: Error (auth/argument-error)', + 'Firebase: Error (auth/argument-error)' ); }); it('resets the verifer after successful verification', async () => { sinon.spy(verifier, 'reset'); - expect(await _verifyPhoneNumber(auth, 'number', verifier)).to.eq('session-info'); + expect(await _verifyPhoneNumber(auth, 'number', verifier)).to.eq( + 'session-info' + ); expect(verifier.reset).to.have.been.called; }); @@ -151,4 +164,4 @@ use(sinonChai); expect(verifier.reset).to.have.been.called; }); }); -}); \ No newline at end of file +}); diff --git a/packages-exp/auth-exp/src/core/strategies/phone.ts b/packages-exp/auth-exp/src/core/strategies/phone.ts index a2b8fe7f834..fa8a8b1f475 100644 --- a/packages-exp/auth-exp/src/core/strategies/phone.ts +++ b/packages-exp/auth-exp/src/core/strategies/phone.ts @@ -31,30 +31,58 @@ interface OnConfirmationCallback { } class ConfirmationResult implements externs.ConfirmationResult { - constructor(readonly verificationId: string, private readonly onConfirmation: OnConfirmationCallback) {} + constructor( + readonly verificationId: string, + private readonly onConfirmation: OnConfirmationCallback + ) {} confirm(verificationCode: string): Promise { - const authCredential = PhoneAuthProvider.credential(this.verificationId, verificationCode); + const authCredential = PhoneAuthProvider.credential( + this.verificationId, + verificationCode + ); return this.onConfirmation(authCredential); } } -export async function signInWithPhoneNumber(auth: externs.Auth, phoneNumber: string, appVerifier: externs.ApplicationVerifier): Promise { - const verificationId = await _verifyPhoneNumber(auth as Auth, phoneNumber, appVerifier); - return new ConfirmationResult(verificationId, cred => signInWithCredential(auth, cred)); +export async function signInWithPhoneNumber( + auth: externs.Auth, + phoneNumber: string, + appVerifier: externs.ApplicationVerifier +): Promise { + const verificationId = await _verifyPhoneNumber( + auth as Auth, + phoneNumber, + appVerifier + ); + return new ConfirmationResult(verificationId, cred => + signInWithCredential(auth, cred) + ); } /** * Returns a verification ID to be used in conjunction with the SMS code that * is sent. */ -export async function _verifyPhoneNumber(auth: Auth, options: externs.PhoneInfoOptions | string, verifier: externs.ApplicationVerifier): Promise { +export async function _verifyPhoneNumber( + auth: Auth, + options: externs.PhoneInfoOptions | string, + verifier: externs.ApplicationVerifier +): Promise { const recaptchaToken = await verifier.verify(); try { - assert(typeof recaptchaToken === 'string', auth.name, AuthErrorCode.ARGUMENT_ERROR); - assert(verifier.type === RECAPTCHA_VERIFIER_TYPE, auth.name, AuthErrorCode.ARGUMENT_ERROR); - + assert( + typeof recaptchaToken === 'string', + auth.name, + AuthErrorCode.ARGUMENT_ERROR + ); + assert( + verifier.type === RECAPTCHA_VERIFIER_TYPE, + auth.name, + AuthErrorCode.ARGUMENT_ERROR + ); + let phoneNumber: string; if (typeof options === 'string') { phoneNumber = options; @@ -63,9 +91,9 @@ export async function _verifyPhoneNumber(auth: Auth, options: externs.PhoneInfoO } // MFA steps should happen here, before this next block - const {sessionInfo} = await sendPhoneVerificationCode(auth, { + const { sessionInfo } = await sendPhoneVerificationCode(auth, { phoneNumber, - recaptchaToken, + recaptchaToken }); return sessionInfo; @@ -73,4 +101,3 @@ export async function _verifyPhoneNumber(auth: Auth, options: externs.PhoneInfoO verifier.reset(); } } - diff --git a/packages-exp/auth-exp/src/core/strategies/phone_credential.test.ts b/packages-exp/auth-exp/src/core/strategies/phone_credential.test.ts index bfa8d3f2531..b35954b05f6 100644 --- a/packages-exp/auth-exp/src/core/strategies/phone_credential.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/phone_credential.test.ts @@ -43,13 +43,13 @@ describe('core/strategies/phone_credential', () => { refreshToken: '', kind: '', expiresIn: '10', - localId: '', + localId: '' }; it('calls the endpoint with session and code', async () => { const cred = new PhoneAuthCredential({ verificationId: 'session-info', - verificationCode: 'code', + verificationCode: 'code' }); const route = mockEndpoint(Endpoint.SIGN_IN_WITH_PHONE_NUMBER, response); @@ -57,14 +57,14 @@ describe('core/strategies/phone_credential', () => { expect(await cred._getIdTokenResponse(auth)).to.eql(response); expect(route.calls[0].request).to.eql({ sessionInfo: 'session-info', - code: 'code', + code: 'code' }); }); it('calls the endpoint with proof and number', async () => { const cred = new PhoneAuthCredential({ temporaryProof: 'temp-proof', - phoneNumber: 'number', + phoneNumber: 'number' }); const route = mockEndpoint(Endpoint.SIGN_IN_WITH_PHONE_NUMBER, response); @@ -72,7 +72,7 @@ describe('core/strategies/phone_credential', () => { expect(await cred._getIdTokenResponse(auth)).to.eql(response); expect(route.calls[0].request).to.eql({ temporaryProof: 'temp-proof', - phoneNumber: 'number', + phoneNumber: 'number' }); }); }); @@ -83,7 +83,7 @@ describe('core/strategies/phone_credential', () => { temporaryProof: 'proof', phoneNumber: 'number', verificationId: 'id', - verificationCode: 'code', + verificationCode: 'code' }); expect(cred.toJSON()).to.eql({ @@ -91,20 +91,20 @@ describe('core/strategies/phone_credential', () => { temporaryProof: 'proof', phoneNumber: 'number', verificationId: 'id', - verificationCode: 'code', + verificationCode: 'code' }); }); it('omits missing fields', () => { const cred = new PhoneAuthCredential({ temporaryProof: 'proof', - phoneNumber: 'number', + phoneNumber: 'number' }); expect(cred.toJSON()).to.eql({ providerId: 'phone', temporaryProof: 'proof', - phoneNumber: 'number', + phoneNumber: 'number' }); }); }); @@ -114,7 +114,7 @@ describe('core/strategies/phone_credential', () => { const cred = PhoneAuthCredential.fromJSON('{"phoneNumber": "number"}'); expect(cred?.toJSON()).to.eql({ providerId: 'phone', - phoneNumber: 'number', + phoneNumber: 'number' }); }); @@ -138,4 +138,4 @@ describe('core/strategies/phone_credential', () => { expect(PhoneAuthCredential.fromJSON({})).to.be.null; }); }); -}); \ No newline at end of file +}); diff --git a/packages-exp/auth-exp/src/core/strategies/phone_credential.ts b/packages-exp/auth-exp/src/core/strategies/phone_credential.ts index 5a5e6d62583..ab643a350b7 100644 --- a/packages-exp/auth-exp/src/core/strategies/phone_credential.ts +++ b/packages-exp/auth-exp/src/core/strategies/phone_credential.ts @@ -1,7 +1,27 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import * as externs from '@firebase/auth-types-exp'; import { PhoneOrOauthTokenResponse } from '../../api/authentication/mfa'; -import { signInWithPhoneNumber, SignInWithPhoneNumberRequest } from '../../api/authentication/sms'; +import { + signInWithPhoneNumber, + SignInWithPhoneNumberRequest +} from '../../api/authentication/sms'; import { Auth } from '../../model/auth'; import { IdTokenResponse } from '../../model/id_token'; import { debugFail } from '../util/assert'; @@ -28,7 +48,7 @@ export class PhoneAuthCredential implements externs.AuthCredential { void idToken; return debugFail('not implemented'); } - + _matchIdTokenWithUid(auth: Auth, uid: string): Promise { void auth; void uid; @@ -77,8 +97,18 @@ export class PhoneAuthCredential implements externs.AuthCredential { json = JSON.parse(json); } - const {verificationId, verificationCode, phoneNumber, temporaryProof} = json as {[key: string]: string}; - if (!verificationCode && !verificationId && !phoneNumber && !temporaryProof) { + const { + verificationId, + verificationCode, + phoneNumber, + temporaryProof + } = json as { [key: string]: string }; + if ( + !verificationCode && + !verificationId && + !phoneNumber && + !temporaryProof + ) { return null; } @@ -86,7 +116,7 @@ export class PhoneAuthCredential implements externs.AuthCredential { verificationId, verificationCode, phoneNumber, - temporaryProof, + temporaryProof }); } -} \ No newline at end of file +} From f3814c0d158442ed7ab94ee35a7a34305bbfc166 Mon Sep 17 00:00:00 2001 From: Sam Olsen Date: Tue, 9 Jun 2020 09:18:32 -0700 Subject: [PATCH 3/4] PR feedback --- .../auth-exp/src/core/providers/phone.test.ts | 3 +-- .../auth-exp/src/core/strategies/phone.test.ts | 4 +++- .../src/core/strategies/phone_credential.ts | 14 +++++++++----- packages-exp/auth-exp/src/index.ts | 1 + .../recaptcha/recaptcha_verifier.test.ts | 2 -- packages-exp/auth-exp/test/mock_auth.ts | 1 + 6 files changed, 15 insertions(+), 10 deletions(-) diff --git a/packages-exp/auth-exp/src/core/providers/phone.test.ts b/packages-exp/auth-exp/src/core/providers/phone.test.ts index 77a805bc9aa..b6c4ee9579c 100644 --- a/packages-exp/auth-exp/src/core/providers/phone.test.ts +++ b/packages-exp/auth-exp/src/core/providers/phone.test.ts @@ -45,7 +45,6 @@ describe('core/providers/phone', () => { sessionInfo: 'verification-id' }); - auth.settings.appVerificationDisabledForTesting = true; const verifier = new RecaptchaVerifier( document.createElement('div'), {}, @@ -70,7 +69,7 @@ describe('core/providers/phone', () => { const credential = PhoneAuthProvider.credential('id', 'code'); // Allows us to inspect the object - const blob = credential.toJSON() as { [key: string]: string }; + const blob = credential.toJSON() as Record; expect(blob.verificationId).to.eq('id'); expect(blob.verificationCode).to.eq('code'); 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 921c49df108..5410f2b7705 100644 --- a/packages-exp/auth-exp/src/core/strategies/phone.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/phone.test.ts @@ -42,7 +42,6 @@ describe('core/strategies/phone', () => { beforeEach(async () => { auth = await testAuth(); - auth.settings.appVerificationDisabledForTesting = true; fetch.setUp(); sendCodeEndpoint = mockEndpoint(Endpoint.SEND_VERIFICATION_CODE, { @@ -82,6 +81,9 @@ describe('core/strategies/phone', () => { 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 diff --git a/packages-exp/auth-exp/src/core/strategies/phone_credential.ts b/packages-exp/auth-exp/src/core/strategies/phone_credential.ts index ab643a350b7..0989594f515 100644 --- a/packages-exp/auth-exp/src/core/strategies/phone_credential.ts +++ b/packages-exp/auth-exp/src/core/strategies/phone_credential.ts @@ -18,10 +18,7 @@ import * as externs from '@firebase/auth-types-exp'; import { PhoneOrOauthTokenResponse } from '../../api/authentication/mfa'; -import { - signInWithPhoneNumber, - SignInWithPhoneNumberRequest -} from '../../api/authentication/sms'; +import { signInWithPhoneNumber, SignInWithPhoneNumberRequest } from '../../api/authentication/sms'; import { Auth } from '../../model/auth'; import { IdTokenResponse } from '../../model/id_token'; import { debugFail } from '../util/assert'; @@ -73,7 +70,7 @@ export class PhoneAuthCredential implements externs.AuthCredential { } toJSON(): object { - const obj: { [key: string]: string } = { + const obj: Record = { providerId: this.providerId }; if (this.params.phoneNumber) { @@ -120,3 +117,10 @@ export class PhoneAuthCredential implements externs.AuthCredential { }); } } + +/** PhoneAuthCredential for public export; has a private constructor */ +export class ExternPhoneAuthCredential extends PhoneAuthCredential{ + private constructor(params: PhoneAuthCredentialParameters) { + super(params); + } +} diff --git a/packages-exp/auth-exp/src/index.ts b/packages-exp/auth-exp/src/index.ts index 04052de8485..10513cf66af 100644 --- a/packages-exp/auth-exp/src/index.ts +++ b/packages-exp/auth-exp/src/index.ts @@ -45,6 +45,7 @@ export { fetchSignInMethodsForEmail, sendEmailVerification } from './core/strategies/email'; +export { ExternPhoneAuthCredential as PhoneAuthCredential } from './core/strategies/phone_credential'; export { signInWithPhoneNumber } from './core/strategies/phone'; // core/user diff --git a/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_verifier.test.ts b/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_verifier.test.ts index e2bb979f435..52d77b7c332 100644 --- a/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_verifier.test.ts +++ b/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_verifier.test.ts @@ -45,8 +45,6 @@ describe('platform_browser/recaptcha/recaptcha_verifier.ts', () => { beforeEach(async () => { fetch.setUp(); auth = await testAuth(); - // Force the verifier to use the mock recaptcha loader - auth.settings.appVerificationDisabledForTesting = true; auth.languageCode = 'fr'; container = document.createElement('div'); parameters = {}; diff --git a/packages-exp/auth-exp/test/mock_auth.ts b/packages-exp/auth-exp/test/mock_auth.ts index c8e7a757c36..a1f4d0519ba 100644 --- a/packages-exp/auth-exp/test/mock_auth.ts +++ b/packages-exp/auth-exp/test/mock_auth.ts @@ -60,6 +60,7 @@ export async function testAuth(): Promise { await auth._initializeWithPersistence([persistence]); auth.persistenceLayer = persistence; + auth.settings.appVerificationDisabledForTesting = true; return auth; } From a9c28a4bdb82f22592aa73fa0df4c67ba7b6aa69 Mon Sep 17 00:00:00 2001 From: Sam Olsen Date: Tue, 9 Jun 2020 09:19:14 -0700 Subject: [PATCH 4/4] Formatting --- .../auth-exp/src/core/strategies/phone_credential.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages-exp/auth-exp/src/core/strategies/phone_credential.ts b/packages-exp/auth-exp/src/core/strategies/phone_credential.ts index 0989594f515..38b07b1d440 100644 --- a/packages-exp/auth-exp/src/core/strategies/phone_credential.ts +++ b/packages-exp/auth-exp/src/core/strategies/phone_credential.ts @@ -18,7 +18,10 @@ import * as externs from '@firebase/auth-types-exp'; import { PhoneOrOauthTokenResponse } from '../../api/authentication/mfa'; -import { signInWithPhoneNumber, SignInWithPhoneNumberRequest } from '../../api/authentication/sms'; +import { + signInWithPhoneNumber, + SignInWithPhoneNumberRequest +} from '../../api/authentication/sms'; import { Auth } from '../../model/auth'; import { IdTokenResponse } from '../../model/id_token'; import { debugFail } from '../util/assert'; @@ -119,7 +122,7 @@ export class PhoneAuthCredential implements externs.AuthCredential { } /** PhoneAuthCredential for public export; has a private constructor */ -export class ExternPhoneAuthCredential extends PhoneAuthCredential{ +export class ExternPhoneAuthCredential extends PhoneAuthCredential { private constructor(params: PhoneAuthCredentialParameters) { super(params); }