use crate::config::AuthModule;
use crate::error::AuthError;
use crate::identity::AuthIdentity;
use crate::strategy::AuthStrategy;
use async_trait::async_trait;
use doido_controller::CookieJar;
use doido_core::Result;
use doido_model::sea_orm::{ConnectionTrait, DatabaseConnection, DbBackend, Statement, Value};
use http::header;
use http::request::Parts;
pub const REMEMBER_COOKIE: &str = "_doido_remember";
fn enabled() -> bool {
matches!(crate::state::try_global(), Some(state) if state.config.has_module(AuthModule::Rememberable))
}
pub fn cookie_value(user_id: &impl serde::Serialize) -> String {
serde_json::to_string(user_id).unwrap_or_default()
}
pub async fn record_remember(db: &DatabaseConnection, email: &str) -> Result<(), AuthError> {
if !enabled() {
return Ok(());
}
db.execute_raw(Statement::from_sql_and_values(
DbBackend::Sqlite,
"UPDATE users SET remember_created_at = ? WHERE email = ?",
[
Value::from(chrono::Utc::now().to_rfc3339()),
Value::from(email.to_string()),
],
))
.await
.map_err(|e| AuthError::Internal(e.to_string()))?;
Ok(())
}
pub async fn forget(db: &DatabaseConnection, email: &str) -> Result<(), AuthError> {
if !enabled() {
return Ok(());
}
db.execute_raw(Statement::from_sql_and_values(
DbBackend::Sqlite,
"UPDATE users SET remember_created_at = NULL WHERE email = ?",
[Value::from(email.to_string())],
))
.await
.map_err(|e| AuthError::Internal(e.to_string()))?;
Ok(())
}
pub struct RememberStrategy;
#[async_trait]
impl AuthStrategy for RememberStrategy {
fn name(&self) -> &str {
"remember"
}
async fn authenticate(
&self,
parts: &Parts,
_db: &DatabaseConnection,
) -> Result<Option<AuthIdentity>> {
let header = parts
.headers
.get(header::COOKIE)
.and_then(|v| v.to_str().ok());
let jar = CookieJar::from_header(header, doido_controller::secret::key_base());
match jar.get_signed(REMEMBER_COOKIE) {
Some(raw) => {
let user_id = serde_json::from_str::<serde_json::Value>(&raw)
.unwrap_or(serde_json::Value::String(raw));
Ok(Some(AuthIdentity { user_id }))
}
None => Ok(None),
}
}
}