fix: first user invite code

This commit is contained in:
lewis
2026-03-03 16:07:57 +02:00
committed by Tangled
parent 1e02c5803f
commit dcdef508de
4 changed files with 74 additions and 27 deletions

View File

@@ -548,28 +548,38 @@ pub async fn create_account(
return ApiError::HandleTaken.into_response();
}
let invite_code_required = tranquil_config::get().server.invite_code_required;
if invite_code_required
&& input
.invite_code
.as_ref()
.map(|c| c.trim().is_empty())
.unwrap_or(true)
{
return ApiError::InviteCodeRequired.into_response();
}
if let Some(code) = &input.invite_code
&& !code.trim().is_empty()
{
let valid = match state.user_repo.check_and_consume_invite_code(code).await {
Ok(v) => v,
Err(e) => {
error!("Error checking invite code: {:?}", e);
return ApiError::InternalError(None).into_response();
let is_bootstrap = state.bootstrap_invite_code.is_some()
&& state.user_repo.count_users().await.unwrap_or(1) == 0;
if is_bootstrap {
match input.invite_code.as_deref() {
Some(code) if Some(code) == state.bootstrap_invite_code.as_deref() => {}
_ => return ApiError::InvalidInviteCode.into_response(),
}
} else {
let invite_code_required = tranquil_config::get().server.invite_code_required;
if invite_code_required
&& input
.invite_code
.as_ref()
.map(|c| c.trim().is_empty())
.unwrap_or(true)
{
return ApiError::InviteCodeRequired.into_response();
}
if let Some(code) = &input.invite_code
&& !code.trim().is_empty()
{
let valid = match state.user_repo.check_and_consume_invite_code(code).await {
Ok(v) => v,
Err(e) => {
error!("Error checking invite code: {:?}", e);
return ApiError::InternalError(None).into_response();
}
};
if !valid {
return ApiError::InvalidInviteCode.into_response();
}
};
if !valid {
return ApiError::InvalidInviteCode.into_response();
}
}
@@ -678,7 +688,11 @@ pub async fn create_account(
commit_cid: commit_cid_str.clone(),
repo_rev: rev_str.clone(),
genesis_block_cids,
invite_code: input.invite_code.clone(),
invite_code: if is_bootstrap {
None
} else {
input.invite_code.clone()
},
birthdate_pref,
};

View File

@@ -14,7 +14,7 @@ use tracing::error;
const BASE32_ALPHABET: &[u8] = b"abcdefghijklmnopqrstuvwxyz234567";
fn gen_random_token() -> String {
pub(crate) fn gen_random_token() -> String {
let mut rng = rand::thread_rng();
let gen_segment = |rng: &mut rand::rngs::ThreadRng, len: usize| -> String {
(0..len)
@@ -24,7 +24,7 @@ fn gen_random_token() -> String {
format!("{}-{}", gen_segment(&mut rng, 5), gen_segment(&mut rng, 5))
}
fn gen_invite_code() -> String {
pub fn gen_invite_code() -> String {
let hostname = &tranquil_config::get().server.hostname;
let hostname_prefix = hostname.replace('.', "-");
format!("{}-{}", hostname_prefix, gen_random_token())

View File

@@ -146,7 +146,15 @@ pub async fn create_passkey_account(
return ApiError::InvalidEmail.into_response();
}
let _validated_invite_code = if let Some(ref code) = input.invite_code {
let is_bootstrap = state.bootstrap_invite_code.is_some()
&& state.user_repo.count_users().await.unwrap_or(1) == 0;
let _validated_invite_code = if is_bootstrap {
match input.invite_code.as_deref() {
Some(code) if Some(code) == state.bootstrap_invite_code.as_deref() => None,
_ => return ApiError::InvalidInviteCode.into_response(),
}
} else if let Some(ref code) = input.invite_code {
match state.infra_repo.validate_invite_code(code).await {
Ok(validated) => Some(validated),
Err(_) => return ApiError::InvalidInviteCode.into_response(),
@@ -447,7 +455,11 @@ pub async fn create_passkey_account(
commit_cid: commit_cid.to_string(),
repo_rev: rev.as_ref().to_string(),
genesis_block_cids,
invite_code: input.invite_code.clone(),
invite_code: if is_bootstrap {
None
} else {
input.invite_code.clone()
},
birthdate_pref,
};

View File

@@ -58,6 +58,7 @@ pub struct AppState {
pub sso_manager: SsoManager,
pub webauthn_config: Arc<WebAuthnConfig>,
pub shutdown: CancellationToken,
pub bootstrap_invite_code: Option<String>,
}
#[derive(Debug, Clone, Copy)]
@@ -232,7 +233,26 @@ impl AppState {
.await
.map_err(|e| format!("Failed to run migrations: {}", e))?;
Ok(Self::from_db(db, shutdown).await)
let bootstrap_invite_code = match (
cfg.server.invite_code_required,
sqlx::query_scalar!("SELECT COUNT(*) FROM users")
.fetch_one(&db)
.await,
) {
(true, Ok(Some(0))) => {
let code = crate::api::server::invite::gen_invite_code();
tracing::info!(
"No users exist and invite codes are required. Bootstrap invite code: {}",
code
);
Some(code)
}
_ => None,
};
let mut state = Self::from_db(db, shutdown).await;
state.bootstrap_invite_code = bootstrap_invite_code;
Ok(state)
}
pub async fn from_db(db: PgPool, shutdown: CancellationToken) -> Self {
@@ -285,6 +305,7 @@ impl AppState {
sso_manager,
webauthn_config,
shutdown,
bootstrap_invite_code: None,
}
}