backend/config/auth/
claims.rs

1//! Responsible for parsing and validating JWT tokens.
2
3use actix_web::http::StatusCode;
4use jsonwebtoken::{decode, decode_header, DecodingKey, Validation};
5use serde::Deserialize;
6use uuid::Uuid;
7
8use crate::error::ServiceError;
9
10use super::Config;
11
12/// Fields the token has (only the necessary fields are actually extracted).
13#[derive(Debug, Clone, Deserialize)]
14pub struct Claims {
15    /// The subject
16    pub sub: Uuid,
17    /// The `OAuth2` scope
18    pub scope: String,
19    /// Realm roles
20    pub realm_access: Option<RealmAccess>,
21}
22
23#[derive(Debug, Clone, Deserialize)]
24/// The roles active at the realm level.
25pub struct RealmAccess {
26    /// The roles active at the realm level.
27    pub roles: Vec<String>,
28}
29
30impl Claims {
31    /// Validate the provided token and parses it.
32    ///
33    /// # Errors
34    /// * If the token is invalid.
35    pub fn validate(token: &str) -> Result<Self, ServiceError> {
36        let header = decode_header(token)
37            .map_err(|_| ServiceError::new(StatusCode::UNAUTHORIZED, "invalid token"))?;
38        let kid = header
39            .kid
40            .as_ref()
41            .ok_or_else(|| ServiceError::new(StatusCode::UNAUTHORIZED, "missing kid in token"))?;
42
43        let jwk = Config::get().jwk_set.find(kid).ok_or_else(|| {
44            ServiceError::new(StatusCode::INTERNAL_SERVER_ERROR, "no valid key found")
45        })?;
46
47        let decoding_key = &DecodingKey::from_jwk(jwk).map_err(|err| {
48            ServiceError::new(StatusCode::INTERNAL_SERVER_ERROR, &err.to_string())
49        })?;
50
51        let claims = decode(token, decoding_key, &Validation::new(header.alg))
52            .map_err(|_| ServiceError::new(StatusCode::UNAUTHORIZED, "invalid token"))?
53            .claims;
54        Ok(claims)
55    }
56}
57
58#[cfg(test)]
59mod test {
60    use crate::test::util::{jwks::init_auth, token::generate_token};
61
62    use super::Claims;
63
64    #[test]
65    fn test_simple_token_succeeds() {
66        let jwk = init_auth();
67        let token = generate_token(&jwk, 300);
68        assert!(Claims::validate(&token).is_ok());
69    }
70
71    #[test]
72    fn test_expired_token_fails() {
73        let jwk = init_auth();
74        let token = generate_token(&jwk, -300);
75        assert!(Claims::validate(&token).is_err());
76    }
77
78    #[test]
79    fn test_invalid_token_fails() {
80        let _ = init_auth();
81        assert!(Claims::validate("not a token").is_err());
82    }
83}