use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
use serde::{Deserialize, Deserializer};
use super::decoded_access_token::DecodedAccessToken;
pub trait IntoPermissions {
fn into_permissions(self) -> Vec<&'static str>;
}
impl IntoPermissions for &'static str {
fn into_permissions(self) -> Vec<&'static str> {
vec![self]
}
}
impl<const N: usize> IntoPermissions for [&'static str; N] {
fn into_permissions(self) -> Vec<&'static str> {
self.to_vec()
}
}
#[derive(Debug)]
pub struct AccessToken {
pub token: String,
}
impl AccessToken {
pub fn new(token: String) -> Self {
Self { token }
}
pub fn as_str(&self) -> &str {
&self.token
}
pub fn decoded(&self) -> Result<DecodedAccessToken, String> {
decode_access_token(&self.token)
}
pub fn is_expired(&self) -> bool {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
self.decoded().map_or(true, |d| d.exp <= now)
}
pub fn auth0_id(&self) -> Option<String> {
self.decoded().ok().map(|d| d.sub)
}
pub fn user_roles(&self) -> Vec<String> {
self.decoded().ok().map(|d| d.user_roles).unwrap_or_default()
}
pub fn permissions(&self) -> Vec<String> {
self.decoded().ok().map(|d| d.permissions).unwrap_or_default()
}
pub fn validate_permissions<P: IntoPermissions>(&self, permissions: P) -> bool {
let Ok(decoded) = self.decoded() else { return false };
permissions
.into_permissions()
.iter()
.all(|p| decoded.permissions.iter().any(|g| g == p))
}
}
impl<'de> Deserialize<'de> for AccessToken {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let token = String::deserialize(d)?;
Ok(Self { token })
}
}
fn decode_access_token(token: &str) -> Result<DecodedAccessToken, String> {
let parts: Vec<&str> = token.split('.').collect();
if parts.len() != 3 {
return Err("Invalid JWT format".to_string());
}
let decoded = URL_SAFE_NO_PAD
.decode(parts[1])
.map_err(|e| format!("Failed to base64-decode payload: {e}"))?;
serde_json::from_slice(&decoded)
.map_err(|e| format!("Failed to parse payload JSON: {e}"))
}