generated from t/malachite
add: login
This commit is contained in:
parent
ce9ce4f635
commit
8c86dd6cda
13 changed files with 407 additions and 25 deletions
1
app/.gitignore
vendored
1
app/.gitignore
vendored
|
@ -5,3 +5,4 @@ templates_build/
|
|||
public/favicon.svg
|
||||
.env
|
||||
app.toml
|
||||
tetratto.toml
|
||||
|
|
|
@ -345,7 +345,7 @@ input {
|
|||
|
||||
input:not([type="checkbox"]):focus {
|
||||
outline: solid 2px var(--color-primary);
|
||||
box-shadow: 0 0 0 4px oklch(87% 0.065 274.039 / 25%);
|
||||
box-shadow: 0 0 0 4px oklch(70.5% 0.213 47.604 / 25%);
|
||||
background: var(--color-super-raised);
|
||||
}
|
||||
|
||||
|
@ -450,19 +450,14 @@ code:not(pre *):not(.dark *) {
|
|||
|
||||
svg.icon {
|
||||
stroke: currentColor;
|
||||
fill: currentColor;
|
||||
width: 18px;
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
svg.icon.filled {
|
||||
.filled svg.icon {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.no_fill svg.icon {
|
||||
fill: transparent;
|
||||
}
|
||||
|
||||
button svg {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
|
24
app/public/tokens.js
Normal file
24
app/public/tokens.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
globalThis.LOGIN_ACCOUNT_TOKENS = JSON.parse(
|
||||
window.localStorage.getItem("login_account_tokens") || "[]",
|
||||
);
|
||||
|
||||
function save_login_account_tokens() {
|
||||
window.localStorage.setItem(
|
||||
"login_account_tokens",
|
||||
JSON.stringify(LOGIN_ACCOUNT_TOKENS),
|
||||
);
|
||||
}
|
||||
|
||||
function user_logout() {
|
||||
if (!confirm("Are you sure you would like to do this?")) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch("/api/v1/auth/logout", { method: "POST" })
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
if (res.ok) {
|
||||
window.location.href = "/";
|
||||
}
|
||||
});
|
||||
}
|
|
@ -1,10 +1,6 @@
|
|||
(text "{% extends \"root.lisp\" %} {% block head %}")
|
||||
(title
|
||||
(text "{{ name }}"))
|
||||
|
||||
(meta ("property" "og:title") ("content" "{{ name }}"))
|
||||
(meta ("property" "twitter:title") ("content" "{{ name }}"))
|
||||
(link ("rel" "icon") ("href" "/public/favicon.svg"))
|
||||
(text "{% endblock %} {% block body %}")
|
||||
(div
|
||||
("class" "card")
|
||||
|
|
109
app/templates_src/login.lisp
Normal file
109
app/templates_src/login.lisp
Normal file
|
@ -0,0 +1,109 @@
|
|||
(text "{% extends \"root.lisp\" %} {% block head %}")
|
||||
(title
|
||||
(text "Login — {{ name }}"))
|
||||
(text "{% endblock %} {% block body %}")
|
||||
(div
|
||||
("class" "card")
|
||||
(h4 (text "Login with Tetratto"))
|
||||
|
||||
(form
|
||||
("class" "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
|
||||
("class" "surface")
|
||||
("type" "text")
|
||||
("placeholder" "username")
|
||||
("required" "")
|
||||
("name" "username")
|
||||
("id" "username")))
|
||||
(div
|
||||
("class" "flex flex_col gap_1")
|
||||
(label ("for" "username") (b (text "Password")))
|
||||
(input
|
||||
("class" "surface")
|
||||
("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
|
||||
("class" "surface")
|
||||
("type" "text")
|
||||
("placeholder" "totp code")
|
||||
("name" "totp")
|
||||
("id" "totp"))))
|
||||
|
||||
(button
|
||||
("class" "button surface")
|
||||
(text "{{ icon \"arrow-right\" }}")
|
||||
(text "Continue")))
|
||||
|
||||
(hr ("class" "margin"))
|
||||
(a ("href" "{{ config.service_hosts.tetratto }}/auth/register") (text "I don't have a Tetratto account")))
|
||||
|
||||
(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}/check_totp`,
|
||||
)
|
||||
).json();
|
||||
|
||||
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(async (res) => {
|
||||
show_message(res.message, res.ok);
|
||||
if (res.ok) {
|
||||
// update tokens
|
||||
LOGIN_ACCOUNT_TOKENS[e.target.username.value] = res.message;
|
||||
save_login_account_tokens();
|
||||
|
||||
// redirect
|
||||
setTimeout(() => {
|
||||
window.location.href = \"/app\";
|
||||
}, 150);
|
||||
}
|
||||
});
|
||||
}"))
|
||||
(text "{% endblock %}")
|
|
@ -15,7 +15,12 @@
|
|||
(meta ("property" "og:type") ("content" "website"))
|
||||
(meta ("property" "og:site_name") ("content" "{{ name }}"))
|
||||
|
||||
(meta ("property" "og:title") ("content" "{{ name }}"))
|
||||
(meta ("property" "twitter:title") ("content" "{{ name }}"))
|
||||
(link ("rel" "icon") ("href" "/public/favicon.svg"))
|
||||
|
||||
(script ("src" "/public/app.js?v={{ build_code }}") ("defer"))
|
||||
(script ("src" "/public/tokens.js?v={{ build_code }}") ("defer"))
|
||||
|
||||
(text "{% block head %}{% endblock %}"))
|
||||
|
||||
|
@ -42,6 +47,30 @@
|
|||
("class" "button")
|
||||
("href" "https://trisua.com/t/malachite")
|
||||
(text "source"))
|
||||
(hr)
|
||||
(text "{% if not user -%}")
|
||||
(a
|
||||
("class" "button")
|
||||
("href" "/login")
|
||||
(text "login"))
|
||||
(a
|
||||
("class" "button")
|
||||
("href" "{{ config.service_hosts.tetratto }}/auth/register")
|
||||
(text "sign up"))
|
||||
(text "{%- else -%}")
|
||||
(a
|
||||
("class" "button")
|
||||
("href" "/app")
|
||||
(text "app"))
|
||||
(a
|
||||
("class" "button")
|
||||
("href" "{{ config.service_hosts.tetratto }}/settings")
|
||||
(text "settings"))
|
||||
(button
|
||||
("class" "button red")
|
||||
("onclick" "user_logout()")
|
||||
(text "logout"))
|
||||
(text "{%- endif %}")
|
||||
(text "{% block dropdown %}{% endblock %}")))
|
||||
(a ("class" "button camo") ("href" "/") (b (text "{{ name }}"))))
|
||||
|
||||
|
@ -51,14 +80,14 @@
|
|||
|
||||
; theme switches
|
||||
(button
|
||||
("class" "button camo fade")
|
||||
("class" "button camo fade filled")
|
||||
("id" "switch_light")
|
||||
("title" "Switch theme")
|
||||
("onclick" "set_theme('Dark')")
|
||||
(text "{{ icon \"sun\" }}"))
|
||||
|
||||
(button
|
||||
("class" "button camo fade hidden")
|
||||
("class" "button camo fade filled hidden")
|
||||
("id" "switch_dark")
|
||||
("title" "Switch theme")
|
||||
("onclick" "set_theme('Light')")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue