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