99 lines
2.8 KiB
Rust
99 lines
2.8 KiB
Rust
|
|
use axum::{
|
||
|
|
extract::{Form, Path, State},
|
||
|
|
http::{header, StatusCode},
|
||
|
|
response::{Html, IntoResponse, Redirect, Response},
|
||
|
|
};
|
||
|
|
use serde::Deserialize;
|
||
|
|
use tera::Context;
|
||
|
|
|
||
|
|
use super::handlers::{render, UiError};
|
||
|
|
use crate::api::AppState;
|
||
|
|
use crate::session::{extract_cookie, COOKIE_NAME};
|
||
|
|
|
||
|
|
pub async fn login_page(
|
||
|
|
State(state): State<AppState>,
|
||
|
|
Path(slug): Path<String>,
|
||
|
|
) -> Result<Html<String>, UiError> {
|
||
|
|
let tera = state.tera.as_ref().ok_or(UiError::NotConfigured)?;
|
||
|
|
let mut ctx = Context::new();
|
||
|
|
ctx.insert("slug", &slug);
|
||
|
|
ctx.insert("error", &false);
|
||
|
|
ctx.insert("base_url", &format!("/ui/{slug}"));
|
||
|
|
render(tera, "pages/login.html", &ctx).await
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Deserialize)]
|
||
|
|
pub struct LoginForm {
|
||
|
|
pub key: String,
|
||
|
|
}
|
||
|
|
|
||
|
|
pub async fn login_submit(
|
||
|
|
State(state): State<AppState>,
|
||
|
|
Path(slug): Path<String>,
|
||
|
|
Form(form): Form<LoginForm>,
|
||
|
|
) -> Response {
|
||
|
|
let Some(ref registry) = state.registry else {
|
||
|
|
return Redirect::to("/ui/").into_response();
|
||
|
|
};
|
||
|
|
let Some(ctx) = registry.get(&slug) else {
|
||
|
|
return StatusCode::NOT_FOUND.into_response();
|
||
|
|
};
|
||
|
|
|
||
|
|
match ctx.verify_key(&form.key) {
|
||
|
|
Some(role) => {
|
||
|
|
let token = state.sessions.create(slug.clone(), role);
|
||
|
|
let cookie = format!(
|
||
|
|
"{}={}; Path=/ui/; HttpOnly; SameSite=Strict; Max-Age={}",
|
||
|
|
COOKIE_NAME,
|
||
|
|
token,
|
||
|
|
30 * 24 * 3600,
|
||
|
|
);
|
||
|
|
(
|
||
|
|
[(header::SET_COOKIE, cookie)],
|
||
|
|
Redirect::to(&format!("/ui/{slug}/")),
|
||
|
|
)
|
||
|
|
.into_response()
|
||
|
|
}
|
||
|
|
None => {
|
||
|
|
let tera = match state.tera.as_ref() {
|
||
|
|
Some(t) => t,
|
||
|
|
None => return StatusCode::INTERNAL_SERVER_ERROR.into_response(),
|
||
|
|
};
|
||
|
|
let mut tctx = Context::new();
|
||
|
|
tctx.insert("slug", &slug);
|
||
|
|
tctx.insert("error", &true);
|
||
|
|
tctx.insert("base_url", &format!("/ui/{slug}"));
|
||
|
|
match render(tera, "pages/login.html", &tctx).await {
|
||
|
|
Ok(html) => html.into_response(),
|
||
|
|
Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
pub async fn logout(
|
||
|
|
State(state): State<AppState>,
|
||
|
|
Path(slug): Path<String>,
|
||
|
|
request: axum::extract::Request,
|
||
|
|
) -> Response {
|
||
|
|
let cookie_str = request
|
||
|
|
.headers()
|
||
|
|
.get(header::COOKIE)
|
||
|
|
.and_then(|v| v.to_str().ok())
|
||
|
|
.unwrap_or("");
|
||
|
|
|
||
|
|
if let Some(token) = extract_cookie(cookie_str, COOKIE_NAME) {
|
||
|
|
state.sessions.revoke(&token);
|
||
|
|
}
|
||
|
|
|
||
|
|
let clear = format!(
|
||
|
|
"{}=; Path=/ui/; HttpOnly; SameSite=Strict; Max-Age=0",
|
||
|
|
COOKIE_NAME
|
||
|
|
);
|
||
|
|
(
|
||
|
|
[(header::SET_COOKIE, clear)],
|
||
|
|
Redirect::to(&format!("/ui/{slug}/login")),
|
||
|
|
)
|
||
|
|
.into_response()
|
||
|
|
}
|