1use base64::Engine;
4use base64::engine::general_purpose::URL_SAFE_NO_PAD;
5use serde_json::Value;
6
7use crate::error::{Error, Result};
8
9pub fn b64url(data: &[u8]) -> String {
10 URL_SAFE_NO_PAD.encode(data)
11}
12
13fn b64url_decode(s: &str) -> Result<Vec<u8>> {
14 URL_SAFE_NO_PAD
15 .decode(s.trim_end_matches('='))
16 .map_err(|e| Error::parse(format!("bad base64url in JWT: {e}")))
17}
18
19pub fn payload(token: &str) -> Result<Value> {
21 let part = token
22 .split('.')
23 .nth(1)
24 .ok_or_else(|| Error::parse("not a JWT"))?;
25 Ok(serde_json::from_slice(&b64url_decode(part)?)?)
26}
27
28pub fn exp(token: &str) -> Option<i64> {
30 payload(token).ok()?.get("exp")?.as_i64()
31}
32
33pub fn now() -> i64 {
34 std::time::SystemTime::now()
35 .duration_since(std::time::UNIX_EPOCH)
36 .map_or(0, |d| i64::try_from(d.as_secs()).unwrap_or(i64::MAX))
37}
38
39#[cfg(test)]
40mod tests {
41 use super::*;
42
43 #[test]
44 fn decodes_payload() -> Result<()> {
45 let tok = format!(
46 "{}.{}.sig",
47 b64url(br#"{"alg":"none"}"#),
48 b64url(br#"{"exp":1700000000,"sessionKey":"abc"}"#)
49 );
50 assert_eq!(exp(&tok), Some(1_700_000_000));
51 assert_eq!(payload(&tok)?["sessionKey"], "abc");
52 Ok(())
53 }
54}