1use std::time::SystemTime;
2
3use crate::{token::RequestReason, token_cache::CacheableToken, Error};
4
5#[derive(Clone, PartialEq, Eq, Debug)]
7pub struct IdToken {
8 pub token: String,
9 pub expiration: SystemTime,
10}
11
12impl IdToken {
13 pub fn new(token: String) -> Result<IdToken, Error> {
14 let claims = token.split('.').nth(1).ok_or(Error::InvalidTokenFormat)?;
16
17 let decoded = data_encoding::BASE64URL_NOPAD.decode(claims.as_bytes())?;
18 let claims: TokenClaims = serde_json::from_slice(&decoded)?;
19
20 Ok(Self {
21 token,
22 expiration: SystemTime::UNIX_EPOCH
23 .checked_add(std::time::Duration::from_secs(claims.exp))
24 .unwrap_or(SystemTime::UNIX_EPOCH),
25 })
26 }
27}
28
29impl CacheableToken for IdToken {
30 #[inline]
32 fn has_expired(&self) -> bool {
33 if self.token.is_empty() {
34 return true;
35 }
36
37 self.expiration <= SystemTime::now()
38 }
39}
40
41pub enum IdTokenOrRequest {
45 AccessTokenRequest {
46 request: AccessTokenRequest,
47 reason: RequestReason,
48 audience_hash: u64,
49 },
50 IdTokenRequest {
51 request: IdTokenRequest,
52 reason: RequestReason,
53 audience_hash: u64,
54 },
55 IdToken(IdToken),
56}
57
58pub type IdTokenRequest = http::Request<Vec<u8>>;
59pub type AccessTokenRequest = http::Request<Vec<u8>>;
60
61pub type AccessTokenResponse<S> = http::Response<S>;
62pub type IdTokenResponse<S> = http::Response<S>;
63
64pub trait IdTokenProvider {
66 fn get_id_token(&self, audience: &str) -> Result<IdTokenOrRequest, Error>;
68
69 fn get_id_token_with_access_token<S>(
72 &self,
73 audience: &str,
74 response: AccessTokenResponse<S>,
75 ) -> Result<IdTokenRequest, Error>
76 where
77 S: AsRef<[u8]>;
78
79 fn parse_id_token_response<S>(
82 &self,
83 hash: u64,
84 response: IdTokenResponse<S>,
85 ) -> Result<IdToken, Error>
86 where
87 S: AsRef<[u8]>;
88}
89
90#[derive(serde::Deserialize, Debug)]
91struct TokenClaims {
92 exp: u64,
93}
94
95#[cfg(test)]
96mod tests {
97 use std::time::SystemTime;
98
99 use super::IdToken;
100
101 #[test]
102 fn test_decode_jwt() {
103 let raw_token = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJteS1hdWQiLCJhenAiOiIxMjMiLCJlbWFpbCI6InRlc3RAZXhhbXBsZS5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiZXhwIjoxNjc2NjQxNzczLCJpYXQiOjE2NzY2MzgxNzMsImlzcyI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbSIsInN1YiI6IjEyMzQiLCJrZXkiOiJ-fn4_In0.RpaD4p5ugL-MH_bkQ3jQ6RPANCDl1nV32xbE5raJF7tZkteQG4ULfRAcVsRnhF3j0yw3e8X9WJJ0rBdnF79MxYbaGB61hl8i6vjoa13zuEw2yaY-pNfEkfsqyf0WcY80_uV3jt-vmcPAlikgtss1YCVl9SW3i2bFXTw_kV-UE8stuCjNcjkORI9hZxEoYZoDJcc4Y8W7JuYD8V8fF8iBtZLCtGCPK64ERrZFkTqLX6FcypEAo6Y5JvmrKGQSMx9q8ozkpqMRTxxfPw6HVTEQJacjkkdJoCrs3zARzzjvm1xyWfJSGGS_g4wismCbDKLtsCSNmugjS-7ruf7rnqUTBg";
118
119 let claims = raw_token.split('.').nth(1).unwrap();
122 assert_ne!(claims.len() % 4, 0);
123
124 assert!(claims.contains('_'));
126 assert!(claims.contains('-'));
127
128 let id_token = IdToken::new(raw_token.to_owned()).unwrap();
129
130 assert_eq!(id_token.token, raw_token);
131 assert_eq!(
132 id_token
133 .expiration
134 .duration_since(SystemTime::UNIX_EPOCH)
135 .unwrap()
136 .as_secs(),
137 1676641773
138 );
139 }
140}