Skip to main content

doido_auth/controllers/
sessions.rs

1//! Default sessions controller (`sign_in` / `sign_out`).
2
3use crate::handlers::{authenticate, sign_in, sign_out};
4use crate::user::AuthUser;
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 sessions controller for [`auth_routes!`](crate::auth_routes).
14pub struct AuthSessions<U>(PhantomData<U>);
15
16#[derive(Debug, Deserialize)]
17pub struct SignInForm {
18    pub email: String,
19    pub password: String,
20}
21
22#[auth_controller]
23impl<U> AuthSessions<U>
24where
25    U: AuthUser + HasSecurePassword + Serialize + Send + Sync + 'static,
26{
27    /// GET `{prefix}/sign_in` — sign-in form (HTML mode).
28    pub async fn new(ctx: doido_controller::Context) -> doido_controller::Response {
29        ctx.render("auth/sign_in", serde_json::json!({}))
30    }
31
32    /// POST `{prefix}/sign_in` — authenticate and establish a session.
33    pub async fn create(mut ctx: doido_controller::Context) -> Result<doido_controller::Response> {
34        let json = ctx.negotiated_format() == Format::Json;
35        let form: SignInForm = if json {
36            ctx.body_json().await?
37        } else {
38            ctx.form().await?
39        };
40
41        match authenticate::<U>(ctx.db(), &form.email, &form.password).await {
42            Ok(user) => {
43                sign_in(ctx, &user)?;
44                if json {
45                    Ok(ctx.json(user))
46                } else {
47                    Ok(ctx.redirect_to("/"))
48                }
49            }
50            Err(_) if json => Ok(ctx.status(401)),
51            Err(_) => Ok(ctx.render(
52                "auth/sign_in",
53                serde_json::json!({ "error": "Invalid email or password" }),
54            )),
55        }
56    }
57
58    /// DELETE `{prefix}/sign_out` — clear the session.
59    pub async fn destroy(mut ctx: doido_controller::Context) -> Result<doido_controller::Response> {
60        sign_out(ctx)?;
61        if ctx.negotiated_format() == Format::Json {
62            Ok(ctx.status(204))
63        } else {
64            Ok(ctx.redirect_to("/"))
65        }
66    }
67}