use crate::{ApiErrorResponse, HttpRequestContext};
use axum::extract::FromRequestParts;
use axum::http::request::Parts;
use platform_core::{ActorContext, AppError, ErrorCode};
pub const CONSOLE_ADMIN_SCOPE: &str = "console.admin";
#[derive(Debug, Clone)]
pub struct OptionalActor(pub ActorContext);
#[derive(Debug, Clone)]
pub struct AuthenticatedActor(pub ActorContext);
#[derive(Debug, Clone)]
pub struct UserActor {
pub user_id: String,
pub scopes: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct ServiceActor {
pub service_id: String,
pub scopes: Vec<String>,
}
#[derive(Debug, Clone)]
pub enum AdminActor {
Service {
service_id: String,
scopes: Vec<String>,
},
User {
user_id: String,
scopes: Vec<String>,
},
System,
}
impl<S> FromRequestParts<S> for OptionalActor
where
S: Send + Sync,
{
type Rejection = ApiErrorResponse;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
let HttpRequestContext(ctx) = HttpRequestContext::from_request_parts(parts, state).await?;
Ok(Self(ctx.actor))
}
}
impl<S> FromRequestParts<S> for AuthenticatedActor
where
S: Send + Sync,
{
type Rejection = ApiErrorResponse;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
let HttpRequestContext(ctx) = HttpRequestContext::from_request_parts(parts, state).await?;
match ctx.actor {
ActorContext::Anonymous => Err(ApiErrorResponse::with_context(
AppError::new(ErrorCode::Unauthorized, "Authentication is required"),
&ctx,
)),
actor => Ok(Self(actor)),
}
}
}
impl<S> FromRequestParts<S> for UserActor
where
S: Send + Sync,
{
type Rejection = ApiErrorResponse;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
let HttpRequestContext(ctx) = HttpRequestContext::from_request_parts(parts, state).await?;
match ctx.actor {
ActorContext::Anonymous => Err(ApiErrorResponse::with_context(
AppError::new(ErrorCode::Unauthorized, "Authentication is required"),
&ctx,
)),
ActorContext::User { user_id, scopes } => Ok(Self { user_id, scopes }),
ActorContext::Service { .. } | ActorContext::System => {
Err(ApiErrorResponse::with_context(
AppError::new(ErrorCode::Forbidden, "User authentication is required"),
&ctx,
))
}
}
}
}
impl<S> FromRequestParts<S> for ServiceActor
where
S: Send + Sync,
{
type Rejection = ApiErrorResponse;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
let HttpRequestContext(ctx) = HttpRequestContext::from_request_parts(parts, state).await?;
match ctx.actor {
ActorContext::Anonymous => Err(ApiErrorResponse::with_context(
AppError::new(ErrorCode::Unauthorized, "Authentication is required"),
&ctx,
)),
ActorContext::Service { service_id, scopes } => Ok(Self { service_id, scopes }),
ActorContext::User { .. } | ActorContext::System => {
Err(ApiErrorResponse::with_context(
AppError::new(ErrorCode::Forbidden, "Service authentication is required"),
&ctx,
))
}
}
}
}
impl<S> FromRequestParts<S> for AdminActor
where
S: Send + Sync,
{
type Rejection = ApiErrorResponse;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
let HttpRequestContext(ctx) = HttpRequestContext::from_request_parts(parts, state).await?;
match ctx.actor {
ActorContext::Anonymous => Err(ApiErrorResponse::with_context(
AppError::new(ErrorCode::Unauthorized, "Authentication is required"),
&ctx,
)),
ActorContext::Service { service_id, scopes } => {
Ok(Self::Service { service_id, scopes })
}
ActorContext::System => Ok(Self::System),
ActorContext::User { user_id, scopes }
if scopes.iter().any(|scope| scope == CONSOLE_ADMIN_SCOPE) =>
{
Ok(Self::User { user_id, scopes })
}
ActorContext::User { .. } => Err(ApiErrorResponse::with_context(
AppError::new(
ErrorCode::Forbidden,
format!("missing console admin scope: {CONSOLE_ADMIN_SCOPE}"),
),
&ctx,
)),
}
}
}