Skip to main content

assay_auth/
lib.rs

1//! Auth layer for `assay-engine` — a self-hosted, single-binary
2//! **Ory replacement** for `assay-engine v0.2.0`.
3//!
4//! `assay-auth` packages every primitive a serious identity provider
5//! needs into one crate that composes into [`crate::AuthCtx`] and is
6//! mounted under `/auth` by the engine:
7//!
8//! | Module                 | Replaces                  | Purpose                                                     |
9//! | ---------------------- | ------------------------- | ----------------------------------------------------------- |
10//! | [`session`]            | Ory Kratos (sessions)     | Cookie + CSRF session manager (Argon2id-backed)             |
11//! | [`password`]           | Ory Kratos (passwords)    | Argon2id PHC strings, peppered hashing                      |
12//! | [`jwt`]                | Hydra (JWT)               | RS256 issue/verify with rotated JWKS                        |
13//! | [`oidc`]               | Kratos (federation)       | OIDC **client** — log in via Google/Apple/GitHub/upstream   |
14//! | [`oidc_provider`]      | Ory Hydra                 | Full OIDC **provider** — `/authorize`, `/token`, `/userinfo`, `/.well-known/*`, RFC 7009 revoke, RFC 7662 introspect, back-channel logout |
15//! | [`passkey`]            | Kratos (WebAuthn)         | `webauthn-rs`-backed passkey register + auth ceremonies     |
16//! | [`zanzibar`]           | Ory Keto / SpiceDB        | ReBAC tuples + recursive-CTE walk on PG18 + SQLite          |
17//! | [`biscuit`]            | (Ory has nothing)         | Datalog-attenuable capability tokens — **always-on**        |
18//! | [`store`]              | —                         | `UserStore` / `SessionStore` traits + PG / SQLite backends  |
19//! | [`admin`]              | Ory Console (HTTP API)    | Cross-cutting admin endpoints (users, sessions, Zanzibar, …)|
20//!
21//! ## Why use `assay-auth` instead of Ory?
22//!
23//! - **One static binary** (`assay-engine`, ~9 MB stripped) replaces a
24//!   stack of Kratos + Hydra + Keto + Oathkeeper containers. Same
25//!   features, ~50× less RAM and one process to ship/log/restart.
26//! - **Backend symmetry.** PG18 + SQLite are both first-class via
27//!   feature flags. SQLite means a self-hosted single-tenant deployment
28//!   needs no database server at all — unique vs Ory.
29//! - **Biscuit out of the box.** Datalog-attenuable capability tokens
30//!   that callers can scope down further without a server round-trip.
31//!   Ory has nothing equivalent; this is a real differentiator.
32//! - **Workflow + auth share storage.** Atomic transactions across
33//!   `auth.users` ⇄ `workflow.workflows` (cross-schema FKs on PG, both
34//!   attachments on SQLite) — signups can mint workflow records in one
35//!   transaction. Splitting Ory + Temporal forces 2-phase commit.
36//! - **Lua-scriptable.** Every auth surface is reachable from the
37//!   `assay.auth` Lua stdlib module — operators can build login,
38//!   admin, and federation flows in scripts that the runtime binary
39//!   ships with.
40//!
41//! ## Getting started
42//!
43//! Compose `AuthCtx` into your axum state via `axum::extract::FromRef`
44//! (the engine binary's `EngineState<S>` is the canonical recipe — see
45//! [`assay_engine`] for the wiring). Out-of-the-box you'll need a
46//! [`store::UserStore`] + [`store::SessionStore`]; the
47//! [`store::PostgresUserStore`] / [`store::SqliteUserStore`] /
48//! [`store::PostgresSessionStore`] / [`store::SqliteSessionStore`]
49//! impls cover both backends.
50//!
51//! ```no_run
52//! # async fn build() -> anyhow::Result<()> {
53//! # let pool = sqlx::SqlitePool::connect("sqlite::memory:").await?;
54//! use std::sync::Arc;
55//! use assay_auth::AuthCtx;
56//! use assay_auth::store::{SqliteUserStore, SqliteSessionStore};
57//!
58//! let users = SqliteUserStore::new(pool.clone()).into_dyn();
59//! let sessions = SqliteSessionStore::new(pool.clone()).into_dyn();
60//! let ctx = AuthCtx::new(users, sessions);
61//! // ctx is now ready to be plugged into your Router via FromRef.
62//! # Ok(()) }
63//! ```
64//!
65//! For the full deployment shape (issuer, JWKS rotation, OIDC provider
66//! discovery, biscuit root key bootstrap, passkey RP setup, Zanzibar
67//! store) lean on `assay_engine::run` — it builds an `AuthCtx` from
68//! `engine.toml`, runs the auth migration, and serves everything on one
69//! port.
70//!
71//! ## Storage model
72//!
73//! All auth tables live in the `auth` schema (PG) or attached `auth`
74//! database (SQLite, default `./data/auth.db`). The migration runner
75//! ([`schema::migrate_postgres`] / [`schema::migrate_sqlite`]) records
76//! each applied version in `engine.migrations` under `module = 'auth'`,
77//! keyed by [`MIGRATION_VERSION`]. Migrations are idempotent — every
78//! `CREATE` uses `IF NOT EXISTS`.
79//!
80//! ## Feature flags
81//!
82//! The default feature `auth` pulls in every module. Slim builds can opt
83//! a la carte — see the per-module `#[cfg(feature = "...")]` gates
84//! below. `backend-postgres` and `backend-sqlite` are independent and
85//! both default-on; downstream binaries pick the one(s) they need.
86//!
87//! ## Phase trail
88//!
89//! Module boundaries and per-module rationale live in plan 11. v0.2.0
90//! alignment (Ory-replacement scope, biscuit-built-in posture, schema
91//! layout) lives in plan 12c §"v0.2.0 alignment".
92
93pub mod error;
94
95pub mod admin;
96pub mod biscuit;
97pub mod ctx;
98pub mod gate;
99pub mod router;
100pub mod schema;
101pub mod state;
102pub mod store;
103
104#[cfg(feature = "auth-session")]
105pub mod session;
106
107#[cfg(feature = "auth-password")]
108pub mod password;
109
110#[cfg(feature = "auth-jwt")]
111pub mod jwt;
112
113/// External JWT issuer pass-through validation (v0.3.2). Trust JWTs
114/// minted by an upstream OIDC provider (e.g. Hydra) without managing
115/// engine-side users. Configured via `[[auth.external_issuers]]` in
116/// `engine.toml`. See module-level docs for the why and how.
117#[cfg(feature = "auth-jwt")]
118pub mod external_jwt;
119
120#[cfg(feature = "auth-oidc")]
121pub mod oidc;
122
123#[cfg(feature = "auth-oidc-provider")]
124pub mod oidc_provider;
125
126#[cfg(feature = "auth-passkey")]
127pub mod passkey;
128
129#[cfg(feature = "auth-zanzibar")]
130pub mod zanzibar;
131
132pub use ctx::AuthCtx;
133pub use error::{Error, Result};
134pub use gate::{Caller, CallerSource};
135pub use router::{engine_auth_router, oidc_spec_router, router};
136pub use schema::{MIGRATION_VERSION, MODULE_NAME};
137
138/// Stable module name registered in `engine.modules` and used as the
139/// schema/attach name on both backends. Engine boot inserts a row with
140/// `name = MODULE_NAME` when `--enable=auth` (or equivalent runtime
141/// signal) flips this module on.
142pub const fn module_name() -> &'static str {
143    MODULE_NAME
144}