authx_plugins/magic_link/
mod.rs1mod service;
2
3pub use service::{MagicLinkService, MagicLinkVerifyResponse};
4
5#[cfg(test)]
6mod tests {
7 use super::service::MagicLinkService;
8 use authx_core::{events::EventBus, models::CreateUser};
9 use authx_storage::{memory::MemoryStore, ports::UserRepository};
10
11 fn setup() -> (MemoryStore, EventBus) {
12 (MemoryStore::new(), EventBus::new())
13 }
14
15 async fn make_user(store: &MemoryStore, email: &str) -> uuid::Uuid {
16 UserRepository::create(
17 store,
18 CreateUser {
19 email: email.into(),
20 username: None,
21 metadata: None,
22 },
23 )
24 .await
25 .unwrap()
26 .id
27 }
28
29 #[tokio::test]
30 async fn request_link_returns_none_for_unknown_email() {
31 let (store, events) = setup();
32 let svc = MagicLinkService::new(store, events, 3600);
33 assert!(svc.request_link("ghost@x.com").await.unwrap().is_none());
34 }
35
36 #[tokio::test]
37 async fn request_link_returns_token_for_known_email() {
38 let (store, events) = setup();
39 make_user(&store, "magic@example.com").await;
40 let svc = MagicLinkService::new(store, events, 3600);
41 let tok = svc.request_link("magic@example.com").await.unwrap();
42 assert!(tok.is_some());
43 assert_eq!(tok.unwrap().len(), 64);
44 }
45
46 #[tokio::test]
47 async fn verify_with_bad_token_fails() {
48 let (store, events) = setup();
49 let svc = MagicLinkService::new(store, events, 3600);
50 let err = svc.verify("notavalidtoken", "127.0.0.1").await.unwrap_err();
51 assert!(matches!(err, authx_core::error::AuthError::InvalidToken));
52 }
53
54 #[tokio::test]
55 async fn verify_creates_session_and_returns_token() {
56 let (store, events) = setup();
57 make_user(&store, "magic@example.com").await;
58 let svc = MagicLinkService::new(store, events, 3600);
59
60 let raw_token = svc
61 .request_link("magic@example.com")
62 .await
63 .unwrap()
64 .unwrap();
65
66 let resp = svc.verify(&raw_token, "10.0.0.1").await.unwrap();
67 assert_eq!(resp.user.email, "magic@example.com");
68 assert_eq!(resp.session.ip_address, "10.0.0.1");
69 assert_eq!(resp.token.len(), 64);
70 }
71
72 #[tokio::test]
73 async fn verify_is_single_use() {
74 let (store, events) = setup();
75 make_user(&store, "magic@example.com").await;
76 let svc = MagicLinkService::new(store, events, 3600);
77
78 let raw_token = svc
79 .request_link("magic@example.com")
80 .await
81 .unwrap()
82 .unwrap();
83
84 svc.verify(&raw_token, "127.0.0.1").await.unwrap();
85
86 let err = svc.verify(&raw_token, "127.0.0.1").await.unwrap_err();
87 assert!(matches!(err, authx_core::error::AuthError::InvalidToken));
88 }
89}