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