UNPKG

veritatisdoloremque

Version:
272 lines (256 loc) 8.57 kB
/** * @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 { ApplicationVerifier, Auth, ConfirmationResult, PhoneInfoOptions, ProviderId, User, UserCredential } from '../../model/public_types'; import { startEnrollPhoneMfa } from '../../api/account_management/mfa'; import { startSignInPhoneMfa } from '../../api/authentication/mfa'; import { sendPhoneVerificationCode } from '../../api/authentication/sms'; import { ApplicationVerifierInternal } from '../../model/application_verifier'; import { PhoneAuthCredential } from '../../core/credentials/phone'; import { AuthErrorCode } from '../../core/errors'; import { _assertLinkedStatus, _link } from '../../core/user/link_unlink'; import { _assert } from '../../core/util/assert'; import { AuthInternal } from '../../model/auth'; import { linkWithCredential, reauthenticateWithCredential, signInWithCredential } from '../../core/strategies/credential'; import { MultiFactorSessionImpl, MultiFactorSessionType } from '../../mfa/mfa_session'; import { UserInternal } from '../../model/user'; import { RECAPTCHA_VERIFIER_TYPE } from '../recaptcha/recaptcha_verifier'; import { _castAuth } from '../../core/auth/auth_impl'; import { getModularInstance } from '@firebase/util'; interface OnConfirmationCallback { (credential: PhoneAuthCredential): Promise<UserCredential>; } class ConfirmationResultImpl implements ConfirmationResult { constructor( readonly verificationId: string, private readonly onConfirmation: OnConfirmationCallback ) {} confirm(verificationCode: string): Promise<UserCredential> { const authCredential = PhoneAuthCredential._fromVerification( this.verificationId, verificationCode ); return this.onConfirmation(authCredential); } } /** * Asynchronously signs in using a phone number. * * @remarks * This method sends a code via SMS to the given * phone number, and returns a {@link ConfirmationResult}. After the user * provides the code sent to their phone, call {@link ConfirmationResult.confirm} * with the code to sign the user in. * * For abuse prevention, this method also requires a {@link ApplicationVerifier}. * This SDK includes a reCAPTCHA-based implementation, {@link RecaptchaVerifier}. * * @example * ```javascript * // 'recaptcha-container' is the ID of an element in the DOM. * const applicationVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container'); * const confirmationResult = await signInWithPhoneNumber(auth, phoneNumber, applicationVerifier); * // Obtain a verificationCode from the user. * const credential = await confirmationResult.confirm(verificationCode); * ``` * * @param auth - The Auth instance. * @param phoneNumber - The user's phone number in E.164 format (e.g. +16505550101). * @param appVerifier - The {@link ApplicationVerifier}. * * @public */ export async function signInWithPhoneNumber( auth: Auth, phoneNumber: string, appVerifier: ApplicationVerifier ): Promise<ConfirmationResult> { const authInternal = _castAuth(auth); const verificationId = await _verifyPhoneNumber( authInternal, phoneNumber, appVerifier as ApplicationVerifierInternal ); return new ConfirmationResultImpl(verificationId, cred => signInWithCredential(authInternal, cred) ); } /** * Links the user account with the given phone number. * * @param user - The user. * @param phoneNumber - The user's phone number in E.164 format (e.g. +16505550101). * @param appVerifier - The {@link ApplicationVerifier}. * * @public */ export async function linkWithPhoneNumber( user: User, phoneNumber: string, appVerifier: ApplicationVerifier ): Promise<ConfirmationResult> { const userInternal = getModularInstance(user) as UserInternal; await _assertLinkedStatus(false, userInternal, ProviderId.PHONE); const verificationId = await _verifyPhoneNumber( userInternal.auth, phoneNumber, appVerifier as ApplicationVerifierInternal ); return new ConfirmationResultImpl(verificationId, cred => linkWithCredential(userInternal, cred) ); } /** * Re-authenticates a user using a fresh phne credential. * * @remarks Use before operations such as {@link updatePassword} that require tokens from recent sign-in attempts. * * @param user - The user. * @param phoneNumber - The user's phone number in E.164 format (e.g. +16505550101). * @param appVerifier - The {@link ApplicationVerifier}. * * @public */ export async function reauthenticateWithPhoneNumber( user: User, phoneNumber: string, appVerifier: ApplicationVerifier ): Promise<ConfirmationResult> { const userInternal = getModularInstance(user) as UserInternal; const verificationId = await _verifyPhoneNumber( userInternal.auth, phoneNumber, appVerifier as ApplicationVerifierInternal ); return new ConfirmationResultImpl(verificationId, cred => reauthenticateWithCredential(userInternal, cred) ); } /** * Returns a verification ID to be used in conjunction with the SMS code that is sent. * */ export async function _verifyPhoneNumber( auth: AuthInternal, options: PhoneInfoOptions | string, verifier: ApplicationVerifierInternal ): Promise<string> { const recaptchaToken = await verifier.verify(); try { _assert( typeof recaptchaToken === 'string', auth, AuthErrorCode.ARGUMENT_ERROR ); _assert( verifier.type === RECAPTCHA_VERIFIER_TYPE, auth, AuthErrorCode.ARGUMENT_ERROR ); let phoneInfoOptions: PhoneInfoOptions; if (typeof options === 'string') { phoneInfoOptions = { phoneNumber: options }; } else { phoneInfoOptions = options; } if ('session' in phoneInfoOptions) { const session = phoneInfoOptions.session as MultiFactorSessionImpl; if ('phoneNumber' in phoneInfoOptions) { _assert( session.type === MultiFactorSessionType.ENROLL, auth, AuthErrorCode.INTERNAL_ERROR ); const response = await startEnrollPhoneMfa(auth, { idToken: session.credential, phoneEnrollmentInfo: { phoneNumber: phoneInfoOptions.phoneNumber, recaptchaToken } }); return response.phoneSessionInfo.sessionInfo; } else { _assert( session.type === MultiFactorSessionType.SIGN_IN, auth, AuthErrorCode.INTERNAL_ERROR ); const mfaEnrollmentId = phoneInfoOptions.multiFactorHint?.uid || phoneInfoOptions.multiFactorUid; _assert(mfaEnrollmentId, auth, AuthErrorCode.MISSING_MFA_INFO); const response = await startSignInPhoneMfa(auth, { mfaPendingCredential: session.credential, mfaEnrollmentId, phoneSignInInfo: { recaptchaToken } }); return response.phoneResponseInfo.sessionInfo; } } else { const { sessionInfo } = await sendPhoneVerificationCode(auth, { phoneNumber: phoneInfoOptions.phoneNumber, recaptchaToken }); return sessionInfo; } } finally { verifier._reset(); } } /** * Updates the user's phone number. * * @example * ``` * // 'recaptcha-container' is the ID of an element in the DOM. * const applicationVerifier = new RecaptchaVerifier('recaptcha-container'); * const provider = new PhoneAuthProvider(auth); * const verificationId = await provider.verifyPhoneNumber('+16505550101', applicationVerifier); * // Obtain the verificationCode from the user. * const phoneCredential = PhoneAuthProvider.credential(verificationId, verificationCode); * await updatePhoneNumber(user, phoneCredential); * ``` * * @param user - The user. * @param credential - A credential authenticating the new phone number. * * @public */ export async function updatePhoneNumber( user: User, credential: PhoneAuthCredential ): Promise<void> { await _link(getModularInstance(user) as UserInternal, credential); }