125 lines
3.4 KiB
Rust
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(
|
|
¶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 {},
|
|
))
|
|
}
|