doido_auth/controllers/
sessions.rs1use crate::handlers::{authenticate, sign_in, sign_out};
4use crate::user::AuthUser;
5use doido_auth_macros::auth_controller;
6use doido_core::Result;
7use doido_model::password::HasSecurePassword;
8use serde::Deserialize;
9use serde::Serialize;
10use std::marker::PhantomData;
11
12pub struct AuthSessions<U>(PhantomData<U>);
14
15#[derive(Debug, Deserialize)]
16pub struct SignInForm {
17 pub email: String,
18 pub password: String,
19 #[serde(default)]
21 pub remember: Option<String>,
22}
23
24fn is_truthy(value: &Option<String>) -> bool {
25 matches!(value.as_deref(), Some("1" | "true" | "on" | "yes"))
26}
27
28#[auth_controller]
29impl<U> AuthSessions<U>
30where
31 U: AuthUser + HasSecurePassword + Serialize + Send + Sync + 'static,
32{
33 pub async fn new(ctx: doido_controller::Context) -> doido_controller::Response {
35 ctx.render("auth/sign_in", serde_json::json!({}))
36 }
37
38 pub async fn create(mut ctx: doido_controller::Context) -> Result<doido_controller::Response> {
40 let json = ctx.wants_json();
41 let form: SignInForm = if json {
42 ctx.body_json().await?
43 } else {
44 ctx.form().await?
45 };
46
47 match authenticate::<U>(ctx.db(), &form.email, &form.password).await {
48 Ok(user) => {
49 let _ = crate::trackable::record_sign_in(ctx.db(), &form.email, None).await;
51 if is_truthy(&form.remember) {
53 let _ = crate::rememberable::record_remember(ctx.db(), &form.email).await;
54 let max_age = crate::state::try_global()
55 .map(|s| s.config.remember_for)
56 .unwrap_or(1_209_600);
57 let value = crate::rememberable::cookie_value(&user.id());
58 ctx.cookies().set_signed_permanent(
59 crate::rememberable::REMEMBER_COOKIE,
60 value,
61 max_age,
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 Err(_) if json => Ok(ctx.status(401)),
72 Err(_) => Ok(ctx.render(
73 "auth/sign_in",
74 serde_json::json!({ "error": "Invalid email or password" }),
75 )),
76 }
77 }
78
79 pub async fn destroy(mut ctx: doido_controller::Context) -> Result<doido_controller::Response> {
81 ctx.cookies()
83 .set_signed_permanent(crate::rememberable::REMEMBER_COOKIE, "", 0);
84 sign_out(ctx)?;
85 if ctx.wants_json() {
86 Ok(ctx.status(204))
87 } else {
88 Ok(ctx.redirect_to("/"))
89 }
90 }
91}