fix(auth): expire stale MFA forms before accepting codes
This commit is contained in:
1 parent
298e34ef2e
commit
0c77de93b4
8 files changed
+82
-11
No files matched your search
@@ -413,7 +413,32 @@
|
||||
const pending = new Map();
|
||||
let expired = false;
|
||||
let disposed = false;
|
||||
let expiryTimer;
|
||||
const expiresAt = Number(saved?.mfaExpiresAt);
|
||||
const expire = () => {
|
||||
if (disposed) return;
|
||||
expired = true;
|
||||
window.clearTimeout(expiryTimer);
|
||||
sessionStorage.removeItem(MFA_STORAGE_KEY);
|
||||
if (tokenInput) tokenInput.value = "";
|
||||
form?.querySelectorAll("input, button").forEach((control) => {
|
||||
control.disabled = true;
|
||||
});
|
||||
setMessage(
|
||||
status,
|
||||
"Your sign-in request has expired. Sign in again to get a fresh verification request.",
|
||||
true,
|
||||
);
|
||||
const restart = element.querySelector("[data-auth-mfa-restart]");
|
||||
if (restart) restart.hidden = false;
|
||||
};
|
||||
const checkExpiry = () => {
|
||||
// Old stored challenges have no verifiable deadline: require fresh sign-in.
|
||||
if (saved?.mfaToken && (!Number.isFinite(expiresAt) || expiresAt <= Date.now())) expire();
|
||||
return expired;
|
||||
};
|
||||
const restoreToken = () => {
|
||||
checkExpiry();
|
||||
if (!expired && tokenInput && !tokenInput.value && saved?.mfaToken)
|
||||
tokenInput.value = String(saved.mfaToken);
|
||||
};
|
||||
@@ -433,6 +458,7 @@
|
||||
const sendCode = async (selected) => {
|
||||
if (disposed || expired || !isOtp(selected)) return;
|
||||
restoreToken();
|
||||
if (expired) return;
|
||||
if (!tokenInput?.value) {
|
||||
setMessage(status, "Sign-in verification has expired. Start again.", true);
|
||||
const restart = element.querySelector("[data-auth-mfa-restart]");
|
||||
@@ -476,10 +502,11 @@
|
||||
);
|
||||
} finally {
|
||||
pending.delete(selected);
|
||||
if (!disposed && method() === selected) setFormBusy(form, false);
|
||||
if (!disposed && !expired && method() === selected) setFormBusy(form, false);
|
||||
}
|
||||
};
|
||||
const updateMethod = () => {
|
||||
if (checkExpiry()) return;
|
||||
const selected = method();
|
||||
setFormBusy(form, false);
|
||||
if (challengeInput) challengeInput.value = "";
|
||||
@@ -524,13 +551,12 @@
|
||||
};
|
||||
const onError = (event) => {
|
||||
if (event.target !== form || event.detail?.code !== "mfa-transaction-expired") return;
|
||||
expired = true;
|
||||
sessionStorage.removeItem(MFA_STORAGE_KEY);
|
||||
if (tokenInput) tokenInput.value = "";
|
||||
const restart = element.querySelector("[data-auth-mfa-restart]");
|
||||
if (restart) restart.hidden = false;
|
||||
expire();
|
||||
};
|
||||
updateMethod();
|
||||
if (!expired && Number.isFinite(expiresAt)) {
|
||||
expiryTimer = window.setTimeout(expire, Math.max(0, expiresAt - Date.now()));
|
||||
}
|
||||
// Capture native change before the shared Radio emits its component output.
|
||||
form?.addEventListener("change", onMethodChange, true);
|
||||
form?.addEventListener("submit", onSubmit, true);
|
||||
@@ -538,6 +564,7 @@
|
||||
element.addEventListener("wrn:error", onError);
|
||||
flowCleanups.set(element, () => {
|
||||
disposed = true;
|
||||
window.clearTimeout(expiryTimer);
|
||||
form?.removeEventListener("change", onMethodChange, true);
|
||||
form?.removeEventListener("submit", onSubmit, true);
|
||||
element.removeEventListener("wrn:success", onSuccess);
|
||||
|
||||
Reference in new issue
Block a user