1use crate::plugin::MetadataMap;
2use crate::session::SessionRequest;
3use crate::wire::{InvitationView, SessionView, UserView};
4use crate::{
5 AdapterOutput, AdapterRecord, AuthAccount, AuthConfig, AuthError, AuthInvitation, AuthRequest,
6 AuthResult, AuthSchema, AuthSession, AuthStore, AuthUser, AuthenticatedUser, ContextExtensions,
7 EmailProvider, SessionManager, VerificationEmailOverrideHandle,
8};
9use std::collections::BTreeMap;
10use std::sync::Arc;
11
12pub struct AuthContext<S: AuthSchema> {
14 pub config: Arc<AuthConfig>,
15 pub database: Arc<dyn AuthStore<S>>,
16 pub email_provider: Option<Arc<dyn EmailProvider>>,
17 pub metadata: MetadataMap,
18 pub extensions: ContextExtensions,
19}
20
21fn insert_null(fields: &mut BTreeMap<String, serde_json::Value>, name: &str) {
22 _ = fields.insert(name.to_owned(), serde_json::Value::Null);
23}
24
25impl<S: AuthSchema> Clone for AuthContext<S> {
26 fn clone(&self) -> Self {
27 Self {
28 config: Arc::clone(&self.config),
29 database: Arc::clone(&self.database),
30 email_provider: self.email_provider.clone(),
31 metadata: self.metadata.clone(),
32 extensions: self.extensions.clone(),
33 }
34 }
35}
36
37impl<S: AuthSchema> AuthContext<S> {
38 pub fn parse_user_fields(
41 &self,
42 input: &indexmap::IndexMap<String, crate::utils::json::JsValue>,
43 creation: bool,
44 ) -> Result<crate::field_policy::FieldValues, crate::field_policy::FieldInputError> {
45 let registered = self.extensions.get::<crate::field_policy::UserFields>();
46 let configured =
47 crate::field_policy::SessionFields(self.config.user.additional_fields.clone());
48 let fields = registered.as_ref().map_or(&configured, |fields| &fields.0);
49 if creation {
50 fields.parse_create(input)
51 } else {
52 fields.parse_update(input)
53 }
54 }
55
56 pub async fn hash_password(
61 &self,
62 hasher: Option<&Arc<dyn crate::utils::password::PasswordHasher>>,
63 password: &str,
64 ) -> AuthResult<String> {
65 let context = crate::endpoint::current_endpoint_call_context().map_or_else(
66 || {
67 crate::hooks::current_request_hook_context()
68 .map(crate::utils::password::PasswordHashContext::from_request)
69 },
70 |call| {
71 Some(crate::utils::password::PasswordHashContext {
72 path: call.path().map(str::to_owned),
73 request: call
74 .request()
75 .map(crate::hooks::RequestHookContext::from_request),
76 })
77 },
78 );
79 self.hash_password_with_context(hasher, password, context.as_ref())
80 .await
81 }
82
83 pub async fn hash_password_with_context(
89 &self,
90 hasher: Option<&Arc<dyn crate::utils::password::PasswordHasher>>,
91 password: &str,
92 context: Option<&crate::utils::password::PasswordHashContext>,
93 ) -> AuthResult<String> {
94 if let Some(hooks) = self
95 .extensions
96 .get::<crate::utils::password::PasswordHashHooks>()
97 {
98 for hook in hooks.0.iter().rev() {
99 hook.before_hash(password, context).await?;
100 }
101 }
102 crate::utils::password::hash_password(hasher, password).await
103 }
104
105 #[must_use]
106 pub fn new(config: Arc<AuthConfig>, database: Arc<dyn AuthStore<S>>) -> Self {
107 Self::with_metadata(config, database, MetadataMap::new())
108 }
109
110 #[must_use]
111 pub fn with_metadata(
112 config: Arc<AuthConfig>,
113 database: Arc<dyn AuthStore<S>>,
114 metadata: MetadataMap,
115 ) -> Self {
116 let email_provider = config.email_provider.clone();
117 Self {
118 config,
119 database,
120 email_provider,
121 metadata,
122 extensions: ContextExtensions::default(),
123 }
124 }
125
126 pub fn set_metadata(&mut self, key: impl Into<String>, value: serde_json::Value) {
127 _ = self.metadata.insert(key.into(), value);
128 }
129
130 #[must_use]
131 pub fn get_metadata(&self, key: &str) -> Option<&serde_json::Value> {
132 self.metadata.get(key)
133 }
134
135 #[must_use]
136 pub fn with_extensions(mut self, extensions: ContextExtensions) -> Self {
137 self.extensions = extensions;
138 self
139 }
140
141 #[must_use]
142 pub fn email_verification_override(&self) -> Option<Arc<VerificationEmailOverrideHandle<S>>> {
143 self.extensions.get()
144 }
145
146 pub fn user_view(&self, user: &impl AuthUser) -> UserView {
147 self.project_user_view(user, true)
148 }
149
150 pub fn trusted_user_view(&self, user: &impl AuthUser) -> UserView {
153 self.project_user_view(user, false)
154 }
155
156 pub(in crate::plugin) fn project_user_view(
157 &self,
158 user: &impl AuthUser,
159 public: bool,
160 ) -> UserView {
161 if let Some(view) = user.retained_user_view() {
162 let mut view = view.clone();
163 if public {
164 let registered = self.extensions.get::<crate::field_policy::UserFields>();
165 let fields = registered
166 .as_ref()
167 .map_or(&self.config.user.additional_fields, |fields| &fields.0.0);
168 view.extension_fields
169 .retain(|name, _| fields.get(name).is_none_or(|field| field.returned));
170 }
171 return view;
172 }
173 let mut view = UserView::from(user);
174 if self.feature_enabled("username.enabled") {
175 for (key, absent) in [
176 ("username", view.username.is_none()),
177 ("displayUsername", view.display_username.is_none()),
178 ] {
179 if absent {
180 insert_null(&mut view.extension_fields, key);
181 }
182 }
183 } else {
184 view.username = None;
185 view.display_username = None;
186 }
187 if self.feature_enabled("two_factor.enabled") {
188 view.two_factor_enabled = user.two_factor_enabled_value();
189 if view.two_factor_enabled.is_none() {
190 insert_null(&mut view.extension_fields, "twoFactorEnabled");
191 }
192 } else {
193 view.two_factor_enabled = None;
194 }
195 if self.feature_enabled("admin.enabled") {
196 view.banned = user.banned_value();
197 for (key, absent) in [
198 ("role", view.role.is_none()),
199 ("banned", view.banned.is_none()),
200 ("banReason", view.ban_reason.is_none()),
201 ("banExpires", view.ban_expires.is_none()),
202 ] {
203 if absent {
204 insert_null(&mut view.extension_fields, key);
205 }
206 }
207 } else {
208 view.role = None;
209 view.banned = None;
210 view.ban_reason = None;
211 view.ban_expires = None;
212 }
213
214 if self.feature_enabled("anonymous.enabled") {
215 view.is_anonymous = Some(user.is_anonymous().unwrap_or(false));
216 } else {
217 view.is_anonymous = None;
218 }
219 if self.feature_enabled("phone-number.enabled") {
220 if view.phone_number.is_none() {
221 insert_null(&mut view.extension_fields, "phoneNumber");
222 }
223 if view.phone_number_verified.is_none() {
224 insert_null(&mut view.extension_fields, "phoneNumberVerified");
225 }
226 } else {
227 view.phone_number = None;
228 view.phone_number_verified = None;
229 }
230 if self.feature_enabled("last-login-method.enabled") {
231 if view.last_login_method.is_none() {
232 insert_null(&mut view.extension_fields, "lastLoginMethod");
233 }
234 } else {
235 view.last_login_method = None;
236 }
237 let registered = self.extensions.get::<crate::field_policy::UserFields>();
238 let fields = registered
239 .as_ref()
240 .map_or(&self.config.user.additional_fields, |fields| &fields.0.0);
241 let physical = user.additional_fields();
242 let values = user
243 .adapter_snapshot()
244 .map_or(&physical, AdapterOutput::values);
245 for (name, field) in fields {
246 _ = view.extension_fields.remove(name);
247 if (!public || field.returned)
248 && let Some(value) = values.get(name).or_else(|| {
249 field
250 .field_name
251 .as_ref()
252 .and_then(|physical| values.get(physical))
253 })
254 {
255 _ = view.extension_fields.insert(name.clone(), value.clone());
256 }
257 }
258 if let Some(snapshot) = user.adapter_snapshot() {
259 for name in [
262 "id",
263 "name",
264 "email",
265 "emailVerified",
266 "image",
267 "createdAt",
268 "updatedAt",
269 "username",
270 "displayUsername",
271 "twoFactorEnabled",
272 "role",
273 "banned",
274 "banReason",
275 "banExpires",
276 "isAnonymous",
277 "phoneNumber",
278 "phoneNumberVerified",
279 "lastLoginMethod",
280 ] {
281 if !snapshot.contains_field(name) || snapshot.field_is_undefined(name) {
282 _ = view.omitted_fields.insert(name.into());
283 }
284 }
285 }
286 if self.config.session.stateless {
287 for (name, absent) in [
288 ("image", view.image.is_none()),
289 ("username", view.username.is_none()),
290 ("displayUsername", view.display_username.is_none()),
291 ("banReason", view.ban_reason.is_none()),
292 ("banExpires", view.ban_expires.is_none()),
293 ] {
294 if absent
295 && view
296 .extension_fields
297 .get(name)
298 .is_none_or(serde_json::Value::is_null)
299 {
300 _ = view.omitted_fields.insert(name.into());
301 }
302 }
303 }
304 view
305 }
306
307 pub(crate) async fn user_adapter_record(
309 &self,
310 user: S::User,
311 ) -> AuthResult<AdapterRecord<S::User>> {
312 use crate::AuthUser;
313 let registered = self
314 .extensions
315 .get::<crate::field_policy::AdapterFieldPolicies>();
316 let fields = registered.as_ref().map_or_else(
317 || {
318 crate::field_policy::SessionAdapterFields(Arc::new(
319 self.config.user.additional_fields.clone(),
320 ))
321 },
322 |fields| fields.user.clone(),
323 );
324 let output = fields
325 .record_output(
326 serde_json::to_value(
327 user.retained_user_view()
328 .cloned()
329 .unwrap_or_else(|| UserView::from(&user)),
330 )?,
331 user.additional_fields(),
332 serde_json::to_value(self.trusted_user_view(&user))?,
333 )
334 .await?;
335 Ok(AdapterRecord::with_output(user, output))
336 }
337
338 #[must_use]
341 pub fn filter_user_record(&self, record: AdapterRecord<S::User>) -> AdapterRecord<S::User> {
342 let registered = self.extensions.get::<crate::field_policy::UserFields>();
343 let fields = registered
344 .as_ref()
345 .map_or(&self.config.user.additional_fields, |fields| &fields.0.0);
346 let output = record.raw_snapshot().filter_returned(fields);
347 AdapterRecord::with_output(record.into_stored(), output)
348 }
349
350 pub fn account_view(
356 &self,
357 account: &impl AuthAccount,
358 ) -> AuthResult<serde_json::Map<String, serde_json::Value>> {
359 let serde_json::Value::Object(mut view) =
360 serde_json::to_value(crate::wire::AccountView::from(account))?
361 else {
362 return Err(AuthError::internal("Account view must be an object"));
363 };
364 let registered = self.extensions.get::<crate::field_policy::AccountFields>();
365 let fields = registered
366 .as_ref()
367 .map_or(&self.config.account.additional_fields, |fields| &fields.0.0);
368 let physical = account.additional_fields();
369 let values = account
370 .adapter_snapshot()
371 .map_or(&physical, AdapterOutput::values);
372 for (name, field) in fields {
373 _ = view.remove(name);
374 if field.returned
375 && let Some(value) = values.get(name).or_else(|| {
376 field
377 .field_name
378 .as_ref()
379 .and_then(|physical| values.get(physical))
380 })
381 {
382 _ = view.insert(name.clone(), value.clone());
383 }
384 }
385 for credential in [
386 "accessToken",
387 "refreshToken",
388 "idToken",
389 "accessTokenExpiresAt",
390 "refreshTokenExpiresAt",
391 "password",
392 ] {
393 _ = view.remove(credential);
394 }
395 Ok(view)
396 }
397
398 pub fn session_view(&self, session: &impl AuthSession) -> SessionView {
399 self.project_session_view(session, true)
400 }
401
402 pub fn trusted_session_view(&self, session: &impl AuthSession) -> SessionView {
403 self.project_session_view(session, false)
404 }
405
406 pub(in crate::plugin) fn project_session_view(
407 &self,
408 session: &impl AuthSession,
409 public: bool,
410 ) -> SessionView {
411 if let Some(view) = session.retained_session_view() {
412 let mut view = view.clone();
413 if public {
414 let registered = self.extensions.get::<crate::field_policy::SessionFields>();
415 let fields = registered
416 .as_ref()
417 .map_or(&self.config.session.additional_fields, |fields| &fields.0);
418 for (name, field) in fields {
419 if !field.returned {
420 _ = view.omitted_fields.insert(name.clone());
421 }
422 }
423 }
424 return view;
425 }
426 let mut view = SessionView::from(session);
427 let registered = self.extensions.get::<crate::field_policy::SessionFields>();
428 let fields = registered
429 .as_ref()
430 .map_or(&self.config.session.additional_fields, |fields| &fields.0);
431 view.extension_fields
432 .retain(|name, _| fields.contains_key(name));
433 if let Some(output) = session.adapter_snapshot().map(AdapterOutput::values) {
434 for name in fields.keys() {
435 _ = view.extension_fields.remove(name);
436 if let Some(value) = output.get(name) {
437 _ = view.extension_fields.insert(name.clone(), value.clone());
438 } else {
439 _ = view.omitted_fields.insert(name.clone());
440 }
441 }
442 }
443 for (name, field) in fields {
444 if public && !field.returned {
445 _ = view.omitted_fields.insert(name.clone());
446 }
447 }
448 let declared = |name: &str| fields.contains_key(name);
449 if self.feature_enabled("admin.enabled") || declared("impersonatedBy") {
450 if view.impersonated_by.is_none() {
451 insert_null(&mut view.extension_fields, "impersonatedBy");
452 }
453 } else {
454 view.impersonated_by = None;
455 }
456 if self.feature_enabled("organization.enabled") || declared("activeOrganizationId") {
457 if view.active_organization_id.is_none() {
458 insert_null(&mut view.extension_fields, "activeOrganizationId");
459 }
460 } else {
461 view.active_organization_id = None;
462 }
463
464 if self.feature_enabled("organization.teams.enabled") || declared("activeTeamId") {
465 if view.active_team_id.is_none() {
466 insert_null(&mut view.extension_fields, "activeTeamId");
467 }
468 } else {
469 view.active_team_id = None;
470 }
471 if self.config.session.stateless {
472 for (name, absent) in [
473 (
474 "activeOrganizationId",
475 view.active_organization_id.is_none(),
476 ),
477 ("impersonatedBy", view.impersonated_by.is_none()),
478 ] {
479 if absent
480 && view
481 .extension_fields
482 .get(name)
483 .is_none_or(serde_json::Value::is_null)
484 {
485 _ = view.omitted_fields.insert(name.into());
486 }
487 }
488 }
489 view
490 }
491
492 pub fn invitation_view(&self, invitation: &impl AuthInvitation) -> InvitationView {
493 let mut view = InvitationView::from(invitation);
494 if self.feature_enabled("organization.teams.enabled") {
495 if view.team_id.is_none() {
496 insert_null(&mut view.extension_fields, "teamId");
497 }
498 } else {
499 view.team_id = None;
500 }
501 view
502 }
503
504 pub(in crate::plugin) fn feature_enabled(&self, key: &str) -> bool {
505 self.metadata
506 .get(key)
507 .and_then(serde_json::Value::as_bool)
508 .unwrap_or(false)
509 }
510
511 pub fn email_provider(&self) -> AuthResult<&dyn EmailProvider> {
517 self.email_provider
518 .as_deref()
519 .ok_or_else(|| AuthError::config("No email provider configured"))
520 }
521
522 #[must_use]
524 pub fn session_manager(&self) -> SessionManager<S> {
525 SessionManager::new(Arc::clone(&self.config), Arc::clone(&self.database))
526 }
527
528 pub async fn require_session(
538 &self,
539 req: &impl SessionRequest,
540 ) -> AuthResult<(S::User, SessionView)> {
541 let (user, session, _) = self.require_session_with_refresh_state(req).await?;
542 Ok((user, session))
543 }
544
545 pub async fn require_cached_session(
552 &self,
553 req: &impl SessionRequest,
554 ) -> AuthResult<(AuthenticatedUser<S>, SessionView)> {
555 let read = crate::session::cookie_cache::runtime::authenticated(self, req, false)
556 .await
557 .map_err(|_| AuthError::Unauthenticated)?
558 .ok_or(AuthError::Unauthenticated)?;
559 Ok((read.user, read.session))
560 }
561
562 pub async fn require_cached_session_strict(
571 &self,
572 req: &impl SessionRequest,
573 ) -> AuthResult<(AuthenticatedUser<S>, SessionView)> {
574 let read = crate::session::cookie_cache::runtime::authenticated_with(
575 self,
576 req,
577 false,
578 crate::session::cookie_cache::runtime::CacheDecoding::MalformedIsMiss,
579 )
580 .await
581 .map_err(|error| {
582 if error.status_code() == 401 {
583 AuthError::Unauthenticated
584 } else {
585 error
586 }
587 })?
588 .ok_or(AuthError::Unauthenticated)?;
589 Ok((read.user, read.session))
590 }
591
592 pub async fn require_session_with_refresh_state(
600 &self,
601 req: &impl SessionRequest,
602 ) -> AuthResult<(S::User, SessionView, Option<bool>)> {
603 self.authenticated_session(req, true).await
604 }
605
606 pub async fn require_authoritative_cached_session(
610 &self,
611 req: &AuthRequest,
612 ) -> AuthResult<(AuthenticatedUser<S>, SessionView)> {
613 self.require_cached_session(&self.physical_request(req))
614 .await
615 }
616
617 pub async fn require_authoritative_session(
625 &self,
626 req: &impl SessionRequest,
627 ) -> AuthResult<(S::User, SessionView)> {
628 crate::session::cookie_cache::runtime::clear_established_session::<S>(req);
629 let (user, session, _) = self.authenticated_session(req, false).await?;
630 Ok((user, session))
631 }
632
633 pub async fn require_authoritative_session_record(
639 &self,
640 req: &AuthRequest,
641 ) -> AuthResult<(AdapterRecord<S::User>, SessionView)> {
642 let physical = self.physical_request(req);
643 let read = crate::session::cookie_cache::runtime::authenticated(self, &physical, false)
644 .await?
645 .ok_or(AuthError::Unauthenticated)?;
646 match read.user {
647 AuthenticatedUser::Stored(user) => Ok((user, read.session)),
648 AuthenticatedUser::Cached(user) if self.config.session.stateless => {
649 let user = S::user_from_cookie_cache(*user).ok_or_else(|| {
650 AuthError::config("This schema requires cache-aware session authority")
651 })?;
652 Ok((AdapterRecord::physical(user)?, read.session))
653 }
654 AuthenticatedUser::Cached(_) => Err(AuthError::Unauthenticated),
655 }
656 }
657
658 pub async fn session_user(&self, session: &impl AuthSession) -> AuthResult<Option<S::User>> {
664 use crate::AuthUser;
665 if let Some(user) = self.database.get_session_user(session.token()).await? {
666 if user.id() != session.user_id() {
667 return Err(AuthError::Unauthenticated);
668 }
669 return Ok(Some(user));
670 }
671 if self.config.session.secondary_storage.is_some()
672 && (!self.config.session.store_in_database || self.config.session.preserve_in_database)
673 {
674 return Ok(None);
675 }
676 self.database
677 .get_user_by_id(session.user_id().as_ref())
678 .await
679 }
680
681 fn physical_request(&self, req: &AuthRequest) -> AuthRequest {
684 crate::session::cookie_cache::runtime::clear_established_session::<S>(req);
685 let mut physical = req.clone();
686 physical.virtual_session = None;
687 if self.config.session.has_server_session_store() {
688 _ = physical
689 .query
690 .insert("disableCookieCache".into(), "true".into());
691 }
692 physical
693 }
694
695 pub(in crate::plugin) async fn authenticated_session(
696 &self,
697 req: &impl SessionRequest,
698 allow_virtual: bool,
699 ) -> AuthResult<(S::User, SessionView, Option<bool>)> {
700 if allow_virtual && let Some(session) = req.virtual_session(self) {
701 let user = if let Some(user) = req.authenticated_user::<S>(self) {
702 user
703 } else {
704 self.database
705 .get_user_by_id(&session.user_id)
706 .await?
707 .ok_or(AuthError::Unauthenticated)?
708 };
709 return Ok((user, session.clone(), None));
710 }
711 if self.config.session.stateless
712 && let Some(cache) = crate::session::cookie_cache::runtime::read(self, req).await?
713 {
714 crate::session::cookie_cache::runtime::renew_cache(self, req, &cache).await?;
715 let user = S::user_from_cookie_cache(cache.user).ok_or_else(|| {
716 AuthError::config("This schema requires cache-aware session authority")
717 })?;
718 return Ok((user, cache.session, None));
719 }
720 let session_manager = self.session_manager();
721
722 let suppressed = session_manager.request_disables_refresh(req);
723 let skip = req
724 .extensions()
725 .get::<crate::session::SessionRefreshSuppressed>()
726 .is_some();
727 let options = crate::session::SessionReadOptions {
728 allow_refresh: !suppressed && !self.config.session.defer_session_refresh && !skip,
729 cleanup_expired: !self.config.session.defer_session_refresh,
730 };
731 let Some(token) = session_manager.extract_session_token(req) else {
732 return Err(AuthError::Unauthenticated);
733 };
734 let read = session_manager
735 .read_session(&token, options)
736 .await
737 .map_err(|_| AuthError::Unauthenticated)?;
738 let Some(session) = read.session else {
739 self.queue_session_cleanup(req)?;
740 return Err(AuthError::Unauthenticated);
741 };
742 let user = self
743 .session_user(&session)
744 .await
745 .map_err(|_| AuthError::Unauthenticated)?;
746 let Some(user) = user else {
747 self.queue_session_cleanup(req)?;
748 return Err(AuthError::Unauthenticated);
749 };
750 if read.refreshed {
751 req.queue_response_header(
752 "Set-Cookie",
753 crate::utils::cookie_utils::create_session_cookie(session.token(), &self.config)?,
754 );
755 }
756 Ok((
757 user,
758 self.session_view(&session),
759 (self.config.session.defer_session_refresh && !suppressed)
760 .then_some(read.needs_refresh && !skip),
761 ))
762 }
763
764 pub(in crate::plugin) fn queue_session_cleanup(
765 &self,
766 req: &impl SessionRequest,
767 ) -> AuthResult<()> {
768 for cookie in crate::utils::cookie_utils::delete_session_cookie_headers(&self.config)? {
769 req.queue_response_header("Set-Cookie", cookie);
770 }
771 Ok(())
772 }
773
774 pub async fn session_without_refresh(
782 &self,
783 req: &impl SessionRequest,
784 ) -> AuthResult<Option<(S::User, SessionView)>> {
785 if let Some(session) = req.virtual_session(self) {
786 if let Some(user) = req.authenticated_user::<S>(self) {
787 return Ok(Some((user, session)));
788 }
789 return Ok(self
790 .database
791 .get_user_by_id(&session.user_id)
792 .await?
793 .map(|user| (user, session.clone())));
794 }
795 self.persistent_session(req).await
796 }
797
798 pub async fn persistent_session(
804 &self,
805 req: &impl SessionRequest,
806 ) -> AuthResult<Option<(S::User, SessionView)>> {
807 if self.config.session.stateless
808 && let Some(cache) = crate::session::cookie_cache::runtime::read(self, req).await?
809 {
810 let user = S::user_from_cookie_cache(cache.user).ok_or_else(|| {
811 AuthError::config("This schema requires cache-aware session authority")
812 })?;
813 return Ok(Some((user, cache.session)));
814 }
815 let Some(token) = self.session_manager().extract_session_token(req) else {
816 return Ok(None);
817 };
818 let Some(session) = self.database.get_session(&token).await? else {
819 return Ok(None);
820 };
821 if !session.active() || session.expires_at() <= chrono::Utc::now() {
822 return Ok(None);
823 }
824 let user = self.session_user(&session).await?;
825 let Some(user) = user else {
826 return Ok(None);
827 };
828 Ok(Some((user, self.session_view(&session))))
829 }
830}
831
832impl<S: AuthSchema> std::fmt::Debug for AuthContext<S> {
833 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
834 f.debug_struct("AuthContext").finish_non_exhaustive()
835 }
836}