generated from t/malachite
add: login
This commit is contained in:
parent
ce9ce4f635
commit
8c86dd6cda
13 changed files with 407 additions and 25 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,3 +1 @@
|
||||||
target/
|
target/
|
||||||
tetratto.toml
|
|
||||||
app.toml
|
|
||||||
|
|
1
app/.gitignore
vendored
1
app/.gitignore
vendored
|
@ -5,3 +5,4 @@ templates_build/
|
||||||
public/favicon.svg
|
public/favicon.svg
|
||||||
.env
|
.env
|
||||||
app.toml
|
app.toml
|
||||||
|
tetratto.toml
|
||||||
|
|
|
@ -345,7 +345,7 @@ input {
|
||||||
|
|
||||||
input:not([type="checkbox"]):focus {
|
input:not([type="checkbox"]):focus {
|
||||||
outline: solid 2px var(--color-primary);
|
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);
|
background: var(--color-super-raised);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -450,19 +450,14 @@ code:not(pre *):not(.dark *) {
|
||||||
|
|
||||||
svg.icon {
|
svg.icon {
|
||||||
stroke: currentColor;
|
stroke: currentColor;
|
||||||
fill: currentColor;
|
|
||||||
width: 18px;
|
width: 18px;
|
||||||
height: 1em;
|
height: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
svg.icon.filled {
|
.filled svg.icon {
|
||||||
fill: currentColor;
|
fill: currentColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no_fill svg.icon {
|
|
||||||
fill: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
button svg {
|
button svg {
|
||||||
pointer-events: none;
|
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 %}")
|
(text "{% extends \"root.lisp\" %} {% block head %}")
|
||||||
(title
|
(title
|
||||||
(text "{{ name }}"))
|
(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 %}")
|
(text "{% endblock %} {% block body %}")
|
||||||
(div
|
(div
|
||||||
("class" "card")
|
("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:type") ("content" "website"))
|
||||||
(meta ("property" "og:site_name") ("content" "{{ name }}"))
|
(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/app.js?v={{ build_code }}") ("defer"))
|
||||||
|
(script ("src" "/public/tokens.js?v={{ build_code }}") ("defer"))
|
||||||
|
|
||||||
(text "{% block head %}{% endblock %}"))
|
(text "{% block head %}{% endblock %}"))
|
||||||
|
|
||||||
|
@ -42,6 +47,30 @@
|
||||||
("class" "button")
|
("class" "button")
|
||||||
("href" "https://trisua.com/t/malachite")
|
("href" "https://trisua.com/t/malachite")
|
||||||
(text "source"))
|
(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 %}")))
|
(text "{% block dropdown %}{% endblock %}")))
|
||||||
(a ("class" "button camo") ("href" "/") (b (text "{{ name }}"))))
|
(a ("class" "button camo") ("href" "/") (b (text "{{ name }}"))))
|
||||||
|
|
||||||
|
@ -51,14 +80,14 @@
|
||||||
|
|
||||||
; theme switches
|
; theme switches
|
||||||
(button
|
(button
|
||||||
("class" "button camo fade")
|
("class" "button camo fade filled")
|
||||||
("id" "switch_light")
|
("id" "switch_light")
|
||||||
("title" "Switch theme")
|
("title" "Switch theme")
|
||||||
("onclick" "set_theme('Dark')")
|
("onclick" "set_theme('Dark')")
|
||||||
(text "{{ icon \"sun\" }}"))
|
(text "{{ icon \"sun\" }}"))
|
||||||
|
|
||||||
(button
|
(button
|
||||||
("class" "button camo fade hidden")
|
("class" "button camo fade filled hidden")
|
||||||
("id" "switch_dark")
|
("id" "switch_dark")
|
||||||
("title" "Switch theme")
|
("title" "Switch theme")
|
||||||
("onclick" "set_theme('Light')")
|
("onclick" "set_theme('Light')")
|
||||||
|
|
|
@ -36,7 +36,7 @@ fn default_name() -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_theme_color() -> String {
|
fn default_theme_color() -> String {
|
||||||
"#cd5700".to_string()
|
"#f97316".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_real_ip_header() -> String {
|
fn default_real_ip_header() -> String {
|
||||||
|
|
191
src/routes/api/auth.rs
Normal file
191
src/routes/api/auth.rs
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
use crate::{State, get_user_from_token};
|
||||||
|
use axum::{
|
||||||
|
Extension, Json,
|
||||||
|
extract::{Path, Query},
|
||||||
|
http::{HeaderMap, HeaderValue},
|
||||||
|
response::{IntoResponse, Redirect},
|
||||||
|
};
|
||||||
|
use axum_extra::extract::CookieJar;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use tetratto_core::model::{ApiReturn, Error, addr::RemoteAddr, auth::User};
|
||||||
|
use tetratto_shared::hash::hash;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct LoginProps {
|
||||||
|
pub username: String,
|
||||||
|
pub password: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub totp: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `/api/v1/auth/login`
|
||||||
|
pub async fn login_request(
|
||||||
|
headers: HeaderMap,
|
||||||
|
// jar: CookieJar,
|
||||||
|
Extension(data): Extension<State>,
|
||||||
|
Json(props): Json<LoginProps>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let data = &(data.read().await).0;
|
||||||
|
// let user = get_user_from_token!(jar, data);
|
||||||
|
|
||||||
|
// if user.is_some() {
|
||||||
|
// return (None, Json(Error::AlreadyAuthenticated.into()));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// get real ip
|
||||||
|
let real_ip = headers
|
||||||
|
.get(data.2.0.0.security.real_ip_header.to_owned())
|
||||||
|
.unwrap_or(&HeaderValue::from_static(""))
|
||||||
|
.to_str()
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
// check for ip ban
|
||||||
|
if data
|
||||||
|
.2
|
||||||
|
.get_ipban_by_addr(&RemoteAddr::from(real_ip.as_str()))
|
||||||
|
.await
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
return (None, Json(Error::NotAllowed.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify password
|
||||||
|
let user = match data
|
||||||
|
.2
|
||||||
|
.get_user_by_username_no_cache(&props.username.to_lowercase())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(ua) => ua,
|
||||||
|
Err(_) => return (None, Json(Error::IncorrectPassword.into())),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !user.check_password(props.password) {
|
||||||
|
return (None, Json(Error::IncorrectPassword.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify totp code
|
||||||
|
if !data.2.check_totp(&user, &props.totp) {
|
||||||
|
return (None, Json(Error::NotAllowed.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// update tokens
|
||||||
|
let mut new_tokens = user.tokens.clone();
|
||||||
|
let (unhashed_token_id, token) = User::create_token(&real_ip);
|
||||||
|
new_tokens.push(token);
|
||||||
|
|
||||||
|
if let Err(e) = data.2.update_user_tokens(user.id, new_tokens).await {
|
||||||
|
return (None, Json(e.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...
|
||||||
|
(
|
||||||
|
Some([(
|
||||||
|
"Set-Cookie",
|
||||||
|
format!(
|
||||||
|
"__Secure-atto-token={}; SameSite=Lax; Secure; Path=/; HostOnly=true; HttpOnly=true; Max-Age={}",
|
||||||
|
unhashed_token_id,
|
||||||
|
60 * 60 * 24 * 365
|
||||||
|
),
|
||||||
|
)]),
|
||||||
|
Json(ApiReturn {
|
||||||
|
ok: true,
|
||||||
|
message: unhashed_token_id,
|
||||||
|
payload: (),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `/api/v1/auth/logout`
|
||||||
|
pub async fn logout_request(
|
||||||
|
jar: CookieJar,
|
||||||
|
Extension(data): Extension<State>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let data = &(data.read().await).0;
|
||||||
|
let user = match get_user_from_token!(jar, data.2) {
|
||||||
|
Some(ua) => ua,
|
||||||
|
None => return (None, Json(Error::NotAllowed.into())),
|
||||||
|
};
|
||||||
|
|
||||||
|
// update tokens
|
||||||
|
let token = jar
|
||||||
|
.get("__Secure-atto-token")
|
||||||
|
.unwrap()
|
||||||
|
.to_string()
|
||||||
|
.replace("__Secure-atto-token=", "");
|
||||||
|
|
||||||
|
let mut new_tokens = user.tokens.clone();
|
||||||
|
new_tokens.remove(
|
||||||
|
new_tokens
|
||||||
|
.iter()
|
||||||
|
.position(|t| t.1 == hash(token.to_string()))
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Err(e) = data.2.update_user_tokens(user.id, new_tokens).await {
|
||||||
|
return (None, Json(e.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...
|
||||||
|
(
|
||||||
|
Some([(
|
||||||
|
"Set-Cookie",
|
||||||
|
format!(
|
||||||
|
"__Secure-atto-token={}; SameSite=Lax; Secure; Path=/; HostOnly=true; HttpOnly=true; Max-Age=0",
|
||||||
|
"refresh",
|
||||||
|
),
|
||||||
|
)]),
|
||||||
|
Json(ApiReturn {
|
||||||
|
ok: true,
|
||||||
|
message: "Goodbye!".to_string(),
|
||||||
|
payload: Some(user.username.clone()),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct SetTokenQuery {
|
||||||
|
#[serde(default)]
|
||||||
|
pub token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the current user token.
|
||||||
|
pub async fn set_token_request(Query(props): Query<SetTokenQuery>) -> impl IntoResponse {
|
||||||
|
(
|
||||||
|
{
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
|
||||||
|
headers.insert(
|
||||||
|
"Set-Cookie",
|
||||||
|
format!(
|
||||||
|
"__Secure-atto-token={}; SameSite=Lax; Secure; Path=/; HostOnly=true; HttpOnly=true; Max-Age={}",
|
||||||
|
props.token,
|
||||||
|
60* 60 * 24 * 365
|
||||||
|
)
|
||||||
|
.parse()
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
headers
|
||||||
|
},
|
||||||
|
Redirect::to("/"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the given user has TOTP enabled.
|
||||||
|
pub async fn check_totp_request(
|
||||||
|
Path(username): Path<String>,
|
||||||
|
Extension(data): Extension<State>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let data = &(data.read().await).0;
|
||||||
|
let user = match data.2.get_user_by_username(&username).await {
|
||||||
|
Ok(u) => u,
|
||||||
|
Err(e) => return Json(e.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
Json(ApiReturn {
|
||||||
|
ok: true,
|
||||||
|
message: "Success".to_string(),
|
||||||
|
payload: Some(!user.totp.is_empty()),
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,13 +1,24 @@
|
||||||
|
pub mod auth;
|
||||||
pub mod chats;
|
pub mod chats;
|
||||||
pub mod messages;
|
pub mod messages;
|
||||||
|
|
||||||
use axum::routing::{Router, delete, post, put};
|
use axum::routing::{Router, delete, get, post, put};
|
||||||
|
|
||||||
pub fn routes() -> Router {
|
pub fn routes() -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
|
// auth
|
||||||
|
.route("/auth/login", post(auth::login_request))
|
||||||
|
.route("/auth/logout", post(auth::logout_request))
|
||||||
|
.route("/auth/set_token", get(auth::set_token_request))
|
||||||
|
.route(
|
||||||
|
"/auth/user/{username}/check_totp",
|
||||||
|
get(auth::check_totp_request),
|
||||||
|
)
|
||||||
|
// chats
|
||||||
.route("/chats", post(chats::create_request))
|
.route("/chats", post(chats::create_request))
|
||||||
.route("/chats/{id}/leave", post(chats::leave_request))
|
.route("/chats/{id}/leave", post(chats::leave_request))
|
||||||
.route("/chats/{id}/info", post(chats::update_info_request))
|
.route("/chats/{id}/info", post(chats::update_info_request))
|
||||||
|
// messages
|
||||||
.route("/messages", post(messages::create_request))
|
.route("/messages", post(messages::create_request))
|
||||||
.route("/messages/{id}", delete(messages::delete_request))
|
.route("/messages/{id}", delete(messages::delete_request))
|
||||||
.route("/messages/{id}", put(messages::update_content_request))
|
.route("/messages/{id}", put(messages::update_content_request))
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use axum::{Router, routing::get_service};
|
use axum::{Router, routing::get_service};
|
||||||
use tera::Context;
|
use tera::Context;
|
||||||
|
use tetratto_core::model::auth::User;
|
||||||
|
|
||||||
pub mod api;
|
pub mod api;
|
||||||
pub mod pages;
|
pub mod pages;
|
||||||
|
@ -16,10 +17,12 @@ pub fn routes() -> Router {
|
||||||
.nest("/api/v1", api::routes())
|
.nest("/api/v1", api::routes())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn default_context(config: &Config, build_code: &str) -> Context {
|
pub fn default_context(config: &Config, build_code: &str, user: &Option<User>) -> Context {
|
||||||
let mut ctx = Context::new();
|
let mut ctx = Context::new();
|
||||||
ctx.insert("name", &config.name);
|
ctx.insert("name", &config.name);
|
||||||
ctx.insert("theme_color", &config.theme_color);
|
ctx.insert("theme_color", &config.theme_color);
|
||||||
ctx.insert("build_code", &build_code);
|
ctx.insert("build_code", &build_code);
|
||||||
|
ctx.insert("user", &user);
|
||||||
|
ctx.insert("config", &config);
|
||||||
ctx
|
ctx
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,19 @@
|
||||||
use crate::{State, routes::default_context};
|
use crate::{State, get_user_from_token, routes::default_context};
|
||||||
use axum::{
|
use axum::{
|
||||||
Extension,
|
Extension,
|
||||||
response::{Html, IntoResponse},
|
response::{Html, IntoResponse},
|
||||||
};
|
};
|
||||||
|
use axum_extra::extract::CookieJar;
|
||||||
use tetratto_core::model::Error;
|
use tetratto_core::model::Error;
|
||||||
|
|
||||||
pub async fn not_found_request(Extension(data): Extension<State>) -> impl IntoResponse {
|
pub async fn not_found_request(
|
||||||
|
jar: CookieJar,
|
||||||
|
Extension(data): Extension<State>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
let (ref data, ref tera, ref build_code) = *data.read().await;
|
let (ref data, ref tera, ref build_code) = *data.read().await;
|
||||||
|
let user = get_user_from_token!(jar, data.2);
|
||||||
|
|
||||||
let mut ctx = default_context(&data.0.0, &build_code);
|
let mut ctx = default_context(&data.0.0, &build_code, &user);
|
||||||
ctx.insert(
|
ctx.insert(
|
||||||
"error",
|
"error",
|
||||||
&Error::GeneralNotFound("page".to_string()).to_string(),
|
&Error::GeneralNotFound("page".to_string()).to_string(),
|
||||||
|
@ -16,10 +21,28 @@ pub async fn not_found_request(Extension(data): Extension<State>) -> impl IntoRe
|
||||||
return Html(tera.render("error.lisp", &ctx).unwrap());
|
return Html(tera.render("error.lisp", &ctx).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn index_request(Extension(data): Extension<State>) -> impl IntoResponse {
|
pub async fn index_request(jar: CookieJar, Extension(data): Extension<State>) -> impl IntoResponse {
|
||||||
let (ref data, ref tera, ref build_code) = *data.read().await;
|
let (ref data, ref tera, ref build_code) = *data.read().await;
|
||||||
|
let user = get_user_from_token!(jar, data.2);
|
||||||
|
|
||||||
Html(
|
Html(
|
||||||
tera.render("index.lisp", &default_context(&data.0.0, &build_code))
|
tera.render(
|
||||||
.unwrap(),
|
"index.lisp",
|
||||||
|
&default_context(&data.0.0, &build_code, &user),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn login_request(jar: CookieJar, Extension(data): Extension<State>) -> impl IntoResponse {
|
||||||
|
let (ref data, ref tera, ref build_code) = *data.read().await;
|
||||||
|
let user = get_user_from_token!(jar, data.2);
|
||||||
|
|
||||||
|
Html(
|
||||||
|
tera.render(
|
||||||
|
"login.lisp",
|
||||||
|
&default_context(&data.0.0, &build_code, &user),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,5 +3,7 @@ pub mod misc;
|
||||||
use axum::routing::{Router, get};
|
use axum::routing::{Router, get};
|
||||||
|
|
||||||
pub fn routes() -> Router {
|
pub fn routes() -> Router {
|
||||||
Router::new().route("/", get(misc::index_request))
|
Router::new()
|
||||||
|
.route("/", get(misc::index_request))
|
||||||
|
.route("/login", get(misc::login_request))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue