Our Personal Data Server from scratch! tranquil.farm
oauth atproto pds rust postgresql objectstorage fun

fix: first user invite code #34

merged opened by lewis.moe targeting main from fix/first-user-invite-code

Log an invite code to the server, that the first user will use when they create their account.

Labels

None yet.

assignee

None yet.

Participants 1
AT URI
at://did:plc:3fwecdnvtcscjnrx2p4n7alz/sh.tangled.repo.pull/3mg5wzdaiez22
+74 -27
Diff #0
+36 -22
crates/tranquil-pds/src/api/identity/account.rs
··· 548 548 return ApiError::HandleTaken.into_response(); 549 549 } 550 550 551 - let invite_code_required = tranquil_config::get().server.invite_code_required; 552 - if invite_code_required 553 - && input 554 - .invite_code 555 - .as_ref() 556 - .map(|c| c.trim().is_empty()) 557 - .unwrap_or(true) 558 - { 559 - return ApiError::InviteCodeRequired.into_response(); 560 - } 561 - if let Some(code) = &input.invite_code 562 - && !code.trim().is_empty() 563 - { 564 - let valid = match state.user_repo.check_and_consume_invite_code(code).await { 565 - Ok(v) => v, 566 - Err(e) => { 567 - error!("Error checking invite code: {:?}", e); 568 - return ApiError::InternalError(None).into_response(); 551 + let is_bootstrap = state.bootstrap_invite_code.is_some() 552 + && state.user_repo.count_users().await.unwrap_or(1) == 0; 553 + 554 + if is_bootstrap { 555 + match input.invite_code.as_deref() { 556 + Some(code) if Some(code) == state.bootstrap_invite_code.as_deref() => {} 557 + _ => return ApiError::InvalidInviteCode.into_response(), 558 + } 559 + } else { 560 + let invite_code_required = tranquil_config::get().server.invite_code_required; 561 + if invite_code_required 562 + && input 563 + .invite_code 564 + .as_ref() 565 + .map(|c| c.trim().is_empty()) 566 + .unwrap_or(true) 567 + { 568 + return ApiError::InviteCodeRequired.into_response(); 569 + } 570 + if let Some(code) = &input.invite_code 571 + && !code.trim().is_empty() 572 + { 573 + let valid = match state.user_repo.check_and_consume_invite_code(code).await { 574 + Ok(v) => v, 575 + Err(e) => { 576 + error!("Error checking invite code: {:?}", e); 577 + return ApiError::InternalError(None).into_response(); 578 + } 579 + }; 580 + if !valid { 581 + return ApiError::InvalidInviteCode.into_response(); 569 582 } 570 - }; 571 - if !valid { 572 - return ApiError::InvalidInviteCode.into_response(); 573 583 } 574 584 } 575 585 ··· 678 688 commit_cid: commit_cid_str.clone(), 679 689 repo_rev: rev_str.clone(), 680 690 genesis_block_cids, 681 - invite_code: input.invite_code.clone(), 691 + invite_code: if is_bootstrap { 692 + None 693 + } else { 694 + input.invite_code.clone() 695 + }, 682 696 birthdate_pref, 683 697 }; 684 698
+2 -2
crates/tranquil-pds/src/api/server/invite.rs
··· 14 14 15 15 const BASE32_ALPHABET: &[u8] = b"abcdefghijklmnopqrstuvwxyz234567"; 16 16 17 - fn gen_random_token() -> String { 17 + pub(crate) fn gen_random_token() -> String { 18 18 let mut rng = rand::thread_rng(); 19 19 let gen_segment = |rng: &mut rand::rngs::ThreadRng, len: usize| -> String { 20 20 (0..len) ··· 24 24 format!("{}-{}", gen_segment(&mut rng, 5), gen_segment(&mut rng, 5)) 25 25 } 26 26 27 - fn gen_invite_code() -> String { 27 + pub fn gen_invite_code() -> String { 28 28 let hostname = &tranquil_config::get().server.hostname; 29 29 let hostname_prefix = hostname.replace('.', "-"); 30 30 format!("{}-{}", hostname_prefix, gen_random_token())
+14 -2
crates/tranquil-pds/src/api/server/passkey_account.rs
··· 146 146 return ApiError::InvalidEmail.into_response(); 147 147 } 148 148 149 - let _validated_invite_code = if let Some(ref code) = input.invite_code { 149 + let is_bootstrap = state.bootstrap_invite_code.is_some() 150 + && state.user_repo.count_users().await.unwrap_or(1) == 0; 151 + 152 + let _validated_invite_code = if is_bootstrap { 153 + match input.invite_code.as_deref() { 154 + Some(code) if Some(code) == state.bootstrap_invite_code.as_deref() => None, 155 + _ => return ApiError::InvalidInviteCode.into_response(), 156 + } 157 + } else if let Some(ref code) = input.invite_code { 150 158 match state.infra_repo.validate_invite_code(code).await { 151 159 Ok(validated) => Some(validated), 152 160 Err(_) => return ApiError::InvalidInviteCode.into_response(), ··· 447 455 commit_cid: commit_cid.to_string(), 448 456 repo_rev: rev.as_ref().to_string(), 449 457 genesis_block_cids, 450 - invite_code: input.invite_code.clone(), 458 + invite_code: if is_bootstrap { 459 + None 460 + } else { 461 + input.invite_code.clone() 462 + }, 451 463 birthdate_pref, 452 464 }; 453 465
+22 -1
crates/tranquil-pds/src/state.rs
··· 58 58 pub sso_manager: SsoManager, 59 59 pub webauthn_config: Arc<WebAuthnConfig>, 60 60 pub shutdown: CancellationToken, 61 + pub bootstrap_invite_code: Option<String>, 61 62 } 62 63 63 64 #[derive(Debug, Clone, Copy)] ··· 232 233 .await 233 234 .map_err(|e| format!("Failed to run migrations: {}", e))?; 234 235 235 - Ok(Self::from_db(db, shutdown).await) 236 + let bootstrap_invite_code = match ( 237 + cfg.server.invite_code_required, 238 + sqlx::query_scalar!("SELECT COUNT(*) FROM users") 239 + .fetch_one(&db) 240 + .await, 241 + ) { 242 + (true, Ok(Some(0))) => { 243 + let code = crate::api::server::invite::gen_invite_code(); 244 + tracing::info!( 245 + "No users exist and invite codes are required. Bootstrap invite code: {}", 246 + code 247 + ); 248 + Some(code) 249 + } 250 + _ => None, 251 + }; 252 + 253 + let mut state = Self::from_db(db, shutdown).await; 254 + state.bootstrap_invite_code = bootstrap_invite_code; 255 + Ok(state) 236 256 } 237 257 238 258 pub async fn from_db(db: PgPool, shutdown: CancellationToken) -> Self { ··· 285 305 sso_manager, 286 306 webauthn_config, 287 307 shutdown, 308 + bootstrap_invite_code: None, 288 309 } 289 310 } 290 311

History

1 round 0 comments
sign up or login to add to the discussion
lewis.moe submitted #0
1 commit
expand
fix: first user invite code
expand 0 comments
pull request successfully merged