feat: harden account security flows
This commit is contained in:
+120
-1
@@ -1 +1,120 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>{{ title }}</title><link href="{{ url_for('static', filename='css/app.css') }}" rel="stylesheet"></head><body class="min-h-screen bg-page"><main class="mx-auto flex min-h-screen max-w-md items-center px-5"><section class="w-full rounded-2xl border border-line bg-white p-7 shadow-panel"><h1 class="text-2xl font-extrabold">{{ title }}</h1><p class="mt-2 text-sm text-textSub">验证码已发送至 {{ email }},10 分钟内有效。</p>{% with messages=get_flashed_messages(with_categories=true) %}{% for c,m in messages %}<p class="mt-4 text-sm text-dangerText">{{ m }}</p>{% endfor %}{% endwith %}<form method="post" class="mt-6 space-y-4"><input type="hidden" name="csrf_token" value="{{ csrf_token() }}"><label class="block text-sm font-semibold">6 位验证码<input name="code" inputmode="numeric" pattern="[0-9]{6}" maxlength="6" required autofocus class="mt-1 w-full rounded-lg border border-line px-3 py-2 text-center text-xl tracking-[.5em]"></label><button class="ui-btn ui-btn-primary w-full">验证</button></form><form method="post" action="{{ url_for('main.resend_code', purpose=purpose) }}" class="mt-3"><input type="hidden" name="csrf_token" value="{{ csrf_token() }}"><button class="text-sm font-semibold text-primary">重新发送验证码</button></form></section></main></body></html>
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{ title }} | 供水管道健康评估系统</title>
|
||||
<link href="{{ url_for('static', filename='css/app.css') }}" rel="stylesheet">
|
||||
</head>
|
||||
<body class="min-h-screen bg-page text-textMain">
|
||||
<main class="mx-auto flex min-h-screen max-w-md items-center px-5">
|
||||
<section class="w-full rounded-2xl border border-line bg-white p-7 shadow-panel">
|
||||
<span class="material-symbols-outlined text-4xl text-primary">mark_email_read</span>
|
||||
<h1 class="mt-3 text-2xl font-extrabold">{{ title }}</h1>
|
||||
<p class="mt-2 text-sm leading-6 text-textSub">验证码已发送至 {{ email }},10 分钟内有效。</p>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% for category, message in messages %}
|
||||
<p class="mt-4 rounded-lg bg-dangerSoft p-3 text-sm text-dangerText">{{ message }}</p>
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
|
||||
<form method="post" class="mt-7" id="verificationForm">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<input id="verificationCode" type="hidden" name="code" value="">
|
||||
<fieldset>
|
||||
<legend class="text-sm font-semibold">请输入 6 位验证码</legend>
|
||||
<div class="mt-3 grid grid-cols-6 gap-2" id="codeInputs">
|
||||
{% for index in range(6) %}
|
||||
<input
|
||||
type="text"
|
||||
inputmode="numeric"
|
||||
autocomplete="one-time-code"
|
||||
maxlength="1"
|
||||
aria-label="验证码第 {{ index + 1 }} 位"
|
||||
class="h-12 min-w-0 rounded-lg border border-line text-center text-xl font-bold tracking-wide focus:border-primary focus:ring-primary"
|
||||
data-code-digit
|
||||
{% if index == 0 %}autofocus{% endif %}
|
||||
>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</fieldset>
|
||||
<button id="verifyButton" class="ui-btn ui-btn-primary mt-6 w-full" type="submit">验证并继续</button>
|
||||
</form>
|
||||
|
||||
<form method="post" action="{{ url_for('main.resend_code', purpose=purpose) }}" class="mt-4 text-center">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button id="resendButton" class="text-sm font-semibold text-primary disabled:cursor-not-allowed disabled:text-slate-400" type="submit" disabled>
|
||||
<span id="resendText">{{ resend_seconds }} 秒后可重新发送</span>
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
</main>
|
||||
<script>
|
||||
(() => {
|
||||
const inputs = Array.from(document.querySelectorAll('[data-code-digit]'));
|
||||
const hiddenCode = document.getElementById('verificationCode');
|
||||
const form = document.getElementById('verificationForm');
|
||||
const resendButton = document.getElementById('resendButton');
|
||||
const resendText = document.getElementById('resendText');
|
||||
let remainingSeconds = {{ resend_seconds|int }};
|
||||
|
||||
function updateCode() {
|
||||
hiddenCode.value = inputs.map((input) => input.value).join('');
|
||||
}
|
||||
|
||||
function fillDigits(value, startIndex = 0) {
|
||||
const digits = value.replace(/\D/g, '').slice(0, inputs.length - startIndex);
|
||||
for (let index = 0; index < digits.length; index += 1) {
|
||||
inputs[startIndex + index].value = digits[index];
|
||||
}
|
||||
updateCode();
|
||||
const nextIndex = Math.min(startIndex + digits.length, inputs.length - 1);
|
||||
inputs[nextIndex].focus();
|
||||
}
|
||||
|
||||
inputs.forEach((input, index) => {
|
||||
input.addEventListener('input', () => {
|
||||
if (input.value.length > 1) {
|
||||
fillDigits(input.value, index);
|
||||
return;
|
||||
}
|
||||
input.value = input.value.replace(/\D/g, '');
|
||||
updateCode();
|
||||
if (input.value && index < inputs.length - 1) inputs[index + 1].focus();
|
||||
});
|
||||
|
||||
input.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Backspace' && !input.value && index > 0) {
|
||||
inputs[index - 1].focus();
|
||||
}
|
||||
});
|
||||
|
||||
input.addEventListener('paste', (event) => {
|
||||
event.preventDefault();
|
||||
fillDigits(event.clipboardData.getData('text'), index);
|
||||
});
|
||||
});
|
||||
|
||||
form.addEventListener('submit', (event) => {
|
||||
updateCode();
|
||||
if (hiddenCode.value.length !== inputs.length) {
|
||||
event.preventDefault();
|
||||
inputs.find((input) => !input.value)?.focus();
|
||||
}
|
||||
});
|
||||
|
||||
const timer = window.setInterval(() => {
|
||||
remainingSeconds -= 1;
|
||||
if (remainingSeconds <= 0) {
|
||||
window.clearInterval(timer);
|
||||
resendButton.disabled = false;
|
||||
resendText.textContent = '重新发送验证码';
|
||||
return;
|
||||
}
|
||||
resendText.textContent = `${remainingSeconds} 秒后可重新发送`;
|
||||
}, 1000);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user