started with 2fa support

This commit is contained in:
2026-06-09 22:50:29 +02:00
parent 5485fd135d
commit ea8ea45c4c
10 changed files with 757 additions and 142 deletions

View File

@@ -2,22 +2,63 @@
document.addEventListener("DOMContentLoaded", () => {
const form = document.getElementById("login-form");
const errorBox = document.getElementById("error");
const usernameInput = document.getElementById("username");
const passwordInput = document.getElementById("password");
const twoFactorInput = document.getElementById("two-factor-code");
const twoFactorGroup = document.getElementById("two-factor-group");
const submitButton = document.getElementById("login-submit");
let pendingTwoFactorToken = null;
if (!form) return;
function showError(message) {
errorBox.textContent = message || "Login failed.";
errorBox.style.display = "block";
}
function storeTokens(data) {
localStorage.setItem("access_token", data.access_token);
localStorage.setItem("refresh_token", data.refresh_token);
}
function switchToTwoFactorMode(token) {
pendingTwoFactorToken = token;
usernameInput.disabled = true;
passwordInput.disabled = true;
twoFactorGroup.style.display = "block";
twoFactorInput.required = true;
twoFactorInput.focus();
submitButton.textContent = "Verify code";
}
form.addEventListener("submit", async (e) => {
e.preventDefault();
errorBox.style.display = "none";
const username = document.getElementById("username").value;
const password = document.getElementById("password").value;
submitButton.disabled = true;
try {
const response = await fetch("/api/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password })
});
let response;
if (pendingTwoFactorToken) {
response = await fetch("/api/login/2fa", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
two_factor_token: pendingTwoFactorToken,
code: twoFactorInput.value.trim()
})
});
} else {
response = await fetch("/api/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
username: usernameInput.value,
password: passwordInput.value
})
});
}
if (!response.ok) {
const text = await response.text();
@@ -26,17 +67,17 @@ document.addEventListener("DOMContentLoaded", () => {
const data = await response.json();
localStorage.setItem("access_token", data.access_token);
localStorage.setItem("refresh_token", data.refresh_token);
document.cookie = `access_token=${data.access_token}; path=/; max-age=900; SameSite=Lax; Secure`;
document.cookie = `refresh_token=${data.refresh_token}; path=/; max-age=604800; SameSite=Lax; Secure`;
if (data.requires_2fa) {
switchToTwoFactorMode(data.two_factor_token);
return;
}
storeTokens(data);
window.location.href = "/dashboard";
} catch (err) {
errorBox.textContent = err.message || "Login failed.";
errorBox.style.display = "block";
showError(err.message);
} finally {
submitButton.disabled = false;
}
});
});
});

View File

@@ -24,12 +24,18 @@
<input type="text" id="username" placeholder="Username" autocomplete="username" required>
</div>
<div class="form-group">
<div class="form-group" id="password-group">
<label for="password" class="sr-only">Password</label>
<input type="password" id="password" placeholder="Password" autocomplete="current-password" required>
</div>
<button type="submit" class="btn btn-primary">Sign In</button>
<div class="form-group" id="two-factor-group" style="display: none;">
<label for="two-factor-code" class="sr-only">2FA code</label>
<input type="text" id="two-factor-code" placeholder="Authenticator or recovery code" autocomplete="one-time-code" inputmode="text" pattern="[0-9A-Za-z\- ]*">
<p class="subtitle" style="margin-top: 0.75rem;">Enter your 6-digit authenticator code or one recovery code.</p>
</div>
<button type="submit" id="login-submit" class="btn btn-primary">Sign In</button>
</form>
<div id="error" class="message error"></div>