1
use base64::{Engine as _, engine::general_purpose};
2
use jsonwebtoken::errors::{Error as JwtError, ErrorKind};
3
use serde::{Deserialize, Serialize};
4
use uuid::Uuid;
5

            
6
#[derive(Debug, Serialize, Deserialize)]
7
pub struct TokenDetails {
8
    pub token: Option<String>,
9
    pub token_uuid: uuid::Uuid,
10
    pub user_id: uuid::Uuid,
11
    pub token_type: Option<TokenType>,
12
    pub expires_in: Option<i64>,
13
}
14

            
15
/// Token class. A signed claim, so a refresh path can reject an access token
16
/// and vice-versa — without this, one per-user key signs both classes and an
17
/// access token could be replayed as a refresh token.
18
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
19
#[serde(rename_all = "lowercase")]
20
pub enum TokenType {
21
    Access,
22
    Refresh,
23
}
24

            
25
#[derive(Debug, Serialize, Deserialize, Clone)]
26
pub struct TokenClaims {
27
    pub sub: String,
28
    pub token_uuid: String,
29
    #[serde(default)]
30
    pub token_type: Option<TokenType>,
31
    pub exp: i64,
32
    pub iat: i64,
33
    pub nbf: i64,
34
}
35

            
36
/// Decodes a base64-encoded PEM key, mapping a malformed value to a JWT
37
/// `InvalidKeyFormat` error instead of panicking.
38
11
fn decode_pem(b64: &str) -> Result<Vec<u8>, JwtError> {
39
11
    general_purpose::STANDARD
40
11
        .decode(b64)
41
11
        .map_err(|_| JwtError::from(ErrorKind::InvalidKeyFormat))
42
11
}
43

            
44
4
pub fn generate_jwt_token(
45
4
    user_id: uuid::Uuid,
46
4
    ttl: i64,
47
4
    private_key: &str,
48
4
    token_type: TokenType,
49
4
) -> Result<TokenDetails, JwtError> {
50
4
    generate_jwt_token_with_uuid(user_id, Uuid::new_v4(), ttl, private_key, token_type)
51
4
}
52

            
53
7
pub fn generate_jwt_token_with_uuid(
54
7
    user_id: uuid::Uuid,
55
7
    token_uuid: uuid::Uuid,
56
7
    ttl: i64,
57
7
    private_key: &str,
58
7
    token_type: TokenType,
59
7
) -> Result<TokenDetails, JwtError> {
60
7
    let decoded_private_key = decode_pem(private_key)?;
61

            
62
7
    let now = chrono::Utc::now();
63
7
    let mut token_details = TokenDetails {
64
7
        user_id,
65
7
        token_uuid,
66
7
        token_type: Some(token_type),
67
7
        expires_in: Some((now + chrono::Duration::minutes(ttl)).timestamp()),
68
7
        token: None,
69
7
    };
70

            
71
7
    let claims = TokenClaims {
72
7
        sub: token_details.user_id.to_string(),
73
7
        token_uuid: token_details.token_uuid.to_string(),
74
7
        token_type: Some(token_type),
75
7
        exp: token_details.expires_in.unwrap_or_default(),
76
7
        iat: now.timestamp(),
77
7
        nbf: now.timestamp(),
78
7
    };
79

            
80
7
    let header = jsonwebtoken::Header::new(jsonwebtoken::Algorithm::RS256);
81
7
    let token = jsonwebtoken::encode(
82
7
        &header,
83
7
        &claims,
84
7
        &jsonwebtoken::EncodingKey::from_rsa_pem(&decoded_private_key)?,
85
    )?;
86
7
    token_details.token = Some(token);
87
7
    Ok(token_details)
88
7
}
89

            
90
4
pub fn verify_jwt_token(public_key: &str, token: &str) -> Result<TokenDetails, JwtError> {
91
4
    let decoded_public_key = decode_pem(public_key)?;
92

            
93
4
    let validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::RS256);
94

            
95
4
    let decoded = jsonwebtoken::decode::<TokenClaims>(
96
4
        token,
97
4
        &jsonwebtoken::DecodingKey::from_rsa_pem(&decoded_public_key)?,
98
4
        &validation,
99
    )?;
100

            
101
4
    let token_details = claims_to_details(&decoded.claims)?;
102
4
    Ok(token_details)
103
4
}
104

            
105
/// Reads the `sub` (user id) from a token WITHOUT verifying its signature.
106
///
107
/// This is a routing hint ONLY: it tells the verifier whose public key to fetch.
108
/// The token MUST then be re-verified with that key via [`verify_jwt_token`] —
109
/// an attacker can set any `sub`, but a token signed with the wrong key fails
110
/// that real verification. Never trust the result for authorization.
111
///
112
/// # Errors
113
/// Returns the underlying JWT decode error if the token is structurally invalid
114
/// or its `sub` is not a UUID.
115
2
pub fn unverified_user_id(token: &str) -> Result<Uuid, JwtError> {
116
2
    let decoded = jsonwebtoken::dangerous::insecure_decode::<TokenClaims>(token)?;
117
1
    Uuid::parse_str(&decoded.claims.sub).map_err(|_| JwtError::from(ErrorKind::InvalidSubject))
118
2
}
119

            
120
4
fn claims_to_details(claims: &TokenClaims) -> Result<TokenDetails, JwtError> {
121
4
    let user_id =
122
4
        Uuid::parse_str(&claims.sub).map_err(|_| JwtError::from(ErrorKind::InvalidSubject))?;
123
4
    let token_uuid =
124
4
        Uuid::parse_str(&claims.token_uuid).map_err(|_| JwtError::from(ErrorKind::InvalidToken))?;
125
4
    Ok(TokenDetails {
126
4
        token: None,
127
4
        token_uuid,
128
4
        user_id,
129
4
        token_type: claims.token_type,
130
4
        expires_in: None,
131
4
    })
132
4
}