//! Request authentication: [`AuthUser`].
//!
//! Callers sign in with a code sent to their email or with a passkey (the
//! `auth_*` tools) and send the session token they get as
//! `Authorization: Bearer <token>`; `slave_framework::auth` keeps the
//! sessions. There are no passwords.
//!
//! This lives in `base` rather than in the `auth` module because every module
//! checks it; keeping it here means modules never depend on each other just to
//! check who is calling.
use axum::extract::FromRef;
use axum::http::HeaderMap;
use axum::http::header::AUTHORIZATION;
use super::error::{AppError, AppResult};
use super::state::AppState;
use crate::modules::auth::AuthService;
/// The platform operators: full access to every team and user. Granted to
/// the first user who signs in. Per-team roles (owner, admin, staff) live in
/// the `team` module.
pub const SUPER_ADMIN_ROLE: &str = "super_admin";
/// Role granted to every later user.
pub const USER_ROLE: &str = "user";
/// The user making the request, authenticated by `Authorization: Bearer <session token>`.
#[derive(Debug, Clone)]
pub struct AuthUser {
pub id: i64,
pub name: String,
pub email: Option<String>,
pub roles: Vec<String>,
/// Signed in through a customer-facing app (a storefront): only tools that call
/// `McpServer::authenticate_customer` accept it.
pub customer: bool,
}
impl AuthUser {
/// Resolves the caller from a live session token in `headers`.
pub async fn authenticate(state: &AppState, headers: &HeaderMap) -> AppResult<Self> {
let token = bearer_token(headers).ok_or(AppError::Unauthorized)?;
let session = state
.auth
.session(token)
.await?
.ok_or(AppError::Unauthorized)?;
let (user, roles) = AuthService::from_ref(state).load(session.user_id).await?;
Ok(Self {
id: user.id,
name: user.name,
email: user.email,
roles,
customer: session.customer,
})
}
pub fn has_role(&self, role: &str) -> bool {
self.roles.iter().any(|r| r == role)
}
pub fn is_super_admin(&self) -> bool {
self.has_role(SUPER_ADMIN_ROLE)
}
/// Fails with `Forbidden` unless this user is a super admin.
pub fn require_super_admin(&self) -> AppResult<()> {
if self.is_super_admin() {
Ok(())
} else {
Err(AppError::Forbidden)
}
}
/// Fails with `Forbidden` unless this user is `user_id` or a super admin.
pub fn require_self_or_super_admin(&self, user_id: i64) -> AppResult<()> {
if self.id == user_id || self.is_super_admin() {
Ok(())
} else {
Err(AppError::Forbidden)
}
}
}
/// The token of `Authorization: Bearer <token>`.
pub fn bearer_token(headers: &HeaderMap) -> Option<&str> {
let value = headers.get(AUTHORIZATION)?.to_str().ok()?;
let (scheme, token) = value.split_once(' ')?;
let token = token.trim();
(scheme.eq_ignore_ascii_case("bearer") && !token.is_empty()).then_some(token)
}