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    /// User groups
22    pub groups: Option<Vec<String>>,
23}
24
25#[derive(Debug, Clone, Deserialize)]
26/// The roles active at the realm level.
27pub struct RealmAccess {
28    /// The roles active at the realm level.
29    pub roles: Vec<String>,
30}
31
32impl Claims {
33    /// Validate the provided token and parses it.
34    ///
35    /// # Errors
36    /// * If the token is invalid.
37    pub fn validate(token: &str) -> Result<Self, ServiceError> {
38        let header = decode_header(token)
39            .map_err(|_| ServiceError::new(StatusCode::UNAUTHORIZED, "invalid token"))?;
40        let kid = header
41            .kid
42            .as_ref()
43            .ok_or_else(|| ServiceError::new(StatusCode::UNAUTHORIZED, "missing kid in token"))?;
44
45        let jwk = Config::get().jwk_set.find(kid).ok_or_else(|| {
46            ServiceError::new(StatusCode::INTERNAL_SERVER_ERROR, "no valid key found")
47        })?;
48
49        let decoding_key = &DecodingKey::from_jwk(jwk).map_err(|err| {
50            ServiceError::new(StatusCode::INTERNAL_SERVER_ERROR, &err.to_string())
51        })?;
52
53        let mut validation = Validation::new(header.alg);
54        validation.validate_aud = false;
55
56        let claims = decode(token, decoding_key, &validation)
57            .map_err(|_| ServiceError::new(StatusCode::UNAUTHORIZED, "invalid token"))?
58            .claims;
59        Ok(claims)
60    }
61}
62
63#[cfg(test)]
64mod test {
65    use crate::test::util::{generate_jwk::init_auth, token::generate_token, token::KeyCtx};
66
67    use super::Claims;
68
69    #[test]
70    fn test_simple_token_succeeds() {
71        let (header, enc_key) = init_auth();
72        let key_ctx: KeyCtx = KeyCtx {
73            header: header,
74            key: enc_key,
75        };
76        let token = generate_token(&key_ctx, 300);
77        assert!(Claims::validate(&token).is_ok());
78    }
79
80    #[test]
81    fn test_expired_token_fails() {
82        let (header, enc_key) = init_auth();
83        let key_ctx: KeyCtx = KeyCtx {
84            header: header,
85            key: enc_key,
86        };
87        let token = generate_token(&key_ctx, -300);
88        assert!(Claims::validate(&token).is_err());
89    }
90
91    #[test]
92    fn test_invalid_token_fails() {
93        let _ = init_auth();
94        assert!(Claims::validate("not a token").is_err());
95    }
96}