Skip to main content

doido_auth/controllers/
registrations.rs

1//! Default registrations controller (`sign_up`).
2
3use crate::handlers::{register_user, sign_in};
4use crate::user::{AuthUser, RegisterableAuthUser};
5use doido_auth_macros::auth_controller;
6use doido_controller::respond::Format;
7use doido_core::Result;
8use doido_model::password::HasSecurePassword;
9use serde::Deserialize;
10use serde::Serialize;
11use std::marker::PhantomData;
12
13/// Default registrations controller for [`auth_routes!`](crate::auth_routes).
14pub struct AuthRegistrations<U>(PhantomData<U>);
15
16#[derive(Debug, Deserialize)]
17pub struct SignUpForm {
18    pub email: String,
19    pub password: String,
20    #[serde(default)]
21    pub password_confirmation: Option<String>,
22}
23
24#[auth_controller]
25impl<U> AuthRegistrations<U>
26where
27    U: AuthUser + HasSecurePassword + RegisterableAuthUser + Serialize + Send + Sync + 'static,
28{
29    /// GET `{prefix}/sign_up` — registration form (HTML mode).
30    pub async fn new(ctx: doido_controller::Context) -> doido_controller::Response {
31        ctx.render("auth/sign_up", serde_json::json!({}))
32    }
33
34    /// POST `{prefix}/sign_up` — create an account and sign in.
35    pub async fn create(mut ctx: doido_controller::Context) -> Result<doido_controller::Response> {
36        let json = ctx.negotiated_format() == Format::Json;
37        let form: SignUpForm = if json {
38            ctx.body_json().await?
39        } else {
40            ctx.form().await?
41        };
42
43        if let Some(ref confirm) = form.password_confirmation {
44            if form.password != *confirm {
45                return registration_error(ctx, json, "Password confirmation does not match");
46            }
47        }
48
49        let db = ctx.db().clone();
50        let user =
51            match register_user::<U, _, _>(&db, &form.email, &form.password, |email, digest| {
52                let db = db.clone();
53                async move { U::register(&db, email, digest).await }
54            })
55            .await
56            {
57                Ok(user) => user,
58                Err(crate::error::AuthError::EmailTaken) => {
59                    return registration_error(ctx, json, "Email has already been taken");
60                }
61                Err(e) => return Err(doido_core::anyhow::anyhow!(e.to_string())),
62            };
63
64        sign_in(ctx, &user)?;
65        if json {
66            Ok(ctx.json(user))
67        } else {
68            Ok(ctx.redirect_to("/"))
69        }
70    }
71}
72
73fn registration_error(
74    ctx: &doido_controller::Context,
75    json: bool,
76    message: &str,
77) -> Result<doido_controller::Response> {
78    if json {
79        Ok(ctx.status(422))
80    } else {
81        Ok(ctx.render("auth/sign_up", serde_json::json!({ "error": message })))
82    }
83}