use std::sync::Arc;
use better_auth_core::{
AuthConfig, AuthContext, AuthError, AuthInitContext, AuthPlugin, AuthRequest, AuthResponse,
AuthResult, AuthSchema, AuthStore, EmailProvider, HttpMethod, OkResponse, OpenApiRegistry,
OpenApiSpec, SessionManager, UpdateUser,
api_error::{HttpFailure, handle_http_error_for_transport},
core_paths,
hooks::{RequestHookContext, set_request_hook_route, with_request_hook_context_value},
middleware::{
self, BodyLimitConfig, BodyLimitMiddleware, CorsConfig, CorsMiddleware, CsrfConfig,
CsrfMiddleware, Middleware, RateLimitConfig, RateLimitMiddleware,
},
};
fn endpoint_error(error: AuthError, request: &AuthRequest) -> AuthResult<AuthError> {
if !error.is_api_error()? {
return Ok(error);
}
Ok(error.capture_native_headers(request.take_response_headers_value()?))
}
pub(crate) fn core_routes() -> [better_auth_core::AuthRoute; 4] {
[
better_auth_core::AuthRoute::get(core_paths::OK, "ok"),
better_auth_core::AuthRoute::get(core_paths::ERROR, "error"),
better_auth_core::AuthRoute::get(core_paths::OPENAPI_SPEC, "openapi_spec"),
better_auth_core::AuthRoute::post(core_paths::UPDATE_USER, "updateUser")
.body_validator(better_auth_core::endpoint_input::record_body),
]
}
pub struct BetterAuth<S: AuthSchema> {
config: Arc<AuthConfig>,
plugins: Arc<Vec<Box<dyn AuthPlugin<S>>>>,
middlewares: Vec<Box<dyn Middleware>>,
http_middlewares: Vec<Box<dyn Middleware>>,
body_limit: BodyLimitConfig,
store: Arc<dyn AuthStore<S>>,
session_manager: SessionManager<S>,
context: Arc<AuthContext<S>>,
}
type EphemeralStoreFactory<S> = Box<
dyn FnOnce(
Arc<AuthConfig>,
&better_auth_core::plugin_runtime::ModelFields,
) -> Arc<dyn AuthStore<S>>
+ Send,
>;
pub struct AuthBuilder<S: AuthSchema> {
config: AuthConfig,
hooks: better_auth_core::observability::EndpointHooks<S>,
validate_user_info:
Option<Arc<dyn better_auth_api::plugins::user_admission::ValidateUserInfo<S>>>,
api_error_handler: Option<Arc<dyn better_auth_core::api_error::ApiErrorHandler<S>>>,
store: Option<Arc<dyn AuthStore<S>>>,
ephemeral_store: Option<EphemeralStoreFactory<S>>,
secondary_storage: Option<Arc<dyn better_auth_core::store::SecondaryStorage>>,
plugins: Vec<Box<dyn AuthPlugin<S>>>,
csrf_config: Option<CsrfConfig>,
rate_limit_config: Option<RateLimitConfig>,
cors_config: Option<CorsConfig>,
body_limit_config: Option<BodyLimitConfig>,
custom_middlewares: Vec<Box<dyn Middleware>>,
}
impl<S: AuthSchema> AuthBuilder<S> {
pub fn new(config: AuthConfig) -> Self {
Self {
config,
hooks: Default::default(),
validate_user_info: None,
api_error_handler: None,
store: None,
ephemeral_store: None,
secondary_storage: None,
plugins: Vec::new(),
csrf_config: None,
rate_limit_config: None,
cors_config: None,
body_limit_config: None,
custom_middlewares: Vec::new(),
}
}
pub fn store<T>(mut self, store: T) -> Self
where
T: AuthStore<S> + 'static,
{
self.store = Some(Arc::new(store));
self
}
pub fn store_arc(mut self, store: Arc<dyn AuthStore<S>>) -> Self {
self.store = Some(store);
self
}
pub fn secondary_storage(
mut self,
storage: Arc<dyn better_auth_core::store::SecondaryStorage>,
) -> Self {
self.secondary_storage = Some(storage);
self
}
pub fn validate_user_info(
mut self,
callback: Arc<dyn better_auth_api::plugins::user_admission::ValidateUserInfo<S>>,
) -> Self {
self.validate_user_info = Some(callback);
self
}
pub fn on_api_error(
mut self,
callback: Arc<dyn better_auth_core::api_error::ApiErrorHandler<S>>,
) -> Self {
self.api_error_handler = Some(callback);
self
}
pub fn hooks(mut self, hooks: better_auth_core::observability::EndpointHooks<S>) -> Self {
self.hooks = hooks;
self
}
pub fn plugin<P: AuthPlugin<S> + 'static>(mut self, plugin: P) -> Self {
self.plugins.push(Box::new(plugin));
self
}
pub fn csrf(mut self, config: CsrfConfig) -> Self {
self.csrf_config = Some(config);
self
}
pub fn rate_limit(mut self, config: RateLimitConfig) -> Self {
self.rate_limit_config = Some(config);
self
}
pub fn cors(mut self, config: CorsConfig) -> Self {
self.cors_config = Some(config);
self
}
pub fn body_limit(mut self, config: BodyLimitConfig) -> Self {
self.body_limit_config = Some(config);
self
}
pub fn email_provider<E: EmailProvider + 'static>(mut self, provider: E) -> Self {
self.config.email_provider = Some(Arc::new(provider));
self
}
pub fn middleware<M: Middleware + 'static>(mut self, mw: M) -> Self {
self.custom_middlewares.push(Box::new(mw));
self
}
pub async fn build(mut self) -> AuthResult<BetterAuth<S>> {
self.config.initialize_logger()?;
self.config.resolve_secrets()?;
let capabilities = better_auth_core::store::StoreCapabilities {
database: self.store.is_some(),
secondary: self.secondary_storage.is_some(),
};
self.config.resolve_storage_defaults(capabilities)?;
let config = Arc::new(self.config);
let mut model_fields = better_auth_core::plugin_runtime::ModelFields::default();
let mut registered_model_fields = Vec::with_capacity(self.plugins.len());
for plugin in &self.plugins {
plugin.schema(&mut model_fields)?;
registered_model_fields.push(model_fields.take_registered_model_field_names()?);
}
let rate_limit_config = self.rate_limit_config.clone().unwrap_or_default();
model_fields.set_schema_configuration(
&better_auth_core::store::schema::SchemaConfiguration {
config: config.clone(),
plugins: self.plugins.iter().map(|plugin| plugin.name()).collect(),
metadata: Default::default(),
secondary_storage: capabilities.secondary,
rate_limit: rate_limit_config.clone(),
},
);
let (initial_adapter_config, _, initial_fields) = model_fields.clone().resolve(&config);
let initial_adapter_config = Arc::new(initial_adapter_config);
let store = self
.store
.or_else(|| {
self.ephemeral_store
.map(|create| create(initial_adapter_config.clone(), &initial_fields))
})
.ok_or_else(|| AuthError::config("Auth store not configured"))?;
let store = store.with_runtime(initial_adapter_config, Vec::new(), initial_fields)?;
let mut init_context = AuthInitContext::new(config.clone(), store.clone())?;
init_context.initialize_request_context().await?;
init_context.extensions.insert(capabilities);
init_context
.extensions
.insert(self.csrf_config.clone().unwrap_or_default());
init_context.secondary_storage = self.secondary_storage;
let has_api_error_handler = self.api_error_handler.is_some();
if let Some(callback) = self.api_error_handler {
init_context.extensions.insert(callback);
}
if let Some(callback) = self.validate_user_info {
init_context.extensions.insert(callback);
}
for plugin in &self.plugins {
if let Some(hasher) = plugin.password_hasher() {
init_context.password_policy.hasher = Some(hasher);
}
}
let telemetry = better_auth_api::observability::initialize_telemetry(&init_context.config);
if telemetry.enabled() {
let payload = super::telemetry::init_payload(
&config,
&self.plugins,
super::telemetry::InitOptions {
database: capabilities.database,
adapter: store.adapter_id(),
before: self.hooks.before.is_some(),
after: self.hooks.after.is_some(),
secondary: init_context.secondary_storage.is_some(),
on_error: has_api_error_handler,
rate_limit: self.rate_limit_config.as_ref(),
rate_limit_model: store.rate_limit_model_declaration(),
database_hooks: store.database_hook_metadata(),
},
)?;
super::telemetry::start_init(telemetry.clone(), payload).await;
}
init_context.extensions.insert(telemetry);
for plugin in &self.plugins {
init_context.initialize_plugin(plugin.as_ref()).await?;
}
let config = init_context.config.clone();
let mut init_parts = init_context.into_parts();
init_parts
.extensions
.insert(better_auth_core::plugin_runtime::ApplicationUserFields(
config.user.clone(),
));
let mut context = AuthContext::new(config.clone(), store.clone())?;
init_parts.apply_request_runtime(&mut context);
let (adapter_config, endpoint_config, mut model_fields) = model_fields.resolve(&config);
let adapter_fields = adapter_config.user.clone();
let adapter_config = Arc::new(adapter_config);
let schema_configuration = better_auth_core::store::schema::SchemaConfiguration {
config: adapter_config.clone(),
plugins: self.plugins.iter().map(|plugin| plugin.name()).collect(),
metadata: init_parts.metadata.clone(),
secondary_storage: init_parts.secondary_storage.is_some(),
rate_limit: rate_limit_config.clone(),
};
model_fields.set_plugin_metadata(&init_parts.metadata);
let openapi_fields = model_fields.clone();
let store = store.with_runtime(
adapter_config.clone(),
init_parts.database_hooks,
model_fields,
)?;
let config = Arc::new(endpoint_config);
init_parts
.extensions
.insert(better_auth_core::plugin_runtime::AdapterUserFields(
adapter_fields,
));
let schema_check = store.schema_check(&schema_configuration)?;
let schema_validation =
schema_check.map(|check| better_auth_core::store::schema::SchemaValidation {
check,
runtime_enabled: config.advanced.database.validate_schema != Some(false),
});
if schema_validation.is_none() {
match config.advanced.database.validate_schema {
Some(true) => tracing::warn!(
"The database adapter does not support runtime schema validation"
),
None => tracing::debug!(
"The database adapter does not support runtime schema validation"
),
Some(false) => {}
}
}
let store: Arc<dyn AuthStore<S>> = if let Some(secondary) = &init_parts.secondary_storage {
Arc::new(
better_auth_core::store::secondary::SecondaryStore::new(
store,
secondary.clone(),
adapter_config.clone(),
init_parts.metadata.clone(),
)?
.with_schema_validation(schema_validation.clone()),
)
} else {
Arc::new(
better_auth_core::store::secondary::SecondaryStore::without_secondary(
store,
adapter_config.clone(),
init_parts.metadata.clone(),
)
.with_schema_validation(schema_validation.clone()),
)
};
let openapi = OpenApiRegistry::new(
&adapter_config,
&config.user,
self.plugins
.iter()
.zip(registered_model_fields)
.map(|(plugin, groups)| {
plugin
.openapi()
.map(|metadata| metadata.registered_field_names(groups))
})
.collect::<AuthResult<Vec<_>>>()?,
init_parts.secondary_storage.is_some(),
&openapi_fields,
)?;
init_parts.extensions.insert(openapi);
let plugins = Arc::new(self.plugins);
init_parts.extensions.insert(Arc::new(
better_auth_core::endpoint_dispatch::EndpointDispatcher::new(
plugins.clone(),
self.hooks,
core_routes(),
),
));
context.config = config.clone();
context.database = store.clone();
context.metadata = init_parts.metadata;
context.password_policy = init_parts.password_policy;
context.secondary_storage = init_parts.secondary_storage;
context.extensions = init_parts.extensions;
context.email_verification_policy = init_parts.email_verification_policy;
let context = Arc::new(context);
init_parts.runtime.bind(&context)?;
if let Some(validation) = &schema_validation {
validation.start();
}
let session_manager = context.session_manager();
let body_limit = self.body_limit_config.unwrap_or_default();
let mut plugin_limits = Vec::new();
for plugin in plugins.iter() {
plugin_limits.extend(plugin.rate_limits()?);
}
config.advanced.ip_address.warn_invalid_proxies()?;
let http_middlewares: Vec<Box<dyn Middleware>> = vec![
Box::new(BodyLimitMiddleware::new(body_limit.clone())),
Box::new(RateLimitMiddleware::from_context(
rate_limit_config,
&context,
plugin_limits,
)),
];
let mut middlewares: Vec<Box<dyn Middleware>> = vec![Box::new(CorsMiddleware::new(
self.cors_config.unwrap_or_default(),
))];
middlewares.extend(self.custom_middlewares);
Ok(BetterAuth {
config,
plugins,
middlewares,
http_middlewares,
body_limit,
store,
session_manager,
context,
})
}
}
impl BetterAuth<better_auth_core::store::StatelessSchema> {
pub fn stateless(config: AuthConfig) -> AuthBuilder<better_auth_core::store::StatelessSchema> {
AuthBuilder::new(config).database_hooks(Vec::new())
}
}
impl AuthBuilder<better_auth_core::store::StatelessSchema> {
pub fn database_hooks(
mut self,
hooks: Vec<
Arc<
dyn better_auth_core::store::database_hooks::DatabaseHooks<
better_auth_core::store::StatelessSchema,
>,
>,
>,
) -> Self {
self.ephemeral_store = Some(Box::new(move |config, fields| {
Arc::new(
better_auth_core::store::EphemeralStore::from_schema(config, fields)
.with_hooks(hooks),
)
}));
self
}
}
impl<S: AuthSchema> BetterAuth<S> {
#[expect(
clippy::new_ret_no_self,
reason = "returns AuthBuilder by design — builder pattern entry point"
)]
pub fn new(config: AuthConfig) -> AuthBuilder<S> {
AuthBuilder::new(config)
}
}
impl<S: AuthSchema> BetterAuth<S> {
pub async fn handle_request(&self, req: AuthRequest) -> AuthResult<AuthResponse> {
self.handle_request_for_transport(req)
.await
.map_err(HttpFailure::into_error)
}
pub(crate) async fn handle_request_for_transport(
&self,
req: AuthRequest,
) -> Result<AuthResponse, HttpFailure> {
let url = req.url().cloned();
let mounted = req.base_relative_path().is_some();
let query = req.query.or_else(|| {
Some(better_auth_core::query::parse_url_query(
url.as_ref().and_then(|url| url.query()).unwrap_or_default(),
))
});
let mut req = AuthRequest::from_parts(req.method, req.path, req.headers, req.body, query);
if mounted {
req = req.with_base_relative_path();
}
if let Some(url) = url {
req = req.with_url(url);
}
let mut request_context = RequestHookContext::from_request(&req)?;
request_context.is_http = true;
request_context.meta = better_auth_core::RequestMeta::from_request_with_config(
&req,
&self.config.advanced.ip_address,
);
let original = req.clone();
self.context
.with_http_context(&original, |context| async move {
Ok(with_request_hook_context_value(request_context, async {
if let Some(response) = self.handle_http_phase(&req, &context).await? {
let response = middleware::run_after(
&self.middlewares,
&req,
response
.into_http_response()
.map_err(HttpFailure::Conversion)?,
)
.await?;
return response
.into_http_response()
.map_err(HttpFailure::Conversion);
}
let original_request = req.clone();
let response = match self.parse_endpoint_http_body(&mut req, &context) {
Ok(Some(response)) => response,
Err(error) => handle_http_error_for_transport(error, &context).await?,
Ok(None) => match self.dispatch_endpoint(&mut req, true, &context).await {
Ok(response) => response,
Err(error) => handle_http_error_for_transport(error, &context).await?,
},
};
self.finish_http_response(&original_request, response, &context)
.await
})
.await)
})
.await?
}
async fn handle_http_phase(
&self,
req: &AuthRequest,
context: &AuthContext<S>,
) -> AuthResult<Option<AuthResponse>> {
let path = super::http_routing::disabled_path(req, context.base_path());
if context.config.is_path_disabled(path) {
return Ok(Some(
AuthResponse::text(404, "Not Found")
.with_header("content-type", "text/plain;charset=UTF-8"),
));
}
if let Some(validation) = context.database.schema_validation() {
validation.check_runtime().await?;
}
if let Some(response) = middleware::run_before(&self.http_middlewares, req).await? {
return Ok(Some(response));
}
for plugin in self.plugins.iter() {
if let Some(response) = plugin.on_http_request(req, context).await? {
return Ok(Some(response));
}
}
Ok(None)
}
async fn finish_http_response(
&self,
request: &AuthRequest,
mut response: AuthResponse,
context: &AuthContext<S>,
) -> Result<AuthResponse, HttpFailure> {
response = response
.into_http_response()
.map_err(HttpFailure::Conversion)?;
for plugin in self.plugins.iter() {
if let Some(replacement) = plugin
.on_http_response(request, &mut response, context)
.await?
{
response = replacement
.into_http_response()
.map_err(HttpFailure::Conversion)?;
break;
}
}
middleware::run_after(&self.middlewares, request, response)
.await
.map_err(HttpFailure::Source)?
.into_http_response()
.map_err(HttpFailure::Conversion)
}
fn parse_endpoint_http_body(
&self,
req: &mut AuthRequest,
context: &AuthContext<S>,
) -> AuthResult<Option<AuthResponse>> {
let Some(path) = super::http_routing::route_path(req, context.base_path()) else {
return Ok(Some(AuthResponse::new(404)));
};
let selected = core_routes()
.into_iter()
.chain(self.plugins.iter().flat_map(|plugin| plugin.routes()))
.find_map(|route| {
super::http_routing::matched_path(
&route,
req.method(),
path,
context.config.advanced.skip_trailing_slashes,
)
.map(|path| (route, path))
});
let Some((route, path)) = selected else {
return Ok(Some(AuthResponse::new(404)));
};
set_request_hook_route(&path, Some(&route));
req.path = path;
let allowed = if route.allowed_media_types.is_empty() {
vec!["application/json".to_owned()]
} else {
route.allowed_media_types
};
req.parse_http_body(&allowed)?;
Ok(None)
}
pub(crate) async fn dispatch_endpoint(
&self,
req: &mut AuthRequest,
http: bool,
context: &AuthContext<S>,
) -> AuthResult<AuthResponse> {
use better_auth_core::observability::{SpanAttributes, with_span};
let snapshot = better_auth_core::hooks::current_request_hook_context();
let route = snapshot.as_ref().map_or_else(
|| req.path().to_owned(),
|snapshot| snapshot.path.clone().unwrap_or_else(|| "/:virtual".into()),
);
let operation_id = snapshot
.as_ref()
.and_then(|snapshot| snapshot.operation_id.as_deref())
.unwrap_or(&route);
let method = match req.method() {
HttpMethod::Get => "GET",
HttpMethod::Post => "POST",
HttpMethod::Put => "PUT",
HttpMethod::Patch => "PATCH",
HttpMethod::Delete => "DELETE",
HttpMethod::Options => "OPTIONS",
HttpMethod::Head => "HEAD",
};
with_span(
&context.config.experimental.instrumentation,
&format!("{method} {route}"),
SpanAttributes {
route: Some(&route),
operation_id: Some(operation_id),
..Default::default()
},
self.dispatch_endpoint_inner(req, http, context),
)
.await
}
async fn dispatch_endpoint_inner(
&self,
req: &mut AuthRequest,
http: bool,
context: &AuthContext<S>,
) -> AuthResult<AuthResponse> {
if http {
let csrf = CsrfMiddleware::from_context(
context
.extensions
.get::<CsrfConfig>()
.cloned()
.unwrap_or_default(),
context,
);
if let Some(mut response) = csrf.before_request(req).await? {
response.headers.merge(req.take_response_headers()?);
return Ok(response);
}
match middleware::run_before(&self.middlewares, req).await {
Ok(Some(mut response)) => {
response.headers.merge(req.take_response_headers()?);
return Ok(response);
}
Err(error) => return Err(endpoint_error(error, req)?),
Ok(None) => {}
}
}
context
.extensions
.get::<Arc<better_auth_core::endpoint_dispatch::EndpointDispatcher<S>>>()
.ok_or_else(|| AuthError::config("Endpoint dispatcher is not initialized"))?
.run(req, http, context, None, |request, context| async move {
self.execute_endpoint(&request, &context).await
})
.await
}
async fn execute_endpoint(
&self,
internal_req: &AuthRequest,
context: &AuthContext<S>,
) -> AuthResult<AuthResponse> {
if let Some(response) = self.handle_core_request(internal_req, context).await? {
return Ok(response);
}
for plugin in self.plugins.iter() {
if let Some(response) = plugin.on_request(internal_req, context).await? {
return Ok(response);
}
}
Err(AuthError::not_found("No handler found for this request"))
}
pub fn config(&self) -> &AuthConfig {
&self.config
}
pub fn context(&self) -> &AuthContext<S> {
&self.context
}
pub fn store(&self) -> &Arc<dyn AuthStore<S>> {
&self.store
}
pub fn body_limit(&self) -> &BodyLimitConfig {
&self.body_limit
}
pub fn session_manager(&self) -> &SessionManager<S> {
&self.session_manager
}
pub fn routes(&self) -> Vec<(String, &dyn AuthPlugin<S>)> {
let mut routes = Vec::new();
for plugin in self.plugins.iter() {
for route in plugin.routes() {
routes.push((route.path, plugin.as_ref()));
}
}
routes
}
pub fn plugins(&self) -> &[Box<dyn AuthPlugin<S>>] {
&self.plugins
}
pub fn get_plugin(&self, name: &str) -> Option<&dyn AuthPlugin<S>> {
self.plugins
.iter()
.find(|p| p.name() == name)
.map(|p| p.as_ref())
}
pub fn plugin_names(&self) -> Vec<&'static str> {
self.plugins.iter().map(|p| p.name()).collect()
}
pub fn openapi_spec(&self) -> AuthResult<OpenApiSpec> {
self.context
.extensions
.get::<OpenApiRegistry>()
.map(|registry| registry.generate(&self.context.base_url()))
.ok_or_else(|| AuthError::config("OpenAPI registry is not initialized"))
}
async fn handle_core_request(
&self,
req: &AuthRequest,
context: &AuthContext<S>,
) -> AuthResult<Option<AuthResponse>> {
match (req.method(), req.path()) {
(HttpMethod::Get, core_paths::OK) => {
Ok(Some(AuthResponse::json(None, &OkResponse { ok: true })?))
}
(HttpMethod::Get, core_paths::ERROR) => Ok(Some(context.error_page_response(req)?)),
(HttpMethod::Get, core_paths::OPENAPI_SPEC) => {
let spec = context
.extensions
.get::<OpenApiRegistry>()
.ok_or_else(|| AuthError::config("OpenAPI registry is not initialized"))?
.generate(&context.base_url());
Ok(Some(AuthResponse::json(None, &spec)?))
}
(HttpMethod::Post, core_paths::UPDATE_USER) => {
Ok(Some(self.handle_update_user(req, context).await?))
}
_ => Ok(None),
}
}
async fn handle_update_user(
&self,
req: &AuthRequest,
context: &AuthContext<S>,
) -> AuthResult<AuthResponse> {
let mut body = better_auth_core::endpoint_input::record_input(req)?;
let session = context
.require_native_session(req)
.await
.map_err(|error| match error {
AuthError::Unauthenticated => {
AuthError::from_code("UNAUTHORIZED", "UNAUTHORIZED", "Unauthorized")
}
error => error,
})?;
if body
.remove("email")
.map(better_auth_core::FieldValue::from_json)
.transpose()?
.is_some_and(|value| value.is_truthy())
{
return Err(AuthError::from_code(
"BAD_REQUEST",
"EMAIL_CAN_NOT_BE_UPDATED",
"Email can not be updated",
));
}
let name = body
.remove("name")
.map(better_auth_core::FieldValue::from_json)
.transpose()?
.unwrap_or_default();
let image = body
.remove("image")
.map(better_auth_core::FieldValue::from_json)
.transpose()?
.unwrap_or_default();
let additional_fields = context.parse_user_input(&body, false)?;
let has_changes =
!name.is_undefined() || !image.is_undefined() || !additional_fields.is_empty();
if !has_changes {
return Err(AuthError::from_status(
"BAD_REQUEST",
better_auth_core::FieldMap::from([(
"message".into(),
"No fields to update".into(),
)])
.into(),
));
}
let mut update_fields = better_auth_core::FieldMap::from([
("name".into(), name.clone()),
("image".into(), image.clone()),
]);
update_fields.extend(additional_fields.clone());
let update_user = UpdateUser {
additional_fields: update_fields,
..Default::default()
};
let user = match self
.store
.update_user_by_id_value(&session.user_property("id")?, update_user)
.await?
{
Some(user) => better_auth_core::FieldMap::from(user).into(),
None => {
let mut fields = session.user_value()?.enumerable_fields()?;
if !name.is_undefined() {
let _ = fields.insert("name".into(), name);
}
if !image.is_undefined() {
let _ = fields.insert("image".into(), image);
}
fields.extend(additional_fields);
fields.into()
}
};
context
.session_manager()
.set_native_session_cookie(req, session.with_user(user)?, None)
.await?;
AuthResponse::json(None, &better_auth_core::StatusResponse { status: true })
}
}