alibi_core/plugin/contracts.rs
1use super::*;
2/// A plugin override for delivery of the core email-verification challenge.
3#[async_trait]
4pub trait VerificationEmailOverride<S: AuthSchema>: Send + Sync {
5 async fn send(
6 &self,
7 user: &crate::wire::UserView,
8 request: Option<&AuthRequest>,
9 ctx: &AuthContext<S>,
10 ) -> AuthResult<()>;
11
12 async fn send_in_transaction(
13 &self,
14 user: &crate::wire::UserView,
15 request: Option<&AuthRequest>,
16 ctx: &AuthContext<S>,
17 _transaction: &dyn crate::store::AuthTransaction<S>,
18 ) -> AuthResult<()> {
19 self.send(user, request, ctx).await
20 }
21}
22
23pub struct VerificationEmailOverrideHandle<S: AuthSchema>(
24 pub Arc<dyn VerificationEmailOverride<S>>,
25);
26
27/// Action returned by [`AuthPlugin::before_request`].
28#[derive(Debug)]
29#[expect(
30 clippy::large_enum_variant,
31 reason = "Preserve public InjectSession construction as session views gain configured output policies"
32)]
33pub enum BeforeRequestAction {
34 /// Short-circuit with this response (e.g. return session JSON).
35 Respond(AuthResponse),
36 /// Inject a virtual session so downstream handlers see it as authenticated.
37 InjectSession { session: crate::wire::SessionView },
38 /// Replace headers for subsequent hooks, endpoint dispatch and response hooks.
39 /// Authentication adapters can establish a verified signed cookie while
40 /// retaining normal session lookup, expiry and revocation checks.
41 ReplaceHeaders { headers: HashMap<String, String> },
42}
43
44/// Physical HTTP hook control flow, before route selection and endpoint hooks.
45#[derive(Debug)]
46pub enum HttpRequestAction {
47 /// Finish transport processing without endpoint or HTTP response hooks.
48 Respond(AuthResponse),
49 /// Replace physical input for later HTTP hooks and route selection.
50 /// Dispatch discards session state, extensions and queued headers on this value.
51 ReplaceRequest(Box<AuthRequest>),
52}
53
54/// Endpoint output distinguishing a logical value from a native raw response.
55/// Raw responses bypass completed endpoint hooks, while HTTP response hooks
56/// still observe them, matching a Source endpoint returning a `Response`.
57#[derive(Debug)]
58pub enum HttpEndpointResponse {
59 Value(AuthResponse),
60 Raw(AuthResponse),
61}
62
63/// Plugin trait that all authentication plugins must implement.
64///
65#[async_trait]
66pub trait AuthPlugin<S: AuthSchema>: Send + Sync {
67 /// Plugin name - should be unique
68 fn name(&self) -> &'static str;
69
70 /// Routes that this plugin handles
71 fn routes(&self) -> Vec<AuthRoute>;
72
73 /// Ordered rate-limit policies supplied by this installed plugin.
74 fn rate_limits(&self) -> Vec<crate::middleware::rate_limit::PluginRateLimit> {
75 Vec::new()
76 }
77
78 /// Trusted operations available through host dispatch, independently of HTTP visibility.
79 fn server_endpoints(&self) -> Vec<crate::endpoint::EndpointDefinition> {
80 Vec::new()
81 }
82
83 /// Installed logical-call hooks, in this plugin's registration order.
84 fn endpoint_hooks(&self) -> Vec<&dyn crate::endpoint::EndpointHook<S>> {
85 Vec::new()
86 }
87
88 /// Validate actual input after all before-hook patches have been applied.
89 /// # Errors
90 /// Returns an intentional validation error, which completed hooks can observe.
91 fn validate_endpoint(
92 &self,
93 call: &crate::endpoint::EndpointCall,
94 _ctx: &AuthContext<S>,
95 ) -> AuthResult<crate::endpoint::EndpointInput> {
96 Ok(crate::endpoint::EndpointInput {
97 body: call.body().cloned(),
98 query: call.query().cloned(),
99 })
100 }
101
102 /// Execute a registered operation with validated handler-phase context.
103 /// # Errors
104 /// Returns the operation's actual API or application failure.
105 async fn on_endpoint(
106 &self,
107 _call: &crate::endpoint::EndpointCall,
108 _ctx: &AuthContext<S>,
109 ) -> AuthResult<crate::endpoint::EndpointResponse> {
110 Err(AuthError::not_implemented(
111 "This plugin does not implement trusted endpoint calls",
112 ))
113 }
114
115 /// Media types accepted before request hooks and endpoint dispatch. An empty
116 /// list disables the media restriction for an application-owned endpoint.
117 fn allowed_media_types(&self, _route: &AuthRoute) -> Vec<&'static str> {
118 vec!["application/json"]
119 }
120
121 /// Session field policies contributed by this registered plugin.
122 fn session_fields(&self) -> indexmap::IndexMap<String, crate::field_policy::FieldConfig> {
123 indexmap::IndexMap::new()
124 }
125
126 fn user_fields(&self) -> crate::field_policy::FieldConfigs {
127 crate::field_policy::FieldConfigs::new()
128 }
129
130 fn account_fields(&self) -> crate::field_policy::FieldConfigs {
131 crate::field_policy::FieldConfigs::new()
132 }
133
134 /// Documentation annotations collected after all plugins initialize.
135 /// Override this hook for custom endpoints and model field policies.
136 fn openapi_metadata(&self, _ctx: &AuthInitContext<S>) -> crate::openapi::PluginOpenApiMetadata {
137 self.static_openapi_metadata()
138 }
139
140 /// Base declarations for documentation without an initialized auth instance.
141 fn static_openapi_metadata(&self) -> crate::openapi::PluginOpenApiMetadata {
142 crate::openapi::annotations::route_metadata(&self.routes())
143 }
144
145 /// Called when the plugin is initialized
146 async fn on_init(&self, _ctx: &mut AuthInitContext<S>) -> AuthResult<()> {
147 Ok(())
148 }
149
150 /// Inspect the original physical HTTP request before routing, body parsing,
151 /// origin validation and endpoint hooks. Disabled paths and transport/rate
152 /// limiting middleware run first. Returning a response stops dispatch and
153 /// later plugin hooks. Server-only endpoint calls do not invoke this hook.
154 async fn on_http_request(
155 &self,
156 _req: &AuthRequest,
157 _ctx: &AuthContext<S>,
158 ) -> AuthResult<Option<AuthResponse>> {
159 Ok(None)
160 }
161
162 /// Inspect or replace the physical request. The default preserves existing
163 /// `on_http_request` implementations. Request-local configuration has already
164 /// been resolved from the incoming request, matching the Source HTTP handler.
165 async fn on_http_request_action(
166 &self,
167 req: &AuthRequest,
168 ctx: &AuthContext<S>,
169 ) -> AuthResult<Option<HttpRequestAction>> {
170 Ok(self
171 .on_http_request(req, ctx)
172 .await?
173 .map(HttpRequestAction::Respond))
174 }
175
176 /// Observe the routed HTTP response before transport middleware (including CORS).
177 /// Hooks run in registration order; the first replacement stops this chain.
178 /// Early HTTP request-hook responses skip this stage. Errors escape dispatch;
179 /// they cannot reverse writes already committed by an endpoint.
180 async fn on_http_response(
181 &self,
182 _req: &AuthRequest,
183 _ctx: &AuthContext<S>,
184 _response: &AuthResponse,
185 ) -> AuthResult<Option<AuthResponse>> {
186 Ok(None)
187 }
188
189 /// Called after route matching and before endpoint dispatch.
190 ///
191 /// Return `Some(BeforeRequestAction::Respond(..))` to short-circuit with a
192 /// response, `Some(BeforeRequestAction::InjectSession { .. })` to attach a
193 /// virtual session (e.g. API-key → session emulation),
194 /// `Some(BeforeRequestAction::ReplaceHeaders { .. })` to transform request
195 /// headers, or `None` to continue endpoint dispatch.
196 async fn before_request(
197 &self,
198 _req: &AuthRequest,
199 _ctx: &AuthContext<S>,
200 ) -> AuthResult<Option<BeforeRequestAction>> {
201 Ok(None)
202 }
203
204 /// Called for each request - return Some(response) to handle, None to pass through
205 async fn on_request(
206 &self,
207 req: &AuthRequest,
208 ctx: &AuthContext<S>,
209 ) -> AuthResult<Option<AuthResponse>>;
210
211 /// Dispatch the resolved HTTP endpoint. Existing plugins return logical
212 /// values through `on_request`; plugins returning a native raw response can
213 /// override this method to bypass the endpoint after-hook pipeline.
214 async fn on_http_endpoint(
215 &self,
216 req: &AuthRequest,
217 ctx: &AuthContext<S>,
218 ) -> AuthResult<Option<HttpEndpointResponse>> {
219 Ok(self
220 .on_request(req, ctx)
221 .await?
222 .map(HttpEndpointResponse::Value))
223 }
224
225 /// Transform a completed response, including redirects and rejections.
226 ///
227 /// Hooks run in plugin registration order with the normalized auth path.
228 /// This supports cookie and header lifecycles that span other plugins.
229 async fn after_request(
230 &self,
231 _req: &AuthRequest,
232 _ctx: &AuthContext<S>,
233 response: AuthResponse,
234 ) -> AuthResult<AuthResponse> {
235 Ok(response)
236 }
237
238 /// Called after a user is created
239 async fn on_user_created(&self, _user: &S::User, _ctx: &AuthContext<S>) -> AuthResult<()> {
240 Ok(())
241 }
242
243 /// Called after a session is created
244 async fn on_session_created(
245 &self,
246 _session: &S::Session,
247 _ctx: &AuthContext<S>,
248 ) -> AuthResult<()> {
249 Ok(())
250 }
251
252 /// Called before a user is deleted
253 async fn on_user_deleted(&self, _user_id: &str, _ctx: &AuthContext<S>) -> AuthResult<()> {
254 Ok(())
255 }
256
257 /// Called before a session is deleted
258 async fn on_session_deleted(
259 &self,
260 _session_token: &str,
261 _ctx: &AuthContext<S>,
262 ) -> AuthResult<()> {
263 Ok(())
264 }
265}
266
267impl<S: AuthSchema> std::fmt::Debug for VerificationEmailOverrideHandle<S> {
268 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
269 f.debug_struct("VerificationEmailOverrideHandle")
270 .finish_non_exhaustive()
271 }
272}