1use crate::api;
3use crate::api::OpenError;
4use crate::attr::{Attr, AttrState};
5use crate::common::elgamal;
6use crate::common::secret::DigestibleSecret as _;
7use crate::handle;
8use crate::hub;
9use crate::id::Id;
10use crate::misc::crypto;
11use crate::misc::error::{OPAQUE, Opaque};
12use crate::misc::jwt;
13
14use std::collections::{HashMap, HashSet};
15use std::ops::Deref;
16use std::rc::Rc;
17
18use actix_web::web;
19use sha2::digest::Digest as _;
20
21use super::server::*;
22use api::phc::user::*;
23
24use api::phc::user::UserState as ApiUserState;
25
26impl App {
27 pub(super) fn cached_handle_user_welcome(app: &Self) -> api::Result<WelcomeResp> {
29 let running_state = app.running_state_or_please_retry()?;
30
31 let hubs: HashMap<handle::Handle, hub::BasicInfo> = app
32 .shared
33 .hubs
34 .values()
35 .map(|hub| (hub.handles.preferred().clone(), hub.clone()))
36 .collect();
37
38 Ok(WelcomeResp {
39 constellation: (*running_state.constellation).clone(),
40 hubs,
41 })
42 }
43
44 pub(super) async fn handle_cached_hub_info(
46 app: web::Data<Rc<App>>,
47 ) -> impl actix_web::Responder {
48 app.cached_hub_info.borrow().clone()
49 }
50
51 pub(super) async fn handle_user_state(
53 app: Rc<Self>,
54 auth_token: actix_web::web::Header<AuthToken>,
55 ) -> api::Result<StateResp> {
56 let Ok((user_state, _)) = app
57 .open_auth_token_and_get_user_state(auth_token.into_inner())
58 .await?
59 else {
60 return Ok(StateResp::RetryWithNewAuthToken);
61 };
62
63 Ok(StateResp::State(user_state.into_user_version(&app)))
64 }
65
66 pub(super) async fn handle_user_enter(
68 app: Rc<Self>,
69 req: web::Json<EnterReq>,
70 auth_token: Option<actix_web::web::Header<AuthToken>>,
71 ) -> api::Result<EnterResp> {
72 let running_state = &app.running_state_or_please_retry()?;
73
74 let EnterReq {
75 identifying_attr,
76 mode,
77 add_attrs,
78 register_only_with_unique_attrs,
79 } = req.into_inner();
80
81 let auth_token_user_id = if let Some(auth_token) = auth_token {
82 let Ok(user_id) = app.open_auth_token(auth_token.into_inner()) else {
83 return Ok(EnterResp::RetryWithNewAuthToken);
84 };
85
86 if !matches!(mode, EnterMode::Login) {
87 log::debug!("a user tried to enter with an auth token, but not in the login mode");
88 return Err(api::ErrorCode::BadRequest);
89 }
90
91 Some(user_id)
92 } else {
93 None
94 };
95
96 if auth_token_user_id.is_none() && identifying_attr.is_none() {
97 log::debug!("entry request with neither auth token nor identifying attribute");
98 return Err(api::ErrorCode::BadRequest);
99 }
100
101 if register_only_with_unique_attrs {
102 match mode {
103 EnterMode::Login => {
104 log::debug!(
105 "entry request with `register_only_with_unique_attrs` set, but mode `Login`"
106 );
107 }
108 EnterMode::Register | EnterMode::LoginOrRegister => { }
109 }
110 }
111
112 let identifying_attr = if let Some(identifying_attr) = identifying_attr {
114 let identifying_attr = app.id_attr(
115 match identifying_attr.open(&running_state.attr_signing_key, None) {
116 Ok(identifying_attr) => identifying_attr,
117 Err(OpenError::OtherConstellation(..)) | Err(OpenError::InternalError) => {
118 return Err(api::ErrorCode::InternalError);
119 }
120 Err(OpenError::OtherwiseInvalid) => {
121 return Err(api::ErrorCode::BadRequest);
122 }
123 Err(OpenError::Expired) | Err(OpenError::InvalidSignature) => {
124 return Ok(EnterResp::RetryWithNewIdentifyingAttr);
125 }
126 },
127 );
128
129 if identifying_attr.not_identifying {
130 log::warn!(
131 "supposed attribute {} of type {} is not identifying",
132 identifying_attr.value,
133 identifying_attr.attr_type
134 );
135 return Err(api::ErrorCode::BadRequest);
136 }
137
138 Some(identifying_attr)
139 } else {
140 None
141 };
142
143 let attrs: HashMap<Id, IdedAttr> = {
144 let mut attrs: HashMap<Id, IdedAttr> = HashMap::with_capacity(add_attrs.len());
145
146 if let Some(ref identifying_attr) = identifying_attr {
147 attrs.insert(identifying_attr.id, identifying_attr.clone());
148 }
149
150 for (add_attr_index, add_attr) in add_attrs.into_iter().enumerate() {
151 let ided_attr =
152 app.id_attr(match add_attr.open(&running_state.attr_signing_key, None) {
153 Ok(attr) => attr,
154 Err(OpenError::OtherConstellation(..)) | Err(OpenError::InternalError) => {
155 return Err(api::ErrorCode::InternalError);
156 }
157 Err(OpenError::OtherwiseInvalid) => {
158 return Err(api::ErrorCode::BadRequest);
159 }
160 Err(OpenError::Expired) | Err(OpenError::InvalidSignature) => {
161 return Ok(EnterResp::RetryWithNewAddAttr {
162 index: add_attr_index,
163 });
164 }
165 });
166
167 if ided_attr.not_addable {
168 log::warn!(
169 "entry: someone tried to add unaddable attribute of type {}",
170 ided_attr.attr_type
171 );
172 return Err(api::ErrorCode::BadRequest);
173 }
174
175 let previous_value = attrs.insert(ided_attr.id, ided_attr);
176
177 if let Some(attr) = previous_value {
178 log::warn!(
179 "entry: attribute {} of type {} provided twice",
180 attr.value,
181 attr.attr_type
182 );
183 return Err(api::ErrorCode::BadRequest);
184 }
185 }
186
187 attrs
188 };
189
190 let mut attr_states: std::collections::HashMap<
192 Id,
193 (AttrState, object_store::UpdateVersion),
194 > = Default::default();
195
196 let mut retrieved_attr_states = false;
201
202 let mut attr_add_status: HashMap<Id, AttrAddStatus> = Default::default();
204
205 let ((user_state, mut user_state_version), new_account) = 'found_user: {
207 if let Some(auth_token_user_id) = auth_token_user_id {
208 let user_and_version = app
209 .get_object::<UserState>(&auth_token_user_id)
210 .await?
211 .ok_or_else(|| {
212 log::error!(
213 "a valid auth token passed during entry refers to a user with user_id {auth_token_user_id} that does not exist",
214 );
215 api::ErrorCode::InternalError
216 })?;
217
218 break 'found_user (user_and_version, false);
219 }
220
221 let identifying_attr = identifying_attr.expect(
222 "we should (but don't) have either an auth token or an identifying attribute",
223 );
224
225 if matches!(mode, EnterMode::Login | EnterMode::LoginOrRegister) {
226 if let Some((ias, ias_v)) =
228 app.get_object::<AttrState>(&identifying_attr.id).await?
229 {
230 log::trace!(
231 "enter: account exists for attribute {} of type {}",
232 identifying_attr.value,
233 identifying_attr.attr_type,
234 );
235
236 let user_id = ias.may_identify_user.ok_or_else(|| {
237 log::error!(
238 "identifying attribute {} of type {} has may_identify_user set to None",
239 identifying_attr.value,
240 identifying_attr.attr_type
241 );
242 api::ErrorCode::InternalError
243 })?;
244
245 attr_states.insert(identifying_attr.id, (ias, ias_v));
246
247 let user_and_version = app
248 .get_object::<UserState>(&user_id)
249 .await?
250 .ok_or_else(|| {
251 log::error!(
252 "identifying attribute {} of type {} refers to a user \
253 account {user_id} that does not exist",
254 identifying_attr.value,
255 identifying_attr.attr_type
256 );
257 api::ErrorCode::InternalError
258 })?;
259
260 break 'found_user (user_and_version, false);
261 }
262
263 log::trace!(
264 "enter: no account exists for attribute {} of type {}",
265 identifying_attr.value,
266 identifying_attr.attr_type,
267 );
268 }
269
270 if mode == EnterMode::Login {
271 return Ok(EnterResp::AccountDoesNotExist);
272 }
273
274 assert!(matches!(
275 mode,
276 EnterMode::LoginOrRegister | EnterMode::Register
277 ));
278
279 if let Some(resp) = app
280 .precheck_attrs_for_registration(
281 &attrs,
282 &mut attr_states,
283 &mut retrieved_attr_states,
284 register_only_with_unique_attrs,
285 )
286 .await?
287 {
288 return Ok(resp);
289 }
290
291 let Some(master_enc_key) = running_state.master_enc_key.as_ref() else {
314 log::info!("cannot register a new user yet: master encryption key not available");
315 return Err(api::ErrorCode::PleaseRetry);
316 };
317
318 let user_state = UserState {
319 id: Id::random(),
320 card_id: Some(CardPseud(Id::random())),
321 registration_date: Some(api::NumericDate::now()),
322 polymorphic_pseudonym: master_enc_key.encrypt_random(),
323 banned: false,
324 allow_login_by: attrs
325 .values()
326 .filter_map(|attr| {
327 if attr.not_identifying {
328 None
329 } else {
330 Some(attr.id)
331 }
332 })
333 .collect(),
334 could_be_banned_by: Default::default(),
335 stored_objects: Default::default(),
337 };
338
339 let user_state_version = app
340 .put_object::<UserState>(&user_state, None)
341 .await?
342 .ok_or_else(|| {
343 log::error!("User with id {} already exists - very odd", user_state.id);
344 api::ErrorCode::InternalError
345 })?;
346
347 assert!(!attr_states.contains_key(&identifying_attr.id));
350
351 let identifying_attr_state =
352 AttrState::new(identifying_attr.id, &identifying_attr, user_state.id);
353
354 if let Some(identifying_attr_state_version) = app
355 .put_object::<AttrState>(&identifying_attr_state, None)
356 .await
357 .inspect_err(|err| {
358 log::warn!(
359 "orphaned user account {} due to error with putting \
360 identifying attribute: {err}",
361 user_state.id
362 );
363 })?
364 {
365 assert!(
366 attr_states
367 .insert(
368 identifying_attr.id,
369 (identifying_attr_state, identifying_attr_state_version),
370 )
371 .is_none()
372 );
373
374 assert!(
375 attr_add_status
376 .insert(identifying_attr.id, AttrAddStatus::Added)
377 .is_none()
378 );
379 } else {
380 log::warn!(
381 "possibly orphaned user account {} because identifying \
382 attribute {} was just added before our noses",
383 user_state.id,
384 identifying_attr.id
385 );
386 return Ok(EnterResp::AttributeAlreadyTaken {
387 attr: identifying_attr.attr,
388 bans_other_user: false,
389 });
390 }
391
392 log::debug!("created user account {}", user_state.id);
393 break 'found_user ((user_state, user_state_version), true);
394 };
395
396 if user_state.banned {
397 return Ok(EnterResp::Banned);
398 }
399
400 if !retrieved_attr_states {
401 for attr in attrs.values() {
402 if attr_states.contains_key(&attr.id) {
403 continue;
404 }
405
406 if let Some(attr_state_and_version) = app.get_object::<AttrState>(&attr.id).await? {
407 attr_states.insert(attr.id, attr_state_and_version);
408 }
409 }
410
411 retrieved_attr_states = true;
412 }
413
414 assert!(retrieved_attr_states);
415
416 for attr in attrs.values() {
418 if attr_states.contains_key(&attr.id) {
419 attr_add_status
420 .entry(attr.id)
421 .or_insert(AttrAddStatus::AlreadyThere);
422 continue;
423 }
424
425 let attr_state = AttrState::new(attr.id, attr, user_state.id);
426
427 match app.put_object::<AttrState>(&attr_state, None).await {
428 Ok(Some(attr_state_version)) => {
429 assert!(
430 attr_states
431 .insert(attr.id, (attr_state, attr_state_version))
432 .is_none()
433 );
434 assert!(
435 attr_add_status
436 .insert(attr.id, AttrAddStatus::Added)
437 .is_none()
438 );
439 }
440 problem => {
441 log::warn!("problem adding attribute state {}: {problem:?}", attr.value);
442 assert!(
443 attr_add_status
444 .insert(attr.id, AttrAddStatus::PleaseTryAgain)
445 .is_none()
446 );
447 }
448 }
449 }
450
451 for attr in attrs.values() {
453 let (attr_state, attr_state_version) =
454 if let Some(attr_state_and_version) = attr_states.get(&attr.id) {
455 attr_state_and_version
456 } else {
457 continue;
458 };
459
460 if !attr.bannable || attr_state.bans_users.contains(&user_state.id) {
461 continue;
462 }
463
464 let mut attr_state = attr_state.clone();
465 assert!(attr_state.bans_users.insert(user_state.id));
466
467 match app
468 .put_object::<AttrState>(&attr_state, Some(attr_state_version.clone()))
469 .await
470 {
471 Ok(Some(attr_state_version)) => {
472 assert!(
473 attr_states
474 .insert(attr.id, (attr_state.clone(), attr_state_version))
475 .is_some()
476 );
477 assert!(attr_add_status.contains_key(&attr.id));
478 }
479 _ => {
480 assert!(
481 attr_add_status
482 .insert(attr.id, AttrAddStatus::PleaseTryAgain)
483 .is_none()
484 );
485 }
486 }
487 }
488
489 let mut new_user_state = user_state.clone();
490 let mut added_attrs: HashSet<Id> = Default::default();
491
492 for (attr_id, (attr_state, ..)) in attr_states {
494 if *attr_add_status.get(&attr_id).unwrap() == AttrAddStatus::PleaseTryAgain {
495 continue;
496 }
497
498 if let Some(identifies_user_id) = attr_state.may_identify_user {
499 assert_eq!(identifies_user_id, user_state.id);
500
501 if new_user_state.allow_login_by.insert(attr_id) {
502 added_attrs.insert(attr_id);
503 }
504 }
505
506 if attr_state.bans_users.contains(&user_state.id)
507 && new_user_state.could_be_banned_by.insert(attr_id)
508 {
509 added_attrs.insert(attr_id);
510 }
511 }
512
513 if !added_attrs.is_empty() {
514 match app
515 .put_object::<UserState>(&new_user_state, Some(user_state_version))
516 .await
517 {
518 Ok(Some(new_user_state_version)) => {
519 #[expect(unused_assignments)]
520 {
521 user_state_version = new_user_state_version;
522 }
523
524 for added_attr_id in added_attrs {
525 attr_add_status.insert(added_attr_id, AttrAddStatus::Added);
526 }
527 }
528
529 problem => {
530 log::warn!("failed to update user state to add attributes: {problem:?}");
531
532 for added_attr_id in added_attrs {
533 attr_add_status.insert(added_attr_id, AttrAddStatus::PleaseTryAgain);
534 }
535 }
536 }
537 }
538 let user_state = new_user_state;
539
540 let auth_token_package = app.issue_auth_token(&user_state)?;
541
542 Ok(EnterResp::Entered {
543 new_account,
544 auth_token_package,
545 attr_status: attr_add_status
546 .iter()
547 .map(|(attr_id, attr_add_status)| {
548 (attrs.get(attr_id).unwrap().attr.clone(), *attr_add_status)
549 })
550 .collect(),
551 })
552 }
553
554 fn id_attr(&self, attr: Attr) -> IdedAttr {
556 IdedAttr {
557 id: attr.id(&*self.attr_id_secret),
558 attr,
559 }
560 }
561
562 async fn precheck_attrs_for_registration(
580 &self,
581 attrs: &HashMap<Id, IdedAttr>,
582 attr_states: &mut HashMap<Id, (AttrState, object_store::UpdateVersion)>,
583 retrieved_attr_states: &mut bool,
584 register_only_with_unique_attrs: bool,
585 ) -> api::Result<Option<EnterResp>> {
586 if !attrs.values().any(|attr| attr.bannable) {
589 return Ok(Some(EnterResp::NoBannableAttribute));
590 }
591
592 assert!(!*retrieved_attr_states, "not expecting double work here");
593
594 for (attr_id, attr) in attrs {
596 if attr_states.contains_key(attr_id) {
598 continue;
599 }
600
601 if let Some(attr_state_and_version) = self.get_object::<AttrState>(attr_id).await? {
602 attr_states.insert(attr.id, attr_state_and_version);
603 }
604 }
605
606 *retrieved_attr_states = true;
607
608 for (attr_id, (attr_state, ..)) in attr_states.iter() {
609 if attr_state.banned {
610 return Ok(Some(EnterResp::AttributeBanned(
611 attrs.get(attr_id).unwrap().attr.clone(),
612 )));
613 }
614
615 if attr_state.may_identify_user.is_some() {
616 return Ok(Some(EnterResp::AttributeAlreadyTaken {
617 attr: attrs.get(attr_id).unwrap().attr.clone(),
618 bans_other_user: false,
619 }));
620 }
621
622 if register_only_with_unique_attrs && !attr_state.bans_users.is_empty() {
623 return Ok(Some(EnterResp::AttributeAlreadyTaken {
624 attr: attrs.get(attr_id).unwrap().attr.clone(),
625 bans_other_user: true,
626 }));
627 }
628 }
629
630 Ok(None)
631 }
632
633 pub(super) async fn handle_user_refresh(
635 app: Rc<Self>,
636 auth_token: actix_web::web::Header<AuthToken>,
637 ) -> api::Result<RefreshResp> {
638 let Ok((user_state, _)) = app
639 .open_auth_token_and_get_user_state_ext(auth_token.into_inner(), true)
641 .await?
642 else {
643 return Ok(RefreshResp::ReobtainAuthToken);
644 };
645
646 Ok(match app.issue_auth_token(&user_state)? {
647 Ok(atp) => RefreshResp::Success(atp),
648 Err(atdr) => RefreshResp::Denied(atdr),
649 })
650 }
651
652 fn issue_auth_token(
654 &self,
655 user_state: &UserState,
656 ) -> api::Result<Result<AuthTokenPackage, AuthTokenDeniedReason>> {
657 if user_state.could_be_banned_by.is_empty() {
658 return Ok(Err(AuthTokenDeniedReason::NoBannableAttribute));
659 }
660
661 if user_state.banned {
662 return Ok(Err(AuthTokenDeniedReason::Banned));
663 }
664
665 let iat = jwt::NumericDate::now();
666 let exp = iat.add_clamp(self.auth_token_validity.as_secs());
667 Ok(Ok(AuthTokenPackage {
668 expires: exp,
669 auth_token: AuthTokenInner {
670 user_id: user_state.id,
671 iat,
672 exp,
673 }
674 .seal(&self.auth_token_secret)?,
675 }))
676 }
677}
678
679#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
681pub(super) struct AuthTokenInner {
682 user_id: Id,
684
685 exp: jwt::NumericDate,
687
688 iat: jwt::NumericDate,
690}
691
692impl AuthTokenInner {
693 fn seal(&self, key: &crypto::SealingKey) -> api::Result<AuthToken> {
694 Ok(AuthToken {
695 inner: serde_bytes::ByteBuf::from(crypto::seal(&self, key, b"").map_err(|err| {
696 log::warn!("failed to seal AuthTokenInner: {err}");
697 api::ErrorCode::InternalError
698 })?)
699 .into(),
700 })
701 }
702
703 fn unseal(sealed: &AuthToken, key: &crypto::SealingKey) -> Result<AuthTokenInner, Opaque> {
704 crypto::unseal(&*sealed.inner, key, b"")
705 }
706
707 fn open(self, accept_expired: bool) -> Result<Id, Opaque> {
709 if !accept_expired && self.exp < jwt::NumericDate::now() {
710 return Err(OPAQUE);
711 }
712
713 Ok(self.user_id)
714 }
715}
716
717impl App {
718 pub(super) fn open_auth_token(&self, auth_token: AuthToken) -> Result<Id, Opaque> {
720 self.open_auth_token_ext(auth_token, false)
721 }
722
723 pub(super) fn open_auth_token_ext(
725 &self,
726 auth_token: AuthToken,
727 accept_expired: bool,
728 ) -> Result<Id, Opaque> {
729 AuthTokenInner::unseal(&auth_token, &self.auth_token_secret)?.open(accept_expired)
730 }
731
732 pub(super) async fn open_auth_token_and_get_user_state(
736 &self,
737 auth_token: AuthToken,
738 ) -> api::Result<Result<(UserState, object_store::UpdateVersion), Opaque>> {
739 self.open_auth_token_and_get_user_state_ext(auth_token, false)
740 .await
741 }
742
743 pub(super) async fn open_auth_token_and_get_user_state_ext(
746 &self,
747 auth_token: AuthToken,
748 accept_expired: bool,
749 ) -> api::Result<Result<(UserState, object_store::UpdateVersion), Opaque>> {
750 let Ok(user_id) = self.open_auth_token_ext(auth_token, accept_expired) else {
751 return Ok(Err(OPAQUE));
752 };
753
754 Ok(Ok(self
755 .get_object::<UserState>(&user_id)
756 .await?
757 .ok_or_else(|| {
758 log::error!(
759 "auth token refers to non- (or no longer) existing user with id {user_id}",
760 );
761 api::ErrorCode::InternalError
762 })?))
763 }
764}
765
766#[derive(Clone)]
768struct IdedAttr {
769 id: Id,
770 attr: Attr,
771}
772
773impl IdedAttr {
774 #[expect(dead_code)]
775 pub fn id(&self) -> Id {
776 unimplemented!("use the field `id` instead")
777 }
778}
779
780impl Deref for IdedAttr {
781 type Target = Attr;
782
783 fn deref(&self) -> &Attr {
784 &self.attr
785 }
786}
787
788#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
790pub struct UserState {
791 pub id: Id,
793
794 #[serde(default)]
800 card_id: Option<CardPseud>,
801
802 #[serde(default)]
806 pub registration_date: Option<api::NumericDate>,
807
808 pub polymorphic_pseudonym: elgamal::Triple,
811
812 pub banned: bool,
814
815 pub allow_login_by: HashSet<Id>,
821
822 pub could_be_banned_by: HashSet<Id>,
830
831 pub stored_objects: HashMap<handle::Handle, super::user_object_store::UserObjectDetails>,
833}
834
835impl UserState {
836 pub fn card_id(&self) -> CardPseud {
838 if let Some(card_id) = self.card_id {
839 return card_id;
840 }
841
842 CardPseud(b"".as_slice().derive_id(
843 sha2::Sha256::new().chain_update(self.id.as_slice()),
844 "pubhubs-card-id",
845 ))
846 }
847
848 pub(crate) fn update_quota(&self, mut quota: Quota) -> Result<Quota, QuotumName> {
851 quota.object_count = quota
852 .object_count
853 .checked_sub(self.stored_objects.len().try_into().unwrap_or(u16::MAX))
854 .ok_or_else(|| {
855 let quotum = QuotumName::ObjectCount;
856 log::warn!("user {} has reached quotum {quotum}", self.id);
857 quotum
858 })?;
859
860 for sod in self.stored_objects.values() {
861 quota.object_bytes_total =
862 quota
863 .object_bytes_total
864 .checked_sub(sod.size)
865 .ok_or_else(|| {
866 let quotum = QuotumName::ObjectBytesTotal;
867 log::warn!("user {} has reached quotum {quotum}", self.id);
868 quotum
869 })?;
870 }
871
872 Ok(quota)
873 }
874
875 pub(crate) fn into_user_version(self: UserState, app: &App) -> ApiUserState {
877 ApiUserState {
878 allow_login_by: self.allow_login_by,
879 could_be_banned_by: self.could_be_banned_by,
880 stored_objects: self
881 .stored_objects
882 .into_iter()
883 .map(|(handle, uod)| (handle, uod.into_user_version(&app.user_object_hmac_secret)))
884 .collect(),
885 }
886 }
887}