feat(http): implement shared Bearer auth middleware (resolve_from_token, stash Identity in request extensions)

Add src/server/auth.rs with bearer_auth_middleware axum layer that
extracts the Authorization: Bearer header, resolves via
IdentityProvider::resolve_from_token, and stashes Option<Identity> in
request extensions. Shared by HTTP gateway routes and the to_mcp rmcp
service (research §4.4). No token, malformed header, or failed
resolution all yield None (unauthenticated, not an error) — Bearer-only
auth mechanism (ADR-004).

Includes ResolvedIdentity axum extractor reading from extensions, and
wires the middleware into the HttpAdapter router around the
gateway/openapi/mcp routes (excluding the raw /healthz route).
This commit is contained in:
2026-07-01 18:48:00 +00:00
parent a65afb0dfb
commit 36f74dd31b
6 changed files with 328 additions and 6 deletions

View File

@@ -15,6 +15,7 @@ use std::sync::Arc;
use async_trait::async_trait;
use axum::http::StatusCode;
use axum::middleware::from_fn_with_state;
use axum::response::IntoResponse;
use axum::routing::{any, get, post};
use axum::Router;
@@ -28,6 +29,8 @@ use alknet_call::registry::registration::OperationRegistry;
use alknet_core::auth::{AuthContext, IdentityProvider};
use alknet_core::types::{Connection, HandlerError, ProtocolHandler, StreamError};
use super::auth::bearer_auth_middleware;
const ALPN_HTTP1: &[u8] = b"http/1.1";
const ALPN_H2: &[u8] = b"h2";
@@ -123,15 +126,17 @@ impl HttpAdapter {
}
fn build_router(state: RouterState, extra_routes: Option<Router>) -> Router {
let auth_state = Arc::clone(&state.identity_provider);
let default: Router<RouterState> = Router::new()
.route("/search", any(not_implemented))
.route("/schema", any(not_implemented))
.route("/call", any(not_implemented))
.route("/batch", any(not_implemented))
.route("/subscribe", any(not_implemented))
.route("/healthz", get(not_implemented))
.route("/openapi.json", get(not_implemented))
.route("/mcp", post(not_implemented));
.route("/mcp", post(not_implemented))
.route_layer(from_fn_with_state(auth_state.clone(), bearer_auth_middleware))
.route("/healthz", get(not_implemented));
let with_extras = match extra_routes {
Some(extra) => {