112 lines
3.7 KiB
HTML
112 lines
3.7 KiB
HTML
<!doctype html>
|
|||
|
|
<html lang="en">
|
||
|
|
<head>
|
||
|
|
<meta charset="utf-8">
|
||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
|
|
<title>Sign in — Basis</title>
|
||
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||
|
|
<link rel="stylesheet" href="/static/style.css">
|
||
|
|
</head>
|
||
|
|
<body class="login-body">
|
||
|
|
<main class="login-main">
|
||
|
|
<section class="panel login-panel">
|
||
|
|
<h1 class="login-title">Basis</h1>
|
||
|
|
<p class="notice error" id="login-error" hidden></p>
|
||
|
|
<form id="login-form" class="form">
|
||
|
|
<label>Username <input type="text" name="username" autocomplete="username" required autofocus></label>
|
||
|
|
<label>Password <input type="password" name="password" autocomplete="current-password" required></label>
|
||
|
|
<button type="submit">Sign in</button>
|
||
|
|
</form>
|
||
|
|
</section>
|
||
|
|
</main>
|
||
|
|
|
||
|
|
<div class="modal-overlay" id="mfa-overlay" hidden>
|
||
|
|
<div class="modal">
|
||
|
|
<h2>Enter authenticator code</h2>
|
||
|
|
<p class="hint">Your account has MFA enabled — enter the 6-digit code from your authenticator app.</p>
|
||
|
|
<p class="notice error" id="mfa-error" hidden></p>
|
||
|
|
<form id="mfa-form" class="form">
|
||
|
|
<label>Code <input type="text" name="code" inputmode="numeric" pattern="[0-9]{6}" maxlength="6" autocomplete="one-time-code" required></label>
|
||
|
|
<button type="submit">Verify</button>
|
||
|
|
</form>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
(function () {
|
||
|
|
"use strict";
|
||
|
|
|
||
|
|
var loginForm = document.getElementById("login-form");
|
||
|
|
var loginError = document.getElementById("login-error");
|
||
|
|
var overlay = document.getElementById("mfa-overlay");
|
||
|
|
var mfaForm = document.getElementById("mfa-form");
|
||
|
|
var mfaError = document.getElementById("mfa-error");
|
||
|
|
var pendingToken = "";
|
||
|
|
|
||
|
|
function postForm(url, fields) {
|
||
|
|
var body = Object.keys(fields).map(function (k) {
|
||
|
|
return encodeURIComponent(k) + "=" + encodeURIComponent(fields[k]);
|
||
|
|
}).join("&");
|
||
|
|
return fetch(url, {
|
||
|
|
method: "POST",
|
||
|
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||
|
|
body: body,
|
||
|
|
}).then(function (r) { return r.json(); });
|
||
|
|
}
|
||
|
|
|
||
|
|
function showError(el, msg) {
|
||
|
|
el.textContent = msg;
|
||
|
|
el.hidden = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
loginForm.addEventListener("submit", function (e) {
|
||
|
|
e.preventDefault();
|
||
|
|
loginError.hidden = true;
|
||
|
|
var fd = new FormData(loginForm);
|
||
|
|
postForm("/login", { username: fd.get("username"), password: fd.get("password") }).then(function (res) {
|
||
|
|
if (res.error) {
|
||
|
|
showError(loginError, res.error);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (res.mfa_required) {
|
||
|
|
pendingToken = res.pending_token;
|
||
|
|
overlay.hidden = false;
|
||
|
|
mfaForm.querySelector('input[name="code"]').focus();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
window.location.href = res.redirect || "/";
|
||
|
|
}).catch(function () {
|
||
|
|
showError(loginError, "Something went wrong — try again.");
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
mfaForm.addEventListener("submit", function (e) {
|
||
|
|
e.preventDefault();
|
||
|
|
mfaError.hidden = true;
|
||
|
|
var fd = new FormData(mfaForm);
|
||
|
|
postForm("/login/mfa", { pending_token: pendingToken, code: fd.get("code") }).then(function (res) {
|
||
|
|
if (res.error) {
|
||
|
|
showError(mfaError, res.error);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
window.location.href = res.redirect || "/";
|
||
|
|
}).catch(function () {
|
||
|
|
showError(mfaError, "Something went wrong — try again.");
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
overlay.addEventListener("click", function (e) {
|
||
|
|
if (e.target === overlay) {
|
||
|
|
overlay.hidden = true;
|
||
|
|
pendingToken = "";
|
||
|
|
mfaForm.reset();
|
||
|
|
mfaError.hidden = true;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
})();
|
||
|
|
</script>
|
||
|
|
</body>
|
||
|
|
</html>
|