auth_password/
resolver.rs1use crate::jwt::{self, JwtConfig};
2use platform_core::{ActorContext, ActorResolutionRequest, ActorResolver};
3use std::sync::Arc;
4
5#[derive(Debug, Clone)]
11pub struct JwtActorResolver {
12 config: JwtConfig,
13 fallback: Arc<dyn ActorResolver>,
14}
15
16impl JwtActorResolver {
17 #[must_use]
18 pub fn new(config: JwtConfig, fallback: Arc<dyn ActorResolver>) -> Self {
19 Self { config, fallback }
20 }
21}
22
23#[async_trait::async_trait]
24impl ActorResolver for JwtActorResolver {
25 async fn resolve_actor(&self, request: ActorResolutionRequest) -> ActorContext {
26 if let Some(token) = bearer_jwt(&request) {
27 if let Some(claims) = jwt::verify_token(&token, &self.config) {
28 return ActorContext::User {
29 user_id: claims.sub,
30 scopes: Vec::new(),
31 };
32 }
33 }
34
35 self.fallback.resolve_actor(request).await
36 }
37}
38
39fn bearer_jwt(request: &ActorResolutionRequest) -> Option<String> {
46 request
47 .authorization
48 .as_deref()
49 .and_then(|header| header.strip_prefix("Bearer "))
50 .map(str::trim)
51 .filter(|token| !token.is_empty())
52 .filter(|token| !token.starts_with("dev-user:") && !token.starts_with("dev-service:"))
53 .filter(|token| !token.starts_with("sess_"))
54 .map(ToOwned::to_owned)
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60
61 #[test]
62 fn extracts_bearer_jwt() {
63 let request = ActorResolutionRequest {
64 authorization: Some("Bearer eyJhbGciOiJIUzI1NiJ9.test".to_owned()),
65 cookie: None,
66 };
67 assert_eq!(
68 bearer_jwt(&request),
69 Some("eyJhbGciOiJIUzI1NiJ9.test".to_owned())
70 );
71 }
72
73 #[test]
74 fn skips_session_tokens() {
75 let request = ActorResolutionRequest {
76 authorization: Some("Bearer sess_abc123".to_owned()),
77 cookie: None,
78 };
79 assert!(bearer_jwt(&request).is_none());
80 }
81
82 #[test]
83 fn skips_dev_tokens() {
84 let request = ActorResolutionRequest {
85 authorization: Some("Bearer dev-user:test".to_owned()),
86 cookie: None,
87 };
88 assert!(bearer_jwt(&request).is_none());
89 }
90
91 #[test]
92 fn returns_none_when_no_authorization() {
93 let request = ActorResolutionRequest {
94 authorization: None,
95 cookie: None,
96 };
97 assert!(bearer_jwt(&request).is_none());
98 }
99}