Jump to…
snowinitial commitqoxwzsukwmkx1mo
1//! One-time site-admin bootstrap (decided; mirrors the Ory console's pattern).
2
3use axum::extract::State;
4use axum::response::{IntoResponse, Response};
5use axum::Form;
6use df_auth::provisioning;
7use serde::Deserialize;
8
9use crate::error::{AppError, AppResult};
10use crate::state::{AppState, CsrfToken, CurrentUser, Nonce};
11use crate::views::{self, Chrome};
12
13pub async fn show(
14 CurrentUser(user): CurrentUser,
15 CsrfToken(csrf): CsrfToken,
16 Nonce(nonce): Nonce,
17) -> AppResult<Response> {
18 // Claiming admin attaches it to an account, so one is required first.
19 let Some(user) = user else {
20 return Err(AppError::Unauthorized);
21 };
22
23 Ok(views::page(
24 Chrome { title: "Setup", user: Some(&user), csrf: &csrf, nonce: &nonce },
25 views::pages::setup(&csrf, None),
26 )
27 .into_response())
28}
29
30#[derive(Deserialize)]
31pub struct SetupForm {
32 pub token: String,
33}
34
35pub async fn claim(
36 State(state): State<AppState>,
37 CurrentUser(user): CurrentUser,
38 CsrfToken(csrf): CsrfToken,
39 Nonce(nonce): Nonce,
40 Form(form): Form<SetupForm>,
41) -> AppResult<Response> {
42 let Some(user) = user else {
43 return Err(AppError::Unauthorized);
44 };
45
46 let ok = provisioning::claim_admin(&state.db, user.id, form.token.trim()).await?;
47
48 if !ok {
49 tracing::warn!(user = %user.id, "setup token rejected");
50 return Ok(views::page(
51 Chrome { title: "Setup", user: Some(&user), csrf: &csrf, nonce: &nonce },
52 views::pages::setup(&csrf, Some("That token is not valid, or has already been used.")),
53 )
54 .into_response());
55 }
56
57 tracing::info!(user = %user.id, "site admin claimed");
58 Ok(views::page(
59 Chrome { title: "Setup complete", user: Some(&user), csrf: &csrf, nonce: &nonce },
60 views::pages::setup_done(),
61 )
62 .into_response())
63}

63 lines · Rust