rs_firebase_admin_sdk/auth/token/jwt/
mod.rs1#[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
103pub 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}