use std::sync::Arc;
use axum::Json;
use axum::http::{HeaderMap, StatusCode};
use axum::response::{IntoResponse, Response};
use serde::Serialize;
use sqlx::{Pool, Sqlite};
use crate::auth::AuthContext;
use crate::moderators::Role;
use crate::writer::WriterHandle;
use super::AdminConfig;
#[derive(Debug, Clone)]
pub(super) struct VerifiedAdmin {
pub caller_did: String,
pub role: Role,
#[allow(dead_code)]
pub key_id: String,
}
#[derive(Clone)]
pub(super) struct AdminState {
pub pool: Pool<Sqlite>,
pub writer: WriterHandle,
pub auth: Arc<AuthContext>,
pub config: Arc<AdminConfig>,
}
#[derive(Debug)]
pub(super) enum AdminError {
AuthenticationRequired,
Forbidden,
InvalidRequest(&'static str),
LabelNotFound,
ReportNotFound,
InvalidLabelValue,
Internal,
}
#[derive(Serialize)]
struct ErrorBody {
error: &'static str,
message: &'static str,
}
impl IntoResponse for AdminError {
fn into_response(self) -> Response {
let (status, body) = match self {
AdminError::AuthenticationRequired => (
StatusCode::UNAUTHORIZED,
ErrorBody {
error: "AuthenticationRequired",
message: "authentication required",
},
),
AdminError::Forbidden => (
StatusCode::FORBIDDEN,
ErrorBody {
error: "Forbidden",
message: "forbidden",
},
),
AdminError::InvalidRequest(msg) => (
StatusCode::BAD_REQUEST,
ErrorBody {
error: "InvalidRequest",
message: msg,
},
),
AdminError::LabelNotFound => (
StatusCode::NOT_FOUND,
ErrorBody {
error: "LabelNotFound",
message: "no applied label for the given tuple",
},
),
AdminError::ReportNotFound => (
StatusCode::NOT_FOUND,
ErrorBody {
error: "ReportNotFound",
message: "report not found",
},
),
AdminError::InvalidLabelValue => (
StatusCode::BAD_REQUEST,
ErrorBody {
error: "InvalidLabelValue",
message: "label value not accepted by this labeler",
},
),
AdminError::Internal => (
StatusCode::INTERNAL_SERVER_ERROR,
ErrorBody {
error: "InternalServerError",
message: "service temporarily unavailable",
},
),
};
(status, Json(body)).into_response()
}
}
pub(super) async fn verify_and_authorize(
state: &AdminState,
headers: &HeaderMap,
lxm: &str,
) -> Result<VerifiedAdmin, AdminError> {
if headers.contains_key("origin") {
return Err(AdminError::Forbidden);
}
let token = headers
.get("authorization")
.and_then(|h| h.to_str().ok())
.and_then(|s| s.strip_prefix("Bearer "))
.ok_or(AdminError::AuthenticationRequired)?;
let caller = state
.auth
.verify_service_auth(token, lxm)
.await
.map_err(|_| AdminError::AuthenticationRequired)?;
let row = sqlx::query!("SELECT role FROM moderators WHERE did = ?1", caller.iss)
.fetch_optional(&state.pool)
.await
.map_err(|_| AdminError::Internal)?;
let role = match row.as_ref().map(|r| r.role.as_str()) {
Some("admin") => Role::Admin,
Some("mod") => Role::Mod,
Some(_) => return Err(AdminError::Internal), None => return Err(AdminError::Forbidden),
};
Ok(VerifiedAdmin {
caller_did: caller.iss,
role,
key_id: caller.key_id,
})
}
pub(super) async fn verify_and_authorize_admin_only(
state: &AdminState,
headers: &HeaderMap,
lxm: &str,
) -> Result<VerifiedAdmin, AdminError> {
let v = verify_and_authorize(state, headers, lxm).await?;
if v.role != Role::Admin {
return Err(AdminError::Forbidden);
}
Ok(v)
}