use crate::AuthService; use crate::errors::AuthResult; use crate::events::{Labels, label}; use crate::models::users::user_events::UserEventType; use crate::session::Session; use sea_orm::Set; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; #[derive(Clone, Debug, Deserialize, Serialize, ToSchema)] pub struct LogoutResponse { pub user_id: Option, } impl AuthService { #[tracing::instrument(skip_all, fields(request_id = %request_id, client_ip = ?session.client_ip().ok().flatten()))] pub async fn logout(&self, session: Session, request_id: String) -> AuthResult { let user_id = match session.user_id() { Ok(value) => value, Err(error) => { tracing::warn!(%error, %request_id, "failed to read the session user, logging out anyway"); None } }; let client_ip = session.client_ip().ok().flatten(); let user_agent = session.user_agent().ok().flatten(); let stored_session_id = match session.get::("auth_session_id") { Ok(value) => value.and_then(|id| match uuid::Uuid::parse_str(&id) { Ok(id) => Some(id), Err(error) => { tracing::warn!(%error, %request_id, "malformed auth session id"); None } }), Err(error) => { tracing::warn!(%error, %request_id, "failed to read the auth session id"); None } }; // 凭据销毁必须先于任何可能失败的副作用:登出失败也要让会话失效。 session.purge(); if let Some(id) = stored_session_id { use crate::models::users::user_sessions; use sea_orm::{ColumnTrait, EntityTrait, QueryFilter}; if let Err(error) = user_sessions::Entity::update_many() .set(user_sessions::ActiveModel { revoked_at: Set(Some(chrono::Utc::now())), ..Default::default() }) .filter(user_sessions::Column::Id.eq(id)) .exec(self.db().write()) .await { tracing::warn!(%error, %id, %request_id, "failed to revoke the persistent session"); } } if let Some(uid) = user_id { crate::services::me::invalidate_me_cache(self.cache(), uid).await; if let Err(error) = crate::share::audit::entry( self.db(), uid, Some(uid), UserEventType::Logout, None, Some(&request_id), client_ip, user_agent, ) .await { tracing::warn!(%error, %uid, %request_id, "failed to write the logout audit event"); } } let response = LogoutResponse { user_id }; self.emit_labeled( label::AUTH_LOGOUT_DELETE, Labels::new() .with_opt(label::USER_ID, user_id) .with(label::REQUEST_ID, &request_id), &response, Some(request_id.as_str()), ) .await; Ok(response) } }