use models::users::user_sessions; use models::users::user_sessions::SessionKind; use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter, Set}; use time::{Duration, OffsetDateTime}; use uuid::Uuid; use super::AuthService; use crate::errors::AuthError; const DEFAULT_SESSION_LIFETIME: Duration = Duration::days(30); impl AuthService { pub fn session_lifetime(&self) -> Duration { match self.config().session.lifespan_secs { Some(secs) if secs > 0 => Duration::seconds(secs), _ => DEFAULT_SESSION_LIFETIME, } } pub async fn create_session( &self, token_hash: &str, user_id: Uuid, ua_hash: Option<&str>, ip_hash: Option<&str>, device_id: Option, ) -> Result<(), AuthError> { let now = OffsetDateTime::now_utc(); let id = Uuid::now_v7(); let row = user_sessions::ActiveModel { id: Set(id), user_id: Set(user_id), token_hash: Set(token_hash.to_string()), user_agent_hash: Set(ua_hash.map(str::to_string)), client_ip_hash: Set(ip_hash.map(str::to_string)), device_id: Set(device_id), kind: Set(SessionKind::WebLogin), family_id: Set(id), parent_refresh_id: Set(None), is_revoked: Set(false), revoked_at: Set(None), last_seen_at: Set(now), created_at: Set(now), expires_at: Set(now + self.session_lifetime()), replaced_at: Set(None), grace_expires_at: Set(None), updated_at: Set(now), replaced_by: Set(None), signing_key_id: Set(None), app_id: Set(None), }; row.insert(self.db().write()).await?; Ok(()) } pub async fn touch_session(&self, token_hash: &str) -> Result<(), AuthError> { let now = OffsetDateTime::now_utc(); let Some(row) = user_sessions::Entity::find() .filter(user_sessions::Column::TokenHash.eq(token_hash)) .one(self.db().write()) .await? else { return Ok(()); }; let mut active = row.into_active_model(); active.last_seen_at = Set(now); active.updated_at = Set(now); active.update(self.db().write()).await?; Ok(()) } pub async fn revoke_session(&self, token_hash: &str) -> Result<(), AuthError> { let now = OffsetDateTime::now_utc(); let Some(row) = user_sessions::Entity::find() .filter(user_sessions::Column::TokenHash.eq(token_hash)) .one(self.db().write()) .await? else { return Ok(()); }; let mut active = row.into_active_model(); active.is_revoked = Set(true); active.revoked_at = Set(Some(now)); active.updated_at = Set(now); active.update(self.db().write()).await?; Ok(()) } }