main
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<i64>,
}
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<LogoutResponse> {
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::<String>("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)
}
}