locality/src/admin.rs

125 lines
3.4 KiB
Rust

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<D: Database>() -> Router<AppState<D>> {
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<D: Database>(
db: &D,
cookie_jar: &CookieJar,
) -> Result<AuthenticatedAdminUser, Error> {
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<D: Database>(
cookie_jar: CookieJar,
State(AppState { db, .. }): State<AppState<D>>,
) -> Result<Response, Error> {
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<D: Database>(
cookie_jar: CookieJar,
State(AppState::<D> { db, .. }): State<AppState<D>>,
Form(params): Form<CreateFirstUserParameters>,
) -> Result<(CookieJar, FirstLoginTemplate), Error> {
let user = db
.create_first_admin_user(
&params.real_name,
&params.email,
&Password::new(&params.password)?.into(),
)
.await?;
let user = authenticate_user_with_password(&db, user, &params.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 {},
))
}