Skip to main content

max / makenotwork

2.6 KB · 80 lines History Blame Raw
1 //! Session management: revoke individual or other sessions.
2
3 use axum::{
4 extract::{Path, State},
5 response::IntoResponse,
6 };
7 use tower_sessions::Session;
8
9 use crate::{
10 auth::{AuthUser, SESSION_TRACKING_KEY},
11 db::{self, UserSessionId},
12 error::{AppError, Result},
13 templates::UserSessionsPartialTemplate,
14 AppState,
15 };
16
17 /// Revoke a single session (sign out another device).
18 #[tracing::instrument(skip_all, name = "api::revoke_session")]
19 pub(in crate::routes::api) async fn revoke_session(
20 State(state): State<AppState>,
21 session: Session,
22 AuthUser(user): AuthUser,
23 Path(session_id): Path<UserSessionId>,
24 ) -> Result<impl IntoResponse> {
25 // Don't allow revoking your own current session via this endpoint
26 if let Ok(Some(current_id)) = session.get::<UserSessionId>(SESSION_TRACKING_KEY).await
27 && current_id == session_id
28 {
29 return Err(AppError::BadRequest(
30 "Use logout to end your current session".to_string(),
31 ));
32 }
33
34 db::sessions::delete_user_session(&state.db, session_id, user.id).await?;
35 state.session_cache.remove(&session_id);
36
37 // Re-render the sessions list
38 let sessions = db::sessions::get_user_sessions(&state.db, user.id).await?;
39 let current_tracking_id = session
40 .get::<UserSessionId>(SESSION_TRACKING_KEY)
41 .await
42 .ok()
43 .flatten();
44
45 Ok(UserSessionsPartialTemplate {
46 sessions,
47 current_session_id: current_tracking_id,
48 })
49 }
50
51 /// Revoke all sessions except the current one.
52 #[tracing::instrument(skip_all, name = "api::revoke_other_sessions")]
53 pub(in crate::routes::api) async fn revoke_other_sessions(
54 State(state): State<AppState>,
55 session: Session,
56 AuthUser(user): AuthUser,
57 ) -> Result<impl IntoResponse> {
58 let current_tracking_id = session
59 .get::<UserSessionId>(SESSION_TRACKING_KEY)
60 .await
61 .ok()
62 .flatten();
63
64 if let Some(current_id) = current_tracking_id {
65 let revoked_ids = db::sessions::delete_other_sessions(&state.db, current_id, user.id).await?;
66 for id in &revoked_ids {
67 state.session_cache.remove(id);
68 }
69 tracing::info!(user_id = %user.id, revoked = revoked_ids.len(), event = "revoke_other_sessions", "Revoked other sessions");
70 }
71
72 // Re-render the sessions list
73 let sessions = db::sessions::get_user_sessions(&state.db, user.id).await?;
74
75 Ok(UserSessionsPartialTemplate {
76 sessions,
77 current_session_id: current_tracking_id,
78 })
79 }
80