Our Personal Data Server from scratch!
0

Configure Feed

Select the types of activity you want to include in your feed.

invite: newtype InviteCode for invite endpoints + store

Lewis: May this revision serve well! <lu5a@proton.me>

author
Lewis
date (Jul 12, 2026, 8:03 AM +0200) commit fbfa15b0 parent eb1a89dc change-id xtypuqmv
+201 -143
+8 -8
crates/tranquil-api/src/admin/account/info.rs
··· 8 8 use tranquil_pds::api::error::{ApiError, DbResultExt}; 9 9 use tranquil_pds::auth::{Admin, Auth}; 10 10 use tranquil_pds::state::AppState; 11 - use tranquil_pds::types::{Did, Handle}; 11 + use tranquil_pds::types::{Did, Handle, InviteCode}; 12 12 13 13 #[derive(Deserialize)] 14 14 pub struct GetAccountInfoParams { ··· 39 39 #[derive(Serialize, Clone)] 40 40 #[serde(rename_all = "camelCase")] 41 41 pub struct InviteCodeInfo { 42 - pub code: String, 42 + pub code: InviteCode, 43 43 pub available: i32, 44 44 pub disabled: bool, 45 45 #[serde(skip_serializing_if = "Option::is_none")] ··· 123 123 return None; 124 124 } 125 125 126 - let code_strings: Vec<String> = invite_codes.iter().map(|ic| ic.code.clone()).collect(); 126 + let codes: Vec<InviteCode> = invite_codes.iter().map(|ic| ic.code.clone()).collect(); 127 127 128 128 let uses = state 129 129 .repos 130 130 .infra 131 - .get_invite_code_uses_batch(&code_strings) 131 + .get_invite_code_uses_batch(&codes) 132 132 .await 133 133 .ok()?; 134 134 ··· 157 157 } 158 158 } 159 159 160 - async fn get_invite_code_info(state: &AppState, code: &str) -> Option<InviteCodeInfo> { 160 + async fn get_invite_code_info(state: &AppState, code: &InviteCode) -> Option<InviteCodeInfo> { 161 161 let info = state.repos.infra.get_invite_code_info(code).await.ok()??; 162 162 163 163 let uses = state ··· 217 217 .await 218 218 .unwrap_or_default(); 219 219 220 - let all_codes: Vec<String> = all_invite_codes 220 + let all_codes: Vec<InviteCode> = all_invite_codes 221 221 .iter() 222 222 .map(|(_, c)| c.code.clone()) 223 223 .collect(); ··· 233 233 Vec::new() 234 234 }; 235 235 236 - let invited_by_map: HashMap<uuid::Uuid, String> = state 236 + let invited_by_map: HashMap<uuid::Uuid, InviteCode> = state 237 237 .repos 238 238 .infra 239 239 .get_invite_code_uses_by_users(&user_ids) ··· 249 249 250 250 let (codes_by_user, code_info_map): ( 251 251 HashMap<uuid::Uuid, Vec<InviteCodeInfo>>, 252 - HashMap<String, InviteCodeInfo>, 252 + HashMap<InviteCode, InviteCodeInfo>, 253 253 ) = all_invite_codes.into_iter().fold( 254 254 (HashMap::new(), HashMap::new()), 255 255 |(mut by_user, mut by_code), (user_id, ic)| {
+10 -10
crates/tranquil-api/src/admin/invite.rs
··· 10 10 use tranquil_pds::api::error::{ApiError, DbResultExt}; 11 11 use tranquil_pds::auth::{Admin, Auth}; 12 12 use tranquil_pds::state::AppState; 13 + use tranquil_types::{Did, InviteCode}; 13 14 14 15 #[derive(Deserialize)] 15 16 #[serde(rename_all = "camelCase")] 16 17 pub struct DisableInviteCodesInput { 17 - pub codes: Option<Vec<String>>, 18 + pub codes: Option<Vec<InviteCode>>, 18 19 pub accounts: Option<Vec<Did>>, 19 20 } 20 21 ··· 34 35 if let Err(e) = state 35 36 .repos 36 37 .infra 37 - .disable_invite_codes_by_account(&accounts_typed) 38 + .disable_invite_codes_by_account(accounts) 38 39 .await 39 - { 40 - error!("DB error disabling invite codes by account: {:?}", e); 41 - } 40 + { 41 + error!("DB error disabling invite codes by account: {:?}", e); 42 42 } 43 43 Ok(Json(EmptyResponse {})) 44 44 } ··· 53 53 #[derive(Serialize)] 54 54 #[serde(rename_all = "camelCase")] 55 55 pub struct InviteCodeInfo { 56 - pub code: String, 56 + pub code: InviteCode, 57 57 pub available: i32, 58 58 pub disabled: bool, 59 59 pub for_account: String, ··· 72 72 #[derive(Serialize)] 73 73 pub struct GetInviteCodesOutput { 74 74 #[serde(skip_serializing_if = "Option::is_none")] 75 - pub cursor: Option<String>, 75 + pub cursor: Option<InviteCode>, 76 76 pub codes: Vec<InviteCodeInfo>, 77 77 } 78 78 ··· 95 95 .log_db_err("fetching invite codes")?; 96 96 97 97 let user_ids: Vec<uuid::Uuid> = codes_rows.iter().map(|r| r.created_by_user).collect(); 98 - let code_strings: Vec<String> = codes_rows.iter().map(|r| r.code.clone()).collect(); 98 + let code_values: Vec<InviteCode> = codes_rows.iter().map(|r| r.code.clone()).collect(); 99 99 100 100 let creator_dids: std::collections::HashMap<uuid::Uuid, Did> = state 101 101 .repos ··· 106 106 .into_iter() 107 107 .collect(); 108 108 109 - let uses_by_code = if code_strings.is_empty() { 109 + let uses_by_code = if code_values.is_empty() { 110 110 std::collections::HashMap::new() 111 111 } else { 112 112 common::group_invite_uses_by_code( 113 113 state 114 114 .repos 115 115 .infra 116 - .get_invite_code_uses_batch(&code_strings) 116 + .get_invite_code_uses_batch(&code_values) 117 117 .await 118 118 .unwrap_or_default(), 119 119 |u| InviteCodeUseInfo {
+1 -1
crates/tranquil-api/src/common.rs
··· 76 76 pub fn group_invite_uses_by_code<U, F>( 77 77 uses: Vec<tranquil_db_traits::InviteCodeUse>, 78 78 map_use: F, 79 - ) -> HashMap<String, Vec<U>> 79 + ) -> HashMap<tranquil_types::InviteCode, Vec<U>> 80 80 where 81 81 F: Fn(tranquil_db_traits::InviteCodeUse) -> U, 82 82 {
+2 -1
crates/tranquil-api/src/server/invite.rs
··· 7 7 use tranquil_pds::state::AppState; 8 8 use tranquil_pds::types::Did; 9 9 use tranquil_pds::util::gen_invite_code; 10 + use tranquil_types::InviteCode as InviteCodeValue; 10 11 11 12 #[derive(Deserialize)] 12 13 #[serde(rename_all = "camelCase")] ··· 112 113 let infra_repo = state.repos.infra.clone(); 113 114 let use_count = input.use_count; 114 115 async move { 115 - let codes: Vec<String> = (0..code_count).map(|_| gen_invite_code()).collect(); 116 + let codes: Vec<InviteCodeValue> = (0..code_count).map(|_| gen_invite_code()).collect(); 116 117 infra_repo 117 118 .create_invite_codes_batch(&codes, use_count, admin_user_id, Some(&account)) 118 119 .await
+22 -13
crates/tranquil-db-traits/src/infra.rs
··· 148 148 149 149 #[derive(Debug, Clone, Serialize, Deserialize)] 150 150 pub struct InviteCodeInfo { 151 - pub code: String, 151 + pub code: InviteCode, 152 152 pub available_uses: i32, 153 153 pub state: InviteCodeState, 154 154 pub for_account: Option<Did>, ··· 158 158 159 159 #[derive(Debug, Clone, Serialize, Deserialize)] 160 160 pub struct InviteCodeUse { 161 - pub code: String, 161 + pub code: InviteCode, 162 162 pub used_by_did: Did, 163 163 pub used_by_handle: Option<Handle>, 164 164 pub used_at: DateTime<Utc>, ··· 166 166 167 167 #[derive(Debug, Clone, Serialize, Deserialize)] 168 168 pub struct InviteCodeRow { 169 - pub code: String, 169 + pub code: InviteCode, 170 170 pub available_uses: i32, 171 171 pub disabled: Option<bool>, 172 172 pub created_by_user: Uuid, ··· 248 248 249 249 async fn create_invite_code( 250 250 &self, 251 - code: &str, 251 + code: &InviteCode, 252 252 use_count: i32, 253 253 for_account: Option<&Did>, 254 254 ) -> Result<bool, DbError>; 255 255 256 256 async fn create_invite_codes_batch( 257 257 &self, 258 - codes: &[String], 258 + codes: &[InviteCode], 259 259 use_count: i32, 260 260 created_by_user: Uuid, 261 261 for_account: Option<&Did>, 262 262 ) -> Result<(), DbError>; 263 263 264 - async fn get_invite_code_available_uses(&self, code: &str) -> Result<Option<i32>, DbError>; 264 + async fn get_invite_code_available_uses( 265 + &self, 266 + code: &InviteCode, 267 + ) -> Result<Option<i32>, DbError>; 265 268 266 269 async fn validate_invite_code<'a>( 267 270 &self, 268 - code: &'a str, 271 + code: &'a InviteCode, 269 272 ) -> Result<ValidatedInviteCode<'a>, InviteCodeError>; 270 273 271 274 async fn get_invite_codes_for_account( ··· 273 276 for_account: &Did, 274 277 ) -> Result<Vec<InviteCodeInfo>, DbError>; 275 278 276 - async fn get_invite_code_uses(&self, code: &str) -> Result<Vec<InviteCodeUse>, DbError>; 279 + async fn get_invite_code_uses(&self, code: &InviteCode) -> Result<Vec<InviteCodeUse>, DbError>; 277 280 278 - async fn disable_invite_codes_by_code(&self, codes: &[String]) -> Result<(), DbError>; 281 + async fn disable_invite_codes_by_code(&self, codes: &[InviteCode]) -> Result<(), DbError>; 279 282 280 283 async fn disable_invite_codes_by_account(&self, accounts: &[Did]) -> Result<(), DbError>; 281 284 ··· 290 293 291 294 async fn get_invite_code_uses_batch( 292 295 &self, 293 - codes: &[String], 296 + codes: &[InviteCode], 294 297 ) -> Result<Vec<InviteCodeUse>, DbError>; 295 298 296 299 async fn get_invites_created_by_user( ··· 298 301 user_id: Uuid, 299 302 ) -> Result<Vec<InviteCodeInfo>, DbError>; 300 303 301 - async fn get_invite_code_info(&self, code: &str) -> Result<Option<InviteCodeInfo>, DbError>; 304 + async fn get_invite_code_info( 305 + &self, 306 + code: &InviteCode, 307 + ) -> Result<Option<InviteCodeInfo>, DbError>; 302 308 303 309 async fn get_invite_codes_by_users( 304 310 &self, 305 311 user_ids: &[Uuid], 306 312 ) -> Result<Vec<(Uuid, InviteCodeInfo)>, DbError>; 307 313 308 - async fn get_invite_code_used_by_user(&self, user_id: Uuid) -> Result<Option<String>, DbError>; 314 + async fn get_invite_code_used_by_user( 315 + &self, 316 + user_id: Uuid, 317 + ) -> Result<Option<InviteCode>, DbError>; 309 318 310 319 async fn delete_invite_code_uses_by_user(&self, user_id: Uuid) -> Result<(), DbError>; 311 320 ··· 425 434 async fn get_invite_code_uses_by_users( 426 435 &self, 427 436 user_ids: &[Uuid], 428 - ) -> Result<Vec<(Uuid, String)>, DbError>; 437 + ) -> Result<Vec<(Uuid, InviteCode)>, DbError>; 429 438 430 439 async fn get_deletion_request_by_did( 431 440 &self,
+5 -9
crates/tranquil-db-traits/src/invite_code.rs
··· 1 - use std::marker::PhantomData; 1 + use tranquil_types::InviteCode; 2 2 3 3 use crate::DbError; 4 4 5 5 #[derive(Debug)] 6 6 pub struct ValidatedInviteCode<'a> { 7 - code: &'a str, 8 - _marker: PhantomData<&'a ()>, 7 + code: &'a InviteCode, 9 8 } 10 9 11 10 impl<'a> ValidatedInviteCode<'a> { 12 - pub fn new_validated(code: &'a str) -> Self { 13 - Self { 14 - code, 15 - _marker: PhantomData, 16 - } 11 + pub fn new_validated(code: &'a InviteCode) -> Self { 12 + Self { code } 17 13 } 18 14 19 - pub fn code(&self) -> &str { 15 + pub fn code(&self) -> &'a InviteCode { 20 16 self.code 21 17 } 22 18 }
+3 -3
crates/tranquil-db-traits/src/user.rs
··· 1010 1010 pub commit_cid: CidLink, 1011 1011 pub repo_rev: Tid, 1012 1012 pub genesis_block_cids: Vec<Vec<u8>>, 1013 - pub invite_code: Option<String>, 1013 + pub invite_code: Option<InviteCode>, 1014 1014 pub birthdate_pref: Option<serde_json::Value>, 1015 1015 } 1016 1016 ··· 1062 1062 pub commit_cid: CidLink, 1063 1063 pub repo_rev: Tid, 1064 1064 pub genesis_block_cids: Vec<Vec<u8>>, 1065 - pub invite_code: Option<String>, 1065 + pub invite_code: Option<InviteCode>, 1066 1066 pub birthdate_pref: Option<serde_json::Value>, 1067 1067 } 1068 1068 ··· 1080 1080 pub commit_cid: CidLink, 1081 1081 pub repo_rev: Tid, 1082 1082 pub genesis_block_cids: Vec<Vec<u8>>, 1083 - pub invite_code: Option<String>, 1083 + pub invite_code: Option<InviteCode>, 1084 1084 pub birthdate_pref: Option<serde_json::Value>, 1085 1085 pub sso_provider: SsoProviderType, 1086 1086 pub sso_provider_user_id: String,
+62 -25
crates/tranquil-db/src/postgres/infra.rs
··· 151 151 152 152 async fn create_invite_code( 153 153 &self, 154 - code: &str, 154 + code: &InviteCode, 155 155 use_count: i32, 156 156 for_account: Option<&Did>, 157 157 ) -> Result<bool, DbError> { ··· 172 172 173 173 async fn create_invite_codes_batch( 174 174 &self, 175 - codes: &[String], 175 + codes: &[InviteCode], 176 176 use_count: i32, 177 177 created_by_user: Uuid, 178 178 for_account: Option<&Did>, 179 179 ) -> Result<(), DbError> { 180 180 let for_account_str = for_account.map(|d| d.as_str()); 181 + let code_strs: Vec<String> = codes.iter().map(|c| c.to_string()).collect(); 181 182 sqlx::query!( 182 183 r#"INSERT INTO invite_codes (code, available_uses, created_by_user, for_account) 183 184 SELECT code, $2, $3, $4 FROM UNNEST($1::text[]) AS t(code)"#, 184 - codes, 185 + &code_strs, 185 186 use_count, 186 187 created_by_user, 187 188 for_account_str ··· 193 194 Ok(()) 194 195 } 195 196 196 - async fn get_invite_code_available_uses(&self, code: &str) -> Result<Option<i32>, DbError> { 197 + async fn get_invite_code_available_uses( 198 + &self, 199 + code: &InviteCode, 200 + ) -> Result<Option<i32>, DbError> { 197 201 let result = sqlx::query_scalar!( 198 202 "SELECT available_uses FROM invite_codes WHERE code = $1 FOR UPDATE", 199 203 code ··· 207 211 208 212 async fn validate_invite_code<'a>( 209 213 &self, 210 - code: &'a str, 214 + code: &'a InviteCode, 211 215 ) -> Result<ValidatedInviteCode<'a>, InviteCodeError> { 212 216 let result = sqlx::query!( 213 217 r#"SELECT available_uses, COALESCE(disabled, false) as "disabled!" FROM invite_codes WHERE code = $1"#, ··· 249 253 Ok(results 250 254 .into_iter() 251 255 .map(|r| InviteCodeInfo { 252 - code: r.code, 256 + code: InviteCode::from(r.code), 253 257 available_uses: r.available_uses, 254 258 state: InviteCodeState::from_optional_disabled_flag(r.disabled), 255 259 for_account: Some(Did::from(r.for_account)), ··· 259 263 .collect()) 260 264 } 261 265 262 - async fn get_invite_code_uses(&self, code: &str) -> Result<Vec<InviteCodeUse>, DbError> { 266 + async fn get_invite_code_uses(&self, code: &InviteCode) -> Result<Vec<InviteCodeUse>, DbError> { 263 267 let results = sqlx::query!( 264 268 r#"SELECT u.did, u.handle, icu.used_at 265 269 FROM invite_code_uses icu ··· 283 287 .collect()) 284 288 } 285 289 286 - async fn disable_invite_codes_by_code(&self, codes: &[String]) -> Result<(), DbError> { 290 + async fn disable_invite_codes_by_code(&self, codes: &[InviteCode]) -> Result<(), DbError> { 291 + let code_strs: Vec<String> = codes.iter().map(|c| c.to_string()).collect(); 287 292 sqlx::query!( 288 293 "UPDATE invite_codes SET disabled = TRUE WHERE code = ANY($1)", 289 - codes 294 + &code_strs 290 295 ) 291 296 .execute(&self.pool) 292 297 .await ··· 315 320 limit: i64, 316 321 sort: InviteCodeSortOrder, 317 322 ) -> Result<Vec<InviteCodeRow>, DbError> { 323 + fn to_row( 324 + code: String, 325 + available_uses: i32, 326 + disabled: Option<bool>, 327 + created_by_user: Uuid, 328 + created_at: DateTime<Utc>, 329 + ) -> InviteCodeRow { 330 + InviteCodeRow { 331 + code: InviteCode::from(code), 332 + available_uses, 333 + disabled, 334 + created_by_user, 335 + created_at, 336 + } 337 + } 338 + 318 339 let results = match (cursor, sort) { 319 - (Some(cursor_code), InviteCodeSortOrder::Recent) => sqlx::query_as!( 320 - InviteCodeRow, 340 + (Some(cursor_code), InviteCodeSortOrder::Recent) => sqlx::query!( 321 341 r#"SELECT ic.code, ic.available_uses, ic.disabled, ic.created_by_user, ic.created_at 322 342 FROM invite_codes ic 323 343 WHERE ic.created_at < (SELECT created_at FROM invite_codes WHERE code = $1) ··· 339 359 ) 340 360 .fetch_all(&self.pool) 341 361 .await 342 - .map_err(map_sqlx_error)?, 343 - (Some(cursor_code), InviteCodeSortOrder::Usage) => sqlx::query_as!( 344 - InviteCodeRow, 362 + .map_err(map_sqlx_error)? 363 + .into_iter() 364 + .map(|r| { 365 + to_row( 366 + r.code, 367 + r.available_uses, 368 + r.disabled, 369 + r.created_by_user, 370 + r.created_at, 371 + ) 372 + }) 373 + .collect(), 374 + (Some(cursor_code), InviteCodeSortOrder::Usage) => sqlx::query!( 345 375 r#"SELECT ic.code, ic.available_uses, ic.disabled, ic.created_by_user, ic.created_at 346 376 FROM invite_codes ic 347 377 WHERE ic.created_at < (SELECT created_at FROM invite_codes WHERE code = $1) ··· 383 413 384 414 async fn get_invite_code_uses_batch( 385 415 &self, 386 - codes: &[String], 416 + codes: &[InviteCode], 387 417 ) -> Result<Vec<InviteCodeUse>, DbError> { 418 + let code_strs: Vec<String> = codes.iter().map(|c| c.to_string()).collect(); 388 419 let results = sqlx::query!( 389 420 r#"SELECT icu.code, u.did, icu.used_at 390 421 FROM invite_code_uses icu 391 422 JOIN users u ON icu.used_by_user = u.id 392 423 WHERE icu.code = ANY($1) 393 424 ORDER BY icu.used_at DESC"#, 394 - codes 425 + &code_strs 395 426 ) 396 427 .fetch_all(&self.pool) 397 428 .await ··· 400 431 Ok(results 401 432 .into_iter() 402 433 .map(|r| InviteCodeUse { 403 - code: r.code, 434 + code: InviteCode::from(r.code), 404 435 used_by_did: Did::from(r.did), 405 436 used_by_handle: None, 406 437 used_at: r.used_at, ··· 426 457 Ok(results 427 458 .into_iter() 428 459 .map(|r| InviteCodeInfo { 429 - code: r.code, 460 + code: InviteCode::from(r.code), 430 461 available_uses: r.available_uses, 431 462 state: InviteCodeState::from_optional_disabled_flag(r.disabled), 432 463 for_account: Some(Did::from(r.for_account)), ··· 436 467 .collect()) 437 468 } 438 469 439 - async fn get_invite_code_info(&self, code: &str) -> Result<Option<InviteCodeInfo>, DbError> { 470 + async fn get_invite_code_info( 471 + &self, 472 + code: &InviteCode, 473 + ) -> Result<Option<InviteCodeInfo>, DbError> { 440 474 let result = sqlx::query!( 441 475 r#"SELECT ic.code, ic.available_uses, ic.disabled, ic.for_account, ic.created_at, u.did as created_by 442 476 FROM invite_codes ic ··· 449 483 .map_err(map_sqlx_error)?; 450 484 451 485 Ok(result.map(|r| InviteCodeInfo { 452 - code: r.code, 486 + code: InviteCode::from(r.code), 453 487 available_uses: r.available_uses, 454 488 state: InviteCodeState::from_optional_disabled_flag(r.disabled), 455 489 for_account: Some(Did::from(r.for_account)), ··· 480 514 ( 481 515 r.created_by_user, 482 516 InviteCodeInfo { 483 - code: r.code, 517 + code: InviteCode::from(r.code), 484 518 available_uses: r.available_uses, 485 519 state: InviteCodeState::from_optional_disabled_flag(r.disabled), 486 520 for_account: Some(Did::from(r.for_account)), ··· 492 526 .collect()) 493 527 } 494 528 495 - async fn get_invite_code_used_by_user(&self, user_id: Uuid) -> Result<Option<String>, DbError> { 529 + async fn get_invite_code_used_by_user( 530 + &self, 531 + user_id: Uuid, 532 + ) -> Result<Option<InviteCode>, DbError> { 496 533 let result = sqlx::query_scalar!( 497 534 "SELECT code FROM invite_code_uses WHERE used_by_user = $1", 498 535 user_id ··· 501 538 .await 502 539 .map_err(map_sqlx_error)?; 503 540 504 - Ok(result) 541 + Ok(result.map(InviteCode::from)) 505 542 } 506 543 507 544 async fn delete_invite_code_uses_by_user(&self, user_id: Uuid) -> Result<(), DbError> { ··· 1006 1043 async fn get_invite_code_uses_by_users( 1007 1044 &self, 1008 1045 user_ids: &[Uuid], 1009 - ) -> Result<Vec<(Uuid, String)>, DbError> { 1046 + ) -> Result<Vec<(Uuid, InviteCode)>, DbError> { 1010 1047 let results = sqlx::query!( 1011 1048 r#" 1012 1049 SELECT used_by_user, code ··· 1021 1058 1022 1059 Ok(results 1023 1060 .into_iter() 1024 - .map(|r| (r.used_by_user, r.code)) 1061 + .map(|r| (r.used_by_user, InviteCode::from(r.code))) 1025 1062 .collect()) 1026 1063 } 1027 1064
+1 -2
crates/tranquil-oauth-server/src/endpoints/authorize/login.rs
··· 691 691 } 692 692 let code = AuthorizationCode::generate(); 693 693 let auth_post_device_id = device_id.clone(); 694 - let auth_post_code = AuthorizationCode::from(code.0.clone()); 695 694 if state 696 695 .repos 697 696 .oauth ··· 699 698 &form_request_id, 700 699 &user.did, 701 700 auth_post_device_id.as_ref(), 702 - &auth_post_code, 701 + &code, 703 702 ) 704 703 .await 705 704 .is_err()
+1 -2
crates/tranquil-oauth-server/src/endpoints/authorize/passkey.rs
··· 646 646 647 647 let code = AuthorizationCode::generate(); 648 648 let passkey_final_device_id = device_id.clone(); 649 - let passkey_final_code = AuthorizationCode::from(code.0.clone()); 650 649 if state 651 650 .repos 652 651 .oauth ··· 654 653 &passkey_finish_request_id, 655 654 &did, 656 655 passkey_final_device_id.as_ref(), 657 - &passkey_final_code, 656 + &code, 658 657 ) 659 658 .await 660 659 .is_err()
+2 -4
crates/tranquil-oauth-server/src/endpoints/authorize/two_factor.rs
··· 165 165 let code = AuthorizationCode::generate(); 166 166 let device_id = extract_device_cookie(&headers); 167 167 let twofa_totp_device_id = device_id.clone(); 168 - let twofa_totp_code = AuthorizationCode::from(code.0.clone()); 169 168 if state 170 169 .repos 171 170 .oauth ··· 173 172 &twofa_post_request_id, 174 173 &challenge.did, 175 174 twofa_totp_device_id.as_ref(), 176 - &twofa_totp_code, 175 + &code, 177 176 ) 178 177 .await 179 178 .is_err() ··· 310 309 } 311 310 let code = AuthorizationCode::generate(); 312 311 let twofa_final_device_id = device_id.clone(); 313 - let twofa_final_code = AuthorizationCode::from(code.0.clone()); 314 312 if state 315 313 .repos 316 314 .oauth ··· 318 316 &twofa_post_request_id, 319 317 &did, 320 318 twofa_final_device_id.as_ref(), 321 - &twofa_final_code, 319 + &code, 322 320 ) 323 321 .await 324 322 .is_err()
+10 -5
crates/tranquil-pds/src/api/invite.rs
··· 2 2 3 3 use crate::api::error::ApiError; 4 4 use crate::state::AppState; 5 + use crate::types::InviteCode; 5 6 6 7 #[derive(Debug, Clone, PartialEq, Eq)] 7 8 pub enum InviteRegistration { 8 9 Bootstrap, 9 - Standard(Option<String>), 10 + Standard(Option<InviteCode>), 10 11 } 11 12 12 13 impl InviteRegistration { 13 - pub fn into_invite_code(self) -> Option<String> { 14 + pub fn into_invite_code(self) -> Option<InviteCode> { 14 15 match self { 15 16 InviteRegistration::Bootstrap => None, 16 17 InviteRegistration::Standard(code) => code, ··· 43 44 } 44 45 } 45 46 46 - match invite_code.map(str::trim).filter(|code| !code.is_empty()) { 47 - Some(code) => match state.repos.infra.validate_invite_code(code).await { 48 - Ok(_) => Ok(InviteRegistration::Standard(Some(code.to_owned()))), 47 + match invite_code 48 + .map(str::trim) 49 + .filter(|code| !code.is_empty()) 50 + .map(InviteCode::new) 51 + { 52 + Some(code) => match state.repos.infra.validate_invite_code(&code).await { 53 + Ok(_) => Ok(InviteRegistration::Standard(Some(code))), 49 54 Err(InviteCodeError::DatabaseError(e)) => { 50 55 tracing::error!("failed to validate invite code: {e:?}"); 51 56 Err(ApiError::InternalError(None))
+1 -1
crates/tranquil-pds/src/state.rs
··· 46 46 pub webauthn_config: Arc<WebAuthnConfig>, 47 47 pub cross_pds_oauth: Arc<CrossPdsOAuthClient>, 48 48 pub shutdown: CancellationToken, 49 - pub bootstrap_invite_code: Option<String>, 49 + pub bootstrap_invite_code: Option<crate::types::InviteCode>, 50 50 pub signal_sender: Option<Arc<tranquil_signal::SignalSlot>>, 51 51 pub signal_store_provider: Option<Arc<dyn tranquil_signal::SignalStoreProvider>>, 52 52 pub eventlog_segments_dir: Option<PathBuf>,
+2 -2
crates/tranquil-pds/src/util.rs
··· 305 305 format!("{}-{}", gen_segment(&mut rng, 5), gen_segment(&mut rng, 5)) 306 306 } 307 307 308 - pub fn gen_invite_code() -> String { 308 + pub fn gen_invite_code() -> crate::types::InviteCode { 309 309 let hostname = &tranquil_config::get().server.hostname; 310 310 let hostname_prefix = hostname.replace('.', "-"); 311 - format!("{}-{}", hostname_prefix, gen_invite_random_token()) 311 + crate::types::InviteCode::from(format!("{}-{}", hostname_prefix, gen_invite_random_token())) 312 312 } 313 313 314 314 pub fn is_self_hosted_did_web_enabled() -> bool {
+6 -3
crates/tranquil-pds/tests/invite_registration.rs
··· 4 4 use serde_json::{Value, json}; 5 5 use tranquil_pds::api::error::ApiError; 6 6 use tranquil_pds::api::invite::{InviteRegistration, check_registration_invite}; 7 + use tranquil_types::InviteCode; 7 8 8 - async fn create_invite_code(client: &Client, admin_jwt: &str, use_count: u32) -> String { 9 + async fn create_invite_code(client: &Client, admin_jwt: &str, use_count: u32) -> InviteCode { 9 10 let res = client 10 11 .post(format!( 11 12 "{}/xrpc/com.atproto.server.createInviteCode", ··· 18 19 .expect("failed to create invite code"); 19 20 assert_eq!(res.status(), StatusCode::OK); 20 21 let body: Value = res.json().await.expect("invite code response not json"); 21 - body["code"].as_str().expect("missing code").to_string() 22 + InviteCode::from(body["code"].as_str().expect("missing code").to_string()) 22 23 } 23 24 24 25 #[tokio::test] ··· 30 31 let code = create_invite_code(&client, &admin_jwt, 1).await; 31 32 32 33 assert_eq!( 33 - check_registration_invite(state, Some(&code)).await.unwrap(), 34 + check_registration_invite(state, Some(code.as_str())) 35 + .await 36 + .unwrap(), 34 37 InviteRegistration::Standard(Some(code.clone())) 35 38 ); 36 39 assert_eq!(
+1 -1
crates/tranquil-pds/tests/store_parity.rs
··· 1107 1107 let did = test_did("invite"); 1108 1108 let handle = test_handle("invite"); 1109 1109 let _ = seed_repos(&f, &did, &handle).await; 1110 - let code = format!("parity-invite-{}", Uuid::new_v4()); 1110 + let code = tranquil_types::InviteCode::from(format!("parity-invite-{}", Uuid::new_v4())); 1111 1111 1112 1112 let pg_created = 1113 1113 f.pg.infra
+27 -12
crates/tranquil-store/src/metastore/client.rs
··· 1834 1834 1835 1835 async fn create_invite_code( 1836 1836 &self, 1837 - code: &str, 1837 + code: &InviteCode, 1838 1838 use_count: i32, 1839 1839 for_account: Option<&Did>, 1840 1840 ) -> Result<bool, DbError> { ··· 1851 1851 1852 1852 async fn create_invite_codes_batch( 1853 1853 &self, 1854 - codes: &[String], 1854 + codes: &[InviteCode], 1855 1855 use_count: i32, 1856 1856 created_by_user: Uuid, 1857 1857 for_account: Option<&Did>, ··· 1869 1869 recv(rx).await 1870 1870 } 1871 1871 1872 - async fn get_invite_code_available_uses(&self, code: &str) -> Result<Option<i32>, DbError> { 1872 + async fn get_invite_code_available_uses( 1873 + &self, 1874 + code: &InviteCode, 1875 + ) -> Result<Option<i32>, DbError> { 1873 1876 let (tx, rx) = oneshot::channel(); 1874 1877 self.pool.send(MetastoreRequest::Infra( 1875 1878 InfraRequest::GetInviteCodeAvailableUses { ··· 1882 1885 1883 1886 async fn validate_invite_code<'a>( 1884 1887 &self, 1885 - code: &'a str, 1888 + code: &'a InviteCode, 1886 1889 ) -> Result<ValidatedInviteCode<'a>, InviteCodeError> { 1887 1890 let (tx, rx) = oneshot::channel(); 1888 1891 self.pool ··· 1909 1912 recv(rx).await 1910 1913 } 1911 1914 1912 - async fn get_invite_code_uses(&self, code: &str) -> Result<Vec<InviteCodeUse>, DbError> { 1915 + async fn get_invite_code_uses(&self, code: &InviteCode) -> Result<Vec<InviteCodeUse>, DbError> { 1913 1916 let (tx, rx) = oneshot::channel(); 1914 1917 self.pool 1915 1918 .send(MetastoreRequest::Infra(InfraRequest::GetInviteCodeUses { ··· 1919 1922 recv(rx).await 1920 1923 } 1921 1924 1922 - async fn disable_invite_codes_by_code(&self, codes: &[String]) -> Result<(), DbError> { 1925 + async fn disable_invite_codes_by_code(&self, codes: &[InviteCode]) -> Result<(), DbError> { 1923 1926 let (tx, rx) = oneshot::channel(); 1924 1927 self.pool.send(MetastoreRequest::Infra( 1925 1928 InfraRequest::DisableInviteCodesByCode { ··· 1970 1973 1971 1974 async fn get_invite_code_uses_batch( 1972 1975 &self, 1973 - codes: &[String], 1976 + codes: &[InviteCode], 1974 1977 ) -> Result<Vec<InviteCodeUse>, DbError> { 1975 1978 let (tx, rx) = oneshot::channel(); 1976 1979 self.pool.send(MetastoreRequest::Infra( ··· 1993 1996 recv(rx).await 1994 1997 } 1995 1998 1996 - async fn get_invite_code_info(&self, code: &str) -> Result<Option<InviteCodeInfo>, DbError> { 1999 + async fn get_invite_code_info( 2000 + &self, 2001 + code: &InviteCode, 2002 + ) -> Result<Option<InviteCodeInfo>, DbError> { 1997 2003 let (tx, rx) = oneshot::channel(); 1998 2004 self.pool 1999 2005 .send(MetastoreRequest::Infra(InfraRequest::GetInviteCodeInfo { ··· 2017 2023 recv(rx).await 2018 2024 } 2019 2025 2020 - async fn get_invite_code_used_by_user(&self, user_id: Uuid) -> Result<Option<String>, DbError> { 2026 + async fn get_invite_code_used_by_user( 2027 + &self, 2028 + user_id: Uuid, 2029 + ) -> Result<Option<InviteCode>, DbError> { 2021 2030 let (tx, rx) = oneshot::channel(); 2022 2031 self.pool.send(MetastoreRequest::Infra( 2023 2032 InfraRequest::GetInviteCodeUsedByUser { user_id, tx }, 2024 2033 ))?; 2025 - recv(rx).await 2034 + recv(rx) 2035 + .await 2036 + .map(|code: Option<String>| code.map(InviteCode::from)) 2026 2037 } 2027 2038 2028 2039 async fn delete_invite_code_uses_by_user(&self, user_id: Uuid) -> Result<(), DbError> { ··· 2382 2393 async fn get_invite_code_uses_by_users( 2383 2394 &self, 2384 2395 user_ids: &[Uuid], 2385 - ) -> Result<Vec<(Uuid, String)>, DbError> { 2396 + ) -> Result<Vec<(Uuid, InviteCode)>, DbError> { 2386 2397 let (tx, rx) = oneshot::channel(); 2387 2398 self.pool.send(MetastoreRequest::Infra( 2388 2399 InfraRequest::GetInviteCodeUsesByUsers { ··· 2390 2401 tx, 2391 2402 }, 2392 2403 ))?; 2393 - recv(rx).await 2404 + recv(rx).await.map(|uses: Vec<(Uuid, String)>| { 2405 + uses.into_iter() 2406 + .map(|(id, code)| (id, InviteCode::from(code))) 2407 + .collect() 2408 + }) 2394 2409 } 2395 2410 2396 2411 async fn get_deletion_request_by_did(
+28 -32
crates/tranquil-store/src/metastore/handler.rs
··· 50 50 51 51 type Tx<T> = oneshot::Sender<Result<T, DbError>>; 52 52 53 - fn reserve_invite(infra: &InfraOps, code: Option<&str>) -> Result<(), CreateAccountError> { 53 + fn reserve_invite(infra: &InfraOps, code: Option<&InviteCode>) -> Result<(), CreateAccountError> { 54 54 match code { 55 55 Some(code) => infra.reserve_invite_code(code).map_err(|e| match e { 56 56 InviteCodeError::DatabaseError(e) => CreateAccountError::Database(e.to_string()), ··· 62 62 63 63 fn record_invite_use( 64 64 infra: &InfraOps, 65 - code: Option<&str>, 65 + code: Option<&InviteCode>, 66 66 user_id: Uuid, 67 67 ) -> Result<(), CreateAccountError> { 68 68 match code { ··· 76 76 } 77 77 } 78 78 79 - fn refund_invite(infra: &InfraOps, code: Option<&str>) { 79 + fn refund_invite(infra: &InfraOps, code: Option<&InviteCode>) { 80 80 if let Some(code) = code 81 81 && let Err(e) = infra.refund_invite_code(code) 82 82 { ··· 86 86 87 87 fn finalize_account<T>( 88 88 infra: &InfraOps, 89 - code: Option<&str>, 89 + code: Option<&InviteCode>, 90 90 created: Result<T, CreateAccountError>, 91 91 after: impl FnOnce(&T) -> Result<Uuid, CreateAccountError>, 92 92 ) -> Result<T, CreateAccountError> { ··· 1828 1828 tx: Tx<()>, 1829 1829 }, 1830 1830 CreateInviteCode { 1831 - code: String, 1831 + code: InviteCode, 1832 1832 use_count: i32, 1833 1833 for_account: Option<Did>, 1834 1834 tx: Tx<bool>, 1835 1835 }, 1836 1836 CreateInviteCodesBatch { 1837 - codes: Vec<String>, 1837 + codes: Vec<InviteCode>, 1838 1838 use_count: i32, 1839 1839 created_by_user: Uuid, 1840 1840 for_account: Option<Did>, 1841 1841 tx: Tx<()>, 1842 1842 }, 1843 1843 GetInviteCodeAvailableUses { 1844 - code: String, 1844 + code: InviteCode, 1845 1845 tx: Tx<Option<i32>>, 1846 1846 }, 1847 1847 ValidateInviteCode { 1848 - code: String, 1848 + code: InviteCode, 1849 1849 tx: oneshot::Sender<Result<(), InviteCodeError>>, 1850 1850 }, 1851 1851 GetInviteCodesForAccount { ··· 5777 5777 } 5778 5778 UserRequest::CreatePasswordAccount { input, tx } => { 5779 5779 let infra = state.metastore.infra_ops(); 5780 - let code = input.invite_code.as_deref(); 5780 + let code = input.invite_code.as_ref(); 5781 5781 let result = reserve_invite(&infra, code).and_then(|()| { 5782 5782 finalize_account( 5783 5783 &infra, ··· 5823 5823 } 5824 5824 UserRequest::CreatePasskeyAccount { input, tx } => { 5825 5825 let infra = state.metastore.infra_ops(); 5826 - let code = input.invite_code.as_deref(); 5826 + let code = input.invite_code.as_ref(); 5827 5827 let result = reserve_invite(&infra, code).and_then(|()| { 5828 5828 finalize_account( 5829 5829 &infra, ··· 5844 5844 UserRequest::CreateSsoAccount { input, tx } => { 5845 5845 let sso_ops = state.metastore.sso_ops(); 5846 5846 let infra = state.metastore.infra_ops(); 5847 - let code = input.invite_code.as_deref(); 5847 + let code = input.invite_code.as_ref(); 5848 5848 let result = sso_ops 5849 5849 .consume_pending_registration(&input.pending_registration_token) 5850 5850 .map_err(|e| CreateAccountError::Database(e.to_string())) ··· 6182 6182 ) 6183 6183 .unwrap(); 6184 6184 let infra = metastore.infra_ops(); 6185 + let squid = InviteCode::new("squid-invite"); 6186 + let whelk = InviteCode::new("whelk"); 6185 6187 6186 - assert!(infra.create_invite_code("squid-invite", 1, None).unwrap()); 6188 + assert!(infra.create_invite_code(&squid, 1, None).unwrap()); 6187 6189 6188 - infra.reserve_invite_code("squid-invite").unwrap(); 6190 + infra.reserve_invite_code(&squid).unwrap(); 6189 6191 assert_eq!( 6190 - infra 6191 - .get_invite_code_available_uses("squid-invite") 6192 - .unwrap(), 6192 + infra.get_invite_code_available_uses(&squid).unwrap(), 6193 6193 Some(0) 6194 6194 ); 6195 6195 6196 6196 assert!(matches!( 6197 - infra.reserve_invite_code("squid-invite"), 6197 + infra.reserve_invite_code(&squid), 6198 6198 Err(InviteCodeError::ExhaustedUses) 6199 6199 )); 6200 6200 assert_eq!( 6201 - infra 6202 - .get_invite_code_available_uses("squid-invite") 6203 - .unwrap(), 6201 + infra.get_invite_code_available_uses(&squid).unwrap(), 6204 6202 Some(0), 6205 6203 "a rejected reservation must not decrement below zero" 6206 6204 ); 6207 6205 6208 6206 assert!(matches!( 6209 - infra.reserve_invite_code("whelk"), 6207 + infra.reserve_invite_code(&whelk), 6210 6208 Err(InviteCodeError::NotFound) 6211 6209 )); 6212 6210 } ··· 6222 6220 ) 6223 6221 .unwrap(); 6224 6222 let infra = metastore.infra_ops(); 6223 + let squid = InviteCode::new("squid-invite"); 6224 + let whelk = InviteCode::new("whelk"); 6225 6225 6226 - assert!(infra.create_invite_code("squid-invite", 1, None).unwrap()); 6226 + assert!(infra.create_invite_code(&squid, 1, None).unwrap()); 6227 6227 6228 - infra.reserve_invite_code("squid-invite").unwrap(); 6229 - infra.refund_invite_code("squid-invite").unwrap(); 6228 + infra.reserve_invite_code(&squid).unwrap(); 6229 + infra.refund_invite_code(&squid).unwrap(); 6230 6230 assert_eq!( 6231 - infra 6232 - .get_invite_code_available_uses("squid-invite") 6233 - .unwrap(), 6231 + infra.get_invite_code_available_uses(&squid).unwrap(), 6234 6232 Some(1), 6235 6233 "refund must return the reserved use so a failed signup does not burn it" 6236 6234 ); 6237 6235 6238 - infra.reserve_invite_code("squid-invite").unwrap(); 6236 + infra.reserve_invite_code(&squid).unwrap(); 6239 6237 assert_eq!( 6240 - infra 6241 - .get_invite_code_available_uses("squid-invite") 6242 - .unwrap(), 6238 + infra.get_invite_code_available_uses(&squid).unwrap(), 6243 6239 Some(0) 6244 6240 ); 6245 6241 6246 6242 assert!(matches!( 6247 - infra.refund_invite_code("whelk"), 6243 + infra.refund_invite_code(&whelk), 6248 6244 Err(InviteCodeError::NotFound) 6249 6245 )); 6250 6246 }
+9 -9
crates/tranquil-store/src/metastore/infra_ops.rs
··· 118 118 119 119 fn value_to_invite_info(&self, v: &InviteCodeValue) -> Result<InviteCodeInfo, MetastoreError> { 120 120 Ok(InviteCodeInfo { 121 - code: v.code.clone(), 121 + code: InviteCode::from(v.code.clone()), 122 122 available_uses: v.available_uses, 123 123 state: InviteCodeState::from_disabled_flag(v.disabled), 124 124 for_account: v ··· 360 360 361 361 pub fn create_invite_code( 362 362 &self, 363 - code: &str, 363 + code: &InviteCode, 364 364 use_count: i32, 365 365 for_account: Option<&Did>, 366 366 ) -> Result<bool, MetastoreError> { ··· 390 390 391 391 pub fn create_invite_codes_batch( 392 392 &self, 393 - codes: &[String], 393 + codes: &[InviteCode], 394 394 use_count: i32, 395 395 created_by_user: Uuid, 396 396 for_account: Option<&Did>, ··· 418 418 419 419 pub fn get_invite_code_available_uses( 420 420 &self, 421 - code: &str, 421 + code: &InviteCode, 422 422 ) -> Result<Option<i32>, MetastoreError> { 423 423 let key = invite_code_key(code); 424 424 let val: Option<InviteCodeValue> = point_lookup( ··· 432 432 433 433 pub fn validate_invite_code<'a>( 434 434 &self, 435 - code: &'a str, 435 + code: &'a InviteCode, 436 436 ) -> Result<ValidatedInviteCode<'a>, InviteCodeError> { 437 437 let key = invite_code_key(code); 438 438 let val: Option<InviteCodeValue> = point_lookup( ··· 453 453 } 454 454 } 455 455 456 - pub fn reserve_invite_code(&self, code: &str) -> Result<(), InviteCodeError> { 456 + pub fn reserve_invite_code(&self, code: &InviteCode) -> Result<(), InviteCodeError> { 457 457 let _guard = self.counter_lock.lock(); 458 458 let validated = self.validate_invite_code(code)?; 459 459 self.decrement_invite_code_uses(&validated).map_err(|e| { ··· 461 461 }) 462 462 } 463 463 464 - pub fn refund_invite_code(&self, code: &str) -> Result<(), InviteCodeError> { 464 + pub fn refund_invite_code(&self, code: &InviteCode) -> Result<(), InviteCodeError> { 465 465 let _guard = self.counter_lock.lock(); 466 466 let key = invite_code_key(code); 467 467 let mut val: InviteCodeValue = point_lookup( ··· 557 557 .unwrap_or_else(|| Did::new("did:plc:unknown".to_owned()).unwrap()); 558 558 let used_by_handle = self.resolve_handle_for_uuid(val.used_by); 559 559 acc.push(InviteCodeUse { 560 - code: code.to_owned(), 560 + code: InviteCode::new(code), 561 561 used_by_did, 562 562 used_by_handle, 563 563 used_at: DateTime::from_timestamp_millis(val.used_at_ms).unwrap_or_default(), ··· 628 628 .ok_or(MetastoreError::CorruptData("corrupt invite code"))?; 629 629 let created_by_user = val.created_by.unwrap_or(Uuid::nil()); 630 630 acc.push(InviteCodeRow { 631 - code: val.code, 631 + code: InviteCode::from(val.code), 632 632 available_uses: val.available_uses, 633 633 disabled: Some(val.disabled), 634 634 created_by_user,