1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
//! Pluggable authentication for HTTP routes.
//!
//! Implement [`AuthProvider`] and register it via
//! [`HttpRouter::set_auth`](crate::HttpRouter::set_auth) to add authentication
//! middleware to all routes served by [`AppPlugin`](crate::AppPlugin).
//!
//! # Example
//!
//! ```no_run
//! use polaris_app::{AuthProvider, auth::AuthRejection};
//! use axum::response::IntoResponse;
//! use http::StatusCode;
//!
//! /// Constant-time equality on two byte slices.
//! ///
//! /// Production implementations should reach for a vetted constant-time
//! /// comparator (`subtle::ConstantTimeEq`, `ring::constant_time`,
//! /// `openssl::memcmp::eq`) rather than `==`. A short-circuiting `==`
//! /// leaks the length of the matching prefix through timing, which is
//! /// enough for a remote attacker to recover a bearer token byte by byte.
//! fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
//! if a.len() != b.len() {
//! return false;
//! }
//! let mut diff = 0u8;
//! for (x, y) in a.iter().zip(b.iter()) {
//! diff |= x ^ y;
//! }
//! diff == 0
//! }
//!
//! #[derive(Debug)]
//! struct BearerAuth {
//! expected_header: String,
//! }
//!
//! impl BearerAuth {
//! fn new(token: &str) -> Self {
//! Self { expected_header: format!("Bearer {token}") }
//! }
//! }
//!
//! impl AuthProvider for BearerAuth {
//! fn authenticate(&self, parts: &http::request::Parts) -> Result<(), AuthRejection> {
//! let header = parts
//! .headers
//! .get(http::header::AUTHORIZATION)
//! .and_then(|v| v.to_str().ok())
//! .unwrap_or("");
//!
//! if constant_time_eq(header.as_bytes(), self.expected_header.as_bytes()) {
//! Ok(())
//! } else {
//! Err(Box::new(StatusCode::UNAUTHORIZED.into_response()))
//! }
//! }
//! }
//! ```
/// The rejection type returned by [`AuthProvider::authenticate`].
///
/// Wraps an [`axum::response::Response`] in a `Box` to keep the `Result`
/// return value small on the stack.
pub type AuthRejection = ;
/// Trait for pluggable request authentication.
///
/// Plugins implement this trait and register it via
/// [`HttpRouter::set_auth`](crate::HttpRouter::set_auth) during `build()`.
/// [`AppPlugin`](crate::AppPlugin) applies the provider as middleware in
/// `ready()`, rejecting unauthenticated requests before they reach route
/// handlers.
///
/// Authentication is intentionally synchronous — most schemes (bearer tokens,
/// API keys, HMAC signatures) only need to inspect request headers. If your
/// scheme requires async I/O (e.g., remote token introspection), perform the
/// lookup in a Tower layer registered directly via
/// [`HttpRouter::add_routes`](crate::HttpRouter::add_routes) instead.
///
/// # Public routes
///
/// For path-based exemptions (health checks, login pages, static assets),
/// prefer [`AppConfig::with_public_path`](crate::AppConfig::with_public_path)
/// and [`AppConfig::with_public_prefix`](crate::AppConfig::with_public_prefix)
/// over hand-rolling matching inside the trait. The middleware consults the
/// allowlist before invoking `authenticate`, keeping path-based routing
/// decisions out of credential-validation logic.