use better_auth_core::{
AuthContext, AuthInitContext, AuthPlugin, AuthRequest, AuthResponse, AuthResult, AuthRoute,
AuthSchema, BeforeRequestAction, FieldValue,
};
mod owned;
pub use owned::OwnedEndpointContext;
use serde_json::Value;
use std::sync::Arc;
pub struct EndpointContext<'a, S: AuthSchema> {
input_request: Option<&'a AuthRequest>,
pub request: Option<&'a AuthRequest>,
pub path: Option<&'a str>,
pub params: std::collections::HashMap<String, String>,
pub body: FieldValue,
pub auth: &'a AuthContext<S>,
pub transaction: Option<&'a dyn better_auth_core::store::AuthTransaction<S>>,
pub session: Option<better_auth_core::session::NativeSessionData>,
pub response: Option<&'a AuthResponse>,
}
impl<'a, S: AuthSchema> EndpointContext<'a, S> {
pub fn test(&'a self) -> AuthResult<super::test_utils::TestUtilsApi<'a, S>> {
super::test_utils::TestUtilsApi::from_endpoint(self)
}
pub fn headers(&self) -> Option<&std::collections::HashMap<String, String>> {
self.input_request.and_then(AuthRequest::endpoint_headers)
}
pub fn query(&self) -> AuthResult<Option<Value>> {
self.input_request
.map_or(Ok(None), AuthRequest::input_query)
}
pub fn query_field_value(&self) -> AuthResult<FieldValue> {
self.input_request.map_or(
Ok(FieldValue::Undefined),
AuthRequest::input_query_field_value,
)
}
pub fn input_request(&self) -> Option<&'a AuthRequest> {
self.input_request
}
pub fn set_header(&self, name: &str, value: impl Into<String>) -> AuthResult<()> {
self.input_request
.ok_or_else(|| {
better_auth_core::AuthError::internal("Endpoint response is unavailable")
})?
.set_response_header(name, value.into())
}
pub fn append_header(&self, name: &str, value: impl Into<String>) -> AuthResult<()> {
self.input_request
.ok_or_else(|| {
better_auth_core::AuthError::internal("Endpoint response is unavailable")
})?
.append_response_header(name, value.into())
}
pub fn set_status(
&self,
status: impl Into<better_auth_core::SchemaValue<u16>>,
) -> AuthResult<()> {
self.input_request
.ok_or_else(|| {
better_auth_core::AuthError::internal("Endpoint response is unavailable")
})?
.set_response_status(status)
}
pub fn new_session(&self) -> AuthResult<Option<better_auth_core::session::NativeSessionData>> {
self.input_request
.map(AuthRequest::new_session)
.transpose()
.map(Option::flatten)
}
pub fn email_otp(&self) -> AuthResult<super::email_otp::EmailOtpApi<'a, S>> {
super::email_otp::EmailOtpApi::from_endpoint(self)
}
pub fn organization(&self) -> AuthResult<super::organization::OrganizationApi<'a, S>> {
super::organization::OrganizationApi::from_endpoint(self)
}
pub fn phone_number(&self) -> AuthResult<super::phone_number::PhoneNumberApi<'a, S>> {
super::phone_number::PhoneNumberApi::from_endpoint(self)
}
pub fn jwt(&self) -> AuthResult<super::jwt::JwtApi<'a, S>> {
super::jwt::JwtApi::from_endpoint(self)
}
pub fn native(
input_request: Option<&'a AuthRequest>,
original_request: Option<&'a AuthRequest>,
body: FieldValue,
auth: &'a AuthContext<S>,
) -> Self {
let mut context = Self::new(input_request, body, auth);
context.request = original_request;
context.params.clear();
context
}
pub fn new(
request: Option<&'a AuthRequest>,
body: FieldValue,
auth: &'a AuthContext<S>,
) -> Self {
Self {
input_request: request,
request: request.and_then(|request| {
request.original_request().or_else(|| {
better_auth_core::hooks::current_request_hook_context()
.is_none_or(|context| context.is_http)
.then_some(request)
})
}),
path: request.map(AuthRequest::path),
params: better_auth_core::hooks::current_request_hook_context()
.map(|context| context.params)
.unwrap_or_default(),
body,
auth,
transaction: None,
session: None,
response: None,
}
}
}
pub(crate) struct WithCallbacks<P, C> {
pub(crate) plugin: P,
pub(crate) callbacks: Arc<C>,
}
#[async_trait::async_trait]
impl<S: AuthSchema, P: AuthPlugin<S>, C: Send + Sync + 'static> AuthPlugin<S>
for WithCallbacks<P, C>
{
fn name(&self) -> &'static str {
self.plugin.name()
}
fn telemetry_plugin_id(&self) -> Option<&'static str> {
self.plugin.telemetry_plugin_id()
}
fn schema(&self, fields: &mut better_auth_core::plugin_runtime::ModelFields) -> AuthResult<()> {
self.plugin.schema(fields)
}
fn routes(&self) -> Vec<AuthRoute> {
self.plugin.routes()
}
fn openapi(&self) -> AuthResult<better_auth_core::openapi::OpenApiPluginMetadata> {
self.plugin.openapi()
}
fn password_hasher(&self) -> Option<Arc<dyn better_auth_core::PasswordHasher>> {
self.plugin.password_hasher()
}
fn telemetry(&self, options: &mut better_auth_core::observability::telemetry::PluginTelemetry) {
self.plugin.telemetry(options);
let callbacks = self.callbacks.as_ref() as &dyn std::any::Any;
if let Some(callbacks) =
callbacks.downcast_ref::<super::email_verification::EmailVerificationCallbacks<S>>()
{
options.email_verification.send_verification_email |= callbacks.has_sender();
options.email_verification.before_email_verification |= callbacks.has_before();
options.email_verification.after_email_verification |= callbacks.has_after();
}
if callbacks.is::<super::password_management::PasswordManagementCallbacks<S>>() {
options.email_and_password.send_reset_password = true;
}
if let Some(callbacks) =
callbacks.downcast_ref::<super::user_management::UserManagementCallbacks<S>>()
{
options.send_change_email_confirmation |= callbacks.has_confirmation_sender();
}
if let Some(callbacks) = callbacks.downcast_ref::<super::oauth::OAuthCallbacks<S>>() {
for provider in &mut options.social_providers {
provider.verify_id_token |= callbacks.verifiers.contains_key(&provider.id);
}
}
}
fn rate_limits(&self) -> AuthResult<Vec<better_auth_core::middleware::PluginRateLimit>> {
self.plugin.rate_limits()
}
async fn on_init(&self, ctx: &mut AuthInitContext<S>) -> AuthResult<()> {
ctx.extensions.insert(self.callbacks.clone());
self.plugin.on_init(ctx).await
}
async fn before_request(
&self,
req: &AuthRequest,
ctx: &AuthContext<S>,
) -> AuthResult<Option<BeforeRequestAction>> {
self.plugin.before_request(req, ctx).await
}
async fn on_http_request(
&self,
req: &AuthRequest,
ctx: &AuthContext<S>,
) -> AuthResult<Option<AuthResponse>> {
self.plugin.on_http_request(req, ctx).await
}
async fn on_http_response(
&self,
req: &AuthRequest,
response: &mut AuthResponse,
ctx: &AuthContext<S>,
) -> AuthResult<Option<AuthResponse>> {
self.plugin.on_http_response(req, response, ctx).await
}
async fn on_request(
&self,
req: &AuthRequest,
ctx: &AuthContext<S>,
) -> AuthResult<Option<AuthResponse>> {
self.plugin.on_request(req, ctx).await
}
async fn after_request(
&self,
req: &AuthRequest,
response: &mut AuthResponse,
ctx: &AuthContext<S>,
) -> AuthResult<()> {
self.plugin.after_request(req, response, ctx).await
}
}
#[cfg(test)]
mod tests;