use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::error::AppError;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserContext {
pub user_id: Uuid,
pub email: String,
#[serde(default)]
pub roles: Vec<String>,
#[serde(default)]
pub scopes: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tenant_id: Option<String>,
}
impl UserContext {
pub fn has_role(&self, role: &str) -> bool {
self.roles.iter().any(|r| r == role)
}
pub fn require_role(&self, role: &str) -> Result<(), AppError> {
if self.has_role(role) {
Ok(())
} else {
Err(AppError::Forbidden {
message: format!("Missing role: {}", role),
})
}
}
}
#[cfg(feature = "web")]
mod web_impl {
use super::*;
use actix_web::{dev::Payload, FromRequest, HttpMessage, HttpRequest};
use futures_util::future::{ready, Ready};
impl FromRequest for UserContext {
type Error = AppError;
type Future = Ready<Result<Self, Self::Error>>;
fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
match req.extensions().get::<UserContext>().cloned() {
Some(ctx) => ready(Ok(ctx)),
None => ready(Err(AppError::Unauthorized {
message: "Missing user context".to_string(),
})),
}
}
}
}