![](/img/trans.png)
[英]How to customize firebase action URL for password reset and email verification?
[英]Firebase - Customize reset password landing page
我可以在 Firebase 中自定義密碼重置的登錄頁面嗎? 我想本地化該頁面,因為我的應用程序不是英文的。 有什么辦法嗎?
提前致謝。
您可以在Firebase Console -> Auth -> Email Templates -> Password Reset
下自定義密碼重置電子郵件,並將電子郵件中的鏈接更改為指向您自己的頁面。 請注意, <code>
占位符將被 URL 中的密碼重置代碼替換。
然后,在您的自定義頁面中,您可以從 URL 讀取密碼重置代碼並執行
firebase.auth().confirmPasswordReset(code, newPassword)
.then(function() {
// Success
})
.catch(function() {
// Invalid code
})
或者,您可以在顯示密碼重置表單之前先檢查代碼是否有效
firebase.auth().verifyPasswordResetCode(code)
.then(function(email) {
// Display a "new password" form with the user's email address
})
.catch(function() {
// Invalid code
})
https://firebase.google.com/docs/reference/js/firebase.auth.Auth#confirmPasswordReset https://firebase.google.com/docs/reference/js/firebase.auth.Auth#verifyPasswordResetCode
@Channing Huang 提供的答案是正確答案,但您還需要記住,它將返回的錯誤並不總是invalid-code
。
檢查錯誤是否可能已過期,其中用戶直到稍后才打開 URL 或可能在其他情況下。 如果過期,您可以再發送一封電子郵件。
替換const firebaseConfig = {};
在下面的代碼中使用您的firebaseConfig並且您有一個有效的自定義電子郵件身份驗證處理程序頁面。
有很多理由想要使用自定義電子郵件處理程序頁面來執行 firebase 身份驗證操作。
https://auth.mydomain.com
,下面的代碼在索引中.html 在根目錄。 因此附加了 firebase 電子郵件處理程序參數,電子郵件中的鏈接看起來像https://auth.mydomain.com/?mode=resetPassword&oobCode=longStringCode&apiKey=apiCodeString&lang=en
注意:當您設置自定義操作處理程序 url 時,您的 url 指向的頁面必須處理所有電子郵件操作模式。 例如,您不能只為密碼重置設置自定義 url,並在模板控制台中使用默認的電子郵件驗證 url。 您必須處理自定義電子郵件處理程序頁面 url 上的所有模式。 下面的代碼處理所有模式。
在代碼中,您會找到validatePasswordComplexity
函數。 它當前設置為顯示最低密碼復雜性要求,如您在下面的屏幕截圖中所見。 當用戶輸入所需的最小值時,紅色警報將被移除。 例如,當用戶輸入特殊字符時,缺少特殊字符的紅色警告將消失,依此類推,直到密碼滿足您的復雜性要求並且警告消失。 在滿足復雜性要求之前,用戶無法重置密碼。 例如,如果您希望要求用戶輸入 2 個特殊字符,則更改hasMinSpecialChar
正則表達式,將{1,}
更改為{2,}
。
自定義身份驗證電子郵件模式處理程序
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Firebase Auth Handlers</title>
<meta name="robots" content="noindex">
<link rel="icon" type="image/png" href="favicon.png"/>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.3.0/font/bootstrap-icons.css" />
<style>
body {
font-family: Roboto,sans-serif;
}
i {
margin-left: -30px;
cursor: pointer;
}
.button {
background-color: #141645;
border: none;
color: white;
padding: 11px 20px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
border-radius: 4px;
}
input {
width: 200px;
padding: 12px 20px;
margin: 8px 0;
display: inline-block;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
.red-alert {
color: #B71C1C;
}
.center {
position: absolute;
left: 50%;
top: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
text-align: center;
}
#cover-spin {
position:fixed;
width:100%;
left:0;right:0;top:0;bottom:0;
background-color: rgba(255,255,255,0.7);
z-index:9999;
}
@-webkit-keyframes spin {
from {-webkit-transform:rotate(0deg);}
to {-webkit-transform:rotate(360deg);}
}
@keyframes spin {
from {transform:rotate(0deg);}
to {transform:rotate(360deg);}
}
#cover-spin::after {
content:'';
display:block;
position:absolute;
left:48%;top:40%;
width:40px;height:40px;
border-style:solid;
border-color:black;
border-top-color:transparent;
border-width: 4px;
border-radius:50%;
-webkit-animation: spin .8s linear infinite;
animation: spin .8s linear infinite;
}
</style>
<script>
const AuthHandler = {
init: props => {
AuthHandler.conf = props
AuthHandler.bindMode()
},
bindMode: () => {
switch (AuthHandler.conf.mode) {
case 'resetPassword':
AuthHandler.setModeTitle('Password Reset')
if (!AuthHandler.validateRequiredAuthParams()) {
AuthHandler.displayErrorMessage(AuthHandler.conf.defaultErrorMessage)
return
}
AuthHandler.handleResetPassword()
break;
case 'recoverEmail':
AuthHandler.setModeTitle('Email Recovery')
if (!AuthHandler.validateRequiredAuthParams()) {
AuthHandler.displayErrorMessage(AuthHandler.conf.defaultErrorMessage)
return
}
AuthHandler.handleRecoverEmail()
break;
case 'verifyEmail':
AuthHandler.setModeTitle('Email Verification')
if (!AuthHandler.validateRequiredAuthParams()) {
AuthHandler.displayErrorMessage(AuthHandler.conf.defaultErrorMessage)
return
}
AuthHandler.handleVerifyEmail()
break;
default:
AuthHandler.displayErrorMessage(AuthHandler.conf.defaultErrorMessage)
break;
}
},
handleResetPassword: () => {
AuthHandler.showLoadingSpinner()
// Verify the code is valid before displaying the reset password form.
AuthHandler.conf.verifyPasswordResetCode(AuthHandler.conf.auth, AuthHandler.conf.oobCode).then(() => {
AuthHandler.hideLoadingSpinner()
// Display the form if we have a valid reset code.
AuthHandler.showPasswordResetForm()
AuthHandler.conf.passwordField.addEventListener('input', AuthHandler.validatePasswordComplexity);
AuthHandler.conf.passwordToggleButton.addEventListener('click', e => {
AuthHandler.conf.passwordField.setAttribute(
'type',
AuthHandler.conf.passwordField.getAttribute('type') === 'password'
? 'text' : 'password');
e.target.classList.toggle('bi-eye');
});
AuthHandler.conf.passwordResetButton.addEventListener('click', () => {
AuthHandler.hideMessages()
// Test the password again. If it does not pass, errors will display.
if (AuthHandler.validatePasswordComplexity(AuthHandler.conf.passwordField)) {
AuthHandler.showLoadingSpinner()
// Attempt to reset the password.
AuthHandler.conf.confirmPasswordReset(
AuthHandler.conf.auth,
AuthHandler.conf.oobCode,
AuthHandler.conf.passwordField.value.trim()
).then(() => {
AuthHandler.hidePasswordResetForm()
AuthHandler.hideLoadingSpinner()
AuthHandler.displaySuccessMessage('Password has been reset!')
}).catch(() => {
AuthHandler.hideLoadingSpinner()
AuthHandler.displayErrorMessage('Password reset failed. Please try again.')
})
}
});
}).catch(() => {
AuthHandler.hideLoadingSpinner()
AuthHandler.hidePasswordResetForm()
AuthHandler.displayErrorMessage('Invalid password reset code. Please try again.')
});
},
handleRecoverEmail: () => {
AuthHandler.showLoadingSpinner()
let restoredEmail = null;
AuthHandler.conf.checkActionCode(AuthHandler.conf.auth, AuthHandler.conf.oobCode).then(info => {
restoredEmail = info['data']['email'];
AuthHandler.conf.applyActionCode(AuthHandler.conf.auth, AuthHandler.conf.oobCode).then(() => {
AuthHandler.conf.sendPasswordResetEmail(AuthHandler.conf.auth, restoredEmail).then(() => {
AuthHandler.hideLoadingSpinner()
AuthHandler.displaySuccessMessage(`Your email has been restored and a reset password email has been sent to ${restoredEmail}. For security, please reset your password immediately.`)
}).catch(() => {
AuthHandler.hideLoadingSpinner()
AuthHandler.displaySuccessMessage(`Your email ${restoredEmail} has been restored. For security, please reset your password immediately.`)
})
}).catch(() => {
AuthHandler.hideLoadingSpinner()
AuthHandler.displayErrorMessage('Sorry, something went wrong recovering your email. Please try again or contact support.')
})
}).catch(() => {
AuthHandler.hideLoadingSpinner()
AuthHandler.displayErrorMessage('Invalid action code. Please try again.')
})
},
handleVerifyEmail: () => {
AuthHandler.showLoadingSpinner()
AuthHandler.conf.applyActionCode(AuthHandler.conf.auth, AuthHandler.conf.oobCode).then(() => {
AuthHandler.hideLoadingSpinner()
AuthHandler.displaySuccessMessage('Email verified! Your account is now active. Time to send some messages!')
}).catch(() => {
AuthHandler.hideLoadingSpinner()
AuthHandler.displayErrorMessage('Your code is invalid or has expired. Please try to verify your email address again by tapping the resend email button in app.')
})
},
validateRequiredAuthParams: () => {
// Mode is evaluated in the bindMode switch. If no mode will display default error message. So, we're just
// checking for a valid oobCode here.
return !!AuthHandler.conf.oobCode
},
setModeTitle: title => {
AuthHandler.conf.modeTitle.innerText = title
},
validatePasswordComplexity: e => {
const password = !!e.target ? e.target.value.trim() : e.value.trim()
const isValidString = typeof password === 'string'
/// Checks if password has minLength
const hasMinLength = isValidString && password.length >= 8
AuthHandler.conf.passwordHasMinLength.style.display = hasMinLength ? 'none' : ''
/// Checks if password has at least 1 normal char letter matches
const hasMinNormalChar = isValidString && password.toUpperCase().match(RegExp('^(.*?[A-Z]){1,}')) !== null
AuthHandler.conf.passwordHasMinNormalChar.style.display = hasMinNormalChar ? 'none' : ''
/// Checks if password has at least 1 uppercase letter matches
const hasMinUppercase =
isValidString && password.match(RegExp('^(.*?[A-Z]){1,}')) !== null
AuthHandler.conf.passwordHasMinUppercase.style.display = hasMinUppercase ? 'none' : ''
/// Checks if password has at least 1 numeric character matches
const hasMinNumericChar =
isValidString && password.match(RegExp('^(.*?[0-9]){1,}')) !== null
AuthHandler.conf.passwordHasMinNumericChar.style.display = hasMinNumericChar ? 'none' : ''
/// Checks if password has at least 1 special character matches
const hasMinSpecialChar = isValidString && password.match(RegExp("^(.*?[\$&+,:;/=?@#|'<>.^*()_%!-]){1,}")) !== null
AuthHandler.conf.passwordHasMinSpecialChar.style.display = hasMinSpecialChar ? 'none' : ''
const passing = hasMinLength &&
hasMinNormalChar &&
hasMinUppercase &&
hasMinNumericChar &&
hasMinSpecialChar
AuthHandler.conf.passwordIncreaseComplexity.style.display = passing ? 'none' : ''
return passing
},
showLoadingSpinner: () => {
AuthHandler.conf.loading.style.display = ''
},
hideLoadingSpinner: () => {
AuthHandler.conf.loading.style.display = 'none'
},
showPasswordResetForm: () => {
AuthHandler.conf.passwordForm.style.display = '';
},
hidePasswordResetForm: () => {
AuthHandler.conf.passwordForm.style.display = 'none';
},
displaySuccessMessage: message => {
AuthHandler.hideErrorMessage()
AuthHandler.conf.success.innerText = message
AuthHandler.conf.success.style.display = ''
},
hideSuccessMessage: () => {
AuthHandler.conf.success.innerText = ''
AuthHandler.conf.success.style.display = 'none'
},
displayErrorMessage: message => {
AuthHandler.hideSuccessMessage()
AuthHandler.conf.error.innerText = message
AuthHandler.conf.error.style.display = ''
},
hideErrorMessage: () => {
AuthHandler.conf.error.innerText = ''
AuthHandler.conf.error.style.display = 'none'
},
hideMessages: () => {
AuthHandler.hideErrorMessage()
AuthHandler.hideSuccessMessage()
},
}
</script>
</head>
<body>
<div class="center">
<div id="cover-spin" style="display: none;"></div>
<p>
<image src="https://via.placeholder.com/400x70/000000/FFFFFF?text=Your+Logo"/>
</p>
<p id="mode-title" style="font-size: 20px; font-weight: bold;"></p>
<p id="error" class="red-alert" style="display: none;"></p>
<p id="success" style="display: none;"></p>
<div id="password-form" style="min-width: 700px; min-height: 300px; display: none;">
<label for="password">New Password</label>
<input id="password" type="password" minlength="8" maxlength="35" autocomplete="off" placeholder="Enter new password" style="margin-left: 10px;" required>
<i class="bi bi-eye-slash" id="toggle-password"></i>
<button id="reset-button" type="button" class="button" style="margin-left: 20px;">Reset</button>
<p class="red-alert" id="increase-complexity" style="display: none;"><strong>Increase Complexity</strong></p>
<p class="red-alert" id="has-min-length" style="display: none;">Minimum 8 characters</p>
<p class="red-alert" id="has-min-normal-char" style="display: none;">Minimum 1 normal characters</p>
<p class="red-alert" id="has-min-uppercase" style="display: none;">Minimum 1 uppercase characters</p>
<p class="red-alert" id="has-min-numeric-char" style="display: none;">Minimum 1 numeric characters</p>
<p class="red-alert" id="has-min-special-char" style="display: none;">Minimum 1 special characters</p>
</div>
</div>
<script type="module">
// https://firebase.google.com/docs/web/setup#available-libraries
// https://firebase.google.com/docs/web/alt-setup
import { initializeApp } from 'https://www.gstatic.com/firebasejs/9.15.0/firebase-app.js';
import {
applyActionCode,
checkActionCode,
confirmPasswordReset,
getAuth,
sendPasswordResetEmail,
verifyPasswordResetCode,
} from 'https://www.gstatic.com/firebasejs/9.15.0/firebase-auth.js';
// Replace {} with your firebaseConfig
// https://firebase.google.com/docs/web/learn-more#config-object
const firebaseConfig = {};
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
document.addEventListener('DOMContentLoaded', () => {
// Get the mode and oobCode from url params
const params = new Proxy(new URLSearchParams(window.location.search), {
get: (searchParams, prop) => searchParams.get(prop),
});
AuthHandler.init({
app,
auth,
applyActionCode,
checkActionCode,
confirmPasswordReset,
getAuth,
sendPasswordResetEmail,
verifyPasswordResetCode,
// Used by all modes to display error or success messages
error: document.getElementById('error'),
success: document.getElementById('success'),
// https://firebase.google.com/docs/auth/custom-email-handler#create_the_email_action_handler_page
mode: params.mode,
oobCode: params.oobCode,
modeTitle: document.getElementById('mode-title'),
loading: document.getElementById('cover-spin'),
// Password reset elements
passwordForm: document.getElementById('password-form'),
passwordField: document.getElementById('password'),
passwordResetButton: document.getElementById('reset-button'),
passwordToggleButton: document.getElementById('toggle-password'),
passwordHasMinLength: document.getElementById('has-min-length'),
passwordHasMinNormalChar: document.getElementById('has-min-normal-char'),
passwordHasMinUppercase: document.getElementById('has-min-uppercase'),
passwordHasMinNumericChar: document.getElementById('has-min-numeric-char'),
passwordHasMinSpecialChar: document.getElementById('has-min-special-char'),
passwordIncreaseComplexity: document.getElementById('increase-complexity'),
defaultErrorMessage: 'Invalid auth parameters. Please try again.',
});
});
</script>
</body>
</html>
注意:我選擇不使用新的模塊化 tree shakable 做事方式,因為我不想設置 webpack,並選擇了alt 設置樣式,這樣我就可以使用最新的 firebasejs 版本,在撰寫本文時,是 v9.15.0。 如果您擔心單頁自定義電子郵件處理程序頁面膨脹,請查看 tree shaking 和 webpack。 我選擇了配置更少的超快速選項。
注意:我沒有處理來自 firebase 的 url 中包含的lang
或apiKey
參數。 我的用例不需要進行任何語言更改。
點擊重置后,如果 firebase 身份驗證一切順利,用戶將看到這個。 對於每種操作模式,成功和錯誤消息將相應顯示。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.