use jsonwebtoken::{DecodingKey, EncodingKey, Header, TokenData, decode, encode};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
pub use jsonwebtoken::{Algorithm, Validation};
use crate::prelude::AppResult;
#[derive(Clone)]
pub struct Jwt {
public_key: String,
private_key: String,
token_lifetime: i64,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JwtTokenClaims {
pub sub: String,
pub iat: usize,
pub exp: usize,
pub iss: String,
pub aud: String,
pub jti: String,
}
#[derive(Serialize, Debug)]
pub struct AuthTokenData {
pub access_token: String,
pub token_type: String,
pub expires_in: i64,
}
impl Jwt {
pub fn new(public_key: String, private_key: String, token_lifetime: i64) -> Self {
Jwt {
public_key,
private_key,
token_lifetime,
}
}
pub fn generate<C: Serialize>(&self, claims: C) -> AppResult<AuthTokenData> {
let token_header = Header::new(Algorithm::RS256);
let encoding_key = EncodingKey::from_rsa_pem(self.private_key.as_bytes())?;
let token = encode(&token_header, &claims, &encoding_key)?;
Ok(AuthTokenData {
access_token: token,
token_type: "bearer".to_string(),
expires_in: self.token_lifetime,
})
}
pub fn decode<C: DeserializeOwned + Clone>(
&self,
token: &str,
val: &Validation,
) -> AppResult<TokenData<C>> {
Ok(decode::<C>(
token,
&DecodingKey::from_rsa_pem(self.public_key.as_ref())?,
val,
)?)
}
pub fn dummy_keys() -> (String, String) {
let private_key = "-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEA45YvzSUfRDq2wHp7kvgCn1wk6MVGyosLIf9nAutXNVA4DLDI
SAAO1C2nmZyCMX78xqkbM47WqsDLOD/WpreMz//EZffdQh5iZt0cwnBXLJvON3AO
gGtKMsTP0tb211944V2tyBVsY/Dgvs3bJfv+Q3eZXYN5HnBW1m6jmK+9i/J3gdiZ
UlfiOUxRJRz+UMwCxi++zN2FKrNJYS29SzJ3nWuZqgw4TqDlIohlS4NJj1WsLG8i
G7TM5uTxvL8W31qRT/3bWu5NPDP64+7RIx7nh2LUuGyIocvnsRYleX123ziO/Phq
iW+ieTkx3HO0Z4DJuTLqgAtKDr/+CWhE+ROQQQIDAQABAoIBAF1TWI3Ew8IMW6Wp
dGQrrJOhywbi4ukqxGlwHNNfI1da6mkV00NnNq1+fURqIB9g5hInBV8Km2/Q+GS8
FO8vxKAEz4pK7qHu6MYqtODtBWpnB9TG8ENr+7S+7UQV34oo/d5YtmsekeCXxemo
a83zPGx5LlxhDflT8uAi5ZY2tsEcajaGrKsr4bgrgZ93duLmW3M/a2mxJsk2KsUZ
kU/FVxTqkAiV8Xt7UR4K+yg4TX9UckZ5evoaHUsfwcgyH1kToe0rDNeuqaNmdA6M
bqrIBMPqxSsIHkIEhL8e3ayuUstJYFFvgcX/wjjBxN750C8DADfD5PufDRLn+6BA
to/yLysCgYEA9N9JwOsVtB/qwPabQQJ5NaeexAM4LKKjSu4pbqWk5lGAjoVFLtZA
6yQDqIIi9MD+sNzOR3eT6+unKaIQgZfqEKIuqcHK5f/UMCW7DVY6Tsfcmk63yals
9F5I7ECLvcV3DPSLMsHvuf/x2ExWCvzhHJdUeIR4XIOmmbAaZUbFAR8CgYEA7e3O
y25E3NRjfoCKsymNO9Ju1UFQB1DxLtyFWn+O3U4OJ+ygzMl+UKzZNcCYC50T7co8
mI4uIopIzwGPBhwaKnkEj8vUXqhETki+tJwImBT6xS+hKIOp1ugiymUBcg+r1zHq
8Q4kccXrFQcNf/PZnVJffjuRUioS88fclyp2Yp8CgYAlY97oJT1IJsN6uW0VbSJC
7hhRB8jRD/htHZaX/ZUDUhiWKoVY1IBPJ02jslNSGhIJwX2B7iFZGb+JnOR2VVL5
PTpw01V+7yRgQbKhI7R9iQkHStPHaCZp3ee2o1hzHq7B/Kkwk+pd9VXhWC6fOOPK
sNTGHjwerD+JIhm1syurswKBgH4QVlVrI4lt7xmDufmbtn1PzfGoHWMC/Ac8SFve
4i9XwCBfPAPFbuwH6T6VwPnGTFzdnqWmD8O1SUaFKOav1R1T6ZrXALr2pNplqMXB
Nrx9wTDhP55bxI1dibF7OvzYWNA4XqdCOwUdPKVziy+UYGT+1CHqJeFG8avA5Zwi
n385AoGAKmh3MHMfWKBiR7rUUJQa57oKU4oaU57AyhOVuV3cm7s/x4z0vdFsplmp
isI1KHomCrqIg89Ybn4n6/Ph30H7OyyMt3L7y6P+GqdnfMocvFMfQBZPagsSsDlG
69eSqwbzi08UVhDCOYqD117jm97DgK015jnom2dutUR+IUaiYWo=
-----END RSA PRIVATE KEY-----";
let public_key = "-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA45YvzSUfRDq2wHp7kvgCn1wk6MVGyosLIf9nAutXNVA4DLDISAAO
1C2nmZyCMX78xqkbM47WqsDLOD/WpreMz//EZffdQh5iZt0cwnBXLJvON3AOgGtK
MsTP0tb211944V2tyBVsY/Dgvs3bJfv+Q3eZXYN5HnBW1m6jmK+9i/J3gdiZUlfi
OUxRJRz+UMwCxi++zN2FKrNJYS29SzJ3nWuZqgw4TqDlIohlS4NJj1WsLG8iG7TM
5uTxvL8W31qRT/3bWu5NPDP64+7RIx7nh2LUuGyIocvnsRYleX123ziO/PhqiW+i
eTkx3HO0Z4DJuTLqgAtKDr/+CWhE+ROQQQIDAQAB
-----END RSA PUBLIC KEY-----";
(public_key.to_string(), private_key.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
use jsonwebtoken::errors::{Error, ErrorKind};
use std::time::{SystemTime, UNIX_EPOCH};
fn get_sample_claim() -> JwtTokenClaims {
JwtTokenClaims {
sub: "test_subject".to_string(),
iat: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as usize,
exp: (SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
+ 3600) as usize,
iss: "test_issuer".to_string(),
aud: "test_audience".to_string(),
jti: "test_jti".to_string(),
}
}
#[test]
fn test_jwt_new() {
let (public_key, private_key) = Jwt::dummy_keys();
let jwt = Jwt::new(public_key.clone(), private_key.clone(), 60);
assert_eq!(jwt.public_key, public_key);
assert_eq!(jwt.private_key, private_key);
assert_eq!(jwt.token_lifetime, 60);
}
#[test]
fn test_jwt_generate() {
let (public_key, private_key) = Jwt::dummy_keys();
let jwt = Jwt::new(public_key, private_key, 60);
let claims = get_sample_claim();
let result = jwt.generate(claims);
assert!(result.is_ok());
let auth_token_data = result.unwrap();
assert_eq!(auth_token_data.token_type, "bearer");
assert_eq!(auth_token_data.expires_in, 60);
assert!(!auth_token_data.access_token.is_empty());
}
#[test]
fn test_jwt_decode() {
let (public_key, private_key) = Jwt::dummy_keys();
let jwt = Jwt::new(public_key.clone(), private_key, 60);
let claims = get_sample_claim();
let generated_token = jwt.generate(claims.clone()).unwrap();
let mut validation = Validation::new(Algorithm::RS256);
validation.set_audience(&["test_audience"]);
let result = jwt.decode::<JwtTokenClaims>(&generated_token.access_token, &validation);
assert!(result.is_ok());
let decoded_claims = result.unwrap().claims;
assert_eq!(decoded_claims.sub, claims.sub);
assert_eq!(decoded_claims.iat, claims.iat);
assert_eq!(decoded_claims.exp, claims.exp);
assert_eq!(decoded_claims.iss, claims.iss);
}
#[test]
fn test_jwt_decode_invalid_token() {
let (public_key, private_key) = Jwt::dummy_keys();
let jwt = Jwt::new(public_key, private_key, 60);
let invalid_token = "invalid_token";
let result =
jwt.decode::<JwtTokenClaims>(invalid_token, &Validation::new(Algorithm::RS256));
assert!(result.is_err());
match result.unwrap_err().downcast::<Error>() {
Ok(err) => {
assert_eq!(err, Error::from(ErrorKind::InvalidToken));
}
Err(err) => {
panic!("Expected jsonwebtoken::Error, got something else: {err:?}")
}
}
}
}