Skip to main content

doido_auth/controllers/
confirmations.rs

1//! Default confirmations controller (`confirmable` — confirm + resend).
2
3use crate::confirmable;
4use doido_auth_macros::auth_controller;
5use doido_core::Result;
6use serde::Deserialize;
7use std::marker::PhantomData;
8
9/// Default confirmations controller for [`auth_routes!`](crate::auth_routes).
10pub struct AuthConfirmations<U>(PhantomData<U>);
11
12#[derive(Debug, Deserialize)]
13pub struct ConfirmQuery {
14    #[serde(default)]
15    pub confirmation_token: String,
16}
17
18#[derive(Debug, Deserialize)]
19pub struct ResendForm {
20    pub email: String,
21}
22
23#[auth_controller]
24impl<U> AuthConfirmations<U>
25where
26    U: Send + Sync + 'static,
27{
28    /// GET `{prefix}/confirmation?confirmation_token=…` — confirm an account.
29    pub async fn show(ctx: doido_controller::Context) -> Result<doido_controller::Response> {
30        let json = ctx.wants_json();
31        let token = ctx
32            .params::<ConfirmQuery>()
33            .map(|q| q.confirmation_token)
34            .unwrap_or_default();
35
36        let confirmed = confirmable::confirm(ctx.db(), &token).await?;
37        if !confirmed {
38            return if json {
39                Ok(ctx.status(422))
40            } else {
41                Ok(ctx.render(
42                    "auth/sign_in",
43                    serde_json::json!({ "error": "Confirmation link is invalid or has expired" }),
44                ))
45            };
46        }
47        if json {
48            Ok(ctx.json(serde_json::json!({ "status": "confirmed" })))
49        } else {
50            Ok(ctx.redirect_to("/users/sign_in"))
51        }
52    }
53
54    /// POST `{prefix}/confirmation` — resend confirmation instructions. Responds
55    /// generically to avoid leaking which emails exist.
56    pub async fn create(mut ctx: doido_controller::Context) -> Result<doido_controller::Response> {
57        let json = ctx.wants_json();
58        let form: ResendForm = if json {
59            ctx.body_json().await?
60        } else {
61            ctx.form().await?
62        };
63
64        if let Some(token) = confirmable::generate_confirmation(ctx.db(), &form.email).await? {
65            let _ = confirmable::send_confirmation_email(&form.email, &token).await;
66        }
67
68        if json {
69            Ok(ctx.json(serde_json::json!({ "status": "confirmation_sent" })))
70        } else {
71            Ok(ctx.render(
72                "auth/sign_in",
73                serde_json::json!({ "notice": "If your email exists, a confirmation link was sent." }),
74            ))
75        }
76    }
77}