1use async_trait::async_trait;
2use serde::{Deserialize, Serialize};
3use validator::Validate;
4
5use better_auth_core::config::AuthConfig;
6use better_auth_core::entity::{AuthSession, AuthUser};
7use better_auth_core::wire::{SessionView, UserView};
8use better_auth_core::{AuthContext, AuthPlugin, AuthRoute};
9
10use better_auth_core::AuthResult;
11use better_auth_core::{AuthRequest, AuthResponse, HttpMethod};
12
13use super::StatusResponse;
14use super::helpers::{admin_plugin_enabled, delete_session_cookie_headers, get_cookie};
15use better_auth_core::SuccessResponse;
16
17pub struct SessionManagementPlugin {
19 config: SessionManagementConfig,
20}
21
22#[derive(Debug, Clone, better_auth_core::PluginConfig)]
23#[plugin(name = "SessionManagementPlugin")]
24pub struct SessionManagementConfig {
25 #[config(default = true)]
26 pub enable_session_listing: bool,
27 #[config(default = true)]
28 pub enable_session_revocation: bool,
29 #[config(default = true)]
30 pub require_authentication: bool,
31}
32
33#[derive(Debug, Deserialize, Validate)]
35struct RevokeSessionRequest {
36 #[validate(length(min = 1, message = "Token is required"))]
37 token: String,
38}
39
40#[derive(Debug, Serialize)]
41struct GetSessionResponse<S: Serialize, U: Serialize> {
42 session: S,
43 user: U,
44}
45
46#[async_trait]
47impl<S: better_auth_core::AuthSchema> AuthPlugin<S> for SessionManagementPlugin {
48 fn name(&self) -> &'static str {
49 "session-management"
50 }
51
52 fn routes(&self) -> Vec<AuthRoute> {
53 vec![
54 AuthRoute::get("/get-session", "get_session"),
55 AuthRoute::post("/sign-out", "sign_out"),
56 AuthRoute::get("/list-sessions", "list_sessions"),
57 AuthRoute::post("/revoke-session", "revoke_session"),
58 AuthRoute::post("/revoke-sessions", "revoke_sessions"),
59 AuthRoute::post("/revoke-other-sessions", "revoke_other_sessions"),
60 ]
61 }
62
63 async fn on_request(
64 &self,
65 req: &AuthRequest,
66 ctx: &AuthContext<S>,
67 ) -> AuthResult<Option<AuthResponse>> {
68 match (req.method(), req.path()) {
69 (HttpMethod::Get, "/get-session") => Ok(Some(self.handle_get_session(req, ctx).await?)),
70 (HttpMethod::Post, "/sign-out") => Ok(Some(self.handle_sign_out(req, ctx).await?)),
71 (HttpMethod::Get, "/list-sessions") if self.config.enable_session_listing => {
72 Ok(Some(self.handle_list_sessions(req, ctx).await?))
73 }
74 (HttpMethod::Post, "/revoke-session") if self.config.enable_session_revocation => {
75 Ok(Some(self.handle_revoke_session(req, ctx).await?))
76 }
77 (HttpMethod::Post, "/revoke-sessions") if self.config.enable_session_revocation => {
78 Ok(Some(self.handle_revoke_sessions(req, ctx).await?))
79 }
80 (HttpMethod::Post, "/revoke-other-sessions")
81 if self.config.enable_session_revocation =>
82 {
83 Ok(Some(self.handle_revoke_other_sessions(req, ctx).await?))
84 }
85 _ => Ok(None),
86 }
87 }
88}
89
90pub(crate) async fn sign_out_core(
95 session: &impl AuthSession,
96 ctx: &AuthContext<impl better_auth_core::AuthSchema>,
97) -> AuthResult<SuccessResponse> {
98 ctx.database.delete_session(session.token()).await?;
99 Ok(SuccessResponse { success: true })
100}
101
102pub(crate) async fn list_sessions_core(
103 user_id: impl AsRef<str>,
104 ctx: &AuthContext<impl better_auth_core::AuthSchema>,
105) -> AuthResult<Vec<SessionView>> {
106 let sessions = ctx.session_manager().list_user_sessions(user_id).await?;
107 Ok(sessions.iter().map(SessionView::from).collect())
108}
109
110pub(crate) async fn revoke_session_core(
111 user: &impl AuthUser,
112 token: &str,
113 ctx: &AuthContext<impl better_auth_core::AuthSchema>,
114) -> AuthResult<StatusResponse> {
115 let session_manager = ctx.session_manager();
116 if let Some(session_to_revoke) = session_manager.get_session(token).await?
117 && session_to_revoke.user_id() == user.id()
118 {
119 ctx.database.delete_session(token).await?;
120 }
121 Ok(StatusResponse { status: true })
122}
123
124pub(crate) async fn revoke_sessions_core(
125 user_id: impl AsRef<str>,
126 ctx: &AuthContext<impl better_auth_core::AuthSchema>,
127) -> AuthResult<StatusResponse> {
128 ctx.database.delete_user_sessions(user_id.as_ref()).await?;
129 Ok(StatusResponse { status: true })
130}
131
132pub(crate) async fn revoke_other_sessions_core(
133 user_id: impl AsRef<str>,
134 current_session: &impl AuthSession,
135 ctx: &AuthContext<impl better_auth_core::AuthSchema>,
136) -> AuthResult<StatusResponse> {
137 let all_sessions = ctx.session_manager().list_user_sessions(user_id).await?;
138 for session in all_sessions {
139 if session.token() != current_session.token() {
140 ctx.database.delete_session(session.token()).await?;
141 }
142 }
143 Ok(StatusResponse { status: true })
144}
145
146impl SessionManagementPlugin {
151 async fn handle_get_session(
152 &self,
153 req: &AuthRequest,
154 ctx: &AuthContext<impl better_auth_core::AuthSchema>,
155 ) -> AuthResult<AuthResponse> {
156 match ctx.require_session(req).await {
158 Ok((user, session)) => {
159 let response = GetSessionResponse {
160 session: SessionView::from(&session),
161 user: UserView::from(&user),
162 };
163 Ok(AuthResponse::json(200, &response)?)
164 }
165 Err(_) => {
166 let mut response = AuthResponse::json(200, &serde_json::Value::Null)?;
167 if get_cookie(req, &ctx.config.session.cookie_name)
168 .is_some_and(|value| !value.is_empty())
169 {
170 for cookie in delete_session_cookie_headers(&ctx.config) {
171 response.headers.append("Set-Cookie", cookie);
172 }
173 }
174 Ok(response)
175 }
176 }
177 }
178
179 async fn handle_sign_out(
180 &self,
181 req: &AuthRequest,
182 ctx: &AuthContext<impl better_auth_core::AuthSchema>,
183 ) -> AuthResult<AuthResponse> {
184 if let Ok((_user, session)) = ctx.require_session(req).await {
185 let _ = sign_out_core(&session, ctx).await;
186 }
187
188 let mut response = AuthResponse::json(200, &SuccessResponse { success: true })?;
189 for cookie in sign_out_cookies(&ctx.config) {
190 response.headers.append("Set-Cookie", cookie);
191 }
192 Ok(response)
193 }
194
195 async fn handle_list_sessions(
196 &self,
197 req: &AuthRequest,
198 ctx: &AuthContext<impl better_auth_core::AuthSchema>,
199 ) -> AuthResult<AuthResponse> {
200 let (user, _) = ctx.require_session(req).await?;
201 let mut sessions = list_sessions_core(user.id(), ctx).await?;
202 if admin_plugin_enabled(ctx) {
203 sessions.retain(|session| session.impersonated_by.is_none());
204 }
205 Ok(AuthResponse::json(200, &sessions)?)
206 }
207
208 async fn handle_revoke_session(
209 &self,
210 req: &AuthRequest,
211 ctx: &AuthContext<impl better_auth_core::AuthSchema>,
212 ) -> AuthResult<AuthResponse> {
213 let (user, _) = ctx.require_session(req).await?;
214
215 let revoke_req: RevokeSessionRequest = match better_auth_core::validate_request_body(req) {
216 Ok(v) => v,
217 Err(resp) => return Ok(resp),
218 };
219
220 let response = revoke_session_core(&user, &revoke_req.token, ctx).await?;
221 Ok(AuthResponse::json(200, &response)?)
222 }
223
224 async fn handle_revoke_sessions(
225 &self,
226 req: &AuthRequest,
227 ctx: &AuthContext<impl better_auth_core::AuthSchema>,
228 ) -> AuthResult<AuthResponse> {
229 let (user, _) = ctx.require_session(req).await?;
230 let response = revoke_sessions_core(user.id(), ctx).await?;
231 Ok(AuthResponse::json(200, &response)?)
232 }
233
234 async fn handle_revoke_other_sessions(
235 &self,
236 req: &AuthRequest,
237 ctx: &AuthContext<impl better_auth_core::AuthSchema>,
238 ) -> AuthResult<AuthResponse> {
239 let (user, current_session) = ctx.require_session(req).await?;
240 let response = revoke_other_sessions_core(user.id(), ¤t_session, ctx).await?;
241 Ok(AuthResponse::json(200, &response)?)
242 }
243}
244
245fn related_cookie_name(config: &AuthConfig, suffix: &str) -> String {
246 config
247 .session
248 .cookie_name
249 .strip_suffix("session_token")
250 .map(|prefix| format!("{}{}", prefix, suffix))
251 .unwrap_or_else(|| format!("better-auth.{}", suffix))
252}
253
254fn sign_out_cookies(config: &AuthConfig) -> Vec<String> {
255 let mut cookies = vec![
256 better_auth_core::utils::cookie_utils::create_clear_session_cookie(config),
257 better_auth_core::utils::cookie_utils::create_clear_cookie(
258 &related_cookie_name(config, "session_data"),
259 config,
260 ),
261 better_auth_core::utils::cookie_utils::create_clear_cookie(
262 &related_cookie_name(config, "dont_remember"),
263 config,
264 ),
265 ];
266
267 if config.account.store_account_cookie {
268 cookies.push(better_auth_core::utils::cookie_utils::create_clear_cookie(
269 &related_cookie_name(config, "account_data"),
270 config,
271 ));
272 }
273
274 cookies
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280 use crate::plugins::test_helpers;
281 use better_auth_core::config::AccountConfig;
282 use better_auth_core::wire::SessionView;
283 use better_auth_core::{CreateSession, CreateUser};
284 use chrono::{Duration, Utc};
285
286 #[tokio::test]
288 async fn test_get_session_success() {
289 let plugin = SessionManagementPlugin::new();
290 let (ctx, _user, session) = test_helpers::create_test_context_with_user(
291 CreateUser::new()
292 .with_email("test@example.com")
293 .with_name("Test User"),
294 Duration::hours(24),
295 )
296 .await;
297
298 let req = test_helpers::create_auth_request_no_query(
299 HttpMethod::Get,
300 "/get-session",
301 Some(&session.token),
302 None,
303 );
304 let response = plugin.handle_get_session(&req, &ctx).await.unwrap();
305
306 assert_eq!(response.status, 200);
307
308 let body_str = String::from_utf8(response.body).unwrap();
309 let response_data: serde_json::Value = serde_json::from_str(&body_str).unwrap();
310 assert_eq!(
311 response_data["session"]["token"].as_str().unwrap(),
312 session.token
313 );
314 assert_eq!(
315 response_data["user"]["email"]
316 .as_str()
317 .map(|s| s.to_string()),
318 Some("test@example.com".to_string())
319 );
320 }
321
322 #[tokio::test]
324 async fn test_get_session_unauthorized() {
325 let plugin = SessionManagementPlugin::new();
327 let (ctx, _user, _session) = test_helpers::create_test_context_with_user(
328 CreateUser::new()
329 .with_email("test@example.com")
330 .with_name("Test User"),
331 Duration::hours(24),
332 )
333 .await;
334
335 let req =
336 test_helpers::create_auth_request_no_query(HttpMethod::Get, "/get-session", None, None);
337 let response = plugin.handle_get_session(&req, &ctx).await.unwrap();
338 assert_eq!(response.status, 200);
339 let body: serde_json::Value = serde_json::from_slice(&response.body).expect("valid JSON");
340 assert!(body.is_null());
341 }
342
343 #[tokio::test]
345 async fn test_sign_out_success() {
346 let plugin = SessionManagementPlugin::new();
347 let (ctx, _user, session) = test_helpers::create_test_context_with_user(
348 CreateUser::new()
349 .with_email("test@example.com")
350 .with_name("Test User"),
351 Duration::hours(24),
352 )
353 .await;
354
355 let req = test_helpers::create_auth_request_no_query(
356 HttpMethod::Post,
357 "/sign-out",
358 Some(&session.token),
359 Some(b"{}".to_vec()),
360 );
361 let response = plugin.handle_sign_out(&req, &ctx).await.unwrap();
362
363 assert_eq!(response.status, 200);
364
365 let body_str = String::from_utf8(response.body).unwrap();
366 let response_data: SuccessResponse = serde_json::from_str(&body_str).unwrap();
367 assert!(response_data.success);
368
369 let session_check = ctx.database.get_session(&session.token).await.unwrap();
370 assert!(session_check.is_none());
371 }
372
373 #[tokio::test]
374 async fn test_sign_out_clears_account_cookie_when_enabled() {
375 let plugin = SessionManagementPlugin::new();
376 let config = test_helpers::create_test_config().account(AccountConfig {
377 store_account_cookie: true,
378 ..Default::default()
379 });
380 let ctx = test_helpers::create_test_context_with_config(config).await;
381 let (_user, session) = test_helpers::create_user_and_session(
382 &ctx,
383 CreateUser::new()
384 .with_email("test@example.com")
385 .with_name("Test User"),
386 Duration::hours(24),
387 )
388 .await;
389
390 let req = test_helpers::create_auth_request_no_query(
391 HttpMethod::Post,
392 "/sign-out",
393 Some(&session.token),
394 Some(b"{}".to_vec()),
395 );
396 let response = plugin.handle_sign_out(&req, &ctx).await.unwrap();
397
398 let account_cookie_name = format!("{}=", related_cookie_name(&ctx.config, "account_data"));
399 assert!(
400 response
401 .headers
402 .get_all("Set-Cookie")
403 .any(|cookie| cookie.starts_with(&account_cookie_name)),
404 "sign-out should clear the account_data cookie when store_account_cookie is enabled"
405 );
406 }
407
408 #[tokio::test]
409 async fn test_sign_out_does_not_emit_account_cookie_when_disabled() {
410 let plugin = SessionManagementPlugin::new();
411 let (ctx, _user, session) = test_helpers::create_test_context_with_user(
412 CreateUser::new()
413 .with_email("test@example.com")
414 .with_name("Test User"),
415 Duration::hours(24),
416 )
417 .await;
418
419 let req = test_helpers::create_auth_request_no_query(
420 HttpMethod::Post,
421 "/sign-out",
422 Some(&session.token),
423 Some(b"{}".to_vec()),
424 );
425 let response = plugin.handle_sign_out(&req, &ctx).await.unwrap();
426
427 let account_cookie_name = format!("{}=", related_cookie_name(&ctx.config, "account_data"));
428 assert!(
429 !response
430 .headers
431 .get_all("Set-Cookie")
432 .any(|cookie| cookie.starts_with(&account_cookie_name)),
433 "sign-out should not emit account_data clearing cookies when store_account_cookie is disabled"
434 );
435 }
436
437 #[tokio::test]
439 async fn test_list_sessions_success() {
440 let plugin = SessionManagementPlugin::new();
441 let (ctx, user, session) = test_helpers::create_test_context_with_user(
442 CreateUser::new()
443 .with_email("test@example.com")
444 .with_name("Test User"),
445 Duration::hours(24),
446 )
447 .await;
448
449 let create_session2 = CreateSession {
450 user_id: user.id.clone(),
451 expires_at: Utc::now() + Duration::hours(24),
452 ip_address: Some("192.168.1.1".to_string()),
453 user_agent: Some("another-agent".to_string()),
454 impersonated_by: None,
455 active_organization_id: None,
456 };
457 ctx.database.create_session(create_session2).await.unwrap();
458
459 let req = test_helpers::create_auth_request_no_query(
460 HttpMethod::Get,
461 "/list-sessions",
462 Some(&session.token),
463 None,
464 );
465 let response = plugin.handle_list_sessions(&req, &ctx).await.unwrap();
466
467 assert_eq!(response.status, 200);
468
469 let body_str = String::from_utf8(response.body).unwrap();
470 let sessions: Vec<SessionView> = serde_json::from_str(&body_str).unwrap();
471 assert_eq!(sessions.len(), 2);
472 }
473
474 #[tokio::test]
475 async fn test_list_sessions_filters_impersonated_sessions_when_admin_plugin_is_enabled() {
476 let plugin = SessionManagementPlugin::new();
477 let (mut ctx, user, session) = test_helpers::create_test_context_with_user(
478 CreateUser::new()
479 .with_email("test@example.com")
480 .with_name("Test User"),
481 Duration::hours(24),
482 )
483 .await;
484 ctx.set_metadata("admin.enabled", serde_json::Value::Bool(true));
485
486 let direct_session = CreateSession {
487 user_id: user.id.clone(),
488 expires_at: Utc::now() + Duration::hours(24),
489 ip_address: Some("192.168.1.1".to_string()),
490 user_agent: Some("another-agent".to_string()),
491 impersonated_by: None,
492 active_organization_id: None,
493 };
494 ctx.database.create_session(direct_session).await.unwrap();
495
496 let impersonated_session = CreateSession {
497 user_id: user.id.clone(),
498 expires_at: Utc::now() + Duration::hours(24),
499 ip_address: Some("10.0.0.5".to_string()),
500 user_agent: Some("impersonated-agent".to_string()),
501 impersonated_by: Some("admin-user".to_string()),
502 active_organization_id: None,
503 };
504 let impersonated = ctx
505 .database
506 .create_session(impersonated_session)
507 .await
508 .unwrap();
509
510 let req = test_helpers::create_auth_request_no_query(
511 HttpMethod::Get,
512 "/list-sessions",
513 Some(&session.token),
514 None,
515 );
516 let response = plugin.handle_list_sessions(&req, &ctx).await.unwrap();
517
518 assert_eq!(response.status, 200);
519
520 let body_str = String::from_utf8(response.body).unwrap();
521 let sessions: Vec<SessionView> = serde_json::from_str(&body_str).unwrap();
522 assert_eq!(sessions.len(), 2);
523 assert!(
524 sessions
525 .iter()
526 .all(|session| session.token != impersonated.token),
527 "impersonated sessions should not be returned from /list-sessions"
528 );
529 }
530
531 #[tokio::test]
533 async fn test_revoke_session_success() {
534 let plugin = SessionManagementPlugin::new();
535 let (ctx, user, session) = test_helpers::create_test_context_with_user(
536 CreateUser::new()
537 .with_email("test@example.com")
538 .with_name("Test User"),
539 Duration::hours(24),
540 )
541 .await;
542
543 let create_session2 = CreateSession {
544 user_id: user.id.clone(),
545 expires_at: Utc::now() + Duration::hours(24),
546 ip_address: Some("192.168.1.1".to_string()),
547 user_agent: Some("another-agent".to_string()),
548 impersonated_by: None,
549 active_organization_id: None,
550 };
551 let session2 = ctx.database.create_session(create_session2).await.unwrap();
552
553 let body = serde_json::json!({ "token": session2.token });
554 let req = test_helpers::create_auth_request_no_query(
555 HttpMethod::Post,
556 "/revoke-session",
557 Some(&session.token),
558 Some(body.to_string().into_bytes()),
559 );
560
561 let response = plugin.handle_revoke_session(&req, &ctx).await.unwrap();
562 assert_eq!(response.status, 200);
563
564 let session2_check = ctx.database.get_session(&session2.token).await.unwrap();
565 assert!(session2_check.is_none());
566
567 let session1_check = ctx.database.get_session(&session.token).await.unwrap();
568 assert!(session1_check.is_some());
569 }
570
571 #[tokio::test]
573 async fn test_revoke_session_forbidden_different_user() {
574 let plugin = SessionManagementPlugin::new();
575 let (ctx, _user1, session1) = test_helpers::create_test_context_with_user(
576 CreateUser::new()
577 .with_email("test@example.com")
578 .with_name("Test User"),
579 Duration::hours(24),
580 )
581 .await;
582
583 let create_user2 = CreateUser::new()
584 .with_email("user2@example.com")
585 .with_name("User Two");
586 let user2 = ctx.database.create_user(create_user2).await.unwrap();
587
588 let create_session2 = CreateSession {
589 user_id: user2.id,
590 expires_at: Utc::now() + Duration::hours(24),
591 ip_address: Some("192.168.1.1".to_string()),
592 user_agent: Some("another-agent".to_string()),
593 impersonated_by: None,
594 active_organization_id: None,
595 };
596 let session2 = ctx.database.create_session(create_session2).await.unwrap();
597
598 let body = serde_json::json!({ "token": session2.token });
599 let req = test_helpers::create_auth_request_no_query(
600 HttpMethod::Post,
601 "/revoke-session",
602 Some(&session1.token),
603 Some(body.to_string().into_bytes()),
604 );
605
606 let response = plugin.handle_revoke_session(&req, &ctx).await.unwrap();
607 assert_eq!(response.status, 200);
608
609 let body: serde_json::Value = serde_json::from_slice(&response.body).unwrap();
610 assert_eq!(body["status"], true);
611
612 let still_exists = ctx.database.get_session(&session2.token).await.unwrap();
613 assert!(
614 still_exists.is_some(),
615 "other user's session must not be revoked"
616 );
617 }
618
619 #[tokio::test]
621 async fn test_revoke_sessions_success() {
622 let plugin = SessionManagementPlugin::new();
623 let (ctx, user, session1) = test_helpers::create_test_context_with_user(
624 CreateUser::new()
625 .with_email("test@example.com")
626 .with_name("Test User"),
627 Duration::hours(24),
628 )
629 .await;
630
631 let create_session2 = CreateSession {
632 user_id: user.id.clone(),
633 expires_at: Utc::now() + Duration::hours(24),
634 ip_address: Some("192.168.1.1".to_string()),
635 user_agent: Some("another-agent".to_string()),
636 impersonated_by: None,
637 active_organization_id: None,
638 };
639 ctx.database.create_session(create_session2).await.unwrap();
640
641 let req = test_helpers::create_auth_request_no_query(
642 HttpMethod::Post,
643 "/revoke-sessions",
644 Some(&session1.token),
645 Some(b"{}".to_vec()),
646 );
647 let response = plugin.handle_revoke_sessions(&req, &ctx).await.unwrap();
648
649 assert_eq!(response.status, 200);
650
651 let user_sessions = ctx.database.get_user_sessions(&user.id).await.unwrap();
652 assert_eq!(user_sessions.len(), 0);
653 }
654
655 #[tokio::test]
657 async fn test_plugin_routes() {
658 let plugin = SessionManagementPlugin::new();
659 let routes = AuthPlugin::<
660 better_auth_seaorm::store::__private_test_support::bundled_schema::BundledSchema,
661 >::routes(&plugin);
662
663 assert_eq!(routes.len(), 6);
664 assert!(
665 routes
666 .iter()
667 .any(|r| r.path == "/get-session" && r.method == HttpMethod::Get)
668 );
669 assert!(
670 !routes
671 .iter()
672 .any(|r| r.path == "/get-session" && r.method == HttpMethod::Post)
673 );
674 assert!(
675 routes
676 .iter()
677 .any(|r| r.path == "/sign-out" && r.method == HttpMethod::Post)
678 );
679 assert!(
680 routes
681 .iter()
682 .any(|r| r.path == "/list-sessions" && r.method == HttpMethod::Get)
683 );
684 assert!(
685 routes
686 .iter()
687 .any(|r| r.path == "/revoke-session" && r.method == HttpMethod::Post)
688 );
689 assert!(
690 routes
691 .iter()
692 .any(|r| r.path == "/revoke-sessions" && r.method == HttpMethod::Post)
693 );
694 }
695
696 #[tokio::test]
698 async fn test_plugin_on_request_routing() {
699 let plugin = SessionManagementPlugin::new();
700 let (ctx, _user, session) = test_helpers::create_test_context_with_user(
701 CreateUser::new()
702 .with_email("test@example.com")
703 .with_name("Test User"),
704 Duration::hours(24),
705 )
706 .await;
707
708 let req = test_helpers::create_auth_request_no_query(
710 HttpMethod::Get,
711 "/get-session",
712 Some(&session.token),
713 None,
714 );
715 let response = plugin.on_request(&req, &ctx).await.unwrap();
716 assert!(response.is_some());
717 assert_eq!(response.unwrap().status, 200);
718
719 let req = test_helpers::create_auth_request_no_query(
720 HttpMethod::Post,
721 "/get-session",
722 Some(&session.token),
723 Some(b"{}".to_vec()),
724 );
725 let response = plugin.on_request(&req, &ctx).await.unwrap();
726 assert!(response.is_none());
727
728 let req = test_helpers::create_auth_request_no_query(
730 HttpMethod::Get,
731 "/invalid-route",
732 Some(&session.token),
733 None,
734 );
735 let response = plugin.on_request(&req, &ctx).await.unwrap();
736 assert!(response.is_none());
737 }
738
739 #[tokio::test]
741 async fn test_configuration() {
742 let plugin = SessionManagementPlugin::new()
743 .enable_session_listing(false)
744 .enable_session_revocation(false)
745 .require_authentication(false);
746
747 assert!(!plugin.config.enable_session_listing);
748 assert!(!plugin.config.enable_session_revocation);
749 assert!(!plugin.config.require_authentication);
750
751 let (ctx, _user, session) = test_helpers::create_test_context_with_user(
752 CreateUser::new()
753 .with_email("test@example.com")
754 .with_name("Test User"),
755 Duration::hours(24),
756 )
757 .await;
758
759 let req = test_helpers::create_auth_request_no_query(
760 HttpMethod::Get,
761 "/list-sessions",
762 Some(&session.token),
763 None,
764 );
765 let response = plugin.on_request(&req, &ctx).await.unwrap();
766 assert!(response.is_none());
767
768 let req = test_helpers::create_auth_request_no_query(
769 HttpMethod::Post,
770 "/revoke-session",
771 Some(&session.token),
772 Some(b"{}".to_vec()),
773 );
774 let response = plugin.on_request(&req, &ctx).await.unwrap();
775 assert!(response.is_none());
776 }
777}