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