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}