use std::sync::OnceLock;
use std::{env::var, str::FromStr};
use serde::{Deserialize, Serialize};
use poem::Request;
use poem_openapi::{SecurityScheme, auth::Bearer};
use jiff::{Span, Timestamp};
use rand::{Rng, thread_rng};
use jsonwebtoken::{DecodingKey, EncodingKey, Header, TokenData, Validation, decode, encode};
use uuid::Uuid;
struct JwtKeys {
encode: EncodingKey,
decode: DecodingKey,
}
static JWT_KEYS: OnceLock<JwtKeys> = OnceLock::new();
static JWT_VALIDATION: OnceLock<Validation> = OnceLock::new();
fn init_jwt_keys() -> JwtKeys {
if let Ok(secret) = var("JWT_KEY_BASE64") {
const ERR: &str =
"Environment variable JWT_KEY_BASE64 must be defined with a valid base64 value";
JwtKeys {
encode: EncodingKey::from_base64_secret(&secret).expect(ERR),
decode: DecodingKey::from_base64_secret(&secret).expect(ERR),
}
} else {
let mut rnd = thread_rng();
let mut secret = [0u8; 32];
for byte in &mut secret {
*byte = rnd.r#gen();
}
JwtKeys {
decode: DecodingKey::from_secret(&secret),
encode: EncodingKey::from_secret(&secret),
}
}
}
fn init_jwt_validation() -> Validation {
let mut result = Validation::new(jsonwebtoken::Algorithm::HS256);
result.validate_exp = true;
result
}
pub fn make_jwt(id: Uuid, duration: Span, permission_to_update_credentials: bool) -> String {
let header = Header::new(jsonwebtoken::Algorithm::HS256);
let claims = Claims {
sub: id,
exp: Timestamp::now()
.checked_add(duration)
.unwrap()
.as_second()
.try_into()
.expect("positive"),
upd: permission_to_update_credentials,
};
let key = &JWT_KEYS.get_or_init(init_jwt_keys).encode;
encode(&header, &claims, key).unwrap()
}
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
sub: Uuid,
exp: usize,
upd: bool,
}
pub struct AuthUser {
pub id: Uuid,
pub update_creds: bool,
}
async fn bearer_checker(_req: &Request, bearer: Bearer) -> Option<AuthUser> {
check_jwt(&bearer.token)
}
pub fn check_jwt(token: &str) -> Option<AuthUser> {
let jwt_key = &JWT_KEYS.get_or_init(init_jwt_keys).decode;
let validator = JWT_VALIDATION.get_or_init(init_jwt_validation);
let token: TokenData<Claims> = decode(token, jwt_key, validator).ok()?;
let id = token.claims.sub.to_string();
let id = Uuid::from_str(&id).ok()?;
let update_creds = token.claims.upd;
Some(AuthUser { id, update_creds })
}
#[derive(SecurityScheme)]
#[oai(ty = "bearer", key_in = "header", checker = "bearer_checker")]
pub struct UserToken(pub AuthUser);