use { crate::{ authentication::{ authenticate_user_with_jwt, authenticate_user_with_password, check_if_user_is_admin, create_jwt_for_user, AuthenticatedAdminUser, ParsedJwt, Password, }, db::Database, error::Error, }, askama::Template, askama_axum::{IntoResponse, Response}, axum::{ extract::State, response::Redirect, routing::{get, post}, Form, Router, }, axum_extra::extract::{ cookie::{Cookie, SameSite}, CookieJar, }, serde::Deserialize, }; use super::app::AppState; pub fn routes() -> Router> { Router::new() .route("/", get(root)) .route("/create_first_admin_user", get(get_create_first_admin_user)) .route( "/create_first_admin_user", post(post_create_first_admin_user), ) } #[derive(Template)] #[template(path = "admin/create_first_user.html")] struct CreateFirstUserTemplate {} #[derive(Template)] #[template(path = "admin/first_login.html")] struct FirstLoginTemplate {} #[derive(Template)] #[template(path = "admin/index.html")] struct IndexTemplate<'a> { admin_user_name: &'a str, } async fn check_jwt( db: &D, cookie_jar: &CookieJar, ) -> Result { match authenticate_user_with_jwt( db, cookie_jar .get("jwt") .map(|cookie| cookie.value_trimmed()) .ok_or(Error::Forbidden)?, ) .await? { ParsedJwt::Valid(user) => check_if_user_is_admin(db, &user) .await? .ok_or(Error::Forbidden), ParsedJwt::InvalidSignature => Err(Error::Forbidden), ParsedJwt::UserNotFound => Err(Error::Forbidden), ParsedJwt::Expired(user) => Err(Error::JwtExpired(user)), } } #[tracing::instrument] async fn root( cookie_jar: CookieJar, State(AppState { db, .. }): State>, ) -> Result { Ok(if !db.has_admin_users().await? { Redirect::temporary("admin/create_first_admin_user").into_response() } else { let admin_user = check_jwt(&db, &cookie_jar).await?; IndexTemplate { admin_user_name: &admin_user.real_name, } .into_response() }) } #[tracing::instrument] async fn get_create_first_admin_user() -> CreateFirstUserTemplate { CreateFirstUserTemplate {} } #[derive(Deserialize, Debug)] struct CreateFirstUserParameters { real_name: String, email: String, password: String, } #[tracing::instrument] async fn post_create_first_admin_user( cookie_jar: CookieJar, State(AppState:: { db, .. }): State>, Form(params): Form, ) -> Result<(CookieJar, FirstLoginTemplate), Error> { let user = db .create_first_admin_user( ¶ms.real_name, ¶ms.email, &Password::new(¶ms.password)?.into(), ) .await?; let user = authenticate_user_with_password(&db, user, ¶ms.password) .await? .ok_or(Error::Unexpected( "Could not authenticate newly-created user.".to_string(), ))?; Ok(( cookie_jar .add(Cookie::build(("jwt", create_jwt_for_user(&user)?)).same_site(SameSite::Strict)), FirstLoginTemplate {}, )) }