1use async_trait::async_trait;
2use std::collections::HashMap;
3use std::sync::Arc;
4
5use crate::config::AuthConfig;
6use crate::email::EmailProvider;
7use crate::error::{AuthError, AuthResult};
8use crate::schema::AuthSchema;
9#[cfg(test)]
10use crate::session::SessionManager;
11use crate::store::AuthStore;
12use crate::types::{AuthRequest, AuthResponse, HttpMethod};
13
14pub type MetadataMap = HashMap<String, serde_json::Value>;
16
17pub struct AuthInitParts<S: AuthSchema> {
18 request_runtime: crate::request_runtime::RequestRuntime,
19 pub plugin_fields: crate::plugin_runtime::ModelFields,
20 pub database_hooks: Vec<Arc<dyn crate::store::database_hooks::DatabaseHooks<S>>>,
21 pub runtime: crate::plugin_runtime::PluginRuntime<S>,
22 pub extensions: crate::RuntimeExtensions,
23 pub email_verification_policy: crate::email::EmailVerificationRuntimePolicy,
24 pub metadata: MetadataMap,
25 pub email_provider: Option<Arc<dyn EmailProvider>>,
26 pub secondary_storage: Option<Arc<dyn crate::store::SecondaryStorage>>,
27 pub password_policy: crate::utils::password::PasswordRuntimePolicy,
28}
29
30#[derive(Debug)]
32pub enum BeforeRequestAction {
33 Respond(AuthResponse),
35 ReplaceBody(Vec<u8>),
37 MergeContext(crate::endpoint_input::EndpointInputPatch),
39 InjectSession {
41 session: Box<crate::wire::SessionView>,
42 },
43}
44
45#[async_trait]
48pub trait AuthPlugin<S: AuthSchema>: Send + Sync + std::any::Any {
49 fn name(&self) -> &'static str;
51
52 fn telemetry_plugin_id(&self) -> Option<&'static str> {
54 Some(self.name())
55 }
56
57 fn routes(&self) -> Vec<AuthRoute>;
59
60 fn openapi(&self) -> AuthResult<crate::openapi::OpenApiPluginMetadata> {
62 crate::openapi::OpenApiPluginMetadata::from_routes(self.name(), self.routes())
63 }
64
65 fn password_hasher(&self) -> Option<Arc<dyn crate::utils::password::PasswordHasher>> {
67 None
68 }
69
70 fn telemetry(&self, _options: &mut crate::observability::telemetry::PluginTelemetry) {}
72
73 fn rate_limits(&self) -> AuthResult<Vec<crate::middleware::PluginRateLimit>> {
75 Ok(Vec::new())
76 }
77
78 async fn on_init(&self, ctx: &mut AuthInitContext<S>) -> AuthResult<()> {
80 let _ = ctx;
81 Ok(())
82 }
83
84 async fn on_http_request(
87 &self,
88 _req: &AuthRequest,
89 _ctx: &AuthContext<S>,
90 ) -> AuthResult<Option<AuthResponse>> {
91 Ok(None)
92 }
93
94 async fn on_http_response(
98 &self,
99 _req: &AuthRequest,
100 _response: &mut AuthResponse,
101 _ctx: &AuthContext<S>,
102 ) -> AuthResult<Option<AuthResponse>> {
103 Ok(None)
104 }
105
106 async fn before_request(
114 &self,
115 _req: &AuthRequest,
116 _ctx: &AuthContext<S>,
117 ) -> AuthResult<Option<BeforeRequestAction>> {
118 Ok(None)
119 }
120
121 async fn on_request(
123 &self,
124 req: &AuthRequest,
125 ctx: &AuthContext<S>,
126 ) -> AuthResult<Option<AuthResponse>>;
127
128 async fn after_request(
130 &self,
131 _req: &AuthRequest,
132 _response: &mut AuthResponse,
133 _ctx: &AuthContext<S>,
134 ) -> AuthResult<()> {
135 Ok(())
136 }
137}
138
139#[macro_export]
152macro_rules! impl_auth_plugin {
153 (@pat get) => { $crate::HttpMethod::Get };
154 (@pat post) => { $crate::HttpMethod::Post };
155 (@pat put) => { $crate::HttpMethod::Put };
156 (@pat delete) => { $crate::HttpMethod::Delete };
157 (@pat patch) => { $crate::HttpMethod::Patch };
158 (@pat head) => { $crate::HttpMethod::Head };
159
160 (@route get) => { $crate::AuthRoute::get };
161 (@route post) => { $crate::AuthRoute::post };
162 (@route put) => { $crate::AuthRoute::put };
163 (@route delete) => { $crate::AuthRoute::delete };
164
165 (
166 $plugin:ty, $name:expr;
167 routes {
168 $( $method:ident $path:literal => $handler:ident, $op_id:literal
169 $(, allowed_media_types = [$($media_type:literal),* $(,)?])?
170 $(, body = $body:path)?
171 $(, query = $query:path)?
172 $(, require_headers = $require_headers:literal)?
173 );* $(;)?
174 }
175 $( extra { $($extra:tt)* } )?
176 ) => {
177 #[::async_trait::async_trait]
178 impl<S: $crate::AuthSchema> $crate::AuthPlugin<S> for $plugin {
179 fn name(&self) -> &'static str { $name }
180
181 fn routes(&self) -> Vec<$crate::AuthRoute> {
182 vec![
183 $( $crate::AuthRoute::new($crate::impl_auth_plugin!(@pat $method), $path, $op_id)
184 $(.allowed_media_types(&[$($media_type),*]))?
185 $(.body_validator($body))?
186 $(.query_validator($query))?
187 $(.require_headers($require_headers))?, )*
188 ]
189 }
190
191 async fn on_request(
192 &self,
193 req: &$crate::AuthRequest,
194 ctx: &$crate::AuthContext<S>,
195 ) -> $crate::AuthResult<Option<$crate::AuthResponse>> {
196 match (req.method(), req.path()) {
197 $(
198 ($crate::impl_auth_plugin!(@pat $method), $path) => {
199 Ok(Some(self.$handler(req, ctx).await?))
200 }
201 )*
202 _ => Ok(None),
203 }
204 }
205
206 $( $($extra)* )?
207 }
208 };
209}
210
211pub type QueryValidator = fn(Option<serde_json::Value>) -> AuthResult<Option<serde_json::Value>>;
213
214pub use crate::endpoint_input::BodyValidator;
215
216#[derive(Clone)]
218pub struct AuthRoute {
219 pub server_only: bool,
221 pub path: String,
222 pub method: HttpMethod,
223 pub operation_id: String,
225 pub openapi: Option<crate::openapi::OpenApiRouteMetadata>,
227 pub endpoint_key: Option<String>,
229 pub allowed_media_types: Vec<String>,
231 pub query_validator: Option<QueryValidator>,
233 pub body_validator: Option<BodyValidator>,
234 pub require_headers: bool,
236}
237
238pub struct AuthInitContext<S: AuthSchema> {
240 request_runtime: crate::request_runtime::RequestRuntime,
241 plugin_fields: crate::plugin_runtime::ModelFields,
242 registered_model_field_names: Vec<(crate::store::schema::EntityRole, Vec<String>)>,
243 database_hooks: Vec<Arc<dyn crate::store::database_hooks::DatabaseHooks<S>>>,
244 runtime: crate::plugin_runtime::PluginRuntime<S>,
245 pub extensions: crate::RuntimeExtensions,
246 pub email_verification_policy: crate::email::EmailVerificationRuntimePolicy,
247 pub config: Arc<AuthConfig>,
248 pub database: Arc<dyn AuthStore<S>>,
249 pub email_provider: Option<Arc<dyn EmailProvider>>,
250 pub secondary_storage: Option<Arc<dyn crate::store::SecondaryStorage>>,
251 pub password_policy: crate::utils::password::PasswordRuntimePolicy,
252 pub metadata: MetadataMap,
253}
254
255pub struct AuthContext<S: AuthSchema> {
257 pub(crate) request_runtime: crate::request_runtime::RequestRuntime,
258 pub extensions: crate::RuntimeExtensions,
259 pub email_verification_policy: crate::email::EmailVerificationRuntimePolicy,
260 pub config: Arc<AuthConfig>,
261 pub database: Arc<dyn AuthStore<S>>,
262 pub email_provider: Option<Arc<dyn EmailProvider>>,
263 pub secondary_storage: Option<Arc<dyn crate::store::SecondaryStorage>>,
264 pub password_policy: crate::utils::password::PasswordRuntimePolicy,
265 pub metadata: MetadataMap,
266}
267
268impl<S: AuthSchema> Clone for AuthContext<S> {
269 fn clone(&self) -> Self {
270 Self {
271 request_runtime: self.request_runtime.clone(),
272 extensions: self.extensions.clone(),
273 email_verification_policy: self.email_verification_policy.clone(),
274 config: self.config.clone(),
275 database: self.database.clone(),
276 email_provider: self.email_provider.clone(),
277 secondary_storage: self.secondary_storage.clone(),
278 password_policy: self.password_policy.clone(),
279 metadata: self.metadata.clone(),
280 }
281 }
282}
283
284impl std::fmt::Debug for AuthRoute {
285 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
286 f.debug_struct("AuthRoute")
287 .field("path", &self.path)
288 .field("method", &self.method)
289 .field("operation_id", &self.operation_id)
290 .field("openapi", &self.openapi)
291 .field("endpoint_key", &self.endpoint_key)
292 .field("allowed_media_types", &self.allowed_media_types)
293 .field("query_validator", &self.query_validator)
294 .field("require_headers", &self.require_headers)
295 .field(
296 "body_validator",
297 &self.body_validator.as_ref().map(|_| "configured"),
298 )
299 .finish()
300 }
301}
302
303impl AuthRoute {
304 pub fn server_only(method: HttpMethod, operation_id: impl Into<String>) -> Self {
306 let mut route = Self::new(method, "/", operation_id);
307 route.server_only = true;
308 route
309 }
310
311 pub fn matches(&self, method: &HttpMethod, path: &str) -> bool {
313 if self.server_only || self.method != *method {
314 return false;
315 }
316 let mut actual = path.split('/');
317 for expected in self.path.split('/') {
318 let Some(actual) = actual.next() else {
319 return false;
320 };
321 if expected != actual
322 && !(expected.starts_with('{') && expected.ends_with('}') && !actual.is_empty())
323 {
324 return false;
325 }
326 }
327 actual.next().is_none()
328 }
329
330 pub fn openapi(mut self, metadata: crate::openapi::OpenApiRouteMetadata) -> Self {
331 self.openapi = Some(metadata);
332 self
333 }
334
335 pub fn endpoint_key(mut self, key: impl Into<String>) -> Self {
336 self.endpoint_key = Some(key.into());
337 self
338 }
339
340 pub fn body_validator(
342 mut self,
343 validator: impl Fn(&AuthRequest) -> AuthResult<crate::endpoint_input::ValidatedBody>
344 + Send
345 + Sync
346 + 'static,
347 ) -> Self {
348 self.body_validator = Some(BodyValidator::new(validator));
349 self
350 }
351
352 pub fn body_validator_async<F, Fut>(mut self, validator: F) -> Self
354 where
355 F: Fn(AuthRequest) -> Fut + Send + Sync + 'static,
356 Fut: std::future::Future<Output = AuthResult<crate::endpoint_input::ValidatedBody>>
357 + Send
358 + 'static,
359 {
360 self.body_validator = Some(BodyValidator::new_async(validator));
361 self
362 }
363
364 pub fn query_validator(mut self, validator: QueryValidator) -> Self {
366 self.query_validator = Some(validator);
367 self
368 }
369
370 pub fn require_headers(mut self, required: bool) -> Self {
371 self.require_headers = required;
372 self
373 }
374
375 pub fn allowed_media_types(mut self, types: &[&str]) -> Self {
376 self.allowed_media_types = types.iter().map(|value| (*value).to_owned()).collect();
377 self
378 }
379
380 pub fn new(
381 method: HttpMethod,
382 path: impl Into<String>,
383 operation_id: impl Into<String>,
384 ) -> Self {
385 Self {
386 server_only: false,
387 path: path.into(),
388 method,
389 operation_id: operation_id.into(),
390 openapi: None,
391 endpoint_key: None,
392 allowed_media_types: Vec::new(),
393 query_validator: None,
394 body_validator: None,
395 require_headers: false,
396 }
397 }
398
399 pub fn get(path: impl Into<String>, operation_id: impl Into<String>) -> Self {
400 Self::new(HttpMethod::Get, path, operation_id)
401 }
402
403 pub fn post(path: impl Into<String>, operation_id: impl Into<String>) -> Self {
404 Self::new(HttpMethod::Post, path, operation_id)
405 }
406
407 pub fn put(path: impl Into<String>, operation_id: impl Into<String>) -> Self {
408 Self::new(HttpMethod::Put, path, operation_id)
409 }
410
411 pub fn delete(path: impl Into<String>, operation_id: impl Into<String>) -> Self {
412 Self::new(HttpMethod::Delete, path, operation_id)
413 }
414}
415
416impl<S: AuthSchema> AuthInitContext<S> {
417 pub fn new(config: Arc<AuthConfig>, database: Arc<dyn AuthStore<S>>) -> Self {
418 let email_provider = config.email_provider.clone();
419 let password_policy = crate::utils::password::PasswordRuntimePolicy::new(&config.password);
420 Self {
421 request_runtime: Default::default(),
422 plugin_fields: Default::default(),
423 registered_model_field_names: Vec::new(),
424 database_hooks: Vec::new(),
425 runtime: Default::default(),
426 extensions: crate::RuntimeExtensions::default(),
427 email_verification_policy: crate::email::EmailVerificationRuntimePolicy::default(),
428 config,
429 database,
430 email_provider,
431 secondary_storage: None,
432 password_policy,
433 metadata: MetadataMap::new(),
434 }
435 }
436
437 pub async fn initialize_request_context(&mut self) -> AuthResult<()> {
439 let mut context = AuthContext::new(self.config.clone(), self.database.clone());
440 context.request_runtime = self.request_runtime.clone();
441 let context = context.initialize_request_context().await?;
442 self.config = context.config.clone();
443 self.request_runtime = context.request_runtime.clone();
444 Ok(())
445 }
446
447 pub fn register_user_fields(&mut self, fields: crate::user_fields::UserConfig) {
449 let names = fields
450 .additional_fields
451 .as_ref()
452 .map(|fields| fields.keys().cloned().collect());
453 self.plugin_fields
454 .extend(crate::store::schema::EntityRole::User, fields);
455 if let Some(names) = names {
456 self.registered_model_field_names
457 .push((crate::store::schema::EntityRole::User, names));
458 }
459 }
460
461 pub fn register_model_fields(
464 &mut self,
465 role: crate::store::schema::EntityRole,
466 fields: crate::user_fields::UserConfig,
467 ) -> AuthResult<()> {
468 let names = fields
469 .additional_fields
470 .as_ref()
471 .map(|fields| fields.keys().cloned().collect());
472 self.plugin_fields.register(role, fields)?;
473 if let Some(names) = names {
474 self.registered_model_field_names.push((role, names));
475 }
476 Ok(())
477 }
478
479 pub fn register_custom_model(
484 &mut self,
485 name: impl Into<String>,
486 model_name: Option<&str>,
487 fields: crate::user_fields::UserConfig,
488 ) -> AuthResult<()> {
489 self.plugin_fields
490 .register_custom_model(name.into(), model_name, fields)
491 }
492
493 #[doc(hidden)]
496 pub fn take_registered_model_field_names(
497 &mut self,
498 ) -> Vec<(crate::store::schema::EntityRole, Vec<String>)> {
499 std::mem::take(&mut self.registered_model_field_names)
500 }
501
502 #[doc(hidden)]
504 pub fn register_organization_schema(
505 &mut self,
506 fields: &crate::organization_fields::OrganizationFields,
507 teams_enabled: bool,
508 ) {
509 self.plugin_fields
510 .register_organization_schema(fields, teams_enabled);
511 }
512
513 pub fn register_database_hook(
515 &mut self,
516 hook: Arc<dyn crate::store::database_hooks::DatabaseHooks<S>>,
517 ) {
518 self.database_hooks.push(hook);
519 }
520
521 pub fn runtime(&self) -> crate::plugin_runtime::PluginRuntime<S> {
523 self.runtime.clone()
524 }
525
526 pub fn set_metadata(&mut self, key: impl Into<String>, value: serde_json::Value) {
527 _ = self.metadata.insert(key.into(), value);
528 }
529
530 pub fn get_metadata(&self, key: &str) -> Option<&serde_json::Value> {
531 self.metadata.get(key)
532 }
533
534 pub fn into_parts(self) -> AuthInitParts<S> {
535 AuthInitParts {
536 request_runtime: self.request_runtime,
537 plugin_fields: self.plugin_fields,
538 database_hooks: self.database_hooks,
539 runtime: self.runtime,
540 extensions: self.extensions,
541 email_verification_policy: self.email_verification_policy,
542 metadata: self.metadata,
543 email_provider: self.email_provider,
544 secondary_storage: self.secondary_storage,
545 password_policy: self.password_policy,
546 }
547 }
548}
549
550impl<S: AuthSchema> AuthContext<S> {
551 pub fn new(config: Arc<AuthConfig>, database: Arc<dyn AuthStore<S>>) -> Self {
552 let email_provider = config.email_provider.clone();
553 let password_policy = crate::utils::password::PasswordRuntimePolicy::new(&config.password);
554 Self {
555 request_runtime: Default::default(),
556 extensions: crate::RuntimeExtensions::default(),
557 email_verification_policy: crate::email::EmailVerificationRuntimePolicy::default(),
558 config,
559 database,
560 email_provider,
561 secondary_storage: None,
562 password_policy,
563 metadata: MetadataMap::new(),
564 }
565 }
566
567 pub fn with_metadata(
568 config: Arc<AuthConfig>,
569 database: Arc<dyn AuthStore<S>>,
570 metadata: MetadataMap,
571 ) -> Self {
572 let email_provider = config.email_provider.clone();
573 let password_policy = crate::utils::password::PasswordRuntimePolicy::new(&config.password);
574 Self {
575 request_runtime: Default::default(),
576 extensions: crate::RuntimeExtensions::default(),
577 email_verification_policy: crate::email::EmailVerificationRuntimePolicy::default(),
578 config,
579 database,
580 email_provider,
581 secondary_storage: None,
582 password_policy,
583 metadata,
584 }
585 }
586
587 pub fn set_metadata(&mut self, key: impl Into<String>, value: serde_json::Value) {
588 _ = self.metadata.insert(key.into(), value);
589 }
590
591 pub fn get_metadata(&self, key: &str) -> Option<&serde_json::Value> {
592 self.metadata.get(key)
593 }
594
595 pub fn email_provider(&self) -> AuthResult<&dyn EmailProvider> {
597 self.email_provider
598 .as_deref()
599 .ok_or_else(|| AuthError::config("No email provider configured"))
600 }
601
602 pub fn store_capabilities(&self) -> crate::store::StoreCapabilities {
604 self.extensions
605 .get::<crate::store::StoreCapabilities>()
606 .copied()
607 .unwrap_or_default()
608 }
609
610 pub fn session_manager(&self) -> crate::session::SessionManager<S> {
612 crate::session::SessionManager::new(self.config.clone(), self.database.clone())
613 .with_store_capabilities(self.store_capabilities())
614 .with_user_metadata(self.metadata.clone())
615 .with_adapter_user_fields(self.adapter_user_fields().clone())
616 .with_cookie_signer(
617 self.extensions
618 .get::<Arc<dyn crate::session::SessionCookieSigner<S>>>()
619 .cloned(),
620 )
621 }
622
623 pub fn adapter_user_fields(&self) -> &crate::user_fields::UserConfig {
625 self.extensions
626 .get::<crate::plugin_runtime::AdapterUserFields>()
627 .map_or(&self.config.user, |fields| &fields.0)
628 }
629
630 pub async fn user_view(
632 &self,
633 user: &impl crate::entity::AuthUser,
634 ) -> AuthResult<crate::wire::UserView> {
635 crate::wire::UserView::with_field_policies(
636 user,
637 self.adapter_user_fields(),
638 &self.config.user,
639 &self.metadata,
640 self.database.supports_native_json(),
641 )
642 .await
643 }
644
645 pub async fn internal_user_view(
647 &self,
648 user: &impl crate::entity::AuthUser,
649 ) -> AuthResult<crate::wire::UserView> {
650 crate::wire::UserView::with_internal_fields_for_adapter(
651 user,
652 self.adapter_user_fields(),
653 &self.metadata,
654 self.database.supports_native_json(),
655 )
656 .await
657 }
658
659 pub async fn session_view(
661 &self,
662 session: &impl crate::entity::AuthSession,
663 ) -> AuthResult<crate::wire::SessionView> {
664 self.session_manager().session_view(session).await
665 }
666
667 pub fn parse_user_input(
669 &self,
670 input: &serde_json::Map<String, serde_json::Value>,
671 create: bool,
672 ) -> AuthResult<crate::FieldMap> {
673 let input = crate::FieldMap::from_json(input.clone())?;
674 for (plugin, fields, has_default) in [
675 (
676 "admin.enabled",
677 &["role", "banReason", "banExpires"][..],
678 false,
679 ),
680 ("admin.enabled", &["banned"][..], true),
681 ("phone-number.enabled", &["phoneNumberVerified"][..], false),
682 ("anonymous.enabled", &["isAnonymous"][..], true),
683 ("two_factor.enabled", &["twoFactorEnabled"][..], true),
684 ] {
685 if self
686 .get_metadata(plugin)
687 .and_then(serde_json::Value::as_bool)
688 != Some(true)
689 || (create && has_default)
690 {
691 continue;
692 }
693 for name in fields {
694 if input.get(*name).is_some_and(crate::FieldValue::is_truthy) {
695 return Err(AuthError::FieldInput {
696 code: "FIELD_NOT_ALLOWED",
697 message: format!("{name} is not allowed to be set"),
698 });
699 }
700 }
701 }
702 self.config.user.parse_input(&input, create)
703 }
704
705 pub async fn require_session(
711 &self,
712 req: &AuthRequest,
713 ) -> AuthResult<(crate::wire::UserView, crate::wire::SessionView)> {
714 self.require_session_with_read(req, crate::session::SessionRead::Cached)
715 .await
716 }
717
718 pub async fn require_authoritative_session(
720 &self,
721 req: &AuthRequest,
722 ) -> AuthResult<(crate::wire::UserView, crate::wire::SessionView)> {
723 self.require_session_with_read(req, crate::session::SessionRead::Authoritative)
724 .await
725 }
726
727 async fn require_session_with_read(
728 &self,
729 req: &AuthRequest,
730 read: crate::session::SessionRead,
731 ) -> AuthResult<(crate::wire::UserView, crate::wire::SessionView)> {
732 let resolved = self.session_manager().resolve(req, read).await?;
733 req.set_session_snapshot(resolved.data.clone().map(Into::into))?;
734 let data = resolved.data.ok_or(AuthError::Unauthenticated)?;
735 if !data.user.id.is_undefined() {
736 req.set_server_context("auth.current-user-id", data.user.id.field_value())?;
737 }
738 Ok((data.user, data.session))
739 }
740}
741
742#[cfg(test)]
743mod tests {
744 mod async_body_dispatch;
745
746 use super::*;
747 use crate::entity::AuthUser;
748 use crate::test_store::test_database;
749
750 #[test]
752 fn auth_route_constructors() {
753 let get = AuthRoute::get("/test", "getTest");
754 assert_eq!(get.method, HttpMethod::Get);
755 assert_eq!(get.path, "/test");
756 assert_eq!(get.operation_id, "getTest");
757
758 let post = AuthRoute::post("/create", "createItem");
759 assert_eq!(post.method, HttpMethod::Post);
760
761 let put = AuthRoute::put("/update", "updateItem");
762 assert_eq!(put.method, HttpMethod::Put);
763
764 let delete = AuthRoute::delete("/remove", "deleteItem");
765 assert_eq!(delete.method, HttpMethod::Delete);
766 }
767
768 #[test]
770 fn auth_route_new() {
771 let route = AuthRoute::new(HttpMethod::Patch, "/patch", "patchIt");
772 assert_eq!(route.method, HttpMethod::Patch);
773 assert_eq!(route.path, "/patch");
774 }
775
776 #[test]
778 fn auth_context_new() {
779 let config = Arc::new(AuthConfig::new("test-secret-min-32-chars-1234567"));
780 let runtime = tokio::runtime::Runtime::new().expect("runtime should build");
781 let db = runtime.block_on(test_database());
782 let ctx = AuthContext::new(config.clone(), db);
783 assert!(ctx.email_provider.is_none());
784 assert!(ctx.metadata.is_empty());
785 }
786
787 #[test]
789 fn auth_context_metadata() {
790 let config = Arc::new(AuthConfig::new("test-secret-min-32-chars-1234567"));
791 let runtime = tokio::runtime::Runtime::new().expect("runtime should build");
792 let db = runtime.block_on(test_database());
793 let mut ctx = AuthContext::new(config, db);
794
795 ctx.set_metadata("key", serde_json::json!("value"));
796 assert_eq!(ctx.get_metadata("key"), Some(&serde_json::json!("value")));
797 assert!(ctx.get_metadata("missing").is_none());
798 }
799
800 #[test]
802 fn auth_context_email_provider_error_when_none() {
803 let config = Arc::new(AuthConfig::new("test-secret-min-32-chars-1234567"));
804 let runtime = tokio::runtime::Runtime::new().expect("runtime should build");
805 let db = runtime.block_on(test_database());
806 let ctx = AuthContext::new(config, db);
807 assert!(ctx.email_provider().is_err());
808 }
809
810 #[tokio::test]
812 async fn auth_context_require_session_unauthenticated() {
813 let config = Arc::new(AuthConfig::new("test-secret-min-32-chars-1234567"));
814 let db = test_database().await;
815 let ctx = AuthContext::new(config, db);
816 let req = AuthRequest::new(HttpMethod::Get, "/test");
817 let result = ctx.require_session(&req).await;
818 assert!(result.is_err());
819 }
820
821 #[tokio::test]
823 async fn auth_context_require_session_with_valid_session() {
824 let config = Arc::new(AuthConfig::new("test-secret-min-32-chars-1234567"));
825 let db = test_database().await;
826
827 let user = db
829 .create_user(crate::types::CreateUser::new().with_email("test@test.com"))
830 .await
831 .unwrap();
832
833 let sm = SessionManager::new(config.clone(), db.clone());
835 let session = sm.create_session(&user, None, None).await.unwrap();
836
837 let ctx = AuthContext::new(config.clone(), db);
839 let mut req = AuthRequest::new(HttpMethod::Get, "/test");
840 let _ = req.headers.insert(
841 "cookie".into(),
842 format!(
843 "better-auth.session_token={}",
844 crate::utils::cookie_utils::sign_cookie_value(
845 &session.token,
846 config.signing_secret()
847 )
848 ),
849 );
850
851 let (found_user, _found_session) = ctx.require_session(&req).await.unwrap();
852 assert_eq!(found_user.id(), user.id());
853 }
854}
855
856impl<S: AuthSchema> AuthInitParts<S> {
857 pub fn apply_request_runtime(&self, context: &mut AuthContext<S>) {
859 context.request_runtime = self.request_runtime.clone();
860 }
861}