Skip to main content

alibi_core/plugin/
contracts.rs

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