1mod account;
2mod advanced;
3mod identity;
4mod session;
5
6pub use account::AccountConfig;
7pub use account::AccountLinkingConfig;
8pub use account::OAuthStateStrategy;
9pub use advanced::AdvancedConfig;
10pub use advanced::AdvancedDatabaseConfig;
11pub use advanced::CookieAttributes;
12pub use advanced::CookieOverride;
13pub use advanced::CrossSubDomainConfig;
14pub use advanced::IpAddressConfig;
15pub use advanced::SameSite;
16pub use identity::PasswordConfig;
17pub use identity::UserConfig;
18pub use identity::VerificationConfig;
19pub use session::CookieCacheConfig;
20pub use session::CookieCacheStrategy;
21pub use session::CookieRefreshCache;
22pub use session::JwtConfig;
23pub use session::SessionConfig;
24mod client_ip;
25mod secrets;
26pub use secrets::ManagedSecrets;
27
28pub mod core_paths {
34
35 pub const OK: &str = "/ok";
36 pub const ERROR: &str = "/error";
37 pub const HEALTH: &str = "/health";
38 pub const OPENAPI_SPEC: &str = "/__test/openapi.json";
39 pub const UPDATE_USER: &str = "/update-user";
40 pub const DELETE_USER: &str = "/delete-user";
41 pub const CHANGE_EMAIL: &str = "/change-email";
42 pub const DELETE_USER_CALLBACK: &str = "/delete-user/callback";
43}
44
45use crate::email::EmailProvider;
46use crate::error::AuthError;
47use chrono::Duration;
48use std::collections::HashMap;
49use std::sync::Arc;
50
51#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
54pub enum AwaitedNotificationErrorPolicy {
55 #[default]
57 Propagate,
58 LogAndContinue,
60}
61
62mod origin;
63pub use origin::{
64 BaseUrlProtocol, DynamicBaseUrl, TrustedOriginsResolver, TrustedProvidersResolver,
65};
66
67#[derive(Clone)]
69pub struct AuthConfig {
70 pub secret: String,
72
73 pub managed_secrets: Option<ManagedSecrets>,
76
77 pub app_name: String,
81
82 pub base_url: String,
84
85 pub dynamic_base_url: Option<DynamicBaseUrl>,
87
88 pub trusted_origins_resolver: Option<std::sync::Arc<dyn TrustedOriginsResolver>>,
90
91 pub base_path: String,
99
100 pub trusted_origins: Vec<String>,
106
107 pub disabled_paths: Vec<String>,
112
113 pub render_error_page: bool,
119
120 pub api_error_url: Option<String>,
122
123 pub session: SessionConfig,
125
126 pub user: UserConfig,
128
129 pub verification: VerificationConfig,
131
132 pub jwt: JwtConfig,
134
135 pub password: PasswordConfig,
137
138 pub account: AccountConfig,
140
141 pub email_provider: Option<Arc<dyn EmailProvider>>,
143 pub background_tasks: Option<Arc<dyn crate::BackgroundTaskHandler>>,
146 pub awaited_notification_errors: AwaitedNotificationErrorPolicy,
148
149 pub user_validation: Option<Arc<dyn crate::user_validation::UserInfoValidator>>,
152
153 pub advanced: AdvancedConfig,
155}
156
157impl Default for AuthConfig {
158 fn default() -> Self {
159 Self {
160 secret: String::new(),
161 managed_secrets: None,
162 app_name: "Better Auth".to_owned(),
163 base_url: "http://localhost:3000".to_owned(),
164 dynamic_base_url: None,
165 trusted_origins_resolver: None,
166 base_path: "/api/auth".to_owned(),
167 trusted_origins: Vec::new(),
168 disabled_paths: Vec::new(),
169 render_error_page: !std::env::var("NODE_ENV").is_ok_and(|value| value == "production"),
170 api_error_url: None,
171 session: SessionConfig::default(),
172 user: UserConfig::default(),
173 verification: VerificationConfig::default(),
174 jwt: JwtConfig::default(),
175 password: PasswordConfig::default(),
176 account: AccountConfig::default(),
177 email_provider: None,
178 background_tasks: None,
179 awaited_notification_errors: AwaitedNotificationErrorPolicy::default(),
180 user_validation: None,
181 advanced: AdvancedConfig::default(),
182 }
183 }
184}
185
186impl AuthConfig {
187 #[must_use]
189 pub const fn awaited_notification_errors(
190 mut self,
191 policy: AwaitedNotificationErrorPolicy,
192 ) -> Self {
193 self.awaited_notification_errors = policy;
194 self
195 }
196
197 #[must_use]
199 pub fn background_tasks(mut self, handler: Arc<dyn crate::BackgroundTaskHandler>) -> Self {
200 self.background_tasks = Some(handler);
201 self
202 }
203
204 #[must_use]
205 pub fn new(secret: impl Into<String>) -> Self {
206 Self {
207 secret: secret.into(),
208 ..Default::default()
209 }
210 }
211
212 #[must_use]
214 pub fn app_name(mut self, name: impl Into<String>) -> Self {
215 self.app_name = name.into();
216 self
217 }
218
219 #[must_use]
224 pub fn base_url(mut self, url: impl Into<String>) -> Self {
225 self.base_url = url.into();
226 self.session.cookie_secure = self.base_url.starts_with("https://");
227 self
228 }
229
230 #[must_use]
231 pub fn account(mut self, account: AccountConfig) -> Self {
232 self.account = account;
233 self
234 }
235
236 #[must_use]
239 pub fn api_error_url(mut self, url: impl Into<String>) -> Self {
240 self.api_error_url = Some(url.into());
241 self
242 }
243
244 #[must_use]
246 pub fn base_path(mut self, path: impl Into<String>) -> Self {
247 self.base_path = path.into();
248 self
249 }
250
251 #[must_use]
253 pub fn trusted_origin(mut self, origin: impl Into<String>) -> Self {
254 self.trusted_origins.push(origin.into());
255 self
256 }
257
258 #[must_use]
260 pub fn trusted_origins(mut self, origins: Vec<String>) -> Self {
261 self.trusted_origins = origins;
262 self
263 }
264
265 #[must_use]
267 pub fn disabled_path(mut self, path: impl Into<String>) -> Self {
268 self.disabled_paths.push(path.into());
269 self
270 }
271
272 #[must_use]
274 pub fn disabled_paths(mut self, paths: Vec<String>) -> Self {
275 self.disabled_paths = paths;
276 self
277 }
278
279 #[must_use]
281 pub const fn session_expires_in(mut self, duration: Duration) -> Self {
282 self.session.expires_in = duration;
283 self
284 }
285
286 #[must_use]
287 pub const fn session_update_age(mut self, duration: Duration) -> Self {
288 self.session.update_age = Some(duration);
289 self
290 }
291
292 #[must_use]
293 pub const fn disable_session_refresh(mut self, disabled: bool) -> Self {
294 self.session.disable_session_refresh = disabled;
295 self
296 }
297
298 #[must_use]
299 pub const fn session_fresh_age(mut self, duration: Duration) -> Self {
300 self.session.fresh_age = Some(duration);
301 self
302 }
303
304 #[must_use]
306 pub fn session_cookie_cache(mut self, config: CookieCacheConfig) -> Self {
307 self.session.cookie_cache = Some(config);
308 self
309 }
310
311 #[must_use]
313 pub const fn jwt_expires_in(mut self, duration: Duration) -> Self {
314 self.jwt.expires_in = duration;
315 self
316 }
317
318 #[must_use]
319 pub fn advanced(mut self, advanced: AdvancedConfig) -> Self {
320 self.advanced = advanced;
321 self
322 }
323
324 #[must_use]
325 pub fn cookie_prefix(mut self, prefix: impl Into<String>) -> Self {
326 self.advanced.cookie_prefix = Some(prefix.into());
327 self
328 }
329
330 #[must_use]
331 pub const fn disable_csrf_check(mut self, disabled: bool) -> Self {
332 self.advanced.disable_csrf_check = Some(disabled);
333 self
334 }
335
336 #[must_use]
339 pub fn current_secret(&self) -> &str {
340 self.managed_secrets
341 .as_ref()
342 .map_or(self.secret.as_str(), ManagedSecrets::current_secret)
343 }
344
345 pub fn verification_secrets(&self) -> impl Iterator<Item = &str> {
347 let keys = self
348 .managed_secrets
349 .as_ref()
350 .map(|secrets| secrets.verification_secrets());
351 keys.into_iter().flatten().chain(
352 self.managed_secrets
353 .is_none()
354 .then_some(self.secret.as_str()),
355 )
356 }
357
358 #[must_use]
360 pub fn managed_secrets(mut self, secrets: ManagedSecrets) -> Self {
361 self.managed_secrets = Some(secrets);
362 self
363 }
364
365 #[must_use]
367 pub fn origin_check_disabled_for(&self, path: &str) -> bool {
368 if self.advanced.disable_origin_check {
369 return true;
370 }
371 let path = path
372 .strip_prefix(self.base_path.trim_end_matches('/'))
373 .filter(|suffix| suffix.starts_with('/'))
374 .unwrap_or(path)
375 .trim_end_matches('/');
376 self.advanced.disable_origin_check_paths.iter().any(|skip| {
377 let skip = skip.trim_end_matches('/');
378 path == skip
379 || path
380 .strip_prefix(skip)
381 .is_some_and(|suffix| suffix.starts_with('/'))
382 })
383 }
384
385 #[must_use]
387 pub fn current_origin_check_disabled(&self) -> bool {
388 crate::hooks::current_request_hook_context()
389 .map_or(self.advanced.disable_origin_check, |request| {
390 self.origin_check_disabled_for(&request.path)
391 })
392 }
393
394 #[must_use]
395 pub const fn disable_origin_check(mut self, disabled: bool) -> Self {
396 self.advanced.disable_origin_check = disabled;
397 self
398 }
399
400 #[must_use]
401 pub fn cross_sub_domain_cookies(mut self, domain: impl Into<String>) -> Self {
402 self.advanced.cross_sub_domain_cookies = Some(CrossSubDomainConfig {
403 domain: domain.into(),
404 });
405 self
406 }
407
408 #[must_use]
413 pub fn cross_sub_domain_cookies_from_base_url(mut self) -> Self {
414 self.advanced.cross_sub_domain_cookies = Some(CrossSubDomainConfig::default());
415 self
416 }
417
418 #[must_use]
425 pub fn is_origin_trusted(&self, origin: &str) -> bool {
426 if self.dynamic_base_url.is_none()
428 && let Some(base_origin) = extract_origin(&self.base_url)
429 && (origin == base_origin
430 || (self.base_url.split_once(':').is_some_and(|(scheme, _)| {
431 scheme.eq_ignore_ascii_case("http") || scheme.eq_ignore_ascii_case("https")
432 }) && extract_origin(origin).as_deref() == Some(base_origin.as_str())))
433 {
434 return true;
435 }
436 self.trusted_origins
437 .iter()
438 .any(|pattern| origin::matches_origin(origin, pattern))
439 }
440
441 #[must_use]
450 pub fn is_redirect_target_trusted(&self, url: &str) -> bool {
451 if is_safe_relative_path(url) {
452 return true;
453 }
454 self.is_origin_trusted(url)
455 }
456
457 #[must_use]
459 pub fn is_path_disabled(&self, path: &str) -> bool {
460 self.disabled_paths.iter().any(|disabled| disabled == path)
461 }
462
463 pub fn validate(&self) -> Result<(), AuthError> {
468 if self.session.stateless
469 && (self.session.secondary_storage.is_some()
470 || self.session.store_in_database
471 || self.session.preserve_in_database)
472 {
473 return Err(AuthError::config(
474 "Stateless sessions cannot use server session storage",
475 ));
476 }
477 if self
478 .dynamic_base_url
479 .as_ref()
480 .is_some_and(|policy| policy.allowed_hosts.is_empty())
481 {
482 return Err(AuthError::config(
483 "Dynamic base URL allowed hosts cannot be empty",
484 ));
485 }
486 if let Some(secrets) = &self.managed_secrets {
487 return secrets.validate();
488 }
489 if self.secret.is_empty() {
490 return Err(AuthError::config("Secret key cannot be empty"));
491 }
492
493 if self.secret.len() < 32 {
494 return Err(AuthError::config(
495 "Secret key must be at least 32 characters",
496 ));
497 }
498
499 Ok(())
500 }
501}
502
503impl std::fmt::Debug for AuthConfig {
504 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
505 f.debug_struct("AuthConfig").finish_non_exhaustive()
506 }
507}
508
509#[must_use]
517pub fn is_safe_relative_path(value: &str) -> bool {
518 if !value.starts_with('/')
519 || value.starts_with("//")
520 || value.contains('\\')
521 || value
522 .chars()
523 .any(|character| matches!(character, '\u{0000}'..='\u{001f}' | '\u{007f}'..='\u{009f}'))
524 {
525 return false;
526 }
527
528 let path = value.split(['?', '#']).next().unwrap_or(value);
529 let lower = path.to_ascii_lowercase();
530 !lower.contains("%2f")
531 && !lower.contains("%5c")
532 && url::Url::parse("https://better-auth.invalid")
533 .and_then(|base| base.join(value))
534 .is_ok_and(|url| url.origin().ascii_serialization() == "https://better-auth.invalid")
535}
536
537#[must_use]
544pub fn extract_origin(url: &str) -> Option<String> {
545 if let Ok(parsed) = url::Url::parse(url)
546 && matches!(parsed.scheme(), "http" | "https")
547 {
548 return match parsed.origin() {
549 url::Origin::Tuple(..) => Some(parsed.origin().ascii_serialization()),
550 url::Origin::Opaque(_) => None,
551 };
552 }
553 legacy_pattern_origin(url)
556}
557
558fn legacy_pattern_origin(url: &str) -> Option<String> {
561 let scheme_end = url.find("://")?;
562 let rest = url.get(scheme_end + 3..)?;
563 let host_end = rest.find(['/', '?', '#']).unwrap_or(rest.len());
564 let origin = format!("{}{}", url.get(..scheme_end + 3)?, rest.get(..host_end)?);
565 Some(origin)
566}
567
568#[cfg(test)]
570mod tests {
571 use super::*;
572
573 #[test]
577 fn extract_origin_with_path() {
578 assert_eq!(
579 extract_origin("https://example.com/path"),
580 Some("https://example.com".to_owned())
581 );
582 }
583
584 #[test]
586 fn extract_origin_without_path() {
587 assert_eq!(
588 extract_origin("https://example.com"),
589 Some("https://example.com".to_owned())
590 );
591 }
592
593 #[test]
595 fn extract_origin_with_port() {
596 assert_eq!(
597 extract_origin("http://localhost:3000/api"),
598 Some("http://localhost:3000".to_owned())
599 );
600 }
601
602 #[test]
604 fn extract_origin_with_query() {
605 assert_eq!(
606 extract_origin("https://example.com?foo=bar"),
607 Some("https://example.com".to_owned())
608 );
609 }
610
611 #[test]
613 fn extract_origin_with_fragment() {
614 assert_eq!(
615 extract_origin("https://example.com#fragment"),
616 Some("https://example.com".to_owned())
617 );
618 }
619
620 #[test]
622 fn extract_origin_no_scheme() {
623 assert_eq!(extract_origin("example.com"), None);
624 }
625
626 #[test]
630 fn new_config_sets_secret() {
631 let cfg = AuthConfig::new("a]secret-that-is-at-least-32-characters-long");
632 assert_eq!(cfg.secret, "a]secret-that-is-at-least-32-characters-long");
633 }
634
635 #[test]
637 fn new_config_uses_defaults() {
638 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567");
639 assert_eq!(cfg.app_name, "Better Auth");
640 assert_eq!(cfg.base_url, "http://localhost:3000");
641 assert_eq!(cfg.base_path, "/api/auth");
642 assert_eq!(cfg.trusted_origins, Vec::<String>::new());
643 }
644
645 #[test]
649 fn base_url_sets_cookie_secure_for_https() {
650 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567").base_url("https://myapp.com");
651 assert!(cfg.session.cookie_secure);
652 }
653
654 #[test]
656 fn base_url_clears_cookie_secure_for_http() {
657 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567")
658 .base_url("https://myapp.com")
659 .base_url("http://localhost:3000");
660 assert!(!cfg.session.cookie_secure);
661 }
662
663 #[test]
665 fn builder_chaining() {
666 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567")
667 .app_name("MyApp")
668 .base_path("/auth")
669 .disable_csrf_check(true)
670 .disable_origin_check(true)
671 .cookie_prefix("myapp");
672
673 assert_eq!(cfg.app_name, "MyApp");
674 assert_eq!(cfg.base_path, "/auth");
675 assert_eq!(cfg.advanced.disable_csrf_check, Some(true));
676 assert!(cfg.advanced.disable_origin_check);
677 assert_eq!(cfg.advanced.cookie_prefix, Some("myapp".to_owned()));
678 }
679
680 #[test]
682 fn trusted_origin_appends() {
683 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567")
684 .trusted_origin("https://a.com")
685 .trusted_origin("https://b.com");
686 assert_eq!(cfg.trusted_origins.len(), 2);
687 }
688
689 #[test]
691 fn trusted_origins_replaces() {
692 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567")
693 .trusted_origin("https://old.com")
694 .trusted_origins(vec!["https://new.com".to_owned()]);
695 assert_eq!(cfg.trusted_origins, vec!["https://new.com"]);
696 }
697
698 #[test]
700 fn disabled_path_appends() {
701 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567")
702 .disabled_path("/admin")
703 .disabled_path("/debug");
704 assert_eq!(cfg.disabled_paths.len(), 2);
705 }
706
707 #[test]
709 fn disabled_paths_replaces() {
710 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567")
711 .disabled_path("/old")
712 .disabled_paths(vec!["/new".to_owned()]);
713 assert_eq!(cfg.disabled_paths, vec!["/new"]);
714 }
715
716 #[test]
720 fn is_origin_trusted_matches_base_url() {
721 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567").base_url("https://myapp.com");
722 assert!(cfg.is_origin_trusted("https://myapp.com"));
723 let native_custom =
724 AuthConfig::new("test-secret-min-32-chars-1234567").base_url("myapp://auth");
725 assert!(native_custom.is_origin_trusted("myapp://auth"));
726 assert!(!native_custom.is_origin_trusted("myapp://auth/callback"));
727 }
728
729 #[test]
731 fn is_origin_trusted_rejects_unknown() {
732 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567").base_url("https://myapp.com");
733 assert!(!cfg.is_origin_trusted("https://evil.com"));
734 }
735
736 #[test]
738 fn is_origin_trusted_glob_pattern() {
739 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567")
740 .trusted_origin("https://*.example.com");
741 assert!(cfg.is_origin_trusted("https://sub.example.com"));
742 assert!(!cfg.is_origin_trusted("https://other.com"));
743 }
744
745 #[test]
749 fn redirect_target_allows_relative_path() {
750 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567").base_url("https://myapp.com");
751 assert!(cfg.is_redirect_target_trusted("/dashboard"));
752 assert!(cfg.is_redirect_target_trusted("/callback?foo=bar"));
753 }
754
755 #[test]
757 fn redirect_target_allows_same_origin() {
758 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567").base_url("https://myapp.com");
759 assert!(cfg.is_redirect_target_trusted("https://myapp.com/verified"));
760 }
761
762 #[test]
764 fn redirect_target_allows_trusted_origin() {
765 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567")
766 .base_url("https://myapp.com")
767 .trusted_origin("https://trusted.com");
768 assert!(cfg.is_redirect_target_trusted("https://trusted.com/path"));
769 }
770
771 #[test]
773 fn redirect_target_rejects_untrusted_origin() {
774 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567").base_url("https://myapp.com");
775 assert!(!cfg.is_redirect_target_trusted("https://evil.com/phish"));
776 }
777
778 #[test]
780 fn redirect_target_rejects_protocol_relative() {
781 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567").base_url("https://myapp.com");
782 assert!(!cfg.is_redirect_target_trusted("//evil.com"));
783 }
784
785 #[test]
789 fn is_path_disabled_matches() {
790 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567").disabled_path("/admin");
791 assert!(cfg.is_path_disabled("/admin"));
792 assert!(!cfg.is_path_disabled("/user"));
793 }
794
795 #[test]
799 fn validate_rejects_empty_secret() {
800 let cfg = AuthConfig::default();
801 assert!(cfg.validate().is_err());
802 }
803
804 #[test]
806 fn validate_rejects_short_secret() {
807 let cfg = AuthConfig::new("short");
808 assert!(cfg.validate().is_err());
809 }
810
811 #[test]
813 fn validate_accepts_valid_secret() {
814 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567");
815 assert!(cfg.validate().is_ok());
816 }
817
818 #[test]
822 fn session_config_defaults() {
823 let s = SessionConfig::default();
824 assert_eq!(s.expires_in, Duration::hours(24 * 7));
825 assert_eq!(s.update_age, Some(Duration::hours(24)));
826 assert!(!s.disable_session_refresh);
827 assert_eq!(s.cookie_name, "better-auth.session_token");
828 assert!(s.cookie_http_only);
829 assert_eq!(s.cookie_same_site, SameSite::Lax);
830 }
831
832 #[test]
834 fn jwt_config_defaults() {
835 let j = JwtConfig::default();
836 assert_eq!(j.expires_in, Duration::hours(24));
837 assert_eq!(j.algorithm, "HS256");
838 }
839
840 #[test]
842 fn password_config_defaults() {
843 let p = PasswordConfig::default();
844 assert!(!p.require_uppercase);
845 }
846
847 #[test]
849 fn same_site_display() {
850 assert_eq!(SameSite::Strict.to_string(), "Strict");
851 assert_eq!(SameSite::Lax.to_string(), "Lax");
852 assert_eq!(SameSite::None.to_string(), "None");
853 }
854
855 #[test]
857 fn cookie_cache_config_defaults() {
858 let c = CookieCacheConfig::default();
859 assert!(!c.enabled);
860 assert!((c.max_age - 300.0).abs() < f64::EPSILON);
861 assert_eq!(c.strategy, CookieCacheStrategy::Compact);
862 }
863
864 #[test]
866 fn account_config_defaults() {
867 let a = AccountConfig::default();
868 assert!(a.update_account_on_sign_in);
869 assert!(!a.encrypt_oauth_tokens);
870 assert!(a.account_linking.enabled);
871 }
872
873 #[test]
875 fn core_paths_error_page() {
876 let html = crate::error::page::error_page_html("TEST_ERROR");
877 assert!(html.contains("TEST_ERROR"));
878 assert!(html.contains("Ask AI"));
879 assert!(html.contains("<title>Error</title>"));
880 }
881
882 #[test]
884 fn error_page_sanitizes_script_tag() {
885 let html = crate::error::page::error_page_html("<script>alert(1)</script>");
886 assert!(html.contains("UNKNOWN"));
887 assert!(!html.contains("<script>"));
888 }
889
890 #[test]
892 fn error_page_allows_valid_codes() {
893 assert!(crate::error::page::error_page_html("SOME_ERROR-CODE").contains("SOME_ERROR-CODE"));
894 assert!(crate::error::page::error_page_html("it's").contains("it's"));
895 }
896
897 #[test]
901 fn session_builder_methods() {
902 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567")
903 .session_expires_in(Duration::hours(1))
904 .session_update_age(Duration::minutes(30))
905 .disable_session_refresh(true)
906 .session_fresh_age(Duration::minutes(5));
907
908 assert_eq!(cfg.session.expires_in, Duration::hours(1));
909 assert_eq!(cfg.session.update_age, Some(Duration::minutes(30)));
910 assert!(cfg.session.disable_session_refresh);
911 assert_eq!(cfg.session.fresh_age, Some(Duration::minutes(5)));
912 }
913
914 #[test]
916 fn session_cookie_cache_builder() {
917 let cache = CookieCacheConfig {
918 enabled: true,
919 max_age: 600.0,
920 strategy: CookieCacheStrategy::Jwt,
921 ..CookieCacheConfig::default()
922 };
923 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567").session_cookie_cache(cache);
924
925 let cc = cfg.session.cookie_cache.as_ref();
926 assert!(cc.is_some());
927 let cc = cc.unwrap();
928 assert!(cc.enabled);
929 assert_eq!(cc.strategy, CookieCacheStrategy::Jwt);
930 }
931
932 #[test]
934 fn cross_sub_domain_cookies_builder() {
935 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567")
936 .cross_sub_domain_cookies(".example.com");
937 let csd = cfg.advanced.cross_sub_domain_cookies.as_ref();
938 assert!(csd.is_some());
939 assert_eq!(csd.unwrap().domain, ".example.com");
940 }
941
942 #[test]
944 fn advanced_database_defaults() {
945 let d = AdvancedDatabaseConfig::default();
946 assert_eq!(d.default_find_many_limit, 100);
947 assert!(!d.use_number_id);
948 }
949
950 #[test]
952 fn ip_address_config_defaults() {
953 let ip = IpAddressConfig::default();
954 assert_eq!(ip.headers, vec!["x-forwarded-for"]);
955 assert!(!ip.disable_ip_tracking);
956 }
957}
958