UNPKG

voluptasmollitia

Version:
1,705 lines (1,608 loc) 57.9 kB
/* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable @typescript-eslint/explicit-function-return-type */ /** * @license * Copyright 2017 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. */ /** * @fileoverview This code is for the most part copied over from the packages/auth/demo * package. */ import { initializeApp } from '@firebase/app-exp'; import { applyActionCode, browserLocalPersistence, browserSessionPersistence, confirmPasswordReset, createUserWithEmailAndPassword, EmailAuthProvider, fetchSignInMethodsForEmail, indexedDBLocalPersistence, initializeAuth, getAuth, inMemoryPersistence, isSignInWithEmailLink, linkWithCredential, multiFactor, PhoneAuthProvider, PhoneMultiFactorGenerator, reauthenticateWithCredential, RecaptchaVerifier, sendEmailVerification, sendPasswordResetEmail, sendSignInLinkToEmail, signInAnonymously, signInWithCredential, signInWithCustomToken, signInWithEmailAndPassword, unlink, updateEmail, updatePassword, updateProfile, verifyPasswordResetCode, getMultiFactorResolver, OAuthProvider, GoogleAuthProvider, FacebookAuthProvider, TwitterAuthProvider, GithubAuthProvider, signInWithPopup, linkWithPopup, reauthenticateWithPopup, signInWithRedirect, linkWithRedirect, reauthenticateWithRedirect, getRedirectResult, browserPopupRedirectResolver } from '@firebase/auth-exp'; import { config } from './config'; import { alertError, alertNotImplemented, alertSuccess, clearLogs, log, logAtLevel_ } from './logging'; let app = null; let auth = null; let currentTab = null; let lastUser = null; let applicationVerifier = null; let multiFactorErrorResolver = null; let selectedMultiFactorHint = null; let recaptchaSize = 'normal'; let webWorker = null; // The corresponding Font Awesome icons for each provider. const providersIcons = { 'google.com': 'fa-google', 'facebook.com': 'fa-facebook-official', 'twitter.com': 'fa-twitter-square', 'github.com': 'fa-github', 'yahoo.com': 'fa-yahoo', 'phone': 'fa-phone' }; /** * Returns the active user (i.e. currentUser or lastUser). * @return {!firebase.User} */ function activeUser() { const type = $('input[name=toggle-user-selection]:checked').val(); if (type === 'lastUser') { return lastUser; } else { return auth.currentUser; } } /** * Refreshes the current user data in the UI, displaying a user info box if * a user is signed in, or removing it. */ function refreshUserData() { if (activeUser()) { const user = activeUser(); $('.profile').show(); $('body').addClass('user-info-displayed'); $('div.profile-email,span.profile-email').text(user.email || 'No Email'); $('div.profile-phone,span.profile-phone').text( user.phoneNumber || 'No Phone' ); $('div.profile-uid,span.profile-uid').text(user.uid); $('div.profile-name,span.profile-name').text(user.displayName || 'No Name'); $('input.profile-name').val(user.displayName); $('input.photo-url').val(user.photoURL); if (user.photoURL != null) { let photoURL = user.photoURL; // Append size to the photo URL for Google hosted images to avoid requesting // the image with its original resolution (using more bandwidth than needed) // when it is going to be presented in smaller size. if ( photoURL.indexOf('googleusercontent.com') !== -1 || photoURL.indexOf('ggpht.com') !== -1 ) { photoURL = photoURL + '?sz=' + $('img.profile-image').height(); } $('img.profile-image').attr('src', photoURL).show(); } else { $('img.profile-image').hide(); } $('.profile-email-verified').toggle(user.emailVerified); $('.profile-email-not-verified').toggle(!user.emailVerified); $('.profile-anonymous').toggle(user.isAnonymous); // Display/Hide providers icons. $('.profile-providers').empty(); if (user['providerData'] && user['providerData'].length) { const providersCount = user['providerData'].length; for (let i = 0; i < providersCount; i++) { addProviderIcon(user['providerData'][i]['providerId']); } } // Show enrolled second factors if available for the active user. showMultiFactorStatus(user); // Change color. if (user === auth.currentUser) { $('#user-info').removeClass('last-user'); $('#user-info').addClass('current-user'); } else { $('#user-info').removeClass('current-user'); $('#user-info').addClass('last-user'); } } else { $('.profile').slideUp(); $('body').removeClass('user-info-displayed'); $('input.profile-data').val(''); } } /** * Sets last signed in user and updates UI. * @param {?firebase.User} user The last signed in user. */ function setLastUser(user) { lastUser = user; if (user) { // Displays the toggle. $('#toggle-user').show(); $('#toggle-user-placeholder').hide(); } else { $('#toggle-user').hide(); $('#toggle-user-placeholder').show(); } } /** * Add a provider icon to the profile info. * @param {string} providerId The providerId of the provider. */ function addProviderIcon(providerId) { const pElt = $('<i>') .addClass('fa ' + providersIcons[providerId]) .attr('title', providerId) .data({ 'toggle': 'tooltip', 'placement': 'bottom' }); $('.profile-providers').append(pElt); pElt.tooltip(); } /** * Updates the active user's multi-factor enrollment status. * @param {!firebase.User} activeUser The corresponding user. */ function showMultiFactorStatus(activeUser) { mfaUser = multiFactor(activeUser); const enrolledFactors = (mfaUser && mfaUser.enrolledFactors) || []; const $listGroup = $('#user-info .dropdown-menu.enrolled-second-factors'); // Hide the drop down menu initially. $listGroup.empty().parent().hide(); if (enrolledFactors.length) { // If enrolled factors are available, show the drop down menu. $listGroup.parent().show(); // Populate the enrolled factors. showMultiFactors( $listGroup, enrolledFactors, // On row click, do nothing. This is needed to prevent the drop down // menu from closing. e => { e.preventDefault(); e.stopPropagation(); }, // On delete click unenroll the selected factor. function (e) { e.preventDefault(); // Get the corresponding second factor index. const index = parseInt($(this).attr('data-index'), 10); // Get the second factor info. const info = enrolledFactors[index]; // Get the display name. If not available, use uid. const label = info && (info.displayName || info.uid); if (label) { $('#enrolled-factors-drop-down').removeClass('open'); mfaUser.unenroll(info).then(() => { refreshUserData(); alertSuccess('Multi-factor successfully unenrolled.'); }, onAuthError); } } ); } } /** * Updates the UI when the user is successfully authenticated. * @param {!firebase.User} user User authenticated. */ function onAuthSuccess(user) { console.log(user); alertSuccess('User authenticated, id: ' + user.uid); refreshUserData(); } /** * Displays an error message when the authentication failed. * @param {!Error} error Error message to display. */ function onAuthError(error) { logAtLevel_(error, 'error'); if (error.code === 'auth/multi-factor-auth-required') { // Handle second factor sign-in. handleMultiFactorSignIn(getMultiFactorResolver(auth, error)); } else { alertError('Error: ' + error.code); } } /** * Changes the UI when the user has been signed out. */ function signOut() { log('User successfully signed out.'); alertSuccess('User successfully signed out.'); refreshUserData(); } /** * Saves the new language code provided in the language code input field. */ function onSetLanguageCode() { const languageCode = $('#language-code').val() || null; try { auth.languageCode = languageCode; alertSuccess('Language code changed to "' + languageCode + '".'); } catch (error) { alertError('Error: ' + error.code); } } /** * Switches Auth instance language to device language. */ function onUseDeviceLanguage() { auth.useDeviceLanguage(); $('#language-code').val(auth.languageCode); alertSuccess('Using device language "' + auth.languageCode + '".'); } /** * Changes the Auth state persistence to the specified one. */ function onSetPersistence() { const type = $('#persistence-type').val(); let persistence; switch (type) { case 'local': persistence = browserLocalPersistence; break; case 'session': persistence = browserSessionPersistence; break; case 'indexedDB': persistence = indexedDBLocalPersistence; break; case 'none': persistence = inMemoryPersistence; break; default: alertError('Unexpected persistence type: ' + type); } try { auth.setPersistence(persistence).then( () => { log('Persistence state change to "' + type + '".'); alertSuccess('Persistence state change to "' + type + '".'); }, error => { alertError('Error: ' + error.code); } ); } catch (error) { alertError('Error: ' + error.code); } } /** * Signs up a new user with an email and a password. */ function onSignUp() { const email = $('#signup-email').val(); const password = $('#signup-password').val(); createUserWithEmailAndPassword(auth, email, password).then( onAuthUserCredentialSuccess, onAuthError ); } /** * Signs in a user with an email and a password. */ function onSignInWithEmailAndPassword() { const email = $('#signin-email').val(); const password = $('#signin-password').val(); signInWithEmailAndPassword(auth, email, password).then( onAuthUserCredentialSuccess, onAuthError ); } /** * Signs in a user with an email link. */ function onSignInWithEmailLink() { const email = $('#sign-in-with-email-link-email').val(); const link = $('#sign-in-with-email-link-link').val() || undefined; if (isSignInWithEmailLink(auth, link)) { signInWithEmailLink(auth, email, link).then(onAuthSuccess, onAuthError); } else { alertError('Sign in link is invalid'); } } /** * Links a user with an email link. */ function onLinkWithEmailLink() { const email = $('#link-with-email-link-email').val(); const link = $('#link-with-email-link-link').val() || undefined; const credential = EmailAuthProvider.credentialWithLink(email, link); linkWithCredential(activeUser(), credential).then( onAuthUserCredentialSuccess, onAuthError ); } /** * Re-authenticate a user with email link credential. */ function onReauthenticateWithEmailLink() { const email = $('#link-with-email-link-email').val(); const link = $('#link-with-email-link-link').val() || undefined; const credential = EmailAuthProvider.credentialWithLink(email, link); reauthenticateWithCredential(activeUser(), credential).then(result => { logAdditionalUserInfo(result); refreshUserData(); alertSuccess('User reauthenticated!'); }, onAuthError); } /** * Signs in with a custom token. * @param {DOMEvent} _event HTML DOM event returned by the listener. */ function onSignInWithCustomToken(_event) { // The token can be directly specified on the html element. const token = $('#user-custom-token').val(); signInWithCustomToken(auth, token).then( onAuthUserCredentialSuccess, onAuthError ); } /** * Signs in anonymously. */ function onSignInAnonymously() { signInAnonymously(auth).then(onAuthUserCredentialSuccess, onAuthError); } /** * Signs in with a generic IdP credential. */ function onSignInWithGenericIdPCredential() { alertNotImplemented(); // var providerId = $('#signin-generic-idp-provider-id').val(); // var idToken = $('#signin-generic-idp-id-token').val() || undefined; // var rawNonce = $('#signin-generic-idp-raw-nonce').val() || undefined; // var accessToken = $('#signin-generic-idp-access-token').val() || undefined; // var provider = new OAuthProvider(providerId); // signInWithCredential( // auth, // provider.credential({ // idToken: idToken, // accessToken: accessToken, // rawNonce: rawNonce, // })).then(onAuthUserCredentialSuccess, onAuthError); } /** * Initializes the ApplicationVerifier. * @param {string} submitButtonId The ID of the DOM element of the button to * which we attach the invisible reCAPTCHA. This is required even in visible * mode. */ function makeApplicationVerifier(submitButtonId) { const container = recaptchaSize === 'invisible' ? submitButtonId : 'recaptcha-container'; applicationVerifier = new RecaptchaVerifier( container, { 'size': recaptchaSize }, auth ); } /** * Clears the ApplicationVerifier. */ function clearApplicationVerifier() { if (applicationVerifier) { applicationVerifier.clear(); applicationVerifier = null; } } /** * Sends a phone number verification code for sign-in. */ function onSignInVerifyPhoneNumber() { const phoneNumber = $('#signin-phone-number').val(); const provider = new PhoneAuthProvider(auth); // Clear existing reCAPTCHA as an existing reCAPTCHA could be targeted for a // link/re-auth operation. clearApplicationVerifier(); // Initialize a reCAPTCHA application verifier. makeApplicationVerifier('signin-verify-phone-number'); provider.verifyPhoneNumber(phoneNumber, applicationVerifier).then( verificationId => { clearApplicationVerifier(); $('#signin-phone-verification-id').val(verificationId); alertSuccess('Phone verification sent!'); }, error => { clearApplicationVerifier(); onAuthError(error); } ); } /** * Confirms a phone number verification for sign-in. */ function onSignInConfirmPhoneVerification() { const verificationId = $('#signin-phone-verification-id').val(); const verificationCode = $('#signin-phone-verification-code').val(); const credential = PhoneAuthProvider.credential( verificationId, verificationCode ); signInOrLinkCredential(credential); } /** * Sends a phone number verification code for linking or reauth. */ function onLinkReauthVerifyPhoneNumber() { const phoneNumber = $('#link-reauth-phone-number').val(); const provider = new PhoneAuthProvider(auth); // Clear existing reCAPTCHA as an existing reCAPTCHA could be targeted for a // sign-in operation. clearApplicationVerifier(); // Initialize a reCAPTCHA application verifier. makeApplicationVerifier('link-reauth-verify-phone-number'); provider.verifyPhoneNumber(phoneNumber, applicationVerifier).then( verificationId => { clearApplicationVerifier(); $('#link-reauth-phone-verification-id').val(verificationId); alertSuccess('Phone verification sent!'); }, error => { clearApplicationVerifier(); onAuthError(error); } ); } /** * Updates the user's phone number. */ function onUpdateConfirmPhoneVerification() { if (!activeUser()) { alertError('You need to sign in before linking an account.'); return; } const verificationId = $('#link-reauth-phone-verification-id').val(); const verificationCode = $('#link-reauth-phone-verification-code').val(); const credential = PhoneAuthProvider.credential( verificationId, verificationCode ); activeUser() .updatePhoneNumber(credential) .then(() => { refreshUserData(); alertSuccess('Phone number updated!'); }, onAuthError); } /** * Confirms a phone number verification for linking. */ function onLinkConfirmPhoneVerification() { const verificationId = $('#link-reauth-phone-verification-id').val(); const verificationCode = $('#link-reauth-phone-verification-code').val(); const credential = PhoneAuthProvider.credential( verificationId, verificationCode ); signInOrLinkCredential(credential); } /** * Confirms a phone number verification for reauthentication. */ function onReauthConfirmPhoneVerification() { const verificationId = $('#link-reauth-phone-verification-id').val(); const verificationCode = $('#link-reauth-phone-verification-code').val(); const credential = PhoneAuthProvider.credential( verificationId, verificationCode ); reauthenticateWithCredential(activeUser(), credential).then(result => { logAdditionalUserInfo(result); refreshUserData(); alertSuccess('User reauthenticated!'); }, onAuthError); } /** * Sends a phone number verification code for enrolling second factor. */ function onStartEnrollWithPhoneMultiFactor() { const phoneNumber = $('#enroll-mfa-phone-number').val(); if (!phoneNumber || !activeUser()) { return; } const provider = new PhoneAuthProvider(auth); // Clear existing reCAPTCHA as an existing reCAPTCHA could be targeted for a // sign-in operation. clearApplicationVerifier(); // Initialize a reCAPTCHA application verifier. makeApplicationVerifier('enroll-mfa-verify-phone-number'); multiFactor(activeUser()) .getSession() .then(multiFactorSession => { const phoneInfoOptions = { phoneNumber, 'session': multiFactorSession }; return provider.verifyPhoneNumber(phoneInfoOptions, applicationVerifier); }) .then( verificationId => { clearApplicationVerifier(); $('#enroll-mfa-phone-verification-id').val(verificationId); alertSuccess('Phone verification sent!'); }, error => { clearApplicationVerifier(); onAuthError(error); } ); } /** * Confirms a phone number verification for MFA enrollment. */ function onFinalizeEnrollWithPhoneMultiFactor() { const verificationId = $('#enroll-mfa-phone-verification-id').val(); const verificationCode = $('#enroll-mfa-phone-verification-code').val(); if (!verificationId || !verificationCode || !activeUser()) { return; } const credential = PhoneAuthProvider.credential( verificationId, verificationCode ); const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(credential); const displayName = $('#enroll-mfa-phone-display-name').val() || undefined; multiFactor(activeUser()) .enroll(multiFactorAssertion, displayName) .then(() => { refreshUserData(); alertSuccess('Phone number enrolled!'); }, onAuthError); } /** * Signs in or links a provider's credential, based on current tab opened. * @param {!AuthCredential} credential The provider's credential. */ function signInOrLinkCredential(credential) { if (currentTab === '#user-section') { if (!activeUser()) { alertError('You need to sign in before linking an account.'); return; } linkWithCredential(activeUser(), credential).then(result => { logAdditionalUserInfo(result); refreshUserData(); alertSuccess('Provider linked!'); }, onAuthError); } else { signInWithCredential(auth, credential).then( onAuthUserCredentialSuccess, onAuthError ); } } /** @return {!Object} The Action Code Settings object. */ function getActionCodeSettings() { const actionCodeSettings = {}; const url = $('#continueUrl').val(); const apn = $('#apn').val(); const amv = $('#amv').val(); const ibi = $('#ibi').val(); const installApp = $('input[name=install-app]:checked').val() === 'Yes'; const handleCodeInApp = $('input[name=handle-in-app]:checked').val() === 'Yes'; if (url || apn || ibi) { actionCodeSettings['url'] = url; if (apn) { actionCodeSettings['android'] = { 'packageName': apn, 'installApp': !!installApp, 'minimumVersion': amv || undefined }; } if (ibi) { actionCodeSettings['iOS'] = { 'bundleId': ibi }; } actionCodeSettings['handleCodeInApp'] = handleCodeInApp; } return actionCodeSettings; } /** Reset action code settings form. */ function onActionCodeSettingsReset() { $('#continueUrl').val(''); $('#apn').val(''); $('#amv').val(''); $('#ibi').val(''); } /** * Changes the user's email. */ function onChangeEmail() { const email = $('#changed-email').val(); updateEmail(activeUser(), email).then(() => { refreshUserData(); alertSuccess('Email changed!'); }, onAuthError); } /** * Changes the user's password. */ function onChangePassword() { const password = $('#changed-password').val(); updatePassword(activeUser(), password).then(() => { refreshUserData(); alertSuccess('Password changed!'); }, onAuthError); } /** * Changes the user's password. */ function onUpdateProfile() { const displayName = $('#display-name').val(); const photoURL = $('#photo-url').val(); updateProfile(activeUser(), { displayName, photoURL }).then(() => { refreshUserData(); alertSuccess('Profile updated!'); }, onAuthError); } /** * Sends sign in with email link to the user. */ function onSendSignInLinkToEmail() { const email = $('#sign-in-with-email-link-email').val(); sendSignInLinkToEmail(auth, email, getActionCodeSettings()).then(() => { alertSuccess('Email sent!'); }, onAuthError); } /** * Sends sign in with email link to the user and pass in current url. */ function onSendSignInLinkToEmailCurrentUrl() { const email = $('#sign-in-with-email-link-email').val(); const actionCodeSettings = { 'url': window.location.href, 'handleCodeInApp': true }; sendSignInLinkToEmail(auth, email, actionCodeSettings).then(() => { if ('localStorage' in window && window['localStorage'] !== null) { window.localStorage.setItem( 'emailForSignIn', // Save the email and the timestamp. JSON.stringify({ email, timestamp: new Date().getTime() }) ); } alertSuccess('Email sent!'); }, onAuthError); } /** * Sends email link to link the user. */ function onSendLinkEmailLink() { const email = $('#link-with-email-link-email').val(); sendSignInLinkToEmail(auth, email, getActionCodeSettings()).then(() => { alertSuccess('Email sent!'); }, onAuthError); } /** * Sends password reset email to the user. */ function onSendPasswordResetEmail() { const email = $('#password-reset-email').val(); sendPasswordResetEmail(auth, email, getActionCodeSettings()).then(() => { alertSuccess('Email sent!'); }, onAuthError); } /** * Verifies the password reset code entered by the user. */ function onVerifyPasswordResetCode() { const code = $('#password-reset-code').val(); verifyPasswordResetCode(auth, code).then(() => { alertSuccess('Password reset code is valid!'); }, onAuthError); } /** * Confirms the password reset with the code and password supplied by the user. */ function onConfirmPasswordReset() { const code = $('#password-reset-code').val(); const password = $('#password-reset-password').val(); confirmPasswordReset(auth, code, password).then(() => { alertSuccess('Password has been changed!'); }, onAuthError); } /** * Gets the list of possible sign in methods for the given email address. */ function onFetchSignInMethodsForEmail() { const email = $('#fetch-sign-in-methods-email').val(); fetchSignInMethodsForEmail(auth, email).then(signInMethods => { log('Sign in methods for ' + email + ' :'); log(signInMethods); if (signInMethods.length === 0) { alertSuccess('Sign In Methods for ' + email + ': N/A'); } else { alertSuccess( 'Sign In Methods for ' + email + ': ' + signInMethods.join(', ') ); } }, onAuthError); } /** * Fetches and logs the user's providers data. */ function onGetProviderData() { log('Providers data:'); log(activeUser()['providerData']); } /** * Links a signed in user with an email and password account. */ function onLinkWithEmailAndPassword() { const email = $('#link-email').val(); const password = $('#link-password').val(); linkWithCredential( activeUser(), EmailAuthProvider.credential(email, password) ).then(onAuthUserCredentialSuccess, onAuthError); } /** * Links with a generic IdP credential. */ function onLinkWithGenericIdPCredential() { alertNotImplemented(); // var providerId = $('#link-generic-idp-provider-id').val(); // var idToken = $('#link-generic-idp-id-token').val() || undefined; // var rawNonce = $('#link-generic-idp-raw-nonce').val() || undefined; // var accessToken = $('#link-generic-idp-access-token').val() || undefined; // var provider = new OAuthProvider(providerId); // activeUser().linkWithCredential( // provider.credential({ // idToken: idToken, // accessToken: accessToken, // rawNonce: rawNonce, // })).then(onAuthUserCredentialSuccess, onAuthError); } /** * Unlinks the specified provider. */ function onUnlinkProvider() { const providerId = $('#unlinked-provider-id').val(); unlink(activeUser(), providerId).then(_user => { alertSuccess('Provider unlinked from user.'); refreshUserData(); }, onAuthError); } /** * Sends email verification to the user. */ function onSendEmailVerification() { sendEmailVerification(activeUser(), getActionCodeSettings()).then(() => { alertSuccess('Email verification sent!'); }, onAuthError); } /** * Confirms the email verification code given. */ function onApplyActionCode() { var code = $('#email-verification-code').val(); applyActionCode(auth, code).then(function () { alertSuccess('Email successfully verified!'); refreshUserData(); }, onAuthError); } /** * Gets or refreshes the ID token. * @param {boolean} forceRefresh Whether to force the refresh of the token * or not. */ function getIdToken(forceRefresh) { if (activeUser() == null) { alertError('No user logged in.'); return; } if (activeUser().getIdToken) { activeUser() .getIdToken(forceRefresh) .then(alertSuccess, () => { log('No token'); }); } else { activeUser() .getToken(forceRefresh) .then(alertSuccess, () => { log('No token'); }); } } /** * Gets or refreshes the ID token result. * @param {boolean} forceRefresh Whether to force the refresh of the token * or not */ function getIdTokenResult(forceRefresh) { if (activeUser() == null) { alertError('No user logged in.'); return; } activeUser() .getIdTokenResult(forceRefresh) .then(idTokenResult => { alertSuccess(JSON.stringify(idTokenResult)); }, onAuthError); } /** * Triggers the retrieval of the ID token result. */ function onGetIdTokenResult() { getIdTokenResult(false); } /** * Triggers the refresh of the ID token result. */ function onRefreshTokenResult() { getIdTokenResult(true); } /** * Triggers the retrieval of the ID token. */ function onGetIdToken() { getIdToken(false); } /** * Triggers the refresh of the ID token. */ function onRefreshToken() { getIdToken(true); } /** * Signs out the user. */ function onSignOut() { setLastUser(auth.currentUser); auth.signOut().then(signOut, onAuthError); } /** * Handles multi-factor sign-in completion. * @param {!MultiFactorResolver} resolver The multi-factor error * resolver. */ function handleMultiFactorSignIn(resolver) { // Save multi-factor error resolver. multiFactorErrorResolver = resolver; // Populate 2nd factor options from resolver. const $listGroup = $('#multiFactorModal div.enrolled-second-factors'); // Populate the list of 2nd factors in the list group specified. showMultiFactors( $listGroup, multiFactorErrorResolver.hints, // On row click, select the corresponding second factor to complete // sign-in with. function (e) { e.preventDefault(); // Remove all other active entries. $listGroup.find('a').removeClass('active'); // Mark current entry as active. $(this).addClass('active'); // Select current factor. onSelectMultiFactorHint(parseInt($(this).attr('data-index'), 10)); }, // Do not show delete option null ); // Hide phone form (other second factor types could be supported). $('#multi-factor-phone').addClass('hidden'); // Show second factor recovery dialog. $('#multiFactorModal').modal(); } /** * Displays the list of multi-factors in the provided list group. * @param {!jQuery<!HTMLElement>} $listGroup The list group where the enrolled * factors will be displayed. * @param {!Array<!MultiFactorInfo>} multiFactorInfo The list of * multi-factors to display. * @param {?function(!jQuery.Event)} onClick The click handler when a second * factor is clicked. * @param {?function(!jQuery.Event)} onDelete The click handler when a second * factor is delete. If not provided, no delete button is shown. */ function showMultiFactors($listGroup, multiFactorInfo, onClick, onDelete) { // Append entry to list. $listGroup.empty(); $.each(multiFactorInfo, i => { // Append entry to list. const info = multiFactorInfo[i]; const displayName = info.displayName || 'N/A'; const $a = $('<a href="#" />') .addClass('list-group-item') .addClass('list-group-item-action') // Set index on entry. .attr('data-index', i) .appendTo($listGroup); $a.append($('<h4 class="list-group-item-heading" />').text(info.uid)); $a.append($('<span class="badge" />').text(info.factorId)); $a.append($('<p class="list-group-item-text" />').text(displayName)); if (info.phoneNumber) { $a.append($('<small />').text(info.phoneNumber)); } // Check if a delete button is to be displayed. if (onDelete) { const $deleteBtn = $( '<span class="pull-right button-group">' + '<button type="button" class="btn btn-danger btn-xs delete-factor" ' + 'data-index="' + i.toString() + '">' + '<i class="fa fa-trash" data-title="Remove" />' + '</button>' + '</span>' ); // Append delete button to row. $a.append($deleteBtn); // Add delete button click handler. $a.find('button.delete-factor').click(onDelete); } // On entry click. if (onClick) { $a.click(onClick); } }); } /** * Handles the user selection of second factor to complete sign-in with. * @param {number} index The selected multi-factor hint index. */ function onSelectMultiFactorHint(index) { // Hide all forms for handling each type of second factors. // Currently only phone is supported. $('#multi-factor-phone').addClass('hidden'); if ( !multiFactorErrorResolver || typeof multiFactorErrorResolver.hints[index] === 'undefined' ) { return; } if (multiFactorErrorResolver.hints[index].factorId === 'phone') { // Save selected second factor. selectedMultiFactorHint = multiFactorErrorResolver.hints[index]; // Show options for phone 2nd factor. // Get reCAPTCHA ready. clearApplicationVerifier(); makeApplicationVerifier('send-2fa-phone-code'); // Show sign-in with phone second factor menu. $('#multi-factor-phone').removeClass('hidden'); // Clear all input. $('#multi-factor-sign-in-verification-id').val(''); $('#multi-factor-sign-in-verification-code').val(''); } else { // 2nd factor not found or not supported by app. alertError('Selected 2nd factor is not supported!'); } } /** * Start sign-in with the 2nd factor phone number. * @param {!jQuery.Event} event The jQuery event object. */ function onStartSignInWithPhoneMultiFactor(event) { event.preventDefault(); // Make sure a second factor is selected. if (!selectedMultiFactorHint || !multiFactorErrorResolver) { return; } // Initialize a reCAPTCHA application verifier. const provider = new PhoneAuthProvider(auth); const signInRequest = { multiFactorHint: selectedMultiFactorHint, session: multiFactorErrorResolver.session }; provider.verifyPhoneNumber(signInRequest, applicationVerifier).then( verificationId => { clearApplicationVerifier(); $('#multi-factor-sign-in-verification-id').val(verificationId); alertSuccess('Phone verification sent!'); }, error => { clearApplicationVerifier(); onAuthError(error); } ); } /** * Completes sign-in with the 2nd factor phone assertion. * @param {!jQuery.Event} event The jQuery event object. */ function onFinalizeSignInWithPhoneMultiFactor(event) { event.preventDefault(); const verificationId = $('#multi-factor-sign-in-verification-id').val(); const code = $('#multi-factor-sign-in-verification-code').val(); if (!code || !verificationId || !multiFactorErrorResolver) { return; } const cred = PhoneAuthProvider.credential(verificationId, code); const assertion = PhoneMultiFactorGenerator.assertion(cred); multiFactorErrorResolver.resolveSignIn(assertion).then(userCredential => { onAuthUserCredentialSuccess(userCredential); $('#multiFactorModal').modal('hide'); }, onAuthError); } /** * Adds a new row to insert an OAuth custom parameter key/value pair. * @param {!jQuery.Event} _event The jQuery event object. */ function onPopupRedirectAddCustomParam(_event) { // Form container. let html = '<form class="customParamItem form form-bordered no-submit">'; // OAuth parameter key input. html += '<input type="text" class="form-control customParamKey" ' + 'placeholder="OAuth Parameter Key"/>'; // OAuth parameter value input. html += '<input type="text" class="form-control customParamValue" ' + 'placeholder="OAuth Parameter Value"/>'; // Button to remove current key/value pair. html += '<button class="btn btn-block btn-primary">Remove</button>'; html += '</form>'; // Create jQuery node. const $node = $(html); // Add button click event listener to remove item. $node.find('button').on('click', function (e) { // Remove button click event listener. $(this).off('click'); // Get row container and remove it. $(this).closest('form.customParamItem').remove(); e.preventDefault(); }); // Append constructed row to parameter list container. $('#popup-redirect-custom-parameters').append($node); } /** * Performs the corresponding popup/redirect action for a generic provider. */ function onPopupRedirectGenericProviderClick() { var providerId = $('#popup-redirect-generic-providerid').val(); var provider = new OAuthProvider(providerId); signInWithPopupRedirect(provider); } /** * Performs the corresponding popup/redirect action for a SAML provider. */ function onPopupRedirectSamlProviderClick() { alertNotImplemented(); // var providerId = $('#popup-redirect-saml-providerid').val(); // var provider = new SAMLAuthProvider(providerId); // signInWithPopupRedirect(provider); } /** * Performs the corresponding popup/redirect action based on user's selection. * @param {!jQuery.Event} _event The jQuery event object. */ function onPopupRedirectProviderClick(_event) { const providerId = $(event.currentTarget).data('provider'); let provider = null; switch (providerId) { case 'google.com': provider = new GoogleAuthProvider(); break; case 'facebook.com': provider = new FacebookAuthProvider(); break; case 'github.com': provider = new GithubAuthProvider(); break; case 'twitter.com': provider = new TwitterAuthProvider(); break; default: return; } signInWithPopupRedirect(provider); } /** * Performs a popup/redirect action based on a given provider and the user's * selections. * @param {!AuthProvider} provider The provider with which to * sign in. */ function signInWithPopupRedirect(provider) { let action = $('input[name=popup-redirect-action]:checked').val(); let type = $('input[name=popup-redirect-type]:checked').val(); let method = null; let inst = null; switch (action) { case 'link': if (!activeUser()) { alertError('No user logged in.'); return; } inst = activeUser(); method = type === 'popup' ? linkWithPopup : linkWithRedirect; break; case 'reauthenticate': if (!activeUser()) { alertError('No user logged in.'); return; } inst = activeUser(); method = type === 'popup' ? reauthenticateWithPopup : reauthenticateWithRedirect; break; default: inst = auth; method = type === 'popup' ? signInWithPopup : signInWithRedirect; } // Get custom OAuth parameters. const customParameters = {}; // For each entry. $('form.customParamItem').each(function (_index) { // Get parameter key. const key = $(this).find('input.customParamKey').val(); // Get parameter value. const value = $(this).find('input.customParamValue').val(); // Save to list if valid. if (key && value) { customParameters[key] = value; } }); console.log('customParameters: ', customParameters); // For older jscore versions that do not support this. if (provider.setCustomParameters) { // Set custom parameters on current provider. provider.setCustomParameters(customParameters); } // Add scopes for providers who do have scopes available (i.e. not Twitter). if (provider.addScope) { // String.prototype.trim not available in IE8. const scopes = $.trim($('#scopes').val()).split(/\s*,\s*/); for (let i = 0; i < scopes.length; i++) { provider.addScope(scopes[i]); } } console.log('Provider:'); console.log(provider); if (type == 'popup') { method(inst, provider, browserPopupRedirectResolver).then(response => { console.log('Popup response:'); console.log(response); alertSuccess(action + ' with ' + provider['providerId'] + ' successful!'); logAdditionalUserInfo(response); onAuthSuccess(activeUser()); }, onAuthError); } else { try { method(inst, provider, browserPopupRedirectResolver).catch(onAuthError); } catch (error) { console.log('Error while calling ' + method); console.error(error); } } } /** * Displays user credential result. * @param {!UserCredential} result The UserCredential result * object. */ function onAuthUserCredentialSuccess(result) { onAuthSuccess(result.user); logAdditionalUserInfo(result); } /** * Displays redirect result. */ function onGetRedirectResult() { getRedirectResult(auth, browserPopupRedirectResolver).then(function ( response ) { log('Redirect results:'); if (response.credential) { log('Credential:'); log(response.credential); } else { log('No credential'); } if (response.user) { log("User's id:"); log(response.user.uid); } else { log('No user'); } logAdditionalUserInfo(response); console.log(response); }, onAuthError); } /** * Logs additional user info returned by a sign-in event, if available. * @param {!Object} response */ function logAdditionalUserInfo(response) { if (response?.additionalUserInfo) { if (response.additionalUserInfo.username) { log( response.additionalUserInfo['providerId'] + ' username: ' + response.additionalUserInfo.username ); } if (response.additionalUserInfo.profile) { log(response.additionalUserInfo['providerId'] + ' profile information:'); log(JSON.stringify(response.additionalUserInfo.profile, null, 2)); } if (typeof response.additionalUserInfo.isNewUser !== 'undefined') { log( response.additionalUserInfo['providerId'] + ' isNewUser: ' + response.additionalUserInfo.isNewUser ); } if (response.credential) { log('credential: ' + JSON.stringify(response.credential.toJSON())); } } } /** * Deletes the user account. */ function onDelete() { activeUser() ['delete']() .then(() => { log('User successfully deleted.'); alertSuccess('User successfully deleted.'); refreshUserData(); }, onAuthError); } /** * Gets a specific query parameter from the current URL. * @param {string} name Name of the parameter. * @return {string} The query parameter requested. */ function getParameterByName(name) { const url = window.location.href; name = name.replace(/[\[\]]/g, '\\$&'); const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'); const results = regex.exec(url); if (!results) { return null; } if (!results[2]) { return ''; } return decodeURIComponent(results[2].replace(/\+/g, ' ')); } /** * Detects if an action code is passed in the URL, and populates accordingly * the input field for the confirm email verification process. */ function populateActionCodes() { let emailForSignIn = null; let signInTime = 0; if ('localStorage' in window && window['localStorage'] !== null) { try { // Try to parse as JSON first using new storage format. const emailForSignInData = JSON.parse( window.localStorage.getItem('emailForSignIn') ); emailForSignIn = emailForSignInData['email'] || null; signInTime = emailForSignInData['timestamp'] || 0; } catch (e) { // JSON parsing failed. This means the email is stored in the old string // format. emailForSignIn = window.localStorage.getItem('emailForSignIn'); } if (emailForSignIn) { // Clear old codes. Old format codes should be cleared immediately. if (new Date().getTime() - signInTime >= 1 * 24 * 3600 * 1000) { // Remove email from storage. window.localStorage.removeItem('emailForSignIn'); } } } const actionCode = getParameterByName('oobCode'); if (actionCode != null) { const mode = getParameterByName('mode'); if (mode === 'verifyEmail') { $('#email-verification-code').val(actionCode); } else if (mode === 'resetPassword') { $('#password-reset-code').val(actionCode); } else if (mode === 'signIn') { if (emailForSignIn) { $('#sign-in-with-email-link-email').val(emailForSignIn); $('#sign-in-with-email-link-link').val(window.location.href); onSignInWithEmailLink(); // Remove email from storage as the code is only usable once. window.localStorage.removeItem('emailForSignIn'); } } else { $('#email-verification-code').val(actionCode); $('#password-reset-code').val(actionCode); } } } /** * Provides basic Database checks for authenticated and unauthenticated access. * The Database node being tested has the following rule: * "users": { * "$user_id": { * ".read": "$user_id === auth.uid", * ".write": "$user_id === auth.uid" * } * } * This applies when Real-time database service is available. */ function checkDatabaseAuthAccess() { const randomString = Math.floor(Math.random() * 10000000).toString(); let dbRef; let dbPath; let errMessage; // Run this check only when Database module is available. if ( typeof firebase !== 'undefined' && typeof firebase.database !== 'undefined' ) { if (lastUser && !auth.currentUser) { dbPath = 'users/' + lastUser.uid; // After sign out, confirm read/write access to users/$user_id blocked. dbRef = firebase.database().ref(dbPath); dbRef .set({ 'test': randomString }) .then(() => { alertError( 'Error: Unauthenticated write to Database node ' + dbPath + ' unexpectedly succeeded!' ); }) .catch(error => { errMessage = error.message.toLowerCase(); // Permission denied error should be thrown. if (errMessage.indexOf('permission_denied') === -1) { alertError('Error: ' + error.code); return; } dbRef .once('value') .then(() => { alertError( 'Error: Unauthenticated read to Database node ' + dbPath + ' unexpectedly succeeded!' ); }) .catch(error => { errMessage = error.message.toLowerCase(); // Permission denied error should be thrown. if (errMessage.indexOf('permission_denied') === -1) { alertError('Error: ' + error.code); return; } log( 'Unauthenticated read/write to Database node ' + dbPath + ' failed as expected!' ); }); }); } else if (auth.currentUser) { dbPath = 'users/' + auth.currentUser.uid; // Confirm read/write access to users/$user_id allowed. dbRef = firebase.database().ref(dbPath); dbRef .set({ 'test': randomString }) .then(() => { return dbRef.once('value'); }) .then(snapshot => { if (snapshot.val().test === randomString) { // read/write successful. log( 'Authenticated read/write to Database node ' + dbPath + ' succeeded!' ); } else { throw new Error( 'Authenticated read/write to Database node ' + dbPath + ' failed!' ); } // Clean up: clear that node's content. return dbRef.remove(); }) .catch(error => { alertError('Error: ' + error.code); }); } } } /** * Runs various Firebase Auth tests in a web worker environment and confirms the * expected behavior. This is useful for manual testing in different browsers. * @param {string} googleIdToken The Google ID token to sign in with. */ function onRunWebWorkTests() { if (!webWorker) { alertError('Error: Web workers are not supported in the current browser!'); return; } signInWithPopup( auth, new GoogleAuthProvider(), browserPopupRedirectResolver ).then( result => { const credential = GoogleAuthProvider.credentialFromResult(result); webWorker.postMessage({ type: 'RUN_TESTS', googleIdToken: credential.idToken }); }, error => { alertError('Error code: ' + error.code + ' message: ' + error.message); } ); } /** Runs service worker tests if supported. */ function onRunServiceWorkTests() { $.ajax('/checkIfAuthenticated').then( (data, _textStatus, _jqXHR) => { alertSuccess('User authenticated: ' + data.uid); }, (jqXHR, _textStatus, _errorThrown) => { alertError(jqXHR.status + ': ' + JSON.stringify(jqXHR.responseJSON)); } ); } /** Copy current user of auth to tempAuth. */ function onCopyActiveUser() { tempAuth.updateCurrentUser(activeUser()).then( () => { alertSuccess('Copied active user to temp Auth'); }, error => { alertError('Error: ' + error.code); } ); } /** Copy last user to auth. */ function onCopyLastUser() { // If last user is null, NULL_USER error will be thrown. auth.updateCurrentUser(lastUser).then( () => { alertSuccess('Copied last user to Auth'); }, error => { alertError('Error: ' + error.code); } ); } /** Applies selected auth settings change. */ function onApplyAuthSettingsChange() { try { auth.settings.appVerificationDisabledForTesting = $('input[name=enable-app-verification]:checked').val() === 'No'; alertSuccess('Auth settings changed'); } catch (error) { alertError('Error: ' + error.code); } } /** * Initiates the application by setting event listeners on the various buttons. */ function initApp() { log('Initializing app...'); app = initializeApp(config); auth = getAuth(app); tempApp = initializeApp( { apiKey: config.apiKey, authDomain: config.authDomain }, `${auth.name}-temp` ); tempAuth = initializeAuth(tempApp, { persistence: inMemoryPersistence, popupRedirectResolver: browserPopupRedirectResolver }); // Listen to reCAPTCHA config togglers. initRecaptchaToggle(size => { clearApplicationVerifier(); recaptchaSize = size; }); // The action code for email verification or password reset // can be passed in the url address as a parameter, and for convenience // this preloads the input field. populateActionCodes(); // Allows to login the user if previously logged in. if (auth.onIdTokenChanged) { auth.onIdTokenChanged(user => { refreshUserData(); if (user) { user.getIdTokenResult(false).then( idTokenResult => { log(JSON.stringify(idTokenResult)); }, () => { log('No token.'); } ); } else { log('No user logged in.'); } }); } if (auth.onAuthStateChanged) { auth.onAuthStateChanged(user => { if (user) { log('user state change detected: ' + user.uid); } else { log('user state change detected: no user'); } // Check Database Auth access. checkDatabaseAuthAccess(); }); } if (tempAuth.onAuthStateChanged) { tempAuth.onAuthStateChanged(user => { if (user) { log('user state change on temp Auth detect: ' + JSON.stringify(user)); alertSuccess('user stat