Skip to main content

max / makenotwork

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