use async_graphql::{Context, Error as GqlError};
use jsonwebtoken::{decode, DecodingKey, Validation};
use super::Claims;
use crate::errors::{gql_error, gql_unauthorized};
use crate::graphql::ContextData;
#[derive(Debug, Clone, PartialEq)]
pub enum AuthRole {
User(i64),
Admin(i64),
Custom(String, i64),
}
impl AuthRole {
pub fn id(&self) -> i64 {
match self {
AuthRole::User(id) => *id,
AuthRole::Admin(id) => *id,
AuthRole::Custom(_, id) => *id,
}
}
pub fn is_admin(&self) -> bool {
matches!(self, AuthRole::Admin(_))
}
pub fn is_user(&self) -> bool {
matches!(self, AuthRole::User(_))
}
pub fn role_name(&self) -> &str {
match self {
AuthRole::User(_) => "user",
AuthRole::Admin(_) => "admin",
AuthRole::Custom(name, _) => name,
}
}
}
pub struct MultiRoleJwtConfig {
secrets: Vec<(String, String)>,
}
impl MultiRoleJwtConfig {
pub fn new() -> Self {
Self {
secrets: Vec::new(),
}
}
pub fn add_role(mut self, name: &str, secret: String) -> Self {
self.secrets.push((name.to_string(), secret));
self
}
pub fn validate(&self, token: &str) -> Option<AuthRole> {
for (role_name, secret) in &self.secrets {
let decoding_key = DecodingKey::from_secret(secret.as_ref());
let validation = Validation::default();
if let Ok(decoded) = decode::<Claims>(token, &decoding_key, &validation) {
let user_id: i64 = decoded.claims.sub.parse().ok()?;
return Some(match role_name.as_str() {
"user" => AuthRole::User(user_id),
"admin" => AuthRole::Admin(user_id),
other => AuthRole::Custom(other.to_string(), user_id),
});
}
}
None
}
}
impl Default for MultiRoleJwtConfig {
fn default() -> Self {
Self::new()
}
}
pub fn require_admin(ctx: &Context<'_>) -> Result<i64, GqlError> {
let data = ctx.data_unchecked::<ContextData>();
match &data.role {
Some(role) if role.is_admin() => Ok(role.id()),
Some(_) => Err(gql_error("FORBIDDEN", "Admin access required")),
None => Err(gql_unauthorized()),
}
}
pub fn get_auth_role<'a>(ctx: &'a Context<'a>) -> Option<&'a AuthRole> {
let data = ctx.data_unchecked::<ContextData>();
data.role.as_ref()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_auth_role_user() {
let role = AuthRole::User(42);
assert_eq!(role.id(), 42);
assert!(role.is_user());
assert!(!role.is_admin());
assert_eq!(role.role_name(), "user");
}
#[test]
fn test_auth_role_admin() {
let role = AuthRole::Admin(1);
assert_eq!(role.id(), 1);
assert!(role.is_admin());
assert!(!role.is_user());
assert_eq!(role.role_name(), "admin");
}
#[test]
fn test_auth_role_custom() {
let role = AuthRole::Custom("moderator".to_string(), 99);
assert_eq!(role.id(), 99);
assert!(!role.is_admin());
assert!(!role.is_user());
assert_eq!(role.role_name(), "moderator");
}
#[test]
fn test_multi_role_config_builder() {
let config = MultiRoleJwtConfig::new()
.add_role("user", "secret1".to_string())
.add_role("admin", "secret2".to_string());
assert_eq!(config.secrets.len(), 2);
}
#[test]
fn test_multi_role_config_default() {
let config = MultiRoleJwtConfig::default();
assert!(config.secrets.is_empty());
}
}