UNPKG

@firebase/auth

Version:

The Firebase Authenticaton component of the Firebase JS SDK.

1,065 lines (1,058 loc) 88.3 kB
import { ai as _performApiRequest, aj as _addTidIfNecessary, ak as Delay, al as _window, am as _assert, an as isV2, ao as _createError, ap as _recaptchaV2ScriptUrl, aq as _loadJS, ar as MockReCaptcha, as as _generateCallbackName, at as _castAuth, au as _isHttpOrHttps, av as _isWorker, aw as getRecaptchaParams, ax as _serverAppCurrentUserOperationNotSupportedError, M as signInWithCredential, ay as _assertLinkedStatus, N as linkWithCredential, Q as reauthenticateWithCredential, az as _initializeRecaptchaConfig, aA as FAKE_TOKEN, aB as startEnrollPhoneMfa, aC as handleRecaptchaFlow, aD as sendPhoneVerificationCode, aE as _link, B as PhoneAuthCredential, aF as _assertInstanceOf, aG as _withDefaultResolver, aH as AbstractPopupRedirectOperation, aI as debugAssert, aJ as _generateEventId, aK as FederatedAuthProvider, aL as _getProjectConfig, aM as _fail, aN as _getCurrentUrl, aO as _gapiScriptUrl, aP as _emulatorUrl, aQ as _isChromeIOS, aR as _isFirefox, aS as _isIOSStandalone, f as browserSessionPersistence, aT as _getRedirectResult, aU as _overrideRedirectResult, aV as _getRedirectUrl, aW as _setWindowLocation, aX as _isMobileBrowser, aY as _isSafari, aZ as _isIOS, a_ as AuthEventManager, a$ as debugFail, b0 as finalizeEnrollPhoneMfa, b1 as startEnrollTotpMfa, b2 as finalizeEnrollTotpMfa, r as registerAuth, i as initializeAuth, c as indexedDBLocalPersistence, e as browserLocalPersistence, j as beforeAuthStateChanged, o as onIdTokenChanged, x as connectAuthEmulator, b3 as _setExternalJSProvider } from './popup_redirect-6806e8a5.js'; export { A as ActionCodeOperation, a6 as ActionCodeURL, y as AuthCredential, w as AuthErrorCodes, b7 as AuthImpl, E as EmailAuthCredential, D as EmailAuthProvider, G as FacebookAuthProvider, F as FactorId, b9 as FetchProvider, I as GithubAuthProvider, H as GoogleAuthProvider, z as OAuthCredential, J as OAuthProvider, O as OperationType, B as PhoneAuthCredential, P as ProviderId, ba as SAMLAuthCredential, K as SAMLAuthProvider, S as SignInMethod, T as TwitterAuthProvider, b5 as UserImpl, am as _assert, at as _castAuth, aM as _fail, aJ as _generateEventId, b8 as _getClientVersion, b6 as _getInstance, aT as _getRedirectResult, aU as _overrideRedirectResult, b4 as _persistenceKeyName, W as applyActionCode, j as beforeAuthStateChanged, e as browserLocalPersistence, f as browserSessionPersistence, X as checkActionCode, V as confirmPasswordReset, x as connectAuthEmulator, d as cordovaPopupRedirectResolver, Z as createUserWithEmailAndPassword, q as debugErrorMap, p as deleteUser, a3 as fetchSignInMethodsForEmail, ae as getAdditionalUserInfo, ab as getIdToken, ac as getIdTokenResult, ag as getMultiFactorResolver, g as getRedirectResult, C as inMemoryPersistence, c as indexedDBLocalPersistence, i as initializeAuth, h as initializeRecaptchaConfig, a1 as isSignInWithEmailLink, N as linkWithCredential, bc as linkWithRedirect, ah as multiFactor, k as onAuthStateChanged, o as onIdTokenChanged, a7 as parseActionCodeURL, t as prodErrorMap, Q as reauthenticateWithCredential, bd as reauthenticateWithRedirect, af as reload, n as revokeAccessToken, a4 as sendEmailVerification, U as sendPasswordResetEmail, a0 as sendSignInLinkToEmail, s as setPersistence, L as signInAnonymously, M as signInWithCredential, R as signInWithCustomToken, $ as signInWithEmailAndPassword, a2 as signInWithEmailLink, bb as signInWithRedirect, m as signOut, ad as unlink, l as updateCurrentUser, a9 as updateEmail, aa as updatePassword, a8 as updateProfile, u as useDeviceLanguage, v as validatePassword, a5 as verifyBeforeUpdateEmail, Y as verifyPasswordResetCode } from './popup_redirect-6806e8a5.js'; import { querystring, getModularInstance, getUA, getExperimentalSetting, getDefaultEmulatorHost } from '@firebase/util'; import { _isFirebaseServerApp, SDK_VERSION, _getProvider, getApp } from '@firebase/app'; import 'tslib'; import '@firebase/component'; import '@firebase/logger'; /** * @license * Copyright 2025 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. */ const POLLING_INTERVAL_MS = 1000; // Pull a cookie value from document.cookie function getDocumentCookie(name) { var _a, _b; const escapedName = name.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&'); const matcher = RegExp(`${escapedName}=([^;]+)`); return (_b = (_a = document.cookie.match(matcher)) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : null; } // Produce a sanitized cookie name from the persistence key function getCookieName(key) { // __HOST- doesn't work in localhost https://issues.chromium.org/issues/40196122 but it has // desirable security properties, so lets use a different cookie name while in dev-mode. // Already checked isSecureContext in _isAvailable, so if it's http we're hitting local. const isDevMode = window.location.protocol === 'http:'; return `${isDevMode ? '__dev_' : '__HOST-'}FIREBASE_${key.split(':')[3]}`; } class CookiePersistence { constructor() { this.type = "COOKIE" /* PersistenceType.COOKIE */; this.listenerUnsubscribes = new Map(); } // used to get the URL to the backend to proxy to _getFinalTarget(originalUrl) { if (typeof window === undefined) { return originalUrl; } const url = new URL(`${window.location.origin}/__cookies__`); url.searchParams.set('finalTarget', originalUrl); return url; } // To be a usable persistence method in a chain browserCookiePersistence ensures that // prerequisites have been met, namely that we're in a secureContext, navigator and document are // available and cookies are enabled. Not all UAs support these method, so fallback accordingly. async _isAvailable() { var _a; if (typeof isSecureContext === 'boolean' && !isSecureContext) { return false; } if (typeof navigator === 'undefined' || typeof document === 'undefined') { return false; } return (_a = navigator.cookieEnabled) !== null && _a !== void 0 ? _a : true; } // Set should be a noop as we expect middleware to handle this async _set(_key, _value) { return; } // Attempt to get the cookie from cookieStore, fallback to document.cookie async _get(key) { if (!this._isAvailable()) { return null; } const name = getCookieName(key); if (window.cookieStore) { const cookie = await window.cookieStore.get(name); return cookie === null || cookie === void 0 ? void 0 : cookie.value; } return getDocumentCookie(name); } // Log out by overriding the idToken with a sentinel value of "" async _remove(key) { if (!this._isAvailable()) { return; } // To make sure we don't hit signout over and over again, only do this operation if we need to // with the logout sentinel value of "" this can cause race conditions. Unnecessary set-cookie // headers will reduce CDN hit rates too. const existingValue = await this._get(key); if (!existingValue) { return; } const name = getCookieName(key); document.cookie = `${name}=;Max-Age=34560000;Partitioned;Secure;SameSite=Strict;Path=/;Priority=High`; await fetch(`/__cookies__`, { method: 'DELETE' }).catch(() => undefined); } // Listen for cookie changes, both cookieStore and fallback to polling document.cookie _addListener(key, listener) { if (!this._isAvailable()) { return; } const name = getCookieName(key); if (window.cookieStore) { const cb = ((event) => { const changedCookie = event.changed.find(change => change.name === name); if (changedCookie) { listener(changedCookie.value); } const deletedCookie = event.deleted.find(change => change.name === name); if (deletedCookie) { listener(null); } }); const unsubscribe = () => window.cookieStore.removeEventListener('change', cb); this.listenerUnsubscribes.set(listener, unsubscribe); return window.cookieStore.addEventListener('change', cb); } let lastValue = getDocumentCookie(name); const interval = setInterval(() => { const currentValue = getDocumentCookie(name); if (currentValue !== lastValue) { listener(currentValue); lastValue = currentValue; } }, POLLING_INTERVAL_MS); const unsubscribe = () => clearInterval(interval); this.listenerUnsubscribes.set(listener, unsubscribe); } _removeListener(_key, listener) { const unsubscribe = this.listenerUnsubscribes.get(listener); if (!unsubscribe) { return; } unsubscribe(); this.listenerUnsubscribes.delete(listener); } } CookiePersistence.type = 'COOKIE'; /** * An implementation of {@link Persistence} of type `COOKIE`, for use on the client side in * applications leveraging hybrid rendering and middleware. * * @remarks This persistence method requires companion middleware to function, such as that provided * by {@link https://firebaseopensource.com/projects/firebaseextended/reactfire/ | ReactFire} for * NextJS. * @beta */ const browserCookiePersistence = CookiePersistence; /** * @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. */ function startSignInPhoneMfa(auth, request) { return _performApiRequest(auth, "POST" /* HttpMethod.POST */, "/v2/accounts/mfaSignIn:start" /* Endpoint.START_MFA_SIGN_IN */, _addTidIfNecessary(auth, request)); } function finalizeSignInPhoneMfa(auth, request) { return _performApiRequest(auth, "POST" /* HttpMethod.POST */, "/v2/accounts/mfaSignIn:finalize" /* Endpoint.FINALIZE_MFA_SIGN_IN */, _addTidIfNecessary(auth, request)); } function finalizeSignInTotpMfa(auth, request) { return _performApiRequest(auth, "POST" /* HttpMethod.POST */, "/v2/accounts/mfaSignIn:finalize" /* Endpoint.FINALIZE_MFA_SIGN_IN */, _addTidIfNecessary(auth, request)); } /** * @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. */ // ReCaptcha will load using the same callback, so the callback function needs // to be kept around const _JSLOAD_CALLBACK = _generateCallbackName('rcb'); const NETWORK_TIMEOUT_DELAY = new Delay(30000, 60000); /** * Loader for the GReCaptcha library. There should only ever be one of this. */ class ReCaptchaLoaderImpl { constructor() { var _a; this.hostLanguage = ''; this.counter = 0; /** * Check for `render()` method. `window.grecaptcha` will exist if the Enterprise * version of the ReCAPTCHA script was loaded by someone else (e.g. App Check) but * `window.grecaptcha.render()` will not. Another load will add it. */ this.librarySeparatelyLoaded = !!((_a = _window().grecaptcha) === null || _a === void 0 ? void 0 : _a.render); } load(auth, hl = '') { _assert(isHostLanguageValid(hl), auth, "argument-error" /* AuthErrorCode.ARGUMENT_ERROR */); if (this.shouldResolveImmediately(hl) && isV2(_window().grecaptcha)) { return Promise.resolve(_window().grecaptcha); } return new Promise((resolve, reject) => { const networkTimeout = _window().setTimeout(() => { reject(_createError(auth, "network-request-failed" /* AuthErrorCode.NETWORK_REQUEST_FAILED */)); }, NETWORK_TIMEOUT_DELAY.get()); _window()[_JSLOAD_CALLBACK] = () => { _window().clearTimeout(networkTimeout); delete _window()[_JSLOAD_CALLBACK]; const recaptcha = _window().grecaptcha; if (!recaptcha || !isV2(recaptcha)) { reject(_createError(auth, "internal-error" /* AuthErrorCode.INTERNAL_ERROR */)); return; } // Wrap the recaptcha render function so that we know if the developer has // called it separately const render = recaptcha.render; recaptcha.render = (container, params) => { const widgetId = render(container, params); this.counter++; return widgetId; }; this.hostLanguage = hl; resolve(recaptcha); }; const url = `${_recaptchaV2ScriptUrl()}?${querystring({ onload: _JSLOAD_CALLBACK, render: 'explicit', hl })}`; _loadJS(url).catch(() => { clearTimeout(networkTimeout); reject(_createError(auth, "internal-error" /* AuthErrorCode.INTERNAL_ERROR */)); }); }); } clearedOneInstance() { this.counter--; } shouldResolveImmediately(hl) { var _a; // We can resolve immediately if: // • grecaptcha is already defined AND ( // 1. the requested language codes are the same OR // 2. there exists already a ReCaptcha on the page // 3. the library was already loaded by the app // In cases (2) and (3), we _can't_ reload as it would break the recaptchas // that are already in the page return (!!((_a = _window().grecaptcha) === null || _a === void 0 ? void 0 : _a.render) && (hl === this.hostLanguage || this.counter > 0 || this.librarySeparatelyLoaded)); } } function isHostLanguageValid(hl) { return hl.length <= 6 && /^\s*[a-zA-Z0-9\-]*\s*$/.test(hl); } class MockReCaptchaLoaderImpl { async load(auth) { return new MockReCaptcha(auth); } clearedOneInstance() { } } /** * @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. */ const RECAPTCHA_VERIFIER_TYPE = 'recaptcha'; const DEFAULT_PARAMS = { theme: 'light', type: 'image' }; /** * An {@link https://www.google.com/recaptcha/ | reCAPTCHA}-based application verifier. * * @remarks * `RecaptchaVerifier` does not work in a Node.js environment. * * @public */ class RecaptchaVerifier { /** * @param authExtern - The corresponding Firebase {@link Auth} instance. * * @param containerOrId - The reCAPTCHA container parameter. * * @remarks * This has different meaning depending on whether the reCAPTCHA is hidden or visible. For a * visible reCAPTCHA the container must be empty. If a string is used, it has to correspond to * an element ID. The corresponding element must also must be in the DOM at the time of * initialization. * * @param parameters - The optional reCAPTCHA parameters. * * @remarks * Check the reCAPTCHA docs for a comprehensive list. All parameters are accepted except for * the sitekey. Firebase Auth backend provisions a reCAPTCHA for each project and will * configure this upon rendering. For an invisible reCAPTCHA, a size key must have the value * 'invisible'. */ constructor(authExtern, containerOrId, parameters = Object.assign({}, DEFAULT_PARAMS)) { this.parameters = parameters; /** * The application verifier type. * * @remarks * For a reCAPTCHA verifier, this is 'recaptcha'. */ this.type = RECAPTCHA_VERIFIER_TYPE; this.destroyed = false; this.widgetId = null; this.tokenChangeListeners = new Set(); this.renderPromise = null; this.recaptcha = null; this.auth = _castAuth(authExtern); this.isInvisible = this.parameters.size === 'invisible'; _assert(typeof document !== 'undefined', this.auth, "operation-not-supported-in-this-environment" /* AuthErrorCode.OPERATION_NOT_SUPPORTED */); const container = typeof containerOrId === 'string' ? document.getElementById(containerOrId) : containerOrId; _assert(container, this.auth, "argument-error" /* AuthErrorCode.ARGUMENT_ERROR */); this.container = container; this.parameters.callback = this.makeTokenCallback(this.parameters.callback); this._recaptchaLoader = this.auth.settings.appVerificationDisabledForTesting ? new MockReCaptchaLoaderImpl() : new ReCaptchaLoaderImpl(); this.validateStartingState(); // TODO: Figure out if sdk version is needed } /** * Waits for the user to solve the reCAPTCHA and resolves with the reCAPTCHA token. * * @returns A Promise for the reCAPTCHA token. */ async verify() { this.assertNotDestroyed(); const id = await this.render(); const recaptcha = this.getAssertedRecaptcha(); const response = recaptcha.getResponse(id); if (response) { return response; } return new Promise(resolve => { const tokenChange = (token) => { if (!token) { return; // Ignore token expirations. } this.tokenChangeListeners.delete(tokenChange); resolve(token); }; this.tokenChangeListeners.add(tokenChange); if (this.isInvisible) { recaptcha.execute(id); } }); } /** * Renders the reCAPTCHA widget on the page. * * @returns A Promise that resolves with the reCAPTCHA widget ID. */ render() { try { this.assertNotDestroyed(); } catch (e) { // This method returns a promise. Since it's not async (we want to return the // _same_ promise if rendering is still occurring), the API surface should // reject with the error rather than just throw return Promise.reject(e); } if (this.renderPromise) { return this.renderPromise; } this.renderPromise = this.makeRenderPromise().catch(e => { this.renderPromise = null; throw e; }); return this.renderPromise; } /** @internal */ _reset() { this.assertNotDestroyed(); if (this.widgetId !== null) { this.getAssertedRecaptcha().reset(this.widgetId); } } /** * Clears the reCAPTCHA widget from the page and destroys the instance. */ clear() { this.assertNotDestroyed(); this.destroyed = true; this._recaptchaLoader.clearedOneInstance(); if (!this.isInvisible) { this.container.childNodes.forEach(node => { this.container.removeChild(node); }); } } validateStartingState() { _assert(!this.parameters.sitekey, this.auth, "argument-error" /* AuthErrorCode.ARGUMENT_ERROR */); _assert(this.isInvisible || !this.container.hasChildNodes(), this.auth, "argument-error" /* AuthErrorCode.ARGUMENT_ERROR */); _assert(typeof document !== 'undefined', this.auth, "operation-not-supported-in-this-environment" /* AuthErrorCode.OPERATION_NOT_SUPPORTED */); } makeTokenCallback(existing) { return token => { this.tokenChangeListeners.forEach(listener => listener(token)); if (typeof existing === 'function') { existing(token); } else if (typeof existing === 'string') { const globalFunc = _window()[existing]; if (typeof globalFunc === 'function') { globalFunc(token); } } }; } assertNotDestroyed() { _assert(!this.destroyed, this.auth, "internal-error" /* AuthErrorCode.INTERNAL_ERROR */); } async makeRenderPromise() { await this.init(); if (!this.widgetId) { let container = this.container; if (!this.isInvisible) { const guaranteedEmpty = document.createElement('div'); container.appendChild(guaranteedEmpty); container = guaranteedEmpty; } this.widgetId = this.getAssertedRecaptcha().render(container, this.parameters); } return this.widgetId; } async init() { _assert(_isHttpOrHttps() && !_isWorker(), this.auth, "internal-error" /* AuthErrorCode.INTERNAL_ERROR */); await domReady(); this.recaptcha = await this._recaptchaLoader.load(this.auth, this.auth.languageCode || undefined); const siteKey = await getRecaptchaParams(this.auth); _assert(siteKey, this.auth, "internal-error" /* AuthErrorCode.INTERNAL_ERROR */); this.parameters.sitekey = siteKey; } getAssertedRecaptcha() { _assert(this.recaptcha, this.auth, "internal-error" /* AuthErrorCode.INTERNAL_ERROR */); return this.recaptcha; } } function domReady() { let resolver = null; return new Promise(resolve => { if (document.readyState === 'complete') { resolve(); return; } // Document not ready, wait for load before resolving. // Save resolver, so we can remove listener in case it was externally // cancelled. resolver = () => resolve(); window.addEventListener('load', resolver); }).catch(e => { if (resolver) { window.removeEventListener('load', resolver); } throw e; }); } /** * @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. */ class ConfirmationResultImpl { constructor(verificationId, onConfirmation) { this.verificationId = verificationId; this.onConfirmation = onConfirmation; } confirm(verificationCode) { 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 requires a {@link ApplicationVerifier}. * This SDK includes an implementation based on reCAPTCHA v2, {@link RecaptchaVerifier}. * This function can work on other platforms that do not support the * {@link RecaptchaVerifier} (like React Native), but you need to use a * third-party {@link ApplicationVerifier} implementation. * * If you've enabled project-level reCAPTCHA Enterprise bot protection in * Enforce mode, you can omit the {@link ApplicationVerifier}. * * This method does not work in a Node.js environment or with {@link Auth} instances created with a * {@link @firebase/app#FirebaseServerApp}. * * @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 {@link Auth} instance. * @param phoneNumber - The user's phone number in E.164 format (e.g. +16505550101). * @param appVerifier - The {@link ApplicationVerifier}. * * @public */ async function signInWithPhoneNumber(auth, phoneNumber, appVerifier) { if (_isFirebaseServerApp(auth.app)) { return Promise.reject(_serverAppCurrentUserOperationNotSupportedError(auth)); } const authInternal = _castAuth(auth); const verificationId = await _verifyPhoneNumber(authInternal, phoneNumber, getModularInstance(appVerifier)); return new ConfirmationResultImpl(verificationId, cred => signInWithCredential(authInternal, cred)); } /** * Links the user account with the given phone number. * * @remarks * This method does not work in a Node.js environment. * * @param user - The user. * @param phoneNumber - The user's phone number in E.164 format (e.g. +16505550101). * @param appVerifier - The {@link ApplicationVerifier}. * * @public */ async function linkWithPhoneNumber(user, phoneNumber, appVerifier) { const userInternal = getModularInstance(user); await _assertLinkedStatus(false, userInternal, "phone" /* ProviderId.PHONE */); const verificationId = await _verifyPhoneNumber(userInternal.auth, phoneNumber, getModularInstance(appVerifier)); return new ConfirmationResultImpl(verificationId, cred => linkWithCredential(userInternal, cred)); } /** * Re-authenticates a user using a fresh phone credential. * * @remarks * Use before operations such as {@link updatePassword} that require tokens from recent sign-in attempts. * * This method does not work in a Node.js environment or on any {@link User} signed in by * {@link Auth} instances created with a {@link @firebase/app#FirebaseServerApp}. * * @param user - The user. * @param phoneNumber - The user's phone number in E.164 format (e.g. +16505550101). * @param appVerifier - The {@link ApplicationVerifier}. * * @public */ async function reauthenticateWithPhoneNumber(user, phoneNumber, appVerifier) { const userInternal = getModularInstance(user); if (_isFirebaseServerApp(userInternal.auth.app)) { return Promise.reject(_serverAppCurrentUserOperationNotSupportedError(userInternal.auth)); } const verificationId = await _verifyPhoneNumber(userInternal.auth, phoneNumber, getModularInstance(appVerifier)); return new ConfirmationResultImpl(verificationId, cred => reauthenticateWithCredential(userInternal, cred)); } /** * Returns a verification ID to be used in conjunction with the SMS code that is sent. * */ async function _verifyPhoneNumber(auth, options, verifier) { var _a; if (!auth._getRecaptchaConfig()) { try { await _initializeRecaptchaConfig(auth); } catch (error) { // If an error occurs while fetching the config, there is no way to know the enablement state // of Phone provider, so we proceed with recaptcha V2 verification. // The error is likely "recaptchaKey undefined", as reCAPTCHA Enterprise is not // enabled for any provider. console.log('Failed to initialize reCAPTCHA Enterprise config. Triggering the reCAPTCHA v2 verification.'); } } try { let phoneInfoOptions; if (typeof options === 'string') { phoneInfoOptions = { phoneNumber: options }; } else { phoneInfoOptions = options; } if ('session' in phoneInfoOptions) { const session = phoneInfoOptions.session; if ('phoneNumber' in phoneInfoOptions) { _assert(session.type === "enroll" /* MultiFactorSessionType.ENROLL */, auth, "internal-error" /* AuthErrorCode.INTERNAL_ERROR */); const startPhoneMfaEnrollmentRequest = { idToken: session.credential, phoneEnrollmentInfo: { phoneNumber: phoneInfoOptions.phoneNumber, clientType: "CLIENT_TYPE_WEB" /* RecaptchaClientType.WEB */ } }; const startEnrollPhoneMfaActionCallback = async (authInstance, request) => { // If reCAPTCHA Enterprise token is FAKE_TOKEN, fetch reCAPTCHA v2 token and inject into request. if (request.phoneEnrollmentInfo.captchaResponse === FAKE_TOKEN) { _assert((verifier === null || verifier === void 0 ? void 0 : verifier.type) === RECAPTCHA_VERIFIER_TYPE, authInstance, "argument-error" /* AuthErrorCode.ARGUMENT_ERROR */); const requestWithRecaptchaV2 = await injectRecaptchaV2Token(authInstance, request, verifier); return startEnrollPhoneMfa(authInstance, requestWithRecaptchaV2); } return startEnrollPhoneMfa(authInstance, request); }; const startPhoneMfaEnrollmentResponse = handleRecaptchaFlow(auth, startPhoneMfaEnrollmentRequest, "mfaSmsEnrollment" /* RecaptchaActionName.MFA_SMS_ENROLLMENT */, startEnrollPhoneMfaActionCallback, "PHONE_PROVIDER" /* RecaptchaAuthProvider.PHONE_PROVIDER */); const response = await startPhoneMfaEnrollmentResponse.catch(error => { return Promise.reject(error); }); return response.phoneSessionInfo.sessionInfo; } else { _assert(session.type === "signin" /* MultiFactorSessionType.SIGN_IN */, auth, "internal-error" /* AuthErrorCode.INTERNAL_ERROR */); const mfaEnrollmentId = ((_a = phoneInfoOptions.multiFactorHint) === null || _a === void 0 ? void 0 : _a.uid) || phoneInfoOptions.multiFactorUid; _assert(mfaEnrollmentId, auth, "missing-multi-factor-info" /* AuthErrorCode.MISSING_MFA_INFO */); const startPhoneMfaSignInRequest = { mfaPendingCredential: session.credential, mfaEnrollmentId, phoneSignInInfo: { clientType: "CLIENT_TYPE_WEB" /* RecaptchaClientType.WEB */ } }; const startSignInPhoneMfaActionCallback = async (authInstance, request) => { // If reCAPTCHA Enterprise token is FAKE_TOKEN, fetch reCAPTCHA v2 token and inject into request. if (request.phoneSignInInfo.captchaResponse === FAKE_TOKEN) { _assert((verifier === null || verifier === void 0 ? void 0 : verifier.type) === RECAPTCHA_VERIFIER_TYPE, authInstance, "argument-error" /* AuthErrorCode.ARGUMENT_ERROR */); const requestWithRecaptchaV2 = await injectRecaptchaV2Token(authInstance, request, verifier); return startSignInPhoneMfa(authInstance, requestWithRecaptchaV2); } return startSignInPhoneMfa(authInstance, request); }; const startPhoneMfaSignInResponse = handleRecaptchaFlow(auth, startPhoneMfaSignInRequest, "mfaSmsSignIn" /* RecaptchaActionName.MFA_SMS_SIGNIN */, startSignInPhoneMfaActionCallback, "PHONE_PROVIDER" /* RecaptchaAuthProvider.PHONE_PROVIDER */); const response = await startPhoneMfaSignInResponse.catch(error => { return Promise.reject(error); }); return response.phoneResponseInfo.sessionInfo; } } else { const sendPhoneVerificationCodeRequest = { phoneNumber: phoneInfoOptions.phoneNumber, clientType: "CLIENT_TYPE_WEB" /* RecaptchaClientType.WEB */ }; const sendPhoneVerificationCodeActionCallback = async (authInstance, request) => { // If reCAPTCHA Enterprise token is FAKE_TOKEN, fetch reCAPTCHA v2 token and inject into request. if (request.captchaResponse === FAKE_TOKEN) { _assert((verifier === null || verifier === void 0 ? void 0 : verifier.type) === RECAPTCHA_VERIFIER_TYPE, authInstance, "argument-error" /* AuthErrorCode.ARGUMENT_ERROR */); const requestWithRecaptchaV2 = await injectRecaptchaV2Token(authInstance, request, verifier); return sendPhoneVerificationCode(authInstance, requestWithRecaptchaV2); } return sendPhoneVerificationCode(authInstance, request); }; const sendPhoneVerificationCodeResponse = handleRecaptchaFlow(auth, sendPhoneVerificationCodeRequest, "sendVerificationCode" /* RecaptchaActionName.SEND_VERIFICATION_CODE */, sendPhoneVerificationCodeActionCallback, "PHONE_PROVIDER" /* RecaptchaAuthProvider.PHONE_PROVIDER */); const response = await sendPhoneVerificationCodeResponse.catch(error => { return Promise.reject(error); }); return response.sessionInfo; } } finally { verifier === null || verifier === void 0 ? void 0 : verifier._reset(); } } /** * Updates the user's phone number. * * @remarks * This method does not work in a Node.js environment or on any {@link User} signed in by * {@link Auth} instances created with a {@link @firebase/app#FirebaseServerApp}. * * @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 */ async function updatePhoneNumber(user, credential) { const userInternal = getModularInstance(user); if (_isFirebaseServerApp(userInternal.auth.app)) { return Promise.reject(_serverAppCurrentUserOperationNotSupportedError(userInternal.auth)); } await _link(userInternal, credential); } // Helper function that fetches and injects a reCAPTCHA v2 token into the request. async function injectRecaptchaV2Token(auth, request, recaptchaV2Verifier) { _assert(recaptchaV2Verifier.type === RECAPTCHA_VERIFIER_TYPE, auth, "argument-error" /* AuthErrorCode.ARGUMENT_ERROR */); const recaptchaV2Token = await recaptchaV2Verifier.verify(); _assert(typeof recaptchaV2Token === 'string', auth, "argument-error" /* AuthErrorCode.ARGUMENT_ERROR */); const newRequest = Object.assign({}, request); if ('phoneEnrollmentInfo' in newRequest) { const phoneNumber = newRequest.phoneEnrollmentInfo.phoneNumber; const captchaResponse = newRequest.phoneEnrollmentInfo.captchaResponse; const clientType = newRequest .phoneEnrollmentInfo.clientType; const recaptchaVersion = newRequest.phoneEnrollmentInfo.recaptchaVersion; Object.assign(newRequest, { 'phoneEnrollmentInfo': { phoneNumber, recaptchaToken: recaptchaV2Token, captchaResponse, clientType, recaptchaVersion } }); return newRequest; } else if ('phoneSignInInfo' in newRequest) { const captchaResponse = newRequest.phoneSignInInfo.captchaResponse; const clientType = newRequest .phoneSignInInfo.clientType; const recaptchaVersion = newRequest.phoneSignInInfo.recaptchaVersion; Object.assign(newRequest, { 'phoneSignInInfo': { recaptchaToken: recaptchaV2Token, captchaResponse, clientType, recaptchaVersion } }); return newRequest; } else { Object.assign(newRequest, { 'recaptchaToken': recaptchaV2Token }); return newRequest; } } /** * @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. */ /** * Provider for generating an {@link PhoneAuthCredential}. * * @remarks * `PhoneAuthProvider` does not work in a Node.js environment. * * @example * ```javascript * // '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); * const userCredential = await signInWithCredential(auth, phoneCredential); * ``` * * @public */ class PhoneAuthProvider { /** * @param auth - The Firebase {@link Auth} instance in which sign-ins should occur. * */ constructor(auth) { /** Always set to {@link ProviderId}.PHONE. */ this.providerId = PhoneAuthProvider.PROVIDER_ID; this.auth = _castAuth(auth); } /** * * Starts a phone number authentication flow by sending a verification code to the given phone * number. * * @example * ```javascript * const provider = new PhoneAuthProvider(auth); * const verificationId = await provider.verifyPhoneNumber(phoneNumber, applicationVerifier); * // Obtain verificationCode from the user. * const authCredential = PhoneAuthProvider.credential(verificationId, verificationCode); * const userCredential = await signInWithCredential(auth, authCredential); * ``` * * @example * An alternative flow is provided using the `signInWithPhoneNumber` method. * ```javascript * const confirmationResult = signInWithPhoneNumber(auth, phoneNumber, applicationVerifier); * // Obtain verificationCode from the user. * const userCredential = confirmationResult.confirm(verificationCode); * ``` * * @param phoneInfoOptions - The user's {@link PhoneInfoOptions}. The phone number should be in * E.164 format (e.g. +16505550101). * @param applicationVerifier - An {@link ApplicationVerifier}, which prevents * requests from unauthorized clients. This SDK includes an implementation * based on reCAPTCHA v2, {@link RecaptchaVerifier}. If you've enabled * reCAPTCHA Enterprise bot protection in Enforce mode, this parameter is * optional; in all other configurations, the parameter is required. * * @returns A Promise for a verification ID that can be passed to * {@link PhoneAuthProvider.credential} to identify this flow. */ verifyPhoneNumber(phoneOptions, applicationVerifier) { return _verifyPhoneNumber(this.auth, phoneOptions, getModularInstance(applicationVerifier)); } /** * Creates a phone auth credential, given the verification ID from * {@link PhoneAuthProvider.verifyPhoneNumber} and the code that was sent to the user's * mobile device. * * @example * ```javascript * const provider = new PhoneAuthProvider(auth); * const verificationId = provider.verifyPhoneNumber(phoneNumber, applicationVerifier); * // Obtain verificationCode from the user. * const authCredential = PhoneAuthProvider.credential(verificationId, verificationCode); * const userCredential = signInWithCredential(auth, authCredential); * ``` * * @example * An alternative flow is provided using the `signInWithPhoneNumber` method. * ```javascript * const confirmationResult = await signInWithPhoneNumber(auth, phoneNumber, applicationVerifier); * // Obtain verificationCode from the user. * const userCredential = await confirmationResult.confirm(verificationCode); * ``` * * @param verificationId - The verification ID returned from {@link PhoneAuthProvider.verifyPhoneNumber}. * @param verificationCode - The verification code sent to the user's mobile device. * * @returns The auth provider credential. */ static credential(verificationId, verificationCode) { return PhoneAuthCredential._fromVerification(verificationId, verificationCode); } /** * Generates an {@link AuthCredential} from a {@link UserCredential}. * @param userCredential - The user credential. */ static credentialFromResult(userCredential) { const credential = userCredential; return PhoneAuthProvider.credentialFromTaggedObject(credential); } /** * Returns an {@link AuthCredential} when passed an error. * * @remarks * * This method works for errors like * `auth/account-exists-with-different-credentials`. This is useful for * recovering when attempting to set a user's phone number but the number * in question is already tied to another account. For example, the following * code tries to update the current user's phone number, and if that * fails, links the user with the account associated with that number: * * ```js * const provider = new PhoneAuthProvider(auth); * const verificationId = await provider.verifyPhoneNumber(number, verifier); * try { * const code = ''; // Prompt the user for the verification code * await updatePhoneNumber( * auth.currentUser, * PhoneAuthProvider.credential(verificationId, code)); * } catch (e) { * if ((e as FirebaseError)?.code === 'auth/account-exists-with-different-credential') { * const cred = PhoneAuthProvider.credentialFromError(e); * await linkWithCredential(auth.currentUser, cred); * } * } * * // At this point, auth.currentUser.phoneNumber === number. * ``` * * @param error - The error to generate a credential from. */ static credentialFromError(error) { return PhoneAuthProvider.credentialFromTaggedObject((error.customData || {})); } static credentialFromTaggedObject({ _tokenResponse: tokenResponse }) { if (!tokenResponse) { return null; } const { phoneNumber, temporaryProof } = tokenResponse; if (phoneNumber && temporaryProof) { return PhoneAuthCredential._fromTokenResponse(phoneNumber, temporaryProof); } return null; } } /** Always set to {@link ProviderId}.PHONE. */ PhoneAuthProvider.PROVIDER_ID = "phone" /* ProviderId.PHONE */; /** Always set to {@link SignInMethod}.PHONE. */ PhoneAuthProvider.PHONE_SIGN_IN_METHOD = "phone" /* SignInMethod.PHONE */; /** * @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. */ const _POLL_WINDOW_CLOSE_TIMEOUT = new Delay(2000, 10000); /** * Authenticates a Firebase client using a popup-based OAuth authentication flow. * * @remarks * If succeeds, returns the signed in user along with the provider's credential. If sign in was * unsuccessful, returns an error object containing additional information about the error. * * This method does not work in a Node.js environment or with {@link Auth} instances created with a * {@link @firebase/app#FirebaseServerApp}. * * @example * ```javascript * // Sign in using a popup. * const provider = new FacebookAuthProvider(); * const result = await signInWithPopup(auth, provider); * * // The signed-in user info. * const user = result.user; * // This gives you a Facebook Access Token. * const credential = provider.credentialFromResult(auth, result); * const token = credential.accessToken; * ``` * * @param auth - The {@link Auth} instance. * @param provider - The provider to authenticate. The provider has to be an {@link OAuthProvider}. * Non-OAuth providers like {@link EmailAuthProvider} will throw an error. * @param resolver - An instance of {@link PopupRedirectResolver}, optional * if already supplied to {@link initializeAuth} or provided by {@link getAuth}. * * @public */ async function signInWithPopup(auth, provider, resolver) { if (_isFirebaseServerApp(auth.app)) { return Promise.reject(_createError(auth, "operation-not-supported-in-this-environment" /* AuthErrorCode.OPERATION_NOT_SUPPORTED */)); } const authInternal = _castAuth(auth); _assertInstanceOf(auth, provider, FederatedAuthProvider); const resolverInternal = _withDefaultResolver(authInternal, resolver); const action = new PopupOperation(authInternal, "signInViaPopup" /* AuthEventType.SIGN_IN_VIA_POPUP */, provider, resolverInternal); return action.executeNotNull(); } /** * Reauthenticates the current user with the specified {@link OAuthProvider} using a pop-up based * OAuth flow. * * @remarks * If the reauthentication is successful, the returned result will contain the user and the * provider's credential. * * This method does not work in a Node.js environment or on any {@link User} signed in by * {@link Auth} instances created with a {@link @firebase/app#FirebaseServerApp}. * * @example * ```javascript * // Sign in using a popup. * const provider = new FacebookAuthProvider(); * const result = await signInWithPopup(auth, provider); * // Reauthenticate using a popup. * await reauthenticateWithPopup(result.user, provider); * ``` * * @param user - The user. * @param provider - The provider to authenticate. The provider has to be an {@link OAuthProvider}. * Non-OAuth providers like {@link EmailAuthProvider} will throw an error. * @param resolver - An instance of {@link PopupRedirectResolver}, optional * if already supplied to {@link initializeAuth} or provided by {@link getAuth}. * * @public */ async function reauthenticateWithPopup(user, provider, resolver) { const userInternal = getModularInstance(user); if (_isFirebaseServerApp(userInternal.auth.app)) { return Promise.reject(_createError(userInternal.auth, "operation-not-supported-in-this-environment" /* AuthErrorCode.OPERATION_NOT_SUPPORTED */)); } _assertInstanceOf(userInternal.auth, provider, FederatedAuthProvider); const resolverInternal = _withDefaultResolver(userInternal.auth, resolver); const action = new PopupOperation(userInternal.auth, "reauthViaPopup" /* AuthEventType.REAUTH_VIA_POPUP */, provider, resolverInternal, userInternal); return action.executeNotNull(); } /** * Links the authenticated provider to the user account using a pop-up based OAuth flow. * * @remarks * If the linking is successful, the returned result will contain the user and the provider's credential. * * This method does not work in a Node.js environment. * * @example * ```javascr