1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
//! Responsible for parsing and validating JWT tokens.

use actix_web::http::StatusCode;
use jsonwebtoken::{decode, decode_header, DecodingKey, Validation};
use serde::Deserialize;
use uuid::Uuid;

use crate::error::ServiceError;

use super::Config;

/// Fields the token has (only the necessary fields are actually extracted).
#[derive(Debug, Clone, Deserialize)]
pub struct Claims {
    /// The subject
    pub sub: Uuid,
    /// The `OAuth2` scope
    pub scope: String,
}

impl Claims {
    /// Validate the provided token and parses it.
    ///
    /// # Errors
    /// * If the token is invalid.
    pub fn validate(token: &str) -> Result<Self, ServiceError> {
        let header = decode_header(token)
            .map_err(|_| ServiceError::new(StatusCode::UNAUTHORIZED, "invalid token"))?;
        let kid = header
            .kid
            .as_ref()
            .ok_or_else(|| ServiceError::new(StatusCode::UNAUTHORIZED, "missing kid in token"))?;

        let jwk = Config::get().jwk_set.find(kid).ok_or_else(|| {
            ServiceError::new(StatusCode::INTERNAL_SERVER_ERROR, "no valid key found")
        })?;

        let decoding_key = &DecodingKey::from_jwk(jwk).map_err(|err| {
            ServiceError::new(StatusCode::INTERNAL_SERVER_ERROR, &err.to_string())
        })?;

        let claims = decode(token, decoding_key, &Validation::new(header.alg))
            .map_err(|_| ServiceError::new(StatusCode::UNAUTHORIZED, "invalid token"))?
            .claims;
        Ok(claims)
    }
}

#[cfg(test)]
mod test {
    use crate::test::util::{jwks::init_auth, token::generate_token};

    use super::Claims;

    #[test]
    fn test_simple_token_succeeds() {
        let jwk = init_auth();
        let token = generate_token(jwk, 300);
        assert!(Claims::validate(&token).is_ok())
    }

    #[test]
    fn test_expired_token_fails() {
        let jwk = init_auth();
        let token = generate_token(jwk, -300);
        assert!(Claims::validate(&token).is_err())
    }

    #[test]
    fn test_invalid_token_fails() {
        let _ = init_auth();
        assert!(Claims::validate("not a token").is_err())
    }
}