122 lines
3.5 KiB
Common Lisp
122 lines
3.5 KiB
Common Lisp
|
(text "{% extends \"auth/base.html\" %} {% block head %}")
|
||
|
(title
|
||
|
(text "Login"))
|
||
|
|
||
|
(text "{% endblock %} {% block title %}Login{% endblock %} {% block content %}")
|
||
|
(form
|
||
|
("class" "w-full flex flex-col gap-4")
|
||
|
("onsubmit" "login(event)")
|
||
|
(div
|
||
|
("id" "flow_1")
|
||
|
("style" "display: contents")
|
||
|
(div
|
||
|
("class" "flex flex-col gap-1")
|
||
|
(label
|
||
|
("for" "username")
|
||
|
(b
|
||
|
(text "Username")))
|
||
|
(input
|
||
|
("type" "text")
|
||
|
("placeholder" "username")
|
||
|
("required" "")
|
||
|
("name" "username")
|
||
|
("id" "username")))
|
||
|
(div
|
||
|
("class" "flex flex-col gap-1")
|
||
|
(label
|
||
|
("for" "username")
|
||
|
(b
|
||
|
(text "Password")))
|
||
|
(input
|
||
|
("type" "password")
|
||
|
("placeholder" "password")
|
||
|
("required" "")
|
||
|
("name" "password")
|
||
|
("id" "password"))))
|
||
|
(div
|
||
|
("id" "flow_2")
|
||
|
("style" "display: none")
|
||
|
(div
|
||
|
("class" "flex flex-col gap-1")
|
||
|
(label
|
||
|
("for" "totp")
|
||
|
(b
|
||
|
(text "TOTP code")))
|
||
|
(input
|
||
|
("type" "text")
|
||
|
("placeholder" "totp code")
|
||
|
("name" "totp")
|
||
|
("id" "totp"))))
|
||
|
(button
|
||
|
(text "Submit")))
|
||
|
|
||
|
(script
|
||
|
(text "let flow_page = 1;
|
||
|
|
||
|
function next_page() {
|
||
|
document.getElementById(`flow_${flow_page}`).style.display = \"none\";
|
||
|
flow_page += 1;
|
||
|
document.getElementById(`flow_${flow_page}`).style.display = \"contents\";
|
||
|
}
|
||
|
|
||
|
async function login(e) {
|
||
|
e.preventDefault();
|
||
|
|
||
|
if (flow_page === 1) {
|
||
|
// check if we need TOTP
|
||
|
const res = await (
|
||
|
await fetch(
|
||
|
`/api/v1/auth/user/${e.target.username.value}/totp/check`,
|
||
|
)
|
||
|
).json();
|
||
|
|
||
|
trigger(\"atto::toast\", [res.ok ? \"success\" : \"error\", res.message]);
|
||
|
|
||
|
if (res.ok && res.payload) {
|
||
|
// user exists AND totp is required
|
||
|
return next_page();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fetch(\"/api/v1/auth/login\", {
|
||
|
method: \"POST\",
|
||
|
headers: {
|
||
|
\"Content-Type\": \"application/json\",
|
||
|
},
|
||
|
body: JSON.stringify({
|
||
|
username: e.target.username.value,
|
||
|
password: e.target.password.value,
|
||
|
totp: e.target.totp.value,
|
||
|
}),
|
||
|
})
|
||
|
.then((res) => res.json())
|
||
|
.then((res) => {
|
||
|
trigger(\"atto::toast\", [
|
||
|
res.ok ? \"success\" : \"error\",
|
||
|
res.message,
|
||
|
]);
|
||
|
|
||
|
if (res.ok) {
|
||
|
// update tokens
|
||
|
const new_tokens = ns(\"me\").LOGIN_ACCOUNT_TOKENS;
|
||
|
new_tokens[e.target.username.value] = res.message;
|
||
|
trigger(\"me::set_login_account_tokens\", [new_tokens]);
|
||
|
|
||
|
// redirect
|
||
|
setTimeout(() => {
|
||
|
window.location.href = \"/\";
|
||
|
}, 150);
|
||
|
}
|
||
|
});
|
||
|
}"))
|
||
|
|
||
|
(text "{% endblock %} {% block footer %}")
|
||
|
(span
|
||
|
("class" "small w-full text-center")
|
||
|
(text "Or, ")
|
||
|
(a
|
||
|
("href" "/auth/register")
|
||
|
(text "register")))
|
||
|
|
||
|
(text "{% endblock %}")
|