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    /// Session field policies contributed by this registered plugin.
127    fn session_fields(&self) -> indexmap::IndexMap<String, crate::field_policy::FieldConfig> {
128        indexmap::IndexMap::new()
129    }
130
131    fn user_fields(&self) -> crate::field_policy::FieldConfigs {
132        crate::field_policy::FieldConfigs::new()
133    }
134
135    fn account_fields(&self) -> crate::field_policy::FieldConfigs {
136        crate::field_policy::FieldConfigs::new()
137    }
138
139    /// Documentation annotations collected after all plugins initialize.
140    /// Override this hook for custom endpoints and model field policies.
141    fn openapi_metadata(&self, _ctx: &AuthInitContext<S>) -> crate::openapi::PluginOpenApiMetadata {
142        self.static_openapi_metadata()
143    }
144
145    /// Base declarations for documentation without an initialized auth instance.
146    fn static_openapi_metadata(&self) -> crate::openapi::PluginOpenApiMetadata {
147        crate::openapi::annotations::route_metadata(&self.routes())
148    }
149
150    /// Called when the plugin is initialized
151    async fn on_init(&self, _ctx: &mut AuthInitContext<S>) -> AuthResult<()> {
152        Ok(())
153    }
154
155    /// Inspect the original physical HTTP request before routing, body parsing,
156    /// origin validation and endpoint hooks. Disabled paths and transport/rate
157    /// limiting middleware run first. Returning a response stops dispatch and
158    /// later plugin hooks. Server-only endpoint calls do not invoke this hook.
159    async fn on_http_request(
160        &self,
161        _req: &AuthRequest,
162        _ctx: &AuthContext<S>,
163    ) -> AuthResult<Option<AuthResponse>> {
164        Ok(None)
165    }
166
167    /// Inspect or replace the physical request. The default preserves existing
168    /// `on_http_request` implementations. Request-local configuration has already
169    /// been resolved from the incoming request, matching the Source HTTP handler.
170    async fn on_http_request_action(
171        &self,
172        req: &AuthRequest,
173        ctx: &AuthContext<S>,
174    ) -> AuthResult<Option<HttpRequestAction>> {
175        Ok(self
176            .on_http_request(req, ctx)
177            .await?
178            .map(HttpRequestAction::Respond))
179    }
180
181    /// Observe the routed HTTP response before transport middleware (including CORS).
182    /// Hooks run in registration order; the first replacement stops this chain.
183    /// Early HTTP request-hook responses skip this stage. Errors escape dispatch;
184    /// they cannot reverse writes already committed by an endpoint.
185    async fn on_http_response(
186        &self,
187        _req: &AuthRequest,
188        _ctx: &AuthContext<S>,
189        _response: &AuthResponse,
190    ) -> AuthResult<Option<AuthResponse>> {
191        Ok(None)
192    }
193
194    /// Called after route matching and before endpoint dispatch.
195    ///
196    /// Return `Some(BeforeRequestAction::Respond(..))` to short-circuit with a
197    /// response, `Some(BeforeRequestAction::InjectSession { .. })` to attach a
198    /// virtual session (e.g. API-key → session emulation),
199    /// `Some(BeforeRequestAction::ReplaceHeaders { .. })` to transform request
200    /// headers, or `None` to continue endpoint dispatch.
201    async fn before_request(
202        &self,
203        _req: &AuthRequest,
204        _ctx: &AuthContext<S>,
205    ) -> AuthResult<Option<BeforeRequestAction>> {
206        Ok(None)
207    }
208
209    /// Called for each request - return Some(response) to handle, None to pass through
210    async fn on_request(
211        &self,
212        req: &AuthRequest,
213        ctx: &AuthContext<S>,
214    ) -> AuthResult<Option<AuthResponse>>;
215
216    /// Dispatch the resolved HTTP endpoint. Existing plugins return logical
217    /// values through `on_request`; plugins returning a native raw response can
218    /// override this method to bypass the endpoint after-hook pipeline.
219    async fn on_http_endpoint(
220        &self,
221        req: &AuthRequest,
222        ctx: &AuthContext<S>,
223    ) -> AuthResult<Option<HttpEndpointResponse>> {
224        Ok(self
225            .on_request(req, ctx)
226            .await?
227            .map(HttpEndpointResponse::Value))
228    }
229
230    /// Transform a completed response, including redirects and rejections.
231    ///
232    /// Hooks run in plugin registration order with the normalized auth path.
233    /// This supports cookie and header lifecycles that span other plugins.
234    async fn after_request(
235        &self,
236        _req: &AuthRequest,
237        _ctx: &AuthContext<S>,
238        response: AuthResponse,
239    ) -> AuthResult<AuthResponse> {
240        Ok(response)
241    }
242
243    /// Called after a user is created
244    async fn on_user_created(&self, _user: &S::User, _ctx: &AuthContext<S>) -> AuthResult<()> {
245        Ok(())
246    }
247
248    /// Called after a session is created
249    async fn on_session_created(
250        &self,
251        _session: &S::Session,
252        _ctx: &AuthContext<S>,
253    ) -> AuthResult<()> {
254        Ok(())
255    }
256
257    /// Called before a user is deleted
258    async fn on_user_deleted(&self, _user_id: &str, _ctx: &AuthContext<S>) -> AuthResult<()> {
259        Ok(())
260    }
261
262    /// Called before a session is deleted
263    async fn on_session_deleted(
264        &self,
265        _session_token: &str,
266        _ctx: &AuthContext<S>,
267    ) -> AuthResult<()> {
268        Ok(())
269    }
270}
271
272impl<S: AuthSchema> std::fmt::Debug for VerificationEmailOverrideHandle<S> {
273    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
274        f.debug_struct("VerificationEmailOverrideHandle")
275            .finish_non_exhaustive()
276    }
277}