1use better_auth_core::{
4 AuthContext, AuthInitContext, AuthPlugin, AuthRequest, AuthResponse, AuthResult, AuthRoute,
5 AuthSchema, BeforeRequestAction, FieldValue,
6};
7mod owned;
8pub use owned::OwnedEndpointContext;
9
10use serde_json::Value;
11use std::sync::Arc;
12
13pub struct EndpointContext<'a, S: AuthSchema> {
15 input_request: Option<&'a AuthRequest>,
16 pub request: Option<&'a AuthRequest>,
18 pub path: Option<&'a str>,
20 pub params: std::collections::HashMap<String, String>,
22 pub body: FieldValue,
24 pub auth: &'a AuthContext<S>,
26 pub transaction: Option<&'a dyn better_auth_core::store::AuthTransaction<S>>,
28 pub session: Option<(
30 better_auth_core::wire::UserView,
31 better_auth_core::wire::SessionView,
32 )>,
33 pub response: Option<&'a AuthResponse>,
35}
36impl<'a, S: AuthSchema> EndpointContext<'a, S> {
37 pub fn test(&'a self) -> AuthResult<super::test_utils::TestUtilsApi<'a, S>> {
39 super::test_utils::TestUtilsApi::from_endpoint(self)
40 }
41
42 pub fn headers(&self) -> Option<&std::collections::HashMap<String, String>> {
44 self.input_request.and_then(AuthRequest::endpoint_headers)
45 }
46
47 pub fn query(&self) -> Option<&Value> {
49 self.input_request
50 .and_then(|request| request.query.as_ref())
51 }
52
53 pub fn input_request(&self) -> Option<&'a AuthRequest> {
55 self.input_request
56 }
57
58 pub fn set_header(&self, name: &str, value: impl Into<String>) -> AuthResult<()> {
60 self.input_request
61 .ok_or_else(|| {
62 better_auth_core::AuthError::internal("Endpoint response is unavailable")
63 })?
64 .set_response_header(name, value.into())
65 }
66
67 pub fn append_header(&self, name: &str, value: impl Into<String>) -> AuthResult<()> {
69 self.input_request
70 .ok_or_else(|| {
71 better_auth_core::AuthError::internal("Endpoint response is unavailable")
72 })?
73 .append_response_header(name, value.into())
74 }
75
76 pub fn new_session(&self) -> AuthResult<Option<better_auth_core::session::NativeSessionData>> {
78 self.input_request
79 .map(AuthRequest::new_session)
80 .transpose()
81 .map(Option::flatten)
82 }
83 pub fn email_otp(&self) -> AuthResult<super::email_otp::EmailOtpApi<'a, S>> {
85 super::email_otp::EmailOtpApi::from_endpoint(self)
86 }
87
88 pub fn organization(&self) -> AuthResult<super::organization::OrganizationApi<'a, S>> {
90 super::organization::OrganizationApi::from_endpoint(self)
91 }
92
93 pub fn phone_number(&self) -> AuthResult<super::phone_number::PhoneNumberApi<'a, S>> {
95 super::phone_number::PhoneNumberApi::from_endpoint(self)
96 }
97
98 pub fn jwt(&self) -> AuthResult<super::jwt::JwtApi<'a, S>> {
100 super::jwt::JwtApi::from_endpoint(self)
101 }
102
103 pub fn native(
105 input_request: Option<&'a AuthRequest>,
106 original_request: Option<&'a AuthRequest>,
107 body: FieldValue,
108 auth: &'a AuthContext<S>,
109 ) -> Self {
110 let mut context = Self::new(input_request, body, auth);
111 context.request = original_request;
112 context.params.clear();
113 context
114 }
115
116 pub fn new(
119 request: Option<&'a AuthRequest>,
120 body: FieldValue,
121 auth: &'a AuthContext<S>,
122 ) -> Self {
123 Self {
124 input_request: request,
125 request: request.and_then(|request| {
126 request.original_request().or_else(|| {
127 better_auth_core::hooks::current_request_hook_context()
128 .is_none_or(|context| context.is_http)
129 .then_some(request)
130 })
131 }),
132 path: request.map(AuthRequest::path),
133 params: better_auth_core::hooks::current_request_hook_context()
134 .map(|context| context.params)
135 .unwrap_or_default(),
136 body,
137 auth,
138 transaction: None,
139 session: None,
140 response: None,
141 }
142 }
143}
144
145pub(crate) struct WithCallbacks<P, C> {
146 pub(crate) plugin: P,
147 pub(crate) callbacks: Arc<C>,
148}
149
150#[async_trait::async_trait]
151impl<S: AuthSchema, P: AuthPlugin<S>, C: Send + Sync + 'static> AuthPlugin<S>
152 for WithCallbacks<P, C>
153{
154 fn name(&self) -> &'static str {
155 self.plugin.name()
156 }
157 fn telemetry_plugin_id(&self) -> Option<&'static str> {
158 self.plugin.telemetry_plugin_id()
159 }
160 fn routes(&self) -> Vec<AuthRoute> {
161 self.plugin.routes()
162 }
163 fn openapi(&self) -> AuthResult<better_auth_core::openapi::OpenApiPluginMetadata> {
164 self.plugin.openapi()
165 }
166 fn password_hasher(&self) -> Option<Arc<dyn better_auth_core::PasswordHasher>> {
167 self.plugin.password_hasher()
168 }
169 fn telemetry(&self, options: &mut better_auth_core::observability::telemetry::PluginTelemetry) {
170 self.plugin.telemetry(options);
171 let callbacks = self.callbacks.as_ref() as &dyn std::any::Any;
172 if callbacks.is::<super::email_verification::EmailVerificationCallbacks<S>>() {
173 options.email_verification.send_verification_email = true;
174 }
175 if callbacks.is::<super::password_management::PasswordManagementCallbacks<S>>() {
176 options.email_and_password.send_reset_password = true;
177 }
178 if let Some(callbacks) =
179 callbacks.downcast_ref::<super::user_management::UserManagementCallbacks<S>>()
180 {
181 options.send_change_email_confirmation |= callbacks.has_confirmation_sender();
182 }
183 if let Some(callbacks) = callbacks.downcast_ref::<super::oauth::OAuthCallbacks<S>>() {
184 for provider in &mut options.social_providers {
185 provider.verify_id_token |= callbacks.verifiers.contains_key(&provider.id);
186 }
187 }
188 }
189 fn rate_limits(&self) -> AuthResult<Vec<better_auth_core::middleware::PluginRateLimit>> {
190 self.plugin.rate_limits()
191 }
192 async fn on_init(&self, ctx: &mut AuthInitContext<S>) -> AuthResult<()> {
193 ctx.extensions.insert(self.callbacks.clone());
194 self.plugin.on_init(ctx).await
195 }
196 async fn before_request(
197 &self,
198 req: &AuthRequest,
199 ctx: &AuthContext<S>,
200 ) -> AuthResult<Option<BeforeRequestAction>> {
201 self.plugin.before_request(req, ctx).await
202 }
203 async fn on_http_request(
204 &self,
205 req: &AuthRequest,
206 ctx: &AuthContext<S>,
207 ) -> AuthResult<Option<AuthResponse>> {
208 self.plugin.on_http_request(req, ctx).await
209 }
210 async fn on_http_response(
211 &self,
212 req: &AuthRequest,
213 response: &mut AuthResponse,
214 ctx: &AuthContext<S>,
215 ) -> AuthResult<Option<AuthResponse>> {
216 self.plugin.on_http_response(req, response, ctx).await
217 }
218 async fn on_request(
219 &self,
220 req: &AuthRequest,
221 ctx: &AuthContext<S>,
222 ) -> AuthResult<Option<AuthResponse>> {
223 self.plugin.on_request(req, ctx).await
224 }
225 async fn after_request(
226 &self,
227 req: &AuthRequest,
228 response: &mut AuthResponse,
229 ctx: &AuthContext<S>,
230 ) -> AuthResult<()> {
231 self.plugin.after_request(req, response, ctx).await
232 }
233}
234
235#[cfg(test)]
236mod tests;