1mod account;
2mod advanced;
3mod client_ip;
4mod id;
5mod identity;
6mod origin;
7mod secrets;
8mod session;
9
10pub use account::{AccountConfig, AccountLinkingConfig, OAuthStateStrategy};
11pub use advanced::{
12 AdvancedConfig, AdvancedDatabaseConfig, CookieAttributes, CookieOverride, CrossSubDomainConfig,
13 IpAddressConfig, SameSite, TwoFactorDatabaseConfig,
14};
15#[doc(hidden)]
16pub use id::serial_id_statements;
17pub use id::{DatabaseIdGenerator, DatabaseIdStrategy};
18pub use identity::{PasswordConfig, UserConfig, VerificationConfig};
19pub use origin::{
20 BaseUrlProtocol, DynamicBaseUrl, TrustedOriginsResolver, TrustedProvidersResolver,
21};
22pub use secrets::ManagedSecrets;
23pub use session::{
24 CookieCacheConfig, CookieCacheStrategy, CookieRefreshCache, JwtConfig, SessionConfig,
25};
26
27pub mod core_paths {
33 pub const OK: &str = "/ok";
34 pub const ERROR: &str = "/error";
35 pub const HEALTH: &str = "/health";
36 pub const OPENAPI_SPEC: &str = "/__test/openapi.json";
37 pub const UPDATE_USER: &str = "/update-user";
38 pub const DELETE_USER: &str = "/delete-user";
39 pub const CHANGE_EMAIL: &str = "/change-email";
40 pub const DELETE_USER_CALLBACK: &str = "/delete-user/callback";
41}
42
43use crate::email::EmailProvider;
44use crate::error::AuthError;
45use chrono::Duration;
46use std::collections::HashMap;
47use std::sync::Arc;
48
49#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
52pub enum AwaitedNotificationErrorPolicy {
53 #[default]
55 Propagate,
56 LogAndContinue,
58}
59
60#[derive(Clone)]
62pub struct AuthConfig {
63 pub secret: String,
65
66 pub managed_secrets: Option<ManagedSecrets>,
69
70 pub app_name: String,
74
75 pub base_url: String,
77
78 pub dynamic_base_url: Option<DynamicBaseUrl>,
80
81 pub trusted_origins_resolver: Option<Arc<dyn TrustedOriginsResolver>>,
83
84 pub base_path: String,
92
93 pub trusted_origins: Vec<String>,
99
100 pub disabled_paths: Vec<String>,
105
106 pub render_error_page: bool,
112
113 pub api_error_url: Option<String>,
115
116 pub throw_api_errors: bool,
119
120 pub session: SessionConfig,
122
123 pub user: UserConfig,
125
126 pub verification: VerificationConfig,
128
129 pub jwt: JwtConfig,
131
132 pub password: PasswordConfig,
134
135 pub account: AccountConfig,
137
138 pub email_provider: Option<Arc<dyn EmailProvider>>,
140 pub background_tasks: Option<Arc<dyn crate::BackgroundTaskHandler>>,
143 pub awaited_notification_errors: AwaitedNotificationErrorPolicy,
145
146 pub user_validation: Option<Arc<dyn crate::user_validation::UserInfoValidator>>,
149
150 pub advanced: AdvancedConfig,
152}
153
154impl Default for AuthConfig {
155 fn default() -> Self {
156 Self {
157 secret: String::new(),
158 managed_secrets: None,
159 app_name: "Better Auth".to_owned(),
160 base_url: "http://localhost:3000".to_owned(),
161 dynamic_base_url: None,
162 trusted_origins_resolver: None,
163 base_path: "/api/auth".to_owned(),
164 trusted_origins: Vec::new(),
165 disabled_paths: Vec::new(),
166 render_error_page: !std::env::var("NODE_ENV").is_ok_and(|value| value == "production"),
167 api_error_url: None,
168 throw_api_errors: false,
169 session: SessionConfig::default(),
170 user: UserConfig::default(),
171 verification: VerificationConfig::default(),
172 jwt: JwtConfig::default(),
173 password: PasswordConfig::default(),
174 account: AccountConfig::default(),
175 email_provider: None,
176 background_tasks: None,
177 awaited_notification_errors: AwaitedNotificationErrorPolicy::default(),
178 user_validation: None,
179 advanced: AdvancedConfig::default(),
180 }
181 }
182}
183
184impl AuthConfig {
185 #[must_use]
187 pub const fn awaited_notification_errors(
188 mut self,
189 policy: AwaitedNotificationErrorPolicy,
190 ) -> Self {
191 self.awaited_notification_errors = policy;
192 self
193 }
194
195 #[must_use]
197 pub fn background_tasks(mut self, handler: Arc<dyn crate::BackgroundTaskHandler>) -> Self {
198 self.background_tasks = Some(handler);
199 self
200 }
201
202 #[must_use]
203 pub fn new(secret: impl Into<String>) -> Self {
204 Self {
205 secret: secret.into(),
206 ..Default::default()
207 }
208 }
209
210 #[must_use]
212 pub fn app_name(mut self, name: impl Into<String>) -> Self {
213 self.app_name = name.into();
214 self
215 }
216
217 #[must_use]
222 pub fn base_url(mut self, url: impl Into<String>) -> Self {
223 self.base_url = url.into();
224 self.session.cookie_secure = self.base_url.starts_with("https://");
225 self
226 }
227
228 #[must_use]
229 pub fn account(mut self, account: AccountConfig) -> Self {
230 self.account = account;
231 self
232 }
233
234 #[must_use]
237 pub fn api_error_url(mut self, url: impl Into<String>) -> Self {
238 self.api_error_url = Some(url.into());
239 self
240 }
241
242 #[must_use]
244 pub fn base_path(mut self, path: impl Into<String>) -> Self {
245 self.base_path = path.into();
246 self
247 }
248
249 #[must_use]
251 pub fn trusted_origin(mut self, origin: impl Into<String>) -> Self {
252 self.trusted_origins.push(origin.into());
253 self
254 }
255
256 #[must_use]
258 pub fn trusted_origins(mut self, origins: Vec<String>) -> Self {
259 self.trusted_origins = origins;
260 self
261 }
262
263 #[must_use]
265 pub fn disabled_path(mut self, path: impl Into<String>) -> Self {
266 self.disabled_paths.push(path.into());
267 self
268 }
269
270 #[must_use]
272 pub fn disabled_paths(mut self, paths: Vec<String>) -> Self {
273 self.disabled_paths = paths;
274 self
275 }
276
277 #[must_use]
279 pub const fn throw_api_errors(mut self, enabled: bool) -> Self {
280 self.throw_api_errors = enabled;
281 self
282 }
283
284 #[must_use]
286 pub const fn session_expires_in(mut self, duration: Duration) -> Self {
287 self.session.expires_in = duration;
288 self
289 }
290
291 #[must_use]
292 pub const fn session_update_age(mut self, duration: Duration) -> Self {
293 self.session.update_age = Some(duration);
294 self
295 }
296
297 #[must_use]
298 pub const fn disable_session_refresh(mut self, disabled: bool) -> Self {
299 self.session.disable_session_refresh = disabled;
300 self
301 }
302
303 #[must_use]
304 pub const fn session_fresh_age(mut self, duration: Duration) -> Self {
305 self.session.fresh_age = Some(duration);
306 self
307 }
308
309 #[must_use]
311 pub fn session_cookie_cache(mut self, config: CookieCacheConfig) -> Self {
312 self.session.cookie_cache = Some(config);
313 self
314 }
315
316 #[must_use]
318 pub const fn jwt_expires_in(mut self, duration: Duration) -> Self {
319 self.jwt.expires_in = duration;
320 self
321 }
322
323 #[must_use]
324 pub fn advanced(mut self, advanced: AdvancedConfig) -> Self {
325 self.advanced = advanced;
326 self
327 }
328
329 #[must_use]
330 pub fn cookie_prefix(mut self, prefix: impl Into<String>) -> Self {
331 self.advanced.cookie_prefix = Some(prefix.into());
332 self
333 }
334
335 #[must_use]
336 pub const fn disable_csrf_check(mut self, disabled: bool) -> Self {
337 self.advanced.disable_csrf_check = Some(disabled);
338 self
339 }
340
341 #[must_use]
344 pub fn current_secret(&self) -> &str {
345 self.managed_secrets
346 .as_ref()
347 .map_or(self.secret.as_str(), ManagedSecrets::current_secret)
348 }
349
350 pub fn verification_secrets(&self) -> impl Iterator<Item = &str> {
352 let keys = self
353 .managed_secrets
354 .as_ref()
355 .map(secrets::ManagedSecrets::verification_secrets);
356 keys.into_iter().flatten().chain(
357 self.managed_secrets
358 .is_none()
359 .then_some(self.secret.as_str()),
360 )
361 }
362
363 #[must_use]
365 pub fn managed_secrets(mut self, secrets: ManagedSecrets) -> Self {
366 self.managed_secrets = Some(secrets);
367 self
368 }
369
370 #[must_use]
372 pub fn origin_check_disabled_for(&self, path: &str) -> bool {
373 if self.advanced.disable_origin_check {
374 return true;
375 }
376 let path = path
377 .strip_prefix(self.base_path.trim_end_matches('/'))
378 .filter(|suffix| suffix.starts_with('/'))
379 .unwrap_or(path)
380 .trim_end_matches('/');
381 self.advanced.disable_origin_check_paths.iter().any(|skip| {
382 let skip = skip.trim_end_matches('/');
383 path == skip
384 || path
385 .strip_prefix(skip)
386 .is_some_and(|suffix| suffix.starts_with('/'))
387 })
388 }
389
390 #[must_use]
392 pub fn current_origin_check_disabled(&self) -> bool {
393 crate::hooks::current_request_hook_context()
394 .map_or(self.advanced.disable_origin_check, |request| {
395 self.origin_check_disabled_for(&request.path)
396 })
397 }
398
399 #[must_use]
400 pub const fn disable_origin_check(mut self, disabled: bool) -> Self {
401 self.advanced.disable_origin_check = disabled;
402 self
403 }
404
405 #[must_use]
406 pub fn cross_sub_domain_cookies(mut self, domain: impl Into<String>) -> Self {
407 self.advanced.cross_sub_domain_cookies = Some(CrossSubDomainConfig {
408 domain: domain.into(),
409 });
410 self
411 }
412
413 #[must_use]
418 pub fn cross_sub_domain_cookies_from_base_url(mut self) -> Self {
419 self.advanced.cross_sub_domain_cookies = Some(CrossSubDomainConfig::default());
420 self
421 }
422
423 #[must_use]
430 pub fn is_origin_trusted(&self, origin: &str) -> bool {
431 if self.dynamic_base_url.is_none()
432 && let Some(base_origin) = extract_origin(&self.base_url)
433 && (origin == base_origin
434 || (self.base_url.split_once(':').is_some_and(|(scheme, _)| {
435 scheme.eq_ignore_ascii_case("http") || scheme.eq_ignore_ascii_case("https")
436 }) && extract_origin(origin).as_deref() == Some(base_origin.as_str())))
437 {
438 return true;
439 }
440 self.trusted_origins
441 .iter()
442 .any(|pattern| origin::matches_origin(origin, pattern))
443 }
444
445 #[must_use]
454 pub fn is_redirect_target_trusted(&self, url: &str) -> bool {
455 is_safe_relative_path(url) || self.is_origin_trusted(url)
456 }
457
458 #[must_use]
460 pub fn is_path_disabled(&self, path: &str) -> bool {
461 self.disabled_paths.iter().any(|disabled| disabled == path)
462 }
463
464 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]
575 fn extract_origin_with_path() {
576 assert_eq!(
577 extract_origin("https://example.com/path"),
578 Some("https://example.com".to_owned())
579 );
580 }
581
582 #[test]
584 fn extract_origin_without_path() {
585 assert_eq!(
586 extract_origin("https://example.com"),
587 Some("https://example.com".to_owned())
588 );
589 }
590
591 #[test]
593 fn extract_origin_with_port() {
594 assert_eq!(
595 extract_origin("http://localhost:3000/api"),
596 Some("http://localhost:3000".to_owned())
597 );
598 }
599
600 #[test]
602 fn extract_origin_with_query() {
603 assert_eq!(
604 extract_origin("https://example.com?foo=bar"),
605 Some("https://example.com".to_owned())
606 );
607 }
608
609 #[test]
611 fn extract_origin_with_fragment() {
612 assert_eq!(
613 extract_origin("https://example.com#fragment"),
614 Some("https://example.com".to_owned())
615 );
616 }
617
618 #[test]
620 fn extract_origin_no_scheme() {
621 assert_eq!(extract_origin("example.com"), None);
622 }
623
624 #[test]
626 fn new_config_sets_secret() {
627 let cfg = AuthConfig::new("a]secret-that-is-at-least-32-characters-long");
628 assert_eq!(cfg.secret, "a]secret-that-is-at-least-32-characters-long");
629 }
630
631 #[test]
633 fn new_config_uses_defaults() {
634 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567");
635 assert_eq!(cfg.app_name, "Better Auth");
636 assert_eq!(cfg.base_url, "http://localhost:3000");
637 assert_eq!(cfg.base_path, "/api/auth");
638 assert_eq!(cfg.trusted_origins, Vec::<String>::new());
639 }
640
641 #[test]
643 fn base_url_sets_cookie_secure_for_https() {
644 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567").base_url("https://myapp.com");
645 assert!(cfg.session.cookie_secure);
646 }
647
648 #[test]
650 fn base_url_clears_cookie_secure_for_http() {
651 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567")
652 .base_url("https://myapp.com")
653 .base_url("http://localhost:3000");
654 assert!(!cfg.session.cookie_secure);
655 }
656
657 #[test]
659 fn builder_chaining() {
660 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567")
661 .app_name("MyApp")
662 .base_path("/auth")
663 .disable_csrf_check(true)
664 .disable_origin_check(true)
665 .cookie_prefix("myapp");
666
667 assert_eq!(cfg.app_name, "MyApp");
668 assert_eq!(cfg.base_path, "/auth");
669 assert_eq!(cfg.advanced.disable_csrf_check, Some(true));
670 assert!(cfg.advanced.disable_origin_check);
671 assert_eq!(cfg.advanced.cookie_prefix, Some("myapp".to_owned()));
672 }
673
674 #[test]
676 fn trusted_origin_appends() {
677 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567")
678 .trusted_origin("https://a.com")
679 .trusted_origin("https://b.com");
680 assert_eq!(cfg.trusted_origins.len(), 2);
681 }
682
683 #[test]
685 fn trusted_origins_replaces() {
686 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567")
687 .trusted_origin("https://old.com")
688 .trusted_origins(vec!["https://new.com".to_owned()]);
689 assert_eq!(cfg.trusted_origins, vec!["https://new.com"]);
690 }
691
692 #[test]
694 fn disabled_path_appends() {
695 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567")
696 .disabled_path("/admin")
697 .disabled_path("/debug");
698 assert_eq!(cfg.disabled_paths.len(), 2);
699 }
700
701 #[test]
703 fn disabled_paths_replaces() {
704 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567")
705 .disabled_path("/old")
706 .disabled_paths(vec!["/new".to_owned()]);
707 assert_eq!(cfg.disabled_paths, vec!["/new"]);
708 }
709
710 #[test]
712 fn is_origin_trusted_matches_base_url() {
713 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567").base_url("https://myapp.com");
714 assert!(cfg.is_origin_trusted("https://myapp.com"));
715 let native_custom =
716 AuthConfig::new("test-secret-min-32-chars-1234567").base_url("myapp://auth");
717 assert!(native_custom.is_origin_trusted("myapp://auth"));
718 assert!(!native_custom.is_origin_trusted("myapp://auth/callback"));
719 }
720
721 #[test]
723 fn is_origin_trusted_rejects_unknown() {
724 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567").base_url("https://myapp.com");
725 assert!(!cfg.is_origin_trusted("https://evil.com"));
726 }
727
728 #[test]
730 fn is_origin_trusted_glob_pattern() {
731 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567")
732 .trusted_origin("https://*.example.com");
733 assert!(cfg.is_origin_trusted("https://sub.example.com"));
734 assert!(!cfg.is_origin_trusted("https://other.com"));
735 }
736
737 #[test]
739 fn redirect_target_allows_relative_path() {
740 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567").base_url("https://myapp.com");
741 assert!(cfg.is_redirect_target_trusted("/dashboard"));
742 assert!(cfg.is_redirect_target_trusted("/callback?foo=bar"));
743 }
744
745 #[test]
747 fn redirect_target_allows_same_origin() {
748 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567").base_url("https://myapp.com");
749 assert!(cfg.is_redirect_target_trusted("https://myapp.com/verified"));
750 }
751
752 #[test]
754 fn redirect_target_allows_trusted_origin() {
755 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567")
756 .base_url("https://myapp.com")
757 .trusted_origin("https://trusted.com");
758 assert!(cfg.is_redirect_target_trusted("https://trusted.com/path"));
759 }
760
761 #[test]
763 fn redirect_target_rejects_untrusted_origin() {
764 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567").base_url("https://myapp.com");
765 assert!(!cfg.is_redirect_target_trusted("https://evil.com/phish"));
766 }
767
768 #[test]
770 fn redirect_target_rejects_protocol_relative() {
771 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567").base_url("https://myapp.com");
772 assert!(!cfg.is_redirect_target_trusted("//evil.com"));
773 }
774
775 #[test]
777 fn is_path_disabled_matches() {
778 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567").disabled_path("/admin");
779 assert!(cfg.is_path_disabled("/admin"));
780 assert!(!cfg.is_path_disabled("/user"));
781 }
782
783 #[test]
785 fn validate_rejects_empty_secret() {
786 let cfg = AuthConfig::default();
787 assert!(cfg.validate().is_err());
788 }
789
790 #[test]
792 fn validate_rejects_short_secret() {
793 let cfg = AuthConfig::new("short");
794 assert!(cfg.validate().is_err());
795 }
796
797 #[test]
799 fn validate_accepts_valid_secret() {
800 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567");
801 assert!(cfg.validate().is_ok());
802 }
803
804 #[test]
806 fn session_config_defaults() {
807 let s = SessionConfig::default();
808 assert_eq!(s.expires_in, Duration::hours(24 * 7));
809 assert_eq!(s.update_age, Some(Duration::hours(24)));
810 assert!(!s.disable_session_refresh);
811 assert_eq!(s.cookie_name, "better-auth.session_token");
812 assert!(s.cookie_http_only);
813 assert_eq!(s.cookie_same_site, SameSite::Lax);
814 }
815
816 #[test]
818 fn jwt_config_defaults() {
819 let j = JwtConfig::default();
820 assert_eq!(j.expires_in, Duration::hours(24));
821 assert_eq!(j.algorithm, "HS256");
822 }
823
824 #[test]
826 fn password_config_defaults() {
827 let p = PasswordConfig::default();
828 assert!(!p.require_uppercase);
829 }
830
831 #[test]
833 fn same_site_display() {
834 assert_eq!(SameSite::Strict.to_string(), "Strict");
835 assert_eq!(SameSite::Lax.to_string(), "Lax");
836 assert_eq!(SameSite::None.to_string(), "None");
837 }
838
839 #[test]
841 fn cookie_cache_config_defaults() {
842 let c = CookieCacheConfig::default();
843 assert!(!c.enabled);
844 assert!((c.max_age - 300.0).abs() < f64::EPSILON);
845 assert_eq!(c.strategy, CookieCacheStrategy::Compact);
846 }
847
848 #[test]
850 fn account_config_defaults() {
851 let a = AccountConfig::default();
852 assert!(a.update_account_on_sign_in);
853 assert!(!a.encrypt_oauth_tokens);
854 assert!(a.account_linking.enabled);
855 }
856
857 #[test]
859 fn core_paths_error_page() {
860 let html = crate::error::page::error_page_html("TEST_ERROR");
861 assert!(html.contains("TEST_ERROR"));
862 assert!(html.contains("Ask AI"));
863 assert!(html.contains("<title>Error</title>"));
864 }
865
866 #[test]
868 fn error_page_sanitizes_script_tag() {
869 let html = crate::error::page::error_page_html("<script>alert(1)</script>");
870 assert!(html.contains("UNKNOWN"));
871 assert!(!html.contains("<script>"));
872 }
873
874 #[test]
876 fn error_page_allows_valid_codes() {
877 assert!(crate::error::page::error_page_html("SOME_ERROR-CODE").contains("SOME_ERROR-CODE"));
878 assert!(crate::error::page::error_page_html("it's").contains("it's"));
879 }
880
881 #[test]
883 fn session_builder_methods() {
884 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567")
885 .session_expires_in(Duration::hours(1))
886 .session_update_age(Duration::minutes(30))
887 .disable_session_refresh(true)
888 .session_fresh_age(Duration::minutes(5));
889
890 assert_eq!(cfg.session.expires_in, Duration::hours(1));
891 assert_eq!(cfg.session.update_age, Some(Duration::minutes(30)));
892 assert!(cfg.session.disable_session_refresh);
893 assert_eq!(cfg.session.fresh_age, Some(Duration::minutes(5)));
894 }
895
896 #[test]
898 fn session_cookie_cache_builder() {
899 let cache = CookieCacheConfig {
900 enabled: true,
901 max_age: 600.0,
902 strategy: CookieCacheStrategy::Jwt,
903 ..CookieCacheConfig::default()
904 };
905 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567").session_cookie_cache(cache);
906
907 let cc = cfg.session.cookie_cache.as_ref();
908 assert!(cc.is_some());
909 let cc = cc.unwrap();
910 assert!(cc.enabled);
911 assert_eq!(cc.strategy, CookieCacheStrategy::Jwt);
912 }
913
914 #[test]
916 fn cross_sub_domain_cookies_builder() {
917 let cfg = AuthConfig::new("test-secret-min-32-chars-1234567")
918 .cross_sub_domain_cookies(".example.com");
919 let csd = cfg.advanced.cross_sub_domain_cookies.as_ref();
920 assert!(csd.is_some());
921 assert_eq!(csd.unwrap().domain, ".example.com");
922 }
923
924 #[test]
926 fn advanced_database_defaults() {
927 let d = AdvancedDatabaseConfig::default();
928 assert_eq!(d.default_find_many_limit, 100);
929 assert!(!d.use_number_id);
930 }
931
932 #[test]
934 fn ip_address_config_defaults() {
935 let ip = IpAddressConfig::default();
936 assert_eq!(ip.headers, vec!["x-forwarded-for"]);
937 assert!(!ip.disable_ip_tracking);
938 }
939}
940