torii_core/session/
jwt.rs1use async_trait::async_trait;
7use chrono::{DateTime, Duration, Utc};
8
9use crate::{Error, JwtConfig, Session, SessionToken, UserId, error::SessionError};
10
11use super::provider::SessionProvider;
12
13pub struct JwtSessionProvider {
19 config: JwtConfig,
20}
21
22impl JwtSessionProvider {
23 pub fn new(config: JwtConfig) -> Self {
25 Self { config }
26 }
27}
28
29#[async_trait]
30impl SessionProvider for JwtSessionProvider {
31 async fn create_session(
32 &self,
33 user_id: &UserId,
34 user_agent: Option<String>,
35 ip_address: Option<String>,
36 duration: Duration,
37 ) -> Result<Session, Error> {
38 let now = Utc::now();
39 let expires_at = now + duration;
40
41 let session = Session::builder()
43 .user_id(user_id.clone())
44 .user_agent(user_agent)
45 .ip_address(ip_address)
46 .created_at(now)
47 .updated_at(now)
48 .expires_at(expires_at)
49 .build()?;
50
51 let claims =
53 session.to_jwt_claims(self.config.issuer.clone(), self.config.include_metadata);
54
55 let jwt_token = SessionToken::new_jwt(&claims, &self.config)?;
57
58 Ok(Session {
60 token: jwt_token,
61 ..session
62 })
63 }
64
65 async fn get_session(&self, token: &SessionToken) -> Result<Session, Error> {
66 let claims = match token.verify_jwt(&self.config) {
68 Ok(claims) => claims,
69 Err(Error::Session(SessionError::InvalidToken(msg))) => {
70 if msg.contains("ExpiredSignature") {
72 return Err(Error::Session(SessionError::Expired));
73 }
74 return Err(Error::Session(SessionError::InvalidToken(msg)));
75 }
76 Err(e) => return Err(e),
77 };
78
79 let now = Utc::now();
81 let exp = DateTime::from_timestamp(claims.exp, 0).unwrap_or(now);
82 if now > exp {
83 return Err(Error::Session(SessionError::Expired));
84 }
85
86 let session = Session::from_jwt_claims(token.clone(), &claims);
88
89 Ok(session)
90 }
91
92 async fn delete_session(&self, _token: &SessionToken) -> Result<(), Error> {
93 Ok(())
96 }
97
98 async fn cleanup_expired_sessions(&self) -> Result<(), Error> {
99 Ok(())
101 }
102
103 async fn delete_sessions_for_user(&self, _user_id: &UserId) -> Result<(), Error> {
104 tracing::warn!(
107 "JwtSessionProvider doesn't support revoking all sessions for a user; tokens will remain valid until they expire"
108 );
109 Ok(())
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 const TEST_HS256_SECRET: &[u8] = b"test_secret_key_for_hs256_jwt_tokens_not_for_production_use";
118
119 #[tokio::test]
120 async fn test_jwt_session_provider_create_and_get() {
121 let config = JwtConfig::new_hs256(TEST_HS256_SECRET.to_vec())
122 .with_issuer("test-issuer")
123 .with_metadata(true);
124
125 let provider = JwtSessionProvider::new(config);
126
127 let user_id = UserId::new_random();
128 let user_agent = Some("test-agent".to_string());
129 let ip_address = Some("127.0.0.1".to_string());
130 let duration = Duration::hours(1);
131
132 let session = provider
134 .create_session(&user_id, user_agent.clone(), ip_address.clone(), duration)
135 .await
136 .unwrap();
137
138 assert_eq!(session.user_id, user_id);
139 assert_eq!(session.user_agent, user_agent);
140 assert_eq!(session.ip_address, ip_address);
141
142 let retrieved = provider.get_session(&session.token).await.unwrap();
144
145 assert_eq!(retrieved.user_id, user_id);
146 assert_eq!(retrieved.user_agent, user_agent);
147 assert_eq!(retrieved.ip_address, ip_address);
148 }
149
150 #[tokio::test]
151 async fn test_jwt_session_provider_expired_session() {
152 let config = JwtConfig::new_hs256(TEST_HS256_SECRET.to_vec());
153 let provider = JwtSessionProvider::new(config.clone());
154
155 let user_id = UserId::new_random();
156
157 let now = Utc::now();
159 let session = Session::builder()
160 .user_id(user_id.clone())
161 .expires_at(now - Duration::minutes(5))
162 .build()
163 .unwrap();
164
165 let claims = session.to_jwt_claims(None, false);
166 let token = SessionToken::new_jwt(&claims, &config).unwrap();
167
168 let result = provider.get_session(&token).await;
170
171 assert!(matches!(result, Err(Error::Session(SessionError::Expired))));
172 }
173
174 #[tokio::test]
175 async fn test_jwt_session_provider_invalid_token() {
176 let config = JwtConfig::new_hs256(TEST_HS256_SECRET.to_vec());
177 let provider = JwtSessionProvider::new(config);
178
179 let invalid_token = SessionToken::Jwt("invalid.jwt.token".to_string());
181
182 let result = provider.get_session(&invalid_token).await;
183
184 assert!(matches!(
185 result,
186 Err(Error::Session(SessionError::InvalidToken(_)))
187 ));
188 }
189}