use std::{
borrow::Cow,
time::{Duration, SystemTime},
};
use jsonwebtoken::{encode, DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct JwtClaims {
pub sub: Cow<'static, str>,
pub exp: usize,
pub iat: usize,
pub teams: Vec<String>,
pub ids: Vec<String>,
}
pub(crate) fn encode_jwt(sub: &str, secret: &[u8]) -> Result<String, anyhow::Error> {
let start = SystemTime::now();
let since = start.duration_since(SystemTime::UNIX_EPOCH)?;
let one_day_in_secs = 60 * 60 * 24;
let in_one_day = since
.checked_add(Duration::from_secs(one_day_in_secs))
.ok_or_else(|| anyhow::anyhow!("JWT expiration out of range"))?;
let claims = JwtClaims {
sub: Cow::Owned(sub.to_string()),
exp: usize::try_from(in_one_day.as_secs())?,
iat: usize::try_from(since.as_secs())?,
teams: vec![],
ids: vec![],
};
Ok(encode(
&Header::default(),
&claims,
&EncodingKey::from_secret(secret),
)?)
}
pub(crate) fn decode_jwt(token: &str, secret: &[u8]) -> Result<JwtClaims, anyhow::Error> {
let data = jsonwebtoken::decode::<JwtClaims>(
token,
&DecodingKey::from_secret(secret),
&Validation::default(),
)?;
Ok(data.claims)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_jwt_with_secret() {
let token = encode_jwt("test", b"secret");
assert!(token.is_ok());
}
#[test]
fn test_encode_and_decode_jwt() {
let token = encode_jwt("user@example.com", b"secret").unwrap();
let claims = decode_jwt(&token, b"secret").unwrap();
assert_eq!(claims.sub, "user@example.com");
assert!(decode_jwt(&token, b"wrong-secret").is_err());
}
#[test]
fn test_decode_jwt_issued_by_jsonwebtoken_v9() {
let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyQGV4YW1wbGUuY29tIiwiZXhwIjo0MTAyNDQ0ODAwLCJpYXQiOjE3NjAwMDAwMDAsInRlYW1zIjpbXSwiaWRzIjpbXX0.a2N3dj2XVgAEq41Aqm228d8VfH4uBhYCSqQDP6z2loY";
let claims = decode_jwt(token, b"proksi-test-secret").unwrap();
assert_eq!(claims.sub, "user@example.com");
assert_eq!(claims.exp, 4_102_444_800);
}
}