use async_graphql::{Context, Result, Error, ErrorExtensions};
use std::sync::Arc;
use crate::handlers::AppState;
pub struct GraphQLContext {
pub app_state: Arc<AppState>,
pub user: Option<AuthenticatedUser>,
}
#[derive(async_graphql::SimpleObject, Clone, Debug)]
pub struct AuthenticatedUser {
pub id: String,
pub username: String,
pub roles: Vec<String>,
pub tenant_id: Option<String>,
}
impl GraphQLContext {
pub fn new(app_state: Arc<AppState>) -> Self {
Self {
app_state,
user: None,
}
}
pub fn with_user(app_state: Arc<AppState>, user: AuthenticatedUser) -> Self {
Self {
app_state,
user: Some(user),
}
}
pub fn require_auth(&self) -> Result<&AuthenticatedUser> {
self.user.as_ref().ok_or_else(|| {
Error::new("Authentication required")
.extend_with(|_, e| e.set("code", "AUTH_REQUIRED"))
})
}
pub fn require_role(&self, role: &str) -> Result<&AuthenticatedUser> {
let user = self.require_auth()?;
if user.roles.contains(&role.to_string()) {
Ok(user)
} else {
Err(Error::new(format!("Required role: {}", role))
.extend_with(|_, e| e.set("code", "INSUFFICIENT_PERMISSIONS")))
}
}
pub fn require_any_role(&self, roles: &[&str]) -> Result<&AuthenticatedUser> {
let user = self.require_auth()?;
if roles.iter().any(|role| user.roles.contains(&role.to_string())) {
Ok(user)
} else {
Err(Error::new(format!("Required one of roles: {:?}", roles))
.extend_with(|_, e| e.set("code", "INSUFFICIENT_PERMISSIONS")))
}
}
pub fn tenant_id(&self) -> Option<&str> {
self.user.as_ref()?.tenant_id.as_deref()
}
}
pub fn from_context<'a>(ctx: &'a Context<'a>) -> Result<&'a GraphQLContext> {
ctx.data::<GraphQLContext>()
.map_err(|_| {
Error::new("GraphQL context not found")
.extend_with(|_, e| e.set("code", "INTERNAL_ERROR"))
})
}