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