backend/config/auth/
claims.rs1use 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#[derive(Debug, Clone, Deserialize)]
14pub struct Claims {
15 pub sub: Uuid,
17 pub scope: String,
19 pub realm_access: Option<RealmAccess>,
21}
22
23#[derive(Debug, Clone, Deserialize)]
24pub struct RealmAccess {
26 pub roles: Vec<String>,
28}
29
30impl Claims {
31 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}