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 pub groups: Option<Vec<String>>,
23}
24
25#[derive(Debug, Clone, Deserialize)]
26pub struct RealmAccess {
28 pub roles: Vec<String>,
30}
31
32impl Claims {
33 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 claims = decode(token, decoding_key, &Validation::new(header.alg))
54 .map_err(|_| ServiceError::new(StatusCode::UNAUTHORIZED, "invalid token"))?
55 .claims;
56 Ok(claims)
57 }
58}
59
60#[cfg(test)]
61mod test {
62 use crate::test::util::{jwks::init_auth, token::generate_token};
63
64 use super::Claims;
65
66 #[test]
67 fn test_simple_token_succeeds() {
68 let jwk = init_auth();
69 let token = generate_token(&jwk, 300);
70 assert!(Claims::validate(&token).is_ok());
71 }
72
73 #[test]
74 fn test_expired_token_fails() {
75 let jwk = init_auth();
76 let token = generate_token(&jwk, -300);
77 assert!(Claims::validate(&token).is_err());
78 }
79
80 #[test]
81 fn test_invalid_token_fails() {
82 let _ = init_auth();
83 assert!(Claims::validate("not a token").is_err());
84 }
85}