use axum::{ extract::{Form, State}, http::StatusCode, response::{IntoResponse, Redirect, Response}, routing::post, Router, }; use crate::{app::App, clock::RequestedAt, error::InternalError}; use super::{app::LoginError, extract::IdentityToken}; pub fn router() -> Router { Router::new() .route("/login", post(on_login)) .route("/logout", post(on_logout)) } #[derive(serde::Deserialize)] struct LoginRequest { name: String, password: String, } async fn on_login( State(app): State, RequestedAt(now): RequestedAt, identity: IdentityToken, Form(form): Form, ) -> Result { match app.logins().login(&form.name, &form.password, now).await { Ok(token) => { let identity = identity.set(&token); Ok(LoginResponse::Successful(identity)) } Err(LoginError::Rejected) => Ok(LoginResponse::Rejected), Err(other) => Err(other.into()), } } enum LoginResponse { Rejected, Successful(IdentityToken), } impl IntoResponse for LoginResponse { fn into_response(self) -> Response { match self { Self::Successful(identity) => (identity, Redirect::to("/")).into_response(), Self::Rejected => { (StatusCode::UNAUTHORIZED, "invalid name or password").into_response() } } } } async fn on_logout( State(app): State, identity: IdentityToken, ) -> Result { if let Some(secret) = identity.secret() { app.logins().logout(secret).await?; } let identity = identity.clear(); Ok((identity, Redirect::to("/"))) }