rs_firebase_admin_sdk/auth/token/jwt/
mod.rs

1#[cfg(test)]
2mod test;
3
4pub mod error;
5pub mod util;
6
7use base64::{self, Engine};
8use error::JWTError;
9use error_stack::{Report, ResultExt};
10use serde::{Deserialize, Serialize};
11use serde_json::{from_slice, to_string, Value};
12use std::collections::BTreeMap;
13use time::{serde::timestamp, OffsetDateTime};
14
15#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
16pub enum JWTAlgorithm {
17    #[serde(rename = "none")]
18    NONE,
19    HS256,
20    HS384,
21    HS512,
22    RS256,
23    RS384,
24    RS512,
25    ES256,
26    ES384,
27    ES512,
28}
29
30#[derive(Debug, Deserialize, Serialize, Clone)]
31pub struct TokenHeader {
32    pub alg: JWTAlgorithm,
33    pub kid: Option<String>,
34}
35
36#[derive(Debug, Deserialize, Serialize, Clone)]
37pub struct TokenClaims {
38    #[serde(with = "timestamp")]
39    pub exp: OffsetDateTime,
40    #[serde(with = "timestamp")]
41    pub iat: OffsetDateTime,
42    pub aud: String,
43    pub iss: String,
44    pub sub: String,
45    #[serde(with = "timestamp")]
46    pub auth_time: OffsetDateTime,
47}
48
49#[derive(Debug, Clone)]
50pub struct JWToken {
51    pub header: TokenHeader,
52    pub critical_claims: TokenClaims,
53    pub all_claims: BTreeMap<String, Value>,
54    pub payload: String,
55    pub signature: Vec<u8>,
56}
57
58impl JWToken {
59    pub fn from_encoded(encoded: &str) -> Result<Self, Report<JWTError>> {
60        let mut parts = encoded.split('.');
61
62        let header_slice = parts.next().ok_or(Report::new(JWTError::MissingHeader))?;
63
64        let header: TokenHeader = from_slice(
65            &base64::engine::general_purpose::URL_SAFE_NO_PAD
66                .decode(header_slice)
67                .change_context(JWTError::FailedToParse)?,
68        )
69        .change_context(JWTError::FailedToParse)?;
70
71        let claims_slice = parts.next().ok_or(Report::new(JWTError::MissingHeader))?;
72        let claims = base64::engine::general_purpose::URL_SAFE_NO_PAD
73            .decode(claims_slice)
74            .change_context(JWTError::FailedToParse)?;
75
76        let critical_claims: TokenClaims =
77            from_slice(&claims).change_context(JWTError::FailedToParse)?;
78        let all_claims: BTreeMap<String, Value> =
79            from_slice(&claims).change_context(JWTError::FailedToParse)?;
80
81        let signature = base64::engine::general_purpose::URL_SAFE_NO_PAD
82            .decode(
83                parts
84                    .next()
85                    .ok_or(Report::new(JWTError::MissingSignature))?,
86            )
87            .change_context(JWTError::FailedToParse)?;
88
89        Ok(Self {
90            header,
91            critical_claims,
92            all_claims,
93            payload: String::new() + header_slice + "." + claims_slice,
94            signature,
95        })
96    }
97}
98
99pub trait JwtSigner {
100    fn sign_jwt(&mut self, header: &str, payload: &str) -> Result<String, Report<JWTError>>;
101}
102
103/// Utility method for generating JWTs
104pub fn encode_jwt<HeaderT: Serialize, PayloadT: Serialize, SignerT: JwtSigner>(
105    header: &HeaderT,
106    payload: &PayloadT,
107    mut signer: SignerT,
108) -> Result<String, Report<JWTError>> {
109    let encoded_header = base64::engine::general_purpose::URL_SAFE_NO_PAD
110        .encode(to_string(header).change_context(JWTError::FailedToEncode)?);
111
112    let encoded_payload = base64::engine::general_purpose::URL_SAFE_NO_PAD
113        .encode(to_string(payload).change_context(JWTError::FailedToEncode)?);
114
115    let encoded_signature = signer.sign_jwt(&encoded_header, &encoded_payload)?;
116
117    Ok(encoded_header + "." + &encoded_payload + "." + &encoded_signature)
118}