doido_auth/
rememberable.rs1use crate::config::AuthModule;
7use crate::error::AuthError;
8use crate::identity::AuthIdentity;
9use crate::strategy::AuthStrategy;
10use async_trait::async_trait;
11use doido_controller::CookieJar;
12use doido_core::Result;
13use doido_model::sea_orm::{ConnectionTrait, DatabaseConnection, DbBackend, Statement, Value};
14use http::header;
15use http::request::Parts;
16
17pub const REMEMBER_COOKIE: &str = "_doido_remember";
19
20fn enabled() -> bool {
21 matches!(crate::state::try_global(), Some(state) if state.config.has_module(AuthModule::Rememberable))
22}
23
24pub fn cookie_value(user_id: &impl serde::Serialize) -> String {
27 serde_json::to_string(user_id).unwrap_or_default()
28}
29
30pub async fn record_remember(db: &DatabaseConnection, email: &str) -> Result<(), AuthError> {
32 if !enabled() {
33 return Ok(());
34 }
35 db.execute_raw(Statement::from_sql_and_values(
36 DbBackend::Sqlite,
37 "UPDATE users SET remember_created_at = ? WHERE email = ?",
38 [
39 Value::from(chrono::Utc::now().to_rfc3339()),
40 Value::from(email.to_string()),
41 ],
42 ))
43 .await
44 .map_err(|e| AuthError::Internal(e.to_string()))?;
45 Ok(())
46}
47
48pub async fn forget(db: &DatabaseConnection, email: &str) -> Result<(), AuthError> {
50 if !enabled() {
51 return Ok(());
52 }
53 db.execute_raw(Statement::from_sql_and_values(
54 DbBackend::Sqlite,
55 "UPDATE users SET remember_created_at = NULL WHERE email = ?",
56 [Value::from(email.to_string())],
57 ))
58 .await
59 .map_err(|e| AuthError::Internal(e.to_string()))?;
60 Ok(())
61}
62
63pub struct RememberStrategy;
67
68#[async_trait]
69impl AuthStrategy for RememberStrategy {
70 fn name(&self) -> &str {
71 "remember"
72 }
73
74 async fn authenticate(
75 &self,
76 parts: &Parts,
77 _db: &DatabaseConnection,
78 ) -> Result<Option<AuthIdentity>> {
79 let header = parts
80 .headers
81 .get(header::COOKIE)
82 .and_then(|v| v.to_str().ok());
83 let jar = CookieJar::from_header(header, doido_controller::secret::key_base());
84 match jar.get_signed(REMEMBER_COOKIE) {
85 Some(raw) => {
86 let user_id = serde_json::from_str::<serde_json::Value>(&raw)
87 .unwrap_or(serde_json::Value::String(raw));
88 Ok(Some(AuthIdentity { user_id }))
89 }
90 None => Ok(None),
91 }
92 }
93}