Skip to main content

aa_auth/
gate.rs

1//! Router-level authentication gate (AAASM-3125).
2//!
3//! Historically every protected handler had to opt in to authentication by
4//! declaring an `AuthenticatedCaller` / `RequireScope` extractor. Handlers
5//! that forgot to do so (e.g. the alert-rule CRUD endpoints, AAASM-3129)
6//! silently shipped unauthenticated. This module provides a single
7//! deny-by-default [`require_authentication`] middleware that is applied as a
8//! `route_layer` over the protected sub-router in the consuming service's
9//! router, so a new route is authenticated unless it is explicitly mounted on
10//! the public router.
11//!
12//! The gate reuses the existing [`AuthenticatedCaller`] extractor, so it
13//! honours `AuthMode::Off` (bypass) and the API-key / JWT validation and
14//! per-key rate-limit logic exactly as the per-handler extractors do.
15
16use axum::extract::{FromRequestParts, Request};
17use axum::middleware::Next;
18use axum::response::{IntoResponse, Response};
19use http::request::Parts;
20
21use crate::AuthenticatedCaller;
22
23/// Deny-by-default authentication middleware.
24///
25/// Resolves [`AuthenticatedCaller`] from the request parts. On success the
26/// request proceeds to the inner service; on failure the [`AuthError`] is
27/// rendered (401 / 403 / 429) and the inner handler is never reached.
28///
29/// [`AuthError`]: crate::AuthError
30pub async fn require_authentication(request: Request, next: Next) -> Response {
31    let (mut parts, body): (Parts, _) = request.into_parts();
32
33    // `()` is a valid `S: Send + Sync` state for the extractor — all auth
34    // inputs come from request extensions, not router state.
35    match AuthenticatedCaller::from_request_parts(&mut parts, &()).await {
36        Ok(_caller) => {
37            let request = Request::from_parts(parts, body);
38            next.run(request).await
39        }
40        Err(rejection) => rejection.into_response(),
41    }
42}