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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
//! Auth capability trait. See `plan/ecosystem/02-capabilities.md` (Auth section).
//!
//! An `AuthPlugin` resolves a request to an authenticated principal and
//! (optionally) drives a login/logout flow. The shape is deliberately
//! vendor-neutral: no JWT-specific fields (`iss`, `aud`, `alg`), no OAuth
//! specifics (`redirect_uri`, `scopes`, `nonce`). Provider-specific knobs
//! live in the plugin's own config section, not on the trait.
//!
//! # Design notes (E1, not yet stabilised)
//!
//! **Sync, not async.** Every other plugin trait in this crate is sync
//! (`middleware.rs`, `cache.rs`, `lifecycle.rs`, `transform.rs`). The plan
//! sketch in `02-capabilities.md` uses `async fn`, but matching the existing
//! convention here keeps the WASM/QuickJS/nsjail ABI consistent (plugins
//! compiled to WASM cannot expose native async at the host boundary) and
//! avoids pulling `async-trait` into a dependency-minimal leaf crate.
//! Backends that need async I/O (OAuth callbacks, JWKS fetches) can use
//! `tokio::runtime::Handle::current().block_on(..)` the same way
//! `bext-server`'s JWKS fetcher already does.
//!
//! **Errors as `String`.** Matches every other trait in this crate. An
//! associated error type would be the first in the API surface and make
//! cross-capability composition (e.g. an auth plugin calling a session
//! plugin) awkward.
//!
//! **Session issuance is delegated.** The `Session` capability owns session
//! storage. An `AuthPlugin` declares `requires_capabilities = [Session]`
//! in its manifest; at runtime the host wires a `SessionId` through
//! `resolve` and `logout` rather than making this trait carry session
//! state. This keeps the two capabilities independent as required by
//! `00-architecture.md` principle 6.
//!
//! **Login-flow methods are optional.** Validation-only backends (JWT,
//! opaque bearer) implement `resolve` and leave `begin_login`,
//! `complete_login`, `logout` as default-no-ops. Full OAuth / magic-link /
//! passkey backends override all four. This lets the existing
//! `bext-server::middleware::auth` JWT middleware port cleanly without
//! inventing stub flows it cannot service.
use HashMap;
/// What kind of auth flow a provider implements. Runtime uses this to
/// decide which HTTP routes to mount (e.g. only OAuth needs a
/// `/auth/callback` route) and to surface provider shape in admin UI.
/// Per-request context passed to every `AuthPlugin` method.
///
/// Pure data, no framework types — matches `middleware::RequestContext`
/// conventions so plugins can live behind the same sandbox boundary.
/// Mirrors the JWT middleware's token-extraction sources (Authorization
/// header, cookies) without hardcoding either.
/// An authenticated principal. Vendor-neutral: every concept here
/// (subject, tenancy, role, permissions, free-form attributes) maps
/// cleanly to JWT claims, OAuth profiles, LDAP records, and
/// custom auth stores.
///
/// `attributes` is the escape hatch for provider-specific data
/// (OAuth ID-token claims, group memberships, etc.) so the trait
/// itself never grows vendor-specific fields.
/// Parameters a caller passes when initiating a login flow.
///
/// Intentionally minimal. Providers that need extra inputs (username,
/// OAuth provider hint, magic-link address) read them from `inputs`
/// as a flat `HashMap<String, String>` — same escape-hatch convention
/// used by `AuthUser::attributes`.
/// Result of `begin_login` — what the runtime should do to continue
/// the login flow.
/// Data a caller hands to `complete_login` — whatever the upstream
/// provider sends back in its callback (OAuth code+state, magic-link
/// token, passkey assertion, etc.).
/// An auth provider plugin.
///
/// Lifecycle (validation-only flow):
/// 1. Every incoming request: host calls `resolve(ctx)` → `Some(user)` or `None`.
/// 2. On logout: host calls `logout(ctx)` (default no-op for stateless providers).
///
/// Lifecycle (interactive flow):
/// 1. User hits a protected route, not yet authed: runtime calls
/// `begin_login(ctx, params)` and applies the returned `LoginAction`.
/// 2. User returns via the callback route: runtime calls
/// `complete_login(ctx, callback)` and — on success — asks the
/// active `Session` provider to issue a session keyed to `user.subject`.
/// 3. Subsequent requests: `resolve(ctx)` reads `ctx.session_id`, looks
/// the session up via the `Session` capability, and returns the user.
/// 4. On logout: `logout(ctx)` and the `Session` capability deletes.
///
/// The plugin never issues sessions directly — that's the `Session`
/// capability's job (see `session.rs`).