Skip to main content

authx_plugins/magic_link/
mod.rs

1mod 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}