Skip to main content

systemprompt_agent/services/shared/
auth.rs

1//! JWT validation for agent requests: decoding bearer tokens into typed
2//! session claims and extracting the authenticated [`UserId`].
3
4use crate::services::shared::error::{AgentServiceError, Result};
5use systemprompt_identifiers::UserId;
6pub use systemprompt_models::auth::JwtClaims;
7use systemprompt_security::jwt::{ValidationPolicy, decode_rs256_claims};
8use systemprompt_traits::AgentJwtClaims;
9
10#[derive(Debug, Default, Clone, Copy)]
11pub struct JwtValidator;
12
13impl JwtValidator {
14    #[must_use]
15    pub const fn new() -> Self {
16        Self
17    }
18
19    #[expect(
20        clippy::unused_self,
21        reason = "trait-shaped method kept on impl for symmetry"
22    )]
23    pub fn validate_token(&self, token: &str) -> Result<JwtClaims> {
24        decode_rs256_claims(token, &ValidationPolicy::session_context())
25            .map_err(|e| AgentServiceError::Authentication(e.to_string()))
26    }
27}
28
29pub fn extract_bearer_token(authorization_header: &str) -> Result<&str> {
30    authorization_header.strip_prefix("Bearer ").ok_or_else(|| {
31        AgentServiceError::Authentication("invalid authorization header format".to_owned())
32    })
33}
34
35#[derive(Debug, Clone)]
36pub struct AgentSessionUser {
37    pub id: UserId,
38    pub username: String,
39    pub user_type: String,
40    pub permissions: Vec<String>,
41}
42
43impl AgentSessionUser {
44    pub fn from_jwt_claims(claims: AgentJwtClaims) -> Self {
45        Self {
46            id: UserId::new(claims.subject),
47            username: claims.username,
48            user_type: claims.user_type,
49            permissions: claims.permissions,
50        }
51    }
52}