Skip to main content

axess_factors/
lib.rs

1#![forbid(unsafe_code)]
2#![deny(missing_docs)]
3//! Factor implementations for axess: password (Argon2id), TOTP (RFC 6238),
4//! HOTP (RFC 4226).
5//!
6//! All comparisons against secret material use [`subtle::ConstantTimeEq`];
7//! all in-memory secrets are wrapped in [`zeroize::Zeroizing`] so the buffer
8//! is wiped on drop. Verification functions clamp digit length and TOTP
9//! drift windows to defensive bounds.
10//!
11//! # Module layout
12//!
13//! - `password`: Argon2id hashing/verification re-exports from
14//!   `password_auth` (`password` feature).
15//! - `hotp`: HOTP per RFC 4226 with SHA-1 / SHA-256 / SHA-512
16//!   variants (`hotp` feature).
17//! - `totp`: TOTP per RFC 6238, secret generation, and `otpauth://`
18//!   URI builder (`totp` feature).
19//!
20//! Each module is feature-gated; `lib.rs` re-exports everything at the
21//! crate root so downstream code can import via either path.
22
23/// Server-issued [`EmailOtpConfig`](email_otp::EmailOtpConfig): the typed
24/// challenge state for the out-of-band email factor (`email_otp` feature).
25#[cfg(feature = "email_otp")]
26pub mod email_otp;
27/// FIDO2/WebAuthn factor data: [`Fido2Config`](fido2::Fido2Config) +
28/// [`Fido2Credential`](fido2::Fido2Credential) +
29/// [`Fido2Options`](fido2::Fido2Options) +
30/// re-exports of `webauthn-rs` primitives (`fido2` feature).
31#[cfg(feature = "fido2")]
32pub mod fido2;
33/// Always-on stub when the `fido2` feature is off; provides the typed
34/// [`Fido2Config`](fido2_stub::Fido2Config) so adopters' admin tooling
35/// can round-trip factor rows that include FIDO2 data without pulling
36/// `webauthn-rs`. Re-exported below as `fido2` so the public path stays
37/// the same across feature configurations.
38#[cfg(not(feature = "fido2"))]
39#[path = "fido2.rs"]
40pub mod fido2_stub;
41#[cfg(not(feature = "fido2"))]
42pub use fido2_stub as fido2;
43/// Bearer JWT workload-auth middleware. Verifies inbound
44/// `Authorization: Bearer <jwt>` headers against configured issuers and
45/// JWKS, inserts a [`WorkloadIdentity`](bearer::WorkloadIdentity) into
46/// axum request extensions (`bearer` feature).
47#[cfg(feature = "bearer")]
48pub mod bearer;
49/// Workload-identity federation. The generic
50/// [`federation::workload::WorkloadResolver`] verifies any JWT-bearer
51/// workload token (GitHub Actions OIDC, Kubernetes SA, GitLab CI OIDC,
52/// Okta, Azure AD, Auth0, axess `LocalIdP`, …) via a caller-supplied
53/// claim parser + mapping closure. Gated on the `jwt` feature.
54#[cfg(feature = "jwt")]
55pub mod federation;
56#[cfg(feature = "hotp")]
57pub mod hotp;
58/// JWT validation + `JwtVerifier` builder + SPIFFE JWT-SVID resolver
59/// (`jwt` / `jwt-svid` features). Adopters performing JWT verification
60/// (workload identity, federated OIDC checks, custom logout flows) share
61/// the same hardened parse-and-verify paths internally used by OAuth +
62/// back-channel logout.
63#[cfg(feature = "jwt")]
64pub mod jwt;
65/// LDAP simple-bind verifier: [`LdapProvider`](ldap::LdapProvider)
66/// trait + [`LdapProviderConfig`](ldap::LdapProviderConfig) production
67/// impl (`ldap3`-backed) + [`MockLdapProvider`](ldap::MockLdapProvider)
68/// (`ldap` feature). HealthCheck integration lives in axess-core as
69/// an extension impl.
70#[cfg(feature = "ldap")]
71pub mod ldap;
72/// mTLS SPIFFE X509-SVID resolver. Extracts a
73/// [`Principal::Workload`](axess_identity::Principal::Workload) from
74/// the leaf client certificate in a rustls peer-cert chain (`mtls`
75/// feature).
76#[cfg(feature = "mtls")]
77pub mod mtls;
78/// OAuth 2.0 / OIDC ceremony surface: `OAuthProvider` trait,
79/// `DefaultOAuthProvider` (openidconnect-backed), builder, device-flow,
80/// FAPI 2.0 DPoP (`oauth` / `fapi` features).
81#[cfg(feature = "oauth")]
82pub mod oauth;
83/// OIDC discovery + JWKS retrieval / rotation primitives (`oidc`
84/// feature). Shared between the full OAuth ceremony surface and adopters
85/// that verify JWTs without taking it.
86#[cfg(feature = "oidc")]
87pub mod oidc;
88/// [`OtpAlgorithm`](otp_algorithm::OtpAlgorithm): the storage-shape HMAC
89/// algorithm tag shared by [`TotpConfig`](totp::TotpConfig) and
90/// [`HotpConfig`](hotp::HotpConfig). Gated on `totp` OR `hotp` because
91/// it has no consumer outside the two OTP configs.
92#[cfg(any(feature = "totp", feature = "hotp"))]
93pub mod otp_algorithm;
94/// Outbound OAuth client: `client_credentials` grant with optional
95/// `private_key_jwt` client assertion (RFC 7523) (`outbound-oauth`
96/// feature).
97#[cfg(feature = "outbound-oauth")]
98pub mod outbound_oauth_client;
99#[cfg(feature = "password")]
100pub mod password;
101/// PKCE (RFC 7636) `code_verifier` predicate. Always-on (no feature
102/// gate) because it's a pure-spec character-class check with no
103/// protocol deps.
104pub mod pkce;
105/// [`ZeroizedString`](secret::ZeroizedString): secret-string primitive
106/// shared across factor configs and other credential-bearing types.
107/// Always on (no feature gate) because the orchestrator's OAuth token
108/// storage and delegated-credential storage hold it without the
109/// `password`/`totp`/`hotp` features.
110pub mod secret;
111/// Plain-OAuth-2.0 user login ("social login") for IdPs that don't
112/// support OIDC (GitHub user login, Twitter/X, Discord, Reddit,
113/// Spotify, …). Off by default. **Weaker security model** than the
114/// OIDC path under [`oauth`]: claims come from a TLS-trusted userinfo
115/// endpoint, not from a signed assertion. See module docs for the
116/// full delta and when to reach for this.
117#[cfg(feature = "social")]
118pub mod social;
119#[cfg(feature = "totp")]
120pub mod totp;
121
122#[cfg(feature = "password")]
123pub use password::{PasswordConfig, PasswordRules, generate_password_hash, verify_password};
124
125#[cfg(feature = "hotp")]
126pub use hotp::{HOTP_LENGTH, HotpAlgorithm, HotpConfig, verify_hotp};
127
128#[cfg(feature = "totp")]
129pub use totp::{
130    TOTP, TOTP_LENGTH, TOTP_PERIOD, TotpAlgorithm, TotpConfig, TotpVerifyParams, build_totp_uri,
131    generate_totp_secret, verify_totp,
132};
133
134#[cfg(feature = "email_otp")]
135pub use email_otp::EmailOtpConfig;
136
137pub use fido2::Fido2Config;
138#[cfg(feature = "fido2")]
139pub use fido2::{
140    AuthenticationResult, AuthenticatorAttachment, CredentialID, DefaultFido2Provider,
141    Fido2Credential, Fido2Options, Fido2Provider, MockFido2Provider,
142};
143
144#[cfg(feature = "ldap")]
145pub use ldap::{
146    LdapBindResult, LdapError, LdapGroupSearch, LdapProvider, LdapProviderConfig, MockLdapProvider,
147};
148
149#[cfg(feature = "mtls")]
150pub use mtls::{MtlsError, MtlsResolver, PeerCertChain, SpiffeIdComponents, peek_spiffe};
151
152#[cfg(feature = "oidc")]
153pub use oidc::{Discovery, DiscoveryDocument, JwksCache, MIN_JWKS_REFRESH_INTERVAL, OidcError};
154
155#[cfg(feature = "bearer")]
156pub use bearer::{
157    BearerConfig, BearerError, BearerIssuerConfig, BearerTokenLayer, BearerTokenService,
158    JwtVerificationError, WorkloadIdentity, validate_bearer_token,
159};
160
161#[cfg(feature = "outbound-oauth")]
162pub use outbound_oauth_client::{ClientAuthMethod, OAuthClientError, OutboundOAuthClient};
163
164#[cfg(any(feature = "totp", feature = "hotp"))]
165pub use otp_algorithm::OtpAlgorithm;
166
167pub use secret::ZeroizedString;
168
169// ── Shared constants ────────────────────────────────────────────────────────
170
171/// Maximum HOTP digit length accepted by verification.
172///
173/// Axess's custom HOTP impl computes truncation modulo `10^digits` in `u64`,
174/// so the full 10-digit range is usable without overflow. Standard codes are
175/// 6–8 digits per RFC 4226; the upper bound exists to cap CPU/memory cost
176/// from a corrupted or malicious factor config.
177#[cfg(feature = "hotp")]
178pub(crate) const MAX_HOTP_DIGITS: usize = 10;
179
180/// Maximum TOTP digit length accepted by verification.
181///
182/// Lower than [`MAX_HOTP_DIGITS`] because the underlying `totp-rs` crate
183/// enforces RFC 6238 §1.2's 6..=8 digit range in `TOTP::new`; values above
184/// 8 are rejected by the upstream library before our own guard runs.
185/// Tracking the real limit here keeps the guard meaningful and prevents
186/// silent rejection from a future caller that assumes the constant is
187/// authoritative.
188#[cfg(feature = "totp")]
189pub(crate) const MAX_TOTP_DIGITS: usize = 8;
190
191/// Minimum acceptable shared-secret length **after** decoding, in bytes.
192///
193/// RFC 4226 §4 R6: "The length of the shared secret MUST be at least 128
194/// bits. This document RECOMMENDs a shared secret length of 160 bits."
195/// Verification refuses any secret shorter than this; a buggy enrollment
196/// path that persisted a 32-bit secret destroys brute-force resistance and
197/// must not be honoured silently. The library's secret generator already
198/// produces 20 bytes (160 bits), so this only fires on user error or a
199/// migrated legacy config.
200#[cfg(any(feature = "hotp", feature = "totp"))]
201pub(crate) const MIN_OTP_SECRET_BYTES: usize = 16;