| 1 | //! One-time site-admin bootstrap (decided; mirrors the Ory console's pattern). |
| 2 | |
| 3 | use axum::extract::State; |
| 4 | use axum::response::{IntoResponse, Response}; |
| 5 | use axum::Form; |
| 6 | use df_auth::provisioning; |
| 7 | use serde::Deserialize; |
| 8 | |
| 9 | use crate::error::{AppError, AppResult}; |
| 10 | use crate::state::{AppState, CsrfToken, CurrentUser, Nonce}; |
| 11 | use crate::views::{self, Chrome}; |
| 12 | |
| 13 | pub 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)] |
| 31 | pub struct SetupForm { |
| 32 | pub token: String, |
| 33 | } |
| 34 | |
| 35 | pub 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