Our Personal Data Server from scratch!
0
fork

Configure Feed

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

feat(tranquil-store): repository traits on MetastoreClient

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

+19220 -89
+22
Cargo.lock
··· 6980 6980 ] 6981 6981 6982 6982 [[package]] 6983 + name = "tikv-jemalloc-sys" 6984 + version = "0.6.1+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7" 6985 + source = "registry+https://github.com/rust-lang/crates.io-index" 6986 + checksum = "cd8aa5b2ab86a2cefa406d889139c162cbb230092f7d1d7cbc1716405d852a3b" 6987 + dependencies = [ 6988 + "cc", 6989 + "libc", 6990 + ] 6991 + 6992 + [[package]] 6993 + name = "tikv-jemallocator" 6994 + version = "0.6.1" 6995 + source = "registry+https://github.com/rust-lang/crates.io-index" 6996 + checksum = "0359b4327f954e0567e69fb191cf1436617748813819c94b8cd4a431422d053a" 6997 + dependencies = [ 6998 + "libc", 6999 + "tikv-jemalloc-sys", 7000 + ] 7001 + 7002 + [[package]] 6983 7003 name = "time" 6984 7004 version = "0.3.47" 6985 7005 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 7863 7883 "sqlx", 7864 7884 "tempfile", 7865 7885 "thiserror 2.0.18", 7886 + "tikv-jemallocator", 7866 7887 "tokio", 7867 7888 "tracing", 7868 7889 "tranquil-db", 7869 7890 "tranquil-db-traits", 7891 + "tranquil-oauth", 7870 7892 "tranquil-repo", 7871 7893 "tranquil-types", 7872 7894 "uuid",
+8 -4
crates/tranquil-api/src/server/trusted_devices.rs
··· 127 127 state 128 128 .repos 129 129 .oauth 130 - .revoke_device_trust(&input.device_id) 130 + .revoke_device_trust(&input.device_id, &auth.did) 131 131 .await 132 132 .log_db_err("revoking device trust")?; 133 133 ··· 166 166 state 167 167 .repos 168 168 .oauth 169 - .update_device_friendly_name(&input.device_id, input.friendly_name.as_deref()) 169 + .update_device_friendly_name(&input.device_id, &auth.did, input.friendly_name.as_deref()) 170 170 .await 171 171 .log_db_err("updating device friendly name")?; 172 172 ··· 198 198 pub async fn trust_device( 199 199 oauth_repo: &dyn OAuthRepository, 200 200 device_id: &DeviceId, 201 + did: &tranquil_types::Did, 201 202 ) -> Result<(), tranquil_db_traits::DbError> { 202 203 let now = Utc::now(); 203 204 let trusted_until = now + Duration::days(TRUST_DURATION_DAYS); 204 - oauth_repo.trust_device(device_id, now, trusted_until).await 205 + oauth_repo 206 + .trust_device(device_id, did, now, trusted_until) 207 + .await 205 208 } 206 209 207 210 pub async fn extend_device_trust( 208 211 oauth_repo: &dyn OAuthRepository, 209 212 device_id: &DeviceId, 213 + did: &tranquil_types::Did, 210 214 ) -> Result<(), tranquil_db_traits::DbError> { 211 215 let trusted_until = Utc::now() + Duration::days(TRUST_DURATION_DAYS); 212 216 oauth_repo 213 - .extend_device_trust(device_id, trusted_until) 217 + .extend_device_trust(device_id, did, trusted_until) 214 218 .await 215 219 }
+4 -1
crates/tranquil-db-traits/src/oauth.rs
··· 291 291 device_id: &DeviceId, 292 292 did: &Did, 293 293 ) -> Result<bool, DbError>; 294 - async fn revoke_device_trust(&self, device_id: &DeviceId) -> Result<(), DbError>; 294 + async fn revoke_device_trust(&self, device_id: &DeviceId, did: &Did) -> Result<(), DbError>; 295 295 async fn update_device_friendly_name( 296 296 &self, 297 297 device_id: &DeviceId, 298 + did: &Did, 298 299 friendly_name: Option<&str>, 299 300 ) -> Result<(), DbError>; 300 301 async fn trust_device( 301 302 &self, 302 303 device_id: &DeviceId, 304 + did: &Did, 303 305 trusted_at: DateTime<Utc>, 304 306 trusted_until: DateTime<Utc>, 305 307 ) -> Result<(), DbError>; 306 308 async fn extend_device_trust( 307 309 &self, 308 310 device_id: &DeviceId, 311 + did: &Did, 309 312 trusted_until: DateTime<Utc>, 310 313 ) -> Result<(), DbError>; 311 314
+4 -1
crates/tranquil-db/src/postgres/oauth.rs
··· 1194 1194 Ok(exists.is_some()) 1195 1195 } 1196 1196 1197 - async fn revoke_device_trust(&self, device_id: &DeviceId) -> Result<(), DbError> { 1197 + async fn revoke_device_trust(&self, device_id: &DeviceId, _did: &Did) -> Result<(), DbError> { 1198 1198 sqlx::query!( 1199 1199 "UPDATE oauth_device SET trusted_at = NULL, trusted_until = NULL WHERE id = $1", 1200 1200 device_id.as_str() ··· 1208 1208 async fn update_device_friendly_name( 1209 1209 &self, 1210 1210 device_id: &DeviceId, 1211 + _did: &Did, 1211 1212 friendly_name: Option<&str>, 1212 1213 ) -> Result<(), DbError> { 1213 1214 sqlx::query!( ··· 1224 1225 async fn trust_device( 1225 1226 &self, 1226 1227 device_id: &DeviceId, 1228 + _did: &Did, 1227 1229 trusted_at: DateTime<Utc>, 1228 1230 trusted_until: DateTime<Utc>, 1229 1231 ) -> Result<(), DbError> { ··· 1242 1244 async fn extend_device_trust( 1243 1245 &self, 1244 1246 device_id: &DeviceId, 1247 + _did: &Did, 1245 1248 trusted_until: DateTime<Utc>, 1246 1249 ) -> Result<(), DbError> { 1247 1250 sqlx::query!(
+8 -4
crates/tranquil-oauth-server/src/endpoints/authorize/login.rs
··· 529 529 530 530 if device_is_trusted { 531 531 if let Some(ref dev_id) = device_cookie { 532 - let _ = 533 - tranquil_api::server::extend_device_trust(state.repos.oauth.as_ref(), dev_id) 534 - .await; 532 + let _ = tranquil_api::server::extend_device_trust( 533 + state.repos.oauth.as_ref(), 534 + dev_id, 535 + &user.did, 536 + ) 537 + .await; 535 538 } 536 539 } else { 537 540 if state ··· 892 895 .into_response(); 893 896 } 894 897 let _ = 895 - tranquil_api::server::extend_device_trust(state.repos.oauth.as_ref(), &device_id).await; 898 + tranquil_api::server::extend_device_trust(state.repos.oauth.as_ref(), &device_id, &did) 899 + .await; 896 900 } 897 901 if user.two_factor_enabled { 898 902 let _ = state
+6 -3
crates/tranquil-oauth-server/src/endpoints/authorize/passkey.rs
··· 1074 1074 1075 1075 if device_is_trusted { 1076 1076 if let Some(ref dev_id) = device_cookie { 1077 - let _ = 1078 - tranquil_api::server::extend_device_trust(state.repos.oauth.as_ref(), dev_id) 1079 - .await; 1077 + let _ = tranquil_api::server::extend_device_trust( 1078 + state.repos.oauth.as_ref(), 1079 + dev_id, 1080 + &did, 1081 + ) 1082 + .await; 1080 1083 } 1081 1084 } else { 1082 1085 let user = match state.repos.user.get_2fa_status_by_did(&did).await {
+2 -1
crates/tranquil-oauth-server/src/endpoints/authorize/two_factor.rs
··· 273 273 .upsert_account_device(&did, &trust_device_id) 274 274 .await; 275 275 let _ = 276 - tranquil_api::server::trust_device(state.repos.oauth.as_ref(), &trust_device_id).await; 276 + tranquil_api::server::trust_device(state.repos.oauth.as_ref(), &trust_device_id, &did) 277 + .await; 277 278 } 278 279 let requested_scope_str = request_data 279 280 .parameters
+72 -1
crates/tranquil-pds/src/repo/mod.rs
··· 1 - pub use tranquil_repo::{PostgresBlockStore, TrackingBlockStore}; 1 + pub use tranquil_repo::PostgresBlockStore; 2 + 3 + pub type TrackingBlockStore = tranquil_repo::TrackingBlockStore<AnyBlockStore>; 4 + 5 + use bytes::Bytes; 6 + use cid::Cid; 7 + use jacquard_repo::error::RepoError; 8 + use jacquard_repo::repo::CommitData; 9 + use jacquard_repo::storage::BlockStore; 10 + use tranquil_store::blockstore::TranquilBlockStore; 11 + 12 + #[derive(Clone)] 13 + pub enum AnyBlockStore { 14 + Postgres(PostgresBlockStore), 15 + TranquilStore(TranquilBlockStore), 16 + } 17 + 18 + impl AnyBlockStore { 19 + pub fn as_postgres(&self) -> Option<&PostgresBlockStore> { 20 + match self { 21 + Self::Postgres(s) => Some(s), 22 + Self::TranquilStore(_) => None, 23 + } 24 + } 25 + } 26 + 27 + impl BlockStore for AnyBlockStore { 28 + async fn get(&self, cid: &Cid) -> Result<Option<Bytes>, RepoError> { 29 + match self { 30 + Self::Postgres(s) => s.get(cid).await, 31 + Self::TranquilStore(s) => s.get(cid).await, 32 + } 33 + } 34 + 35 + async fn put(&self, data: &[u8]) -> Result<Cid, RepoError> { 36 + match self { 37 + Self::Postgres(s) => s.put(data).await, 38 + Self::TranquilStore(s) => s.put(data).await, 39 + } 40 + } 41 + 42 + async fn has(&self, cid: &Cid) -> Result<bool, RepoError> { 43 + match self { 44 + Self::Postgres(s) => s.has(cid).await, 45 + Self::TranquilStore(s) => s.has(cid).await, 46 + } 47 + } 48 + 49 + async fn put_many( 50 + &self, 51 + blocks: impl IntoIterator<Item = (Cid, Bytes)> + Send, 52 + ) -> Result<(), RepoError> { 53 + match self { 54 + Self::Postgres(s) => s.put_many(blocks).await, 55 + Self::TranquilStore(s) => s.put_many(blocks).await, 56 + } 57 + } 58 + 59 + async fn get_many(&self, cids: &[Cid]) -> Result<Vec<Option<Bytes>>, RepoError> { 60 + match self { 61 + Self::Postgres(s) => s.get_many(cids).await, 62 + Self::TranquilStore(s) => s.get_many(cids).await, 63 + } 64 + } 65 + 66 + async fn apply_commit(&self, commit: CommitData) -> Result<(), RepoError> { 67 + match self { 68 + Self::Postgres(s) => s.apply_commit(commit).await, 69 + Self::TranquilStore(s) => s.apply_commit(commit).await, 70 + } 71 + } 72 + }
+17 -26
crates/tranquil-pds/src/scheduled.rs
··· 15 15 }; 16 16 use tranquil_types::{AtUri, CidLink, Did}; 17 17 18 - use crate::repo::PostgresBlockStore; 18 + use crate::repo::AnyBlockStore; 19 19 use crate::storage::BlobStorage; 20 20 use crate::sync::car::encode_car_header; 21 21 ··· 44 44 45 45 async fn process_genesis_commit( 46 46 repo_repo: &dyn RepoRepository, 47 - block_store: &PostgresBlockStore, 47 + block_store: &AnyBlockStore, 48 48 row: BrokenGenesisCommit, 49 49 ) -> Result<(Did, SequenceNumber), (SequenceNumber, GenesisBackfillError)> { 50 50 let commit_cid_str = row ··· 69 69 70 70 pub async fn backfill_genesis_commit_blocks( 71 71 repo_repo: Arc<dyn RepoRepository>, 72 - block_store: PostgresBlockStore, 72 + block_store: AnyBlockStore, 73 73 ) { 74 74 let broken_genesis_commits = match repo_repo.get_broken_genesis_commits().await { 75 75 Ok(rows) => rows, ··· 122 122 123 123 async fn process_repo_rev( 124 124 repo_repo: &dyn RepoRepository, 125 - block_store: &PostgresBlockStore, 125 + block_store: &AnyBlockStore, 126 126 user_id: uuid::Uuid, 127 127 repo_root_cid: String, 128 128 ) -> Result<uuid::Uuid, uuid::Uuid> { ··· 147 147 Ok(user_id) 148 148 } 149 149 150 - pub async fn backfill_repo_rev( 151 - repo_repo: Arc<dyn RepoRepository>, 152 - block_store: PostgresBlockStore, 153 - ) { 150 + pub async fn backfill_repo_rev(repo_repo: Arc<dyn RepoRepository>, block_store: AnyBlockStore) { 154 151 let repos_missing_rev = match repo_repo.get_repos_without_rev().await { 155 152 Ok(rows) => rows, 156 153 Err(e) => { ··· 197 194 198 195 async fn process_user_blocks( 199 196 repo_repo: &dyn RepoRepository, 200 - block_store: &PostgresBlockStore, 197 + block_store: &AnyBlockStore, 201 198 user_id: uuid::Uuid, 202 199 repo_root_cid: String, 203 200 repo_rev: Option<String>, ··· 218 215 Ok((user_id, count)) 219 216 } 220 217 221 - pub async fn backfill_user_blocks( 222 - repo_repo: Arc<dyn RepoRepository>, 223 - block_store: PostgresBlockStore, 224 - ) { 218 + pub async fn backfill_user_blocks(repo_repo: Arc<dyn RepoRepository>, block_store: AnyBlockStore) { 225 219 let users_without_blocks = match repo_repo.get_users_without_blocks().await { 226 220 Ok(rows) => rows, 227 221 Err(e) => { ··· 271 265 } 272 266 273 267 pub async fn collect_current_repo_blocks( 274 - block_store: &PostgresBlockStore, 268 + block_store: &AnyBlockStore, 275 269 head_cid: &Cid, 276 270 ) -> anyhow::Result<Vec<Vec<u8>>> { 277 271 let mut block_cids: Vec<Vec<u8>> = Vec::new(); ··· 324 318 325 319 async fn process_record_blobs( 326 320 repo_repo: &dyn RepoRepository, 327 - block_store: &PostgresBlockStore, 321 + block_store: &AnyBlockStore, 328 322 user_id: uuid::Uuid, 329 323 did: Did, 330 324 ) -> Result<(uuid::Uuid, Did, usize), (uuid::Uuid, &'static str)> { ··· 383 377 Ok((user_id, did, blob_refs_found)) 384 378 } 385 379 386 - pub async fn backfill_record_blobs( 387 - repo_repo: Arc<dyn RepoRepository>, 388 - block_store: PostgresBlockStore, 389 - ) { 380 + pub async fn backfill_record_blobs(repo_repo: Arc<dyn RepoRepository>, block_store: AnyBlockStore) { 390 381 let users_needing_backfill = match repo_repo.get_users_needing_record_blobs_backfill(100).await 391 382 { 392 383 Ok(rows) => rows, ··· 437 428 blob_store: Arc<dyn BlobStorage>, 438 429 sso_repo: Arc<dyn SsoRepository>, 439 430 repo_repo: Arc<dyn RepoRepository>, 440 - block_store: PostgresBlockStore, 431 + block_store: AnyBlockStore, 441 432 shutdown: CancellationToken, 442 433 ) { 443 434 let cfg = tranquil_config::get(); ··· 502 493 } 503 494 } 504 495 _ = gc_ticker.tick() => { 505 - if let Err(e) = run_block_gc(repo_repo.as_ref(), &block_store).await { 496 + if let Some(pg) = block_store.as_postgres() 497 + && let Err(e) = run_block_gc(repo_repo.as_ref(), pg).await 498 + { 506 499 error!("Block GC error: {e}"); 507 500 } 508 501 } ··· 514 507 515 508 async fn run_block_gc( 516 509 repo_repo: &dyn RepoRepository, 517 - block_store: &PostgresBlockStore, 510 + block_store: &crate::repo::PostgresBlockStore, 518 511 ) -> anyhow::Result<()> { 519 512 let mut total_deleted: u64 = 0; 520 513 ··· 632 625 } 633 626 634 627 pub async fn generate_repo_car( 635 - block_store: &PostgresBlockStore, 628 + block_store: &AnyBlockStore, 636 629 head_cid: &Cid, 637 630 ) -> anyhow::Result<Vec<u8>> { 638 - use jacquard_repo::storage::BlockStore; 639 - 640 631 let block_cids_bytes = collect_current_repo_blocks(block_store, head_cid).await?; 641 632 let block_cids: Vec<Cid> = block_cids_bytes 642 633 .iter() ··· 686 677 687 678 pub async fn generate_repo_car_from_user_blocks( 688 679 repo_repo: &dyn tranquil_db_traits::RepoRepository, 689 - block_store: &PostgresBlockStore, 680 + block_store: &AnyBlockStore, 690 681 user_id: uuid::Uuid, 691 682 _head_cid: &Cid, 692 683 ) -> anyhow::Result<Vec<u8>> {
+51 -9
crates/tranquil-pds/src/state.rs
··· 33 33 #[derive(Clone)] 34 34 pub struct AppState { 35 35 pub repos: Arc<PostgresRepositories>, 36 - pub block_store: PostgresBlockStore, 36 + pub block_store: crate::repo::AnyBlockStore, 37 37 pub blob_store: Arc<dyn BlobStorage>, 38 38 pub firehose_tx: broadcast::Sender<SequencedEvent>, 39 39 pub rate_limiters: Arc<RateLimiters>, ··· 266 266 let mut repos = PostgresRepositories::new(db.clone()); 267 267 268 268 let cfg = tranquil_config::get(); 269 - if cfg.storage.repo_backend() == tranquil_config::RepoBackend::TranquilStore { 270 - wire_tranquil_store(&mut repos, &cfg.tranquil_store); 271 - } 269 + let block_store = 270 + match cfg.storage.repo_backend() == tranquil_config::RepoBackend::TranquilStore { 271 + true => { 272 + let bs = wire_tranquil_store(&mut repos, &cfg.tranquil_store, shutdown.clone()); 273 + crate::repo::AnyBlockStore::TranquilStore(bs) 274 + } 275 + false => crate::repo::AnyBlockStore::Postgres(PostgresBlockStore::new(db)), 276 + }; 272 277 273 278 let repos = Arc::new(repos); 274 - let block_store = PostgresBlockStore::new(db); 275 279 let blob_store = create_blob_storage().await; 276 280 277 281 let firehose_buffer_size = tranquil_config::get().firehose.buffer_size; ··· 389 393 fn wire_tranquil_store( 390 394 repos: &mut PostgresRepositories, 391 395 store_cfg: &tranquil_config::TranquilStoreConfig, 392 - ) { 396 + shutdown: CancellationToken, 397 + ) -> tranquil_store::blockstore::TranquilBlockStore { 393 398 use tranquil_store::RealIO; 399 + use tranquil_store::blockstore::{BlockStoreConfig, TranquilBlockStore}; 394 400 use tranquil_store::eventlog::{EventLog, EventLogBridge, EventLogConfig}; 395 401 use tranquil_store::metastore::client::MetastoreClient; 396 402 use tranquil_store::metastore::handler::HandlerPool; ··· 404 410 }; 405 411 let metastore_dir = data_dir.join("metastore"); 406 412 let segments_dir = data_dir.join("eventlog").join("segments"); 413 + let blockstore_data_dir = data_dir.join("blockstore").join("data"); 414 + let blockstore_index_dir = data_dir.join("blockstore").join("index"); 407 415 408 416 std::fs::create_dir_all(&metastore_dir).expect("failed to create metastore directory"); 409 417 std::fs::create_dir_all(&segments_dir).expect("failed to create eventlog segments directory"); 418 + std::fs::create_dir_all(&blockstore_data_dir) 419 + .expect("failed to create blockstore data directory"); 420 + std::fs::create_dir_all(&blockstore_index_dir) 421 + .expect("failed to create blockstore index directory"); 410 422 411 423 let metastore_config = store_cfg 412 424 .memory_budget_mb ··· 418 430 let metastore = 419 431 Metastore::open(&metastore_dir, metastore_config).expect("failed to open metastore"); 420 432 433 + let blockstore = TranquilBlockStore::open(BlockStoreConfig { 434 + data_dir: blockstore_data_dir, 435 + index_dir: blockstore_index_dir, 436 + max_file_size: tranquil_store::blockstore::DEFAULT_MAX_FILE_SIZE, 437 + group_commit: Default::default(), 438 + }) 439 + .expect("failed to open blockstore"); 440 + 421 441 let event_log = EventLog::open( 422 442 EventLogConfig { 423 443 segments_dir, ··· 441 461 442 462 let notifier = bridge.notifier(); 443 463 444 - let pool = HandlerPool::spawn::<RealIO>(metastore, bridge, None, store_cfg.handler_threads); 464 + let pool = Arc::new(HandlerPool::spawn::<RealIO>( 465 + metastore, 466 + bridge, 467 + Some(blockstore.clone()), 468 + store_cfg.handler_threads, 469 + )); 445 470 446 - let client = MetastoreClient::<RealIO>::new(Arc::new(pool)); 471 + tokio::spawn({ 472 + let pool = Arc::clone(&pool); 473 + async move { 474 + shutdown.cancelled().await; 475 + pool.close().await; 476 + } 477 + }); 478 + 479 + let client = MetastoreClient::<RealIO>::new(pool); 447 480 448 481 tracing::info!(data_dir = %store_cfg.data_dir, "tranquil-store data directory"); 449 482 450 483 repos.repo = Arc::new(client.clone()); 451 - repos.backlink = Arc::new(client); 484 + repos.backlink = Arc::new(client.clone()); 485 + repos.blob = Arc::new(client.clone()); 486 + repos.user = Arc::new(client.clone()); 487 + repos.session = Arc::new(client.clone()); 488 + repos.oauth = Arc::new(client.clone()); 489 + repos.infra = Arc::new(client.clone()); 490 + repos.delegation = Arc::new(client.clone()); 491 + repos.sso = Arc::new(client); 452 492 repos.event_notifier = Arc::new(notifier); 493 + 494 + blockstore 453 495 }
+5 -5
crates/tranquil-repo/src/lib.rs
··· 150 150 } 151 151 152 152 #[derive(Clone)] 153 - pub struct TrackingBlockStore { 154 - inner: PostgresBlockStore, 153 + pub struct TrackingBlockStore<S: BlockStore> { 154 + inner: S, 155 155 written_cids: Arc<Mutex<Vec<Cid>>>, 156 156 read_cids: Arc<Mutex<HashSet<Cid>>>, 157 157 } 158 158 159 - impl TrackingBlockStore { 160 - pub fn new(store: PostgresBlockStore) -> Self { 159 + impl<S: BlockStore + Sync> TrackingBlockStore<S> { 160 + pub fn new(store: S) -> Self { 161 161 Self { 162 162 inner: store, 163 163 written_cids: Arc::new(Mutex::new(Vec::new())), ··· 188 188 } 189 189 } 190 190 191 - impl BlockStore for TrackingBlockStore { 191 + impl<S: BlockStore + Sync> BlockStore for TrackingBlockStore<S> { 192 192 async fn get(&self, cid: &Cid) -> Result<Option<Bytes>, RepoError> { 193 193 let result = self.inner.get(cid).await?; 194 194 if result.is_some() {
+1
crates/tranquil-store/Cargo.toml
··· 22 22 serde_json = { workspace = true } 23 23 thiserror = { workspace = true } 24 24 tranquil-db-traits = { workspace = true } 25 + tranquil-oauth = { workspace = true } 25 26 tranquil-types = { workspace = true } 26 27 jacquard-repo = { workspace = true } 27 28 cid = { workspace = true }
+3877 -8
crates/tranquil-store/src/metastore/client.rs
··· 5 5 use chrono::{DateTime, Utc}; 6 6 use tokio::sync::oneshot; 7 7 use tranquil_db_traits::{ 8 - AccountStatus, ApplyCommitError, ApplyCommitInput, ApplyCommitResult, Backlink, 9 - BrokenGenesisCommit, CommitEventData, DbError, EventBlocksCids, ImportBlock, ImportRecord, 10 - ImportRepoError, RepoAccountInfo, RepoInfo, RepoListItem, RepoWithoutRev, SequenceNumber, 11 - SequencedEvent, UserNeedingRecordBlobsBackfill, UserWithoutBlocks, 8 + AccountSearchResult, AccountStatus, AdminAccountInfo, ApplyCommitError, ApplyCommitInput, 9 + ApplyCommitResult, Backlink, BrokenGenesisCommit, CommitEventData, CommsChannel, CommsType, 10 + CompletePasskeySetupInput, CreateAccountError, CreateDelegatedAccountInput, 11 + CreatePasskeyAccountInput, CreatePasswordAccountInput, CreatePasswordAccountResult, 12 + CreateSsoAccountInput, DbError, DeletionRequest, DidWebOverrides, EventBlocksCids, ImportBlock, 13 + ImportRecord, ImportRepoError, InviteCodeError, InviteCodeInfo, InviteCodeRow, 14 + InviteCodeSortOrder, InviteCodeUse, MigrationReactivationError, MigrationReactivationInput, 15 + NotificationHistoryRow, NotificationPrefs, OAuthTokenWithUser, PasswordResetResult, 16 + QueuedComms, ReactivatedAccountInfo, RecoverPasskeyAccountInput, RecoverPasskeyAccountResult, 17 + RepoAccountInfo, RepoInfo, RepoListItem, RepoWithoutRev, ReservedSigningKey, 18 + ScheduledDeletionAccount, ScopePreference, SequenceNumber, SequencedEvent, StoredBackupCode, 19 + StoredPasskey, TokenFamilyId, TotpRecord, TotpRecordState, User2faStatus, UserAuthInfo, 20 + UserCommsPrefs, UserConfirmSignup, UserDidWebInfo, UserEmailInfo, UserForDeletion, 21 + UserForDidDoc, UserForDidDocBuild, UserForPasskeyRecovery, UserForPasskeySetup, 22 + UserForRecovery, UserForVerification, UserIdAndHandle, UserIdAndPasswordHash, 23 + UserIdHandleEmail, UserInfoForAuth, UserKeyInfo, UserKeyWithId, UserLegacyLoginPref, 24 + UserLoginCheck, UserLoginFull, UserLoginInfo, UserNeedingRecordBlobsBackfill, UserPasswordInfo, 25 + UserResendVerification, UserResetCodeInfo, UserRow, UserSessionInfo, UserStatus, 26 + UserVerificationInfo, UserWithKey, UserWithoutBlocks, ValidatedInviteCode, 27 + WebauthnChallengeType, 12 28 }; 13 - use tranquil_types::{AtUri, CidLink, Did, Handle, Nsid, Rkey}; 29 + use tranquil_oauth::{AuthorizedClientData, DeviceData, RequestData, TokenData}; 30 + use tranquil_types::{ 31 + AtUri, AuthorizationCode, CidLink, ClientId, DPoPProofId, DeviceId, Did, Handle, Nsid, 32 + RefreshToken, RequestId, Rkey, TokenId, 33 + }; 14 34 use uuid::Uuid; 15 35 16 36 use super::handler::{ 17 - BacklinkRequest, BlobRequest, CommitRequest, EventRequest, HandlerPool, MetastoreRequest, 18 - RecordRequest, RepoRequest, UserBlockRequest, 37 + BacklinkRequest, BlobRequest, CommitRequest, DelegationRequest, EventRequest, HandlerPool, 38 + InfraRequest, MetastoreRequest, OAuthRequest, RecordRequest, RepoRequest, SessionRequest, 39 + SsoRequest, UserBlockRequest, UserRequest, 19 40 }; 20 41 use super::keys::UserHash; 21 42 use crate::io::StorageIO; ··· 37 58 ) -> Result<(), ImportRepoError> { 38 59 rx.await 39 60 .map_err(|_| ImportRepoError::Database("metastore handler thread closed".to_string()))? 61 + } 62 + 63 + async fn recv_invite( 64 + rx: oneshot::Receiver<Result<(), InviteCodeError>>, 65 + ) -> Result<(), InviteCodeError> { 66 + rx.await.map_err(|_| { 67 + InviteCodeError::DatabaseError(DbError::Connection( 68 + "metastore handler thread closed".to_string(), 69 + )) 70 + })? 71 + } 72 + 73 + async fn recv_create_account<T>( 74 + rx: oneshot::Receiver<Result<T, CreateAccountError>>, 75 + ) -> Result<T, CreateAccountError> { 76 + rx.await 77 + .map_err(|_| CreateAccountError::Database("metastore handler thread closed".to_string()))? 78 + } 79 + 80 + async fn recv_migration_reactivation( 81 + rx: oneshot::Receiver<Result<ReactivatedAccountInfo, MigrationReactivationError>>, 82 + ) -> Result<ReactivatedAccountInfo, MigrationReactivationError> { 83 + rx.await.map_err(|_| { 84 + MigrationReactivationError::Database("metastore handler thread closed".to_string()) 85 + })? 40 86 } 41 87 42 88 pub struct MetastoreClient<S: StorageIO> { ··· 652 698 self.pool 653 699 .send(MetastoreRequest::Repo(RepoRequest::ListReposPaginated { 654 700 cursor_user_hash: cursor_hash, 655 - limit: usize::try_from(limit).unwrap_or(usize::MAX), 701 + limit: usize::try_from(limit).unwrap_or(0), 656 702 tx, 657 703 }))?; 658 704 recv(rx).await ··· 1021 1067 recv(rx).await 1022 1068 } 1023 1069 } 1070 + 1071 + #[async_trait] 1072 + impl<S: StorageIO + 'static> tranquil_db_traits::DelegationRepository for MetastoreClient<S> { 1073 + async fn is_delegated_account(&self, did: &Did) -> Result<bool, DbError> { 1074 + let (tx, rx) = oneshot::channel(); 1075 + self.pool.send(MetastoreRequest::Delegation( 1076 + DelegationRequest::IsDelegatedAccount { 1077 + did: did.clone(), 1078 + tx, 1079 + }, 1080 + ))?; 1081 + recv(rx).await 1082 + } 1083 + 1084 + async fn create_delegation( 1085 + &self, 1086 + delegated_did: &Did, 1087 + controller_did: &Did, 1088 + granted_scopes: &tranquil_db_traits::DbScope, 1089 + granted_by: &Did, 1090 + ) -> Result<Uuid, DbError> { 1091 + let (tx, rx) = oneshot::channel(); 1092 + self.pool.send(MetastoreRequest::Delegation( 1093 + DelegationRequest::CreateDelegation { 1094 + delegated_did: delegated_did.clone(), 1095 + controller_did: controller_did.clone(), 1096 + granted_scopes: granted_scopes.clone(), 1097 + granted_by: granted_by.clone(), 1098 + tx, 1099 + }, 1100 + ))?; 1101 + recv(rx).await 1102 + } 1103 + 1104 + async fn revoke_delegation( 1105 + &self, 1106 + delegated_did: &Did, 1107 + controller_did: &Did, 1108 + revoked_by: &Did, 1109 + ) -> Result<bool, DbError> { 1110 + let (tx, rx) = oneshot::channel(); 1111 + self.pool.send(MetastoreRequest::Delegation( 1112 + DelegationRequest::RevokeDelegation { 1113 + delegated_did: delegated_did.clone(), 1114 + controller_did: controller_did.clone(), 1115 + revoked_by: revoked_by.clone(), 1116 + tx, 1117 + }, 1118 + ))?; 1119 + recv(rx).await 1120 + } 1121 + 1122 + async fn update_delegation_scopes( 1123 + &self, 1124 + delegated_did: &Did, 1125 + controller_did: &Did, 1126 + new_scopes: &tranquil_db_traits::DbScope, 1127 + ) -> Result<bool, DbError> { 1128 + let (tx, rx) = oneshot::channel(); 1129 + self.pool.send(MetastoreRequest::Delegation( 1130 + DelegationRequest::UpdateDelegationScopes { 1131 + delegated_did: delegated_did.clone(), 1132 + controller_did: controller_did.clone(), 1133 + new_scopes: new_scopes.clone(), 1134 + tx, 1135 + }, 1136 + ))?; 1137 + recv(rx).await 1138 + } 1139 + 1140 + async fn get_delegation( 1141 + &self, 1142 + delegated_did: &Did, 1143 + controller_did: &Did, 1144 + ) -> Result<Option<tranquil_db_traits::DelegationGrant>, DbError> { 1145 + let (tx, rx) = oneshot::channel(); 1146 + self.pool.send(MetastoreRequest::Delegation( 1147 + DelegationRequest::GetDelegation { 1148 + delegated_did: delegated_did.clone(), 1149 + controller_did: controller_did.clone(), 1150 + tx, 1151 + }, 1152 + ))?; 1153 + recv(rx).await 1154 + } 1155 + 1156 + async fn get_delegations_for_account( 1157 + &self, 1158 + delegated_did: &Did, 1159 + ) -> Result<Vec<tranquil_db_traits::ControllerInfo>, DbError> { 1160 + let (tx, rx) = oneshot::channel(); 1161 + self.pool.send(MetastoreRequest::Delegation( 1162 + DelegationRequest::GetDelegationsForAccount { 1163 + delegated_did: delegated_did.clone(), 1164 + tx, 1165 + }, 1166 + ))?; 1167 + recv(rx).await 1168 + } 1169 + 1170 + async fn get_accounts_controlled_by( 1171 + &self, 1172 + controller_did: &Did, 1173 + ) -> Result<Vec<tranquil_db_traits::DelegatedAccountInfo>, DbError> { 1174 + let (tx, rx) = oneshot::channel(); 1175 + self.pool.send(MetastoreRequest::Delegation( 1176 + DelegationRequest::GetAccountsControlledBy { 1177 + controller_did: controller_did.clone(), 1178 + tx, 1179 + }, 1180 + ))?; 1181 + recv(rx).await 1182 + } 1183 + 1184 + async fn count_active_controllers(&self, delegated_did: &Did) -> Result<i64, DbError> { 1185 + let (tx, rx) = oneshot::channel(); 1186 + self.pool.send(MetastoreRequest::Delegation( 1187 + DelegationRequest::CountActiveControllers { 1188 + delegated_did: delegated_did.clone(), 1189 + tx, 1190 + }, 1191 + ))?; 1192 + recv(rx).await 1193 + } 1194 + 1195 + async fn controls_any_accounts(&self, did: &Did) -> Result<bool, DbError> { 1196 + let (tx, rx) = oneshot::channel(); 1197 + self.pool.send(MetastoreRequest::Delegation( 1198 + DelegationRequest::ControlsAnyAccounts { 1199 + did: did.clone(), 1200 + tx, 1201 + }, 1202 + ))?; 1203 + recv(rx).await 1204 + } 1205 + 1206 + async fn log_delegation_action( 1207 + &self, 1208 + delegated_did: &Did, 1209 + actor_did: &Did, 1210 + controller_did: Option<&Did>, 1211 + action_type: tranquil_db_traits::DelegationActionType, 1212 + action_details: Option<serde_json::Value>, 1213 + ip_address: Option<&str>, 1214 + user_agent: Option<&str>, 1215 + ) -> Result<Uuid, DbError> { 1216 + let (tx, rx) = oneshot::channel(); 1217 + self.pool.send(MetastoreRequest::Delegation( 1218 + DelegationRequest::LogDelegationAction { 1219 + delegated_did: delegated_did.clone(), 1220 + actor_did: actor_did.clone(), 1221 + controller_did: controller_did.cloned(), 1222 + action_type, 1223 + action_details, 1224 + ip_address: ip_address.map(str::to_owned), 1225 + user_agent: user_agent.map(str::to_owned), 1226 + tx, 1227 + }, 1228 + ))?; 1229 + recv(rx).await 1230 + } 1231 + 1232 + async fn get_audit_log_for_account( 1233 + &self, 1234 + delegated_did: &Did, 1235 + limit: i64, 1236 + offset: i64, 1237 + ) -> Result<Vec<tranquil_db_traits::AuditLogEntry>, DbError> { 1238 + let (tx, rx) = oneshot::channel(); 1239 + self.pool.send(MetastoreRequest::Delegation( 1240 + DelegationRequest::GetAuditLogForAccount { 1241 + delegated_did: delegated_did.clone(), 1242 + limit, 1243 + offset, 1244 + tx, 1245 + }, 1246 + ))?; 1247 + recv(rx).await 1248 + } 1249 + 1250 + async fn count_audit_log_entries(&self, delegated_did: &Did) -> Result<i64, DbError> { 1251 + let (tx, rx) = oneshot::channel(); 1252 + self.pool.send(MetastoreRequest::Delegation( 1253 + DelegationRequest::CountAuditLogEntries { 1254 + delegated_did: delegated_did.clone(), 1255 + tx, 1256 + }, 1257 + ))?; 1258 + recv(rx).await 1259 + } 1260 + } 1261 + 1262 + #[async_trait] 1263 + impl<S: StorageIO + 'static> tranquil_db_traits::SsoRepository for MetastoreClient<S> { 1264 + async fn create_external_identity( 1265 + &self, 1266 + did: &Did, 1267 + provider: tranquil_db_traits::SsoProviderType, 1268 + provider_user_id: &str, 1269 + provider_username: Option<&str>, 1270 + provider_email: Option<&str>, 1271 + ) -> Result<Uuid, DbError> { 1272 + let (tx, rx) = oneshot::channel(); 1273 + self.pool 1274 + .send(MetastoreRequest::Sso(SsoRequest::CreateExternalIdentity { 1275 + did: did.clone(), 1276 + provider, 1277 + provider_user_id: provider_user_id.to_owned(), 1278 + provider_username: provider_username.map(str::to_owned), 1279 + provider_email: provider_email.map(str::to_owned), 1280 + tx, 1281 + }))?; 1282 + recv(rx).await 1283 + } 1284 + 1285 + async fn get_external_identity_by_provider( 1286 + &self, 1287 + provider: tranquil_db_traits::SsoProviderType, 1288 + provider_user_id: &str, 1289 + ) -> Result<Option<tranquil_db_traits::ExternalIdentity>, DbError> { 1290 + let (tx, rx) = oneshot::channel(); 1291 + self.pool.send(MetastoreRequest::Sso( 1292 + SsoRequest::GetExternalIdentityByProvider { 1293 + provider, 1294 + provider_user_id: provider_user_id.to_owned(), 1295 + tx, 1296 + }, 1297 + ))?; 1298 + recv(rx).await 1299 + } 1300 + 1301 + async fn get_external_identities_by_did( 1302 + &self, 1303 + did: &Did, 1304 + ) -> Result<Vec<tranquil_db_traits::ExternalIdentity>, DbError> { 1305 + let (tx, rx) = oneshot::channel(); 1306 + self.pool.send(MetastoreRequest::Sso( 1307 + SsoRequest::GetExternalIdentitiesByDid { 1308 + did: did.clone(), 1309 + tx, 1310 + }, 1311 + ))?; 1312 + recv(rx).await 1313 + } 1314 + 1315 + async fn update_external_identity_login( 1316 + &self, 1317 + id: Uuid, 1318 + provider_username: Option<&str>, 1319 + provider_email: Option<&str>, 1320 + ) -> Result<(), DbError> { 1321 + let (tx, rx) = oneshot::channel(); 1322 + self.pool.send(MetastoreRequest::Sso( 1323 + SsoRequest::UpdateExternalIdentityLogin { 1324 + id, 1325 + provider_username: provider_username.map(str::to_owned), 1326 + provider_email: provider_email.map(str::to_owned), 1327 + tx, 1328 + }, 1329 + ))?; 1330 + recv(rx).await 1331 + } 1332 + 1333 + async fn delete_external_identity(&self, id: Uuid, did: &Did) -> Result<bool, DbError> { 1334 + let (tx, rx) = oneshot::channel(); 1335 + self.pool 1336 + .send(MetastoreRequest::Sso(SsoRequest::DeleteExternalIdentity { 1337 + id, 1338 + did: did.clone(), 1339 + tx, 1340 + }))?; 1341 + recv(rx).await 1342 + } 1343 + 1344 + async fn create_sso_auth_state( 1345 + &self, 1346 + state: &str, 1347 + request_uri: &str, 1348 + provider: tranquil_db_traits::SsoProviderType, 1349 + action: tranquil_db_traits::SsoAction, 1350 + nonce: Option<&str>, 1351 + code_verifier: Option<&str>, 1352 + did: Option<&Did>, 1353 + ) -> Result<(), DbError> { 1354 + let (tx, rx) = oneshot::channel(); 1355 + self.pool 1356 + .send(MetastoreRequest::Sso(SsoRequest::CreateSsoAuthState { 1357 + state: state.to_owned(), 1358 + request_uri: request_uri.to_owned(), 1359 + provider, 1360 + action, 1361 + nonce: nonce.map(str::to_owned), 1362 + code_verifier: code_verifier.map(str::to_owned), 1363 + did: did.cloned(), 1364 + tx, 1365 + }))?; 1366 + recv(rx).await 1367 + } 1368 + 1369 + async fn consume_sso_auth_state( 1370 + &self, 1371 + state: &str, 1372 + ) -> Result<Option<tranquil_db_traits::SsoAuthState>, DbError> { 1373 + let (tx, rx) = oneshot::channel(); 1374 + self.pool 1375 + .send(MetastoreRequest::Sso(SsoRequest::ConsumeSsoAuthState { 1376 + state: state.to_owned(), 1377 + tx, 1378 + }))?; 1379 + recv(rx).await 1380 + } 1381 + 1382 + async fn cleanup_expired_sso_auth_states(&self) -> Result<u64, DbError> { 1383 + let (tx, rx) = oneshot::channel(); 1384 + self.pool.send(MetastoreRequest::Sso( 1385 + SsoRequest::CleanupExpiredSsoAuthStates { tx }, 1386 + ))?; 1387 + recv(rx).await 1388 + } 1389 + 1390 + async fn create_pending_registration( 1391 + &self, 1392 + token: &str, 1393 + request_uri: &str, 1394 + provider: tranquil_db_traits::SsoProviderType, 1395 + provider_user_id: &str, 1396 + provider_username: Option<&str>, 1397 + provider_email: Option<&str>, 1398 + provider_email_verified: bool, 1399 + ) -> Result<(), DbError> { 1400 + let (tx, rx) = oneshot::channel(); 1401 + self.pool.send(MetastoreRequest::Sso( 1402 + SsoRequest::CreatePendingRegistration { 1403 + token: token.to_owned(), 1404 + request_uri: request_uri.to_owned(), 1405 + provider, 1406 + provider_user_id: provider_user_id.to_owned(), 1407 + provider_username: provider_username.map(str::to_owned), 1408 + provider_email: provider_email.map(str::to_owned), 1409 + provider_email_verified, 1410 + tx, 1411 + }, 1412 + ))?; 1413 + recv(rx).await 1414 + } 1415 + 1416 + async fn get_pending_registration( 1417 + &self, 1418 + token: &str, 1419 + ) -> Result<Option<tranquil_db_traits::SsoPendingRegistration>, DbError> { 1420 + let (tx, rx) = oneshot::channel(); 1421 + self.pool 1422 + .send(MetastoreRequest::Sso(SsoRequest::GetPendingRegistration { 1423 + token: token.to_owned(), 1424 + tx, 1425 + }))?; 1426 + recv(rx).await 1427 + } 1428 + 1429 + async fn consume_pending_registration( 1430 + &self, 1431 + token: &str, 1432 + ) -> Result<Option<tranquil_db_traits::SsoPendingRegistration>, DbError> { 1433 + let (tx, rx) = oneshot::channel(); 1434 + self.pool.send(MetastoreRequest::Sso( 1435 + SsoRequest::ConsumePendingRegistration { 1436 + token: token.to_owned(), 1437 + tx, 1438 + }, 1439 + ))?; 1440 + recv(rx).await 1441 + } 1442 + 1443 + async fn cleanup_expired_pending_registrations(&self) -> Result<u64, DbError> { 1444 + let (tx, rx) = oneshot::channel(); 1445 + self.pool.send(MetastoreRequest::Sso( 1446 + SsoRequest::CleanupExpiredPendingRegistrations { tx }, 1447 + ))?; 1448 + recv(rx).await 1449 + } 1450 + } 1451 + 1452 + #[async_trait] 1453 + impl<S: StorageIO + 'static> tranquil_db_traits::SessionRepository for MetastoreClient<S> { 1454 + async fn create_session( 1455 + &self, 1456 + data: &tranquil_db_traits::SessionTokenCreate, 1457 + ) -> Result<tranquil_db_traits::SessionId, DbError> { 1458 + let (tx, rx) = oneshot::channel(); 1459 + self.pool 1460 + .send(MetastoreRequest::Session(SessionRequest::CreateSession { 1461 + data: data.clone(), 1462 + tx, 1463 + }))?; 1464 + recv(rx).await 1465 + } 1466 + 1467 + async fn get_session_by_access_jti( 1468 + &self, 1469 + access_jti: &str, 1470 + ) -> Result<Option<tranquil_db_traits::SessionToken>, DbError> { 1471 + let (tx, rx) = oneshot::channel(); 1472 + self.pool.send(MetastoreRequest::Session( 1473 + SessionRequest::GetSessionByAccessJti { 1474 + access_jti: access_jti.to_owned(), 1475 + tx, 1476 + }, 1477 + ))?; 1478 + recv(rx).await 1479 + } 1480 + 1481 + async fn get_session_for_refresh( 1482 + &self, 1483 + refresh_jti: &str, 1484 + ) -> Result<Option<tranquil_db_traits::SessionForRefresh>, DbError> { 1485 + let (tx, rx) = oneshot::channel(); 1486 + self.pool.send(MetastoreRequest::Session( 1487 + SessionRequest::GetSessionForRefresh { 1488 + refresh_jti: refresh_jti.to_owned(), 1489 + tx, 1490 + }, 1491 + ))?; 1492 + recv(rx).await 1493 + } 1494 + 1495 + async fn update_session_tokens( 1496 + &self, 1497 + session_id: tranquil_db_traits::SessionId, 1498 + new_access_jti: &str, 1499 + new_refresh_jti: &str, 1500 + new_access_expires_at: DateTime<Utc>, 1501 + new_refresh_expires_at: DateTime<Utc>, 1502 + ) -> Result<(), DbError> { 1503 + let (tx, rx) = oneshot::channel(); 1504 + self.pool.send(MetastoreRequest::Session( 1505 + SessionRequest::UpdateSessionTokens { 1506 + session_id, 1507 + new_access_jti: new_access_jti.to_owned(), 1508 + new_refresh_jti: new_refresh_jti.to_owned(), 1509 + new_access_expires_at, 1510 + new_refresh_expires_at, 1511 + tx, 1512 + }, 1513 + ))?; 1514 + recv(rx).await 1515 + } 1516 + 1517 + async fn delete_session_by_access_jti(&self, access_jti: &str) -> Result<u64, DbError> { 1518 + let (tx, rx) = oneshot::channel(); 1519 + self.pool.send(MetastoreRequest::Session( 1520 + SessionRequest::DeleteSessionByAccessJti { 1521 + access_jti: access_jti.to_owned(), 1522 + tx, 1523 + }, 1524 + ))?; 1525 + recv(rx).await 1526 + } 1527 + 1528 + async fn delete_session_by_id( 1529 + &self, 1530 + session_id: tranquil_db_traits::SessionId, 1531 + ) -> Result<u64, DbError> { 1532 + let (tx, rx) = oneshot::channel(); 1533 + self.pool.send(MetastoreRequest::Session( 1534 + SessionRequest::DeleteSessionById { session_id, tx }, 1535 + ))?; 1536 + recv(rx).await 1537 + } 1538 + 1539 + async fn delete_sessions_by_did(&self, did: &Did) -> Result<u64, DbError> { 1540 + let (tx, rx) = oneshot::channel(); 1541 + self.pool.send(MetastoreRequest::Session( 1542 + SessionRequest::DeleteSessionsByDid { 1543 + did: did.clone(), 1544 + tx, 1545 + }, 1546 + ))?; 1547 + recv(rx).await 1548 + } 1549 + 1550 + async fn delete_sessions_by_did_except_jti( 1551 + &self, 1552 + did: &Did, 1553 + except_jti: &str, 1554 + ) -> Result<u64, DbError> { 1555 + let (tx, rx) = oneshot::channel(); 1556 + self.pool.send(MetastoreRequest::Session( 1557 + SessionRequest::DeleteSessionsByDidExceptJti { 1558 + did: did.clone(), 1559 + except_jti: except_jti.to_owned(), 1560 + tx, 1561 + }, 1562 + ))?; 1563 + recv(rx).await 1564 + } 1565 + 1566 + async fn list_sessions_by_did( 1567 + &self, 1568 + did: &Did, 1569 + ) -> Result<Vec<tranquil_db_traits::SessionListItem>, DbError> { 1570 + let (tx, rx) = oneshot::channel(); 1571 + self.pool.send(MetastoreRequest::Session( 1572 + SessionRequest::ListSessionsByDid { 1573 + did: did.clone(), 1574 + tx, 1575 + }, 1576 + ))?; 1577 + recv(rx).await 1578 + } 1579 + 1580 + async fn get_session_access_jti_by_id( 1581 + &self, 1582 + session_id: tranquil_db_traits::SessionId, 1583 + did: &Did, 1584 + ) -> Result<Option<String>, DbError> { 1585 + let (tx, rx) = oneshot::channel(); 1586 + self.pool.send(MetastoreRequest::Session( 1587 + SessionRequest::GetSessionAccessJtiById { 1588 + session_id, 1589 + did: did.clone(), 1590 + tx, 1591 + }, 1592 + ))?; 1593 + recv(rx).await 1594 + } 1595 + 1596 + async fn delete_sessions_by_app_password( 1597 + &self, 1598 + did: &Did, 1599 + app_password_name: &str, 1600 + ) -> Result<u64, DbError> { 1601 + let (tx, rx) = oneshot::channel(); 1602 + self.pool.send(MetastoreRequest::Session( 1603 + SessionRequest::DeleteSessionsByAppPassword { 1604 + did: did.clone(), 1605 + app_password_name: app_password_name.to_owned(), 1606 + tx, 1607 + }, 1608 + ))?; 1609 + recv(rx).await 1610 + } 1611 + 1612 + async fn get_session_jtis_by_app_password( 1613 + &self, 1614 + did: &Did, 1615 + app_password_name: &str, 1616 + ) -> Result<Vec<String>, DbError> { 1617 + let (tx, rx) = oneshot::channel(); 1618 + self.pool.send(MetastoreRequest::Session( 1619 + SessionRequest::GetSessionJtisByAppPassword { 1620 + did: did.clone(), 1621 + app_password_name: app_password_name.to_owned(), 1622 + tx, 1623 + }, 1624 + ))?; 1625 + recv(rx).await 1626 + } 1627 + 1628 + async fn check_refresh_token_used( 1629 + &self, 1630 + refresh_jti: &str, 1631 + ) -> Result<Option<tranquil_db_traits::SessionId>, DbError> { 1632 + let (tx, rx) = oneshot::channel(); 1633 + self.pool.send(MetastoreRequest::Session( 1634 + SessionRequest::CheckRefreshTokenUsed { 1635 + refresh_jti: refresh_jti.to_owned(), 1636 + tx, 1637 + }, 1638 + ))?; 1639 + recv(rx).await 1640 + } 1641 + 1642 + async fn mark_refresh_token_used( 1643 + &self, 1644 + refresh_jti: &str, 1645 + session_id: tranquil_db_traits::SessionId, 1646 + ) -> Result<bool, DbError> { 1647 + let (tx, rx) = oneshot::channel(); 1648 + self.pool.send(MetastoreRequest::Session( 1649 + SessionRequest::MarkRefreshTokenUsed { 1650 + refresh_jti: refresh_jti.to_owned(), 1651 + session_id, 1652 + tx, 1653 + }, 1654 + ))?; 1655 + recv(rx).await 1656 + } 1657 + 1658 + async fn list_app_passwords( 1659 + &self, 1660 + user_id: Uuid, 1661 + ) -> Result<Vec<tranquil_db_traits::AppPasswordRecord>, DbError> { 1662 + let (tx, rx) = oneshot::channel(); 1663 + self.pool.send(MetastoreRequest::Session( 1664 + SessionRequest::ListAppPasswords { user_id, tx }, 1665 + ))?; 1666 + recv(rx).await 1667 + } 1668 + 1669 + async fn get_app_passwords_for_login( 1670 + &self, 1671 + user_id: Uuid, 1672 + ) -> Result<Vec<tranquil_db_traits::AppPasswordRecord>, DbError> { 1673 + let (tx, rx) = oneshot::channel(); 1674 + self.pool.send(MetastoreRequest::Session( 1675 + SessionRequest::GetAppPasswordsForLogin { user_id, tx }, 1676 + ))?; 1677 + recv(rx).await 1678 + } 1679 + 1680 + async fn get_app_password_by_name( 1681 + &self, 1682 + user_id: Uuid, 1683 + name: &str, 1684 + ) -> Result<Option<tranquil_db_traits::AppPasswordRecord>, DbError> { 1685 + let (tx, rx) = oneshot::channel(); 1686 + self.pool.send(MetastoreRequest::Session( 1687 + SessionRequest::GetAppPasswordByName { 1688 + user_id, 1689 + name: name.to_owned(), 1690 + tx, 1691 + }, 1692 + ))?; 1693 + recv(rx).await 1694 + } 1695 + 1696 + async fn create_app_password( 1697 + &self, 1698 + data: &tranquil_db_traits::AppPasswordCreate, 1699 + ) -> Result<Uuid, DbError> { 1700 + let (tx, rx) = oneshot::channel(); 1701 + self.pool.send(MetastoreRequest::Session( 1702 + SessionRequest::CreateAppPassword { 1703 + data: data.clone(), 1704 + tx, 1705 + }, 1706 + ))?; 1707 + recv(rx).await 1708 + } 1709 + 1710 + async fn delete_app_password(&self, user_id: Uuid, name: &str) -> Result<u64, DbError> { 1711 + let (tx, rx) = oneshot::channel(); 1712 + self.pool.send(MetastoreRequest::Session( 1713 + SessionRequest::DeleteAppPassword { 1714 + user_id, 1715 + name: name.to_owned(), 1716 + tx, 1717 + }, 1718 + ))?; 1719 + recv(rx).await 1720 + } 1721 + 1722 + async fn delete_app_passwords_by_controller( 1723 + &self, 1724 + did: &Did, 1725 + controller_did: &Did, 1726 + ) -> Result<u64, DbError> { 1727 + let (tx, rx) = oneshot::channel(); 1728 + self.pool.send(MetastoreRequest::Session( 1729 + SessionRequest::DeleteAppPasswordsByController { 1730 + did: did.clone(), 1731 + controller_did: controller_did.clone(), 1732 + tx, 1733 + }, 1734 + ))?; 1735 + recv(rx).await 1736 + } 1737 + 1738 + async fn get_last_reauth_at(&self, did: &Did) -> Result<Option<DateTime<Utc>>, DbError> { 1739 + let (tx, rx) = oneshot::channel(); 1740 + self.pool 1741 + .send(MetastoreRequest::Session(SessionRequest::GetLastReauthAt { 1742 + did: did.clone(), 1743 + tx, 1744 + }))?; 1745 + recv(rx).await 1746 + } 1747 + 1748 + async fn update_last_reauth(&self, did: &Did) -> Result<DateTime<Utc>, DbError> { 1749 + let (tx, rx) = oneshot::channel(); 1750 + self.pool.send(MetastoreRequest::Session( 1751 + SessionRequest::UpdateLastReauth { 1752 + did: did.clone(), 1753 + tx, 1754 + }, 1755 + ))?; 1756 + recv(rx).await 1757 + } 1758 + 1759 + async fn get_session_mfa_status( 1760 + &self, 1761 + did: &Did, 1762 + ) -> Result<Option<tranquil_db_traits::SessionMfaStatus>, DbError> { 1763 + let (tx, rx) = oneshot::channel(); 1764 + self.pool.send(MetastoreRequest::Session( 1765 + SessionRequest::GetSessionMfaStatus { 1766 + did: did.clone(), 1767 + tx, 1768 + }, 1769 + ))?; 1770 + recv(rx).await 1771 + } 1772 + 1773 + async fn update_mfa_verified(&self, did: &Did) -> Result<(), DbError> { 1774 + let (tx, rx) = oneshot::channel(); 1775 + self.pool.send(MetastoreRequest::Session( 1776 + SessionRequest::UpdateMfaVerified { 1777 + did: did.clone(), 1778 + tx, 1779 + }, 1780 + ))?; 1781 + recv(rx).await 1782 + } 1783 + 1784 + async fn get_app_password_hashes_by_did(&self, did: &Did) -> Result<Vec<String>, DbError> { 1785 + let (tx, rx) = oneshot::channel(); 1786 + self.pool.send(MetastoreRequest::Session( 1787 + SessionRequest::GetAppPasswordHashesByDid { 1788 + did: did.clone(), 1789 + tx, 1790 + }, 1791 + ))?; 1792 + recv(rx).await 1793 + } 1794 + 1795 + async fn refresh_session_atomic( 1796 + &self, 1797 + data: &tranquil_db_traits::SessionRefreshData, 1798 + ) -> Result<tranquil_db_traits::RefreshSessionResult, DbError> { 1799 + let (tx, rx) = oneshot::channel(); 1800 + self.pool.send(MetastoreRequest::Session( 1801 + SessionRequest::RefreshSessionAtomic { 1802 + data: data.clone(), 1803 + tx, 1804 + }, 1805 + ))?; 1806 + recv(rx).await 1807 + } 1808 + } 1809 + 1810 + #[async_trait] 1811 + impl<S: StorageIO + 'static> tranquil_db_traits::InfraRepository for MetastoreClient<S> { 1812 + async fn enqueue_comms( 1813 + &self, 1814 + user_id: Option<Uuid>, 1815 + channel: CommsChannel, 1816 + comms_type: CommsType, 1817 + recipient: &str, 1818 + subject: Option<&str>, 1819 + body: &str, 1820 + metadata: Option<serde_json::Value>, 1821 + ) -> Result<Uuid, DbError> { 1822 + let (tx, rx) = oneshot::channel(); 1823 + self.pool 1824 + .send(MetastoreRequest::Infra(InfraRequest::EnqueueComms { 1825 + user_id, 1826 + channel, 1827 + comms_type, 1828 + recipient: recipient.to_owned(), 1829 + subject: subject.map(str::to_owned), 1830 + body: body.to_owned(), 1831 + metadata, 1832 + tx, 1833 + }))?; 1834 + recv(rx).await 1835 + } 1836 + 1837 + async fn fetch_pending_comms( 1838 + &self, 1839 + now: DateTime<Utc>, 1840 + batch_size: i64, 1841 + ) -> Result<Vec<QueuedComms>, DbError> { 1842 + let (tx, rx) = oneshot::channel(); 1843 + self.pool 1844 + .send(MetastoreRequest::Infra(InfraRequest::FetchPendingComms { 1845 + now, 1846 + batch_size, 1847 + tx, 1848 + }))?; 1849 + recv(rx).await 1850 + } 1851 + 1852 + async fn mark_comms_sent(&self, id: Uuid) -> Result<(), DbError> { 1853 + let (tx, rx) = oneshot::channel(); 1854 + self.pool 1855 + .send(MetastoreRequest::Infra(InfraRequest::MarkCommsSent { 1856 + id, 1857 + tx, 1858 + }))?; 1859 + recv(rx).await 1860 + } 1861 + 1862 + async fn mark_comms_failed(&self, id: Uuid, error: &str) -> Result<(), DbError> { 1863 + let (tx, rx) = oneshot::channel(); 1864 + self.pool 1865 + .send(MetastoreRequest::Infra(InfraRequest::MarkCommsFailed { 1866 + id, 1867 + error: error.to_owned(), 1868 + tx, 1869 + }))?; 1870 + recv(rx).await 1871 + } 1872 + 1873 + async fn create_invite_code( 1874 + &self, 1875 + code: &str, 1876 + use_count: i32, 1877 + for_account: Option<&Did>, 1878 + ) -> Result<bool, DbError> { 1879 + let (tx, rx) = oneshot::channel(); 1880 + self.pool 1881 + .send(MetastoreRequest::Infra(InfraRequest::CreateInviteCode { 1882 + code: code.to_owned(), 1883 + use_count, 1884 + for_account: for_account.cloned(), 1885 + tx, 1886 + }))?; 1887 + recv(rx).await 1888 + } 1889 + 1890 + async fn create_invite_codes_batch( 1891 + &self, 1892 + codes: &[String], 1893 + use_count: i32, 1894 + created_by_user: Uuid, 1895 + for_account: Option<&Did>, 1896 + ) -> Result<(), DbError> { 1897 + let (tx, rx) = oneshot::channel(); 1898 + self.pool.send(MetastoreRequest::Infra( 1899 + InfraRequest::CreateInviteCodesBatch { 1900 + codes: codes.to_vec(), 1901 + use_count, 1902 + created_by_user, 1903 + for_account: for_account.cloned(), 1904 + tx, 1905 + }, 1906 + ))?; 1907 + recv(rx).await 1908 + } 1909 + 1910 + async fn get_invite_code_available_uses(&self, code: &str) -> Result<Option<i32>, DbError> { 1911 + let (tx, rx) = oneshot::channel(); 1912 + self.pool.send(MetastoreRequest::Infra( 1913 + InfraRequest::GetInviteCodeAvailableUses { 1914 + code: code.to_owned(), 1915 + tx, 1916 + }, 1917 + ))?; 1918 + recv(rx).await 1919 + } 1920 + 1921 + async fn validate_invite_code<'a>( 1922 + &self, 1923 + code: &'a str, 1924 + ) -> Result<ValidatedInviteCode<'a>, InviteCodeError> { 1925 + let (tx, rx) = oneshot::channel(); 1926 + self.pool 1927 + .send(MetastoreRequest::Infra(InfraRequest::ValidateInviteCode { 1928 + code: code.to_owned(), 1929 + tx, 1930 + })) 1931 + .map_err(|e| InviteCodeError::DatabaseError(DbError::Connection(e.to_string())))?; 1932 + recv_invite(rx).await?; 1933 + Ok(ValidatedInviteCode::new_validated(code)) 1934 + } 1935 + 1936 + async fn decrement_invite_code_uses( 1937 + &self, 1938 + code: &ValidatedInviteCode<'_>, 1939 + ) -> Result<(), DbError> { 1940 + let (tx, rx) = oneshot::channel(); 1941 + self.pool.send(MetastoreRequest::Infra( 1942 + InfraRequest::DecrementInviteCodeUses { 1943 + code: code.code().to_owned(), 1944 + tx, 1945 + }, 1946 + ))?; 1947 + recv(rx).await 1948 + } 1949 + 1950 + async fn record_invite_code_use( 1951 + &self, 1952 + code: &ValidatedInviteCode<'_>, 1953 + used_by_user: Uuid, 1954 + ) -> Result<(), DbError> { 1955 + let (tx, rx) = oneshot::channel(); 1956 + self.pool 1957 + .send(MetastoreRequest::Infra(InfraRequest::RecordInviteCodeUse { 1958 + code: code.code().to_owned(), 1959 + used_by_user, 1960 + tx, 1961 + }))?; 1962 + recv(rx).await 1963 + } 1964 + 1965 + async fn get_invite_codes_for_account( 1966 + &self, 1967 + for_account: &Did, 1968 + ) -> Result<Vec<InviteCodeInfo>, DbError> { 1969 + let (tx, rx) = oneshot::channel(); 1970 + self.pool.send(MetastoreRequest::Infra( 1971 + InfraRequest::GetInviteCodesForAccount { 1972 + for_account: for_account.clone(), 1973 + tx, 1974 + }, 1975 + ))?; 1976 + recv(rx).await 1977 + } 1978 + 1979 + async fn get_invite_code_uses(&self, code: &str) -> Result<Vec<InviteCodeUse>, DbError> { 1980 + let (tx, rx) = oneshot::channel(); 1981 + self.pool 1982 + .send(MetastoreRequest::Infra(InfraRequest::GetInviteCodeUses { 1983 + code: code.to_owned(), 1984 + tx, 1985 + }))?; 1986 + recv(rx).await 1987 + } 1988 + 1989 + async fn disable_invite_codes_by_code(&self, codes: &[String]) -> Result<(), DbError> { 1990 + let (tx, rx) = oneshot::channel(); 1991 + self.pool.send(MetastoreRequest::Infra( 1992 + InfraRequest::DisableInviteCodesByCode { 1993 + codes: codes.to_vec(), 1994 + tx, 1995 + }, 1996 + ))?; 1997 + recv(rx).await 1998 + } 1999 + 2000 + async fn disable_invite_codes_by_account(&self, accounts: &[Did]) -> Result<(), DbError> { 2001 + let (tx, rx) = oneshot::channel(); 2002 + self.pool.send(MetastoreRequest::Infra( 2003 + InfraRequest::DisableInviteCodesByAccount { 2004 + accounts: accounts.to_vec(), 2005 + tx, 2006 + }, 2007 + ))?; 2008 + recv(rx).await 2009 + } 2010 + 2011 + async fn list_invite_codes( 2012 + &self, 2013 + cursor: Option<&str>, 2014 + limit: i64, 2015 + sort: InviteCodeSortOrder, 2016 + ) -> Result<Vec<InviteCodeRow>, DbError> { 2017 + let (tx, rx) = oneshot::channel(); 2018 + self.pool 2019 + .send(MetastoreRequest::Infra(InfraRequest::ListInviteCodes { 2020 + cursor: cursor.map(str::to_owned), 2021 + limit, 2022 + sort, 2023 + tx, 2024 + }))?; 2025 + recv(rx).await 2026 + } 2027 + 2028 + async fn get_user_dids_by_ids(&self, user_ids: &[Uuid]) -> Result<Vec<(Uuid, Did)>, DbError> { 2029 + let (tx, rx) = oneshot::channel(); 2030 + self.pool 2031 + .send(MetastoreRequest::Infra(InfraRequest::GetUserDidsByIds { 2032 + user_ids: user_ids.to_vec(), 2033 + tx, 2034 + }))?; 2035 + recv(rx).await 2036 + } 2037 + 2038 + async fn get_invite_code_uses_batch( 2039 + &self, 2040 + codes: &[String], 2041 + ) -> Result<Vec<InviteCodeUse>, DbError> { 2042 + let (tx, rx) = oneshot::channel(); 2043 + self.pool.send(MetastoreRequest::Infra( 2044 + InfraRequest::GetInviteCodeUsesBatch { 2045 + codes: codes.to_vec(), 2046 + tx, 2047 + }, 2048 + ))?; 2049 + recv(rx).await 2050 + } 2051 + 2052 + async fn get_invites_created_by_user( 2053 + &self, 2054 + user_id: Uuid, 2055 + ) -> Result<Vec<InviteCodeInfo>, DbError> { 2056 + let (tx, rx) = oneshot::channel(); 2057 + self.pool.send(MetastoreRequest::Infra( 2058 + InfraRequest::GetInvitesCreatedByUser { user_id, tx }, 2059 + ))?; 2060 + recv(rx).await 2061 + } 2062 + 2063 + async fn get_invite_code_info(&self, code: &str) -> Result<Option<InviteCodeInfo>, DbError> { 2064 + let (tx, rx) = oneshot::channel(); 2065 + self.pool 2066 + .send(MetastoreRequest::Infra(InfraRequest::GetInviteCodeInfo { 2067 + code: code.to_owned(), 2068 + tx, 2069 + }))?; 2070 + recv(rx).await 2071 + } 2072 + 2073 + async fn get_invite_codes_by_users( 2074 + &self, 2075 + user_ids: &[Uuid], 2076 + ) -> Result<Vec<(Uuid, InviteCodeInfo)>, DbError> { 2077 + let (tx, rx) = oneshot::channel(); 2078 + self.pool.send(MetastoreRequest::Infra( 2079 + InfraRequest::GetInviteCodesByUsers { 2080 + user_ids: user_ids.to_vec(), 2081 + tx, 2082 + }, 2083 + ))?; 2084 + recv(rx).await 2085 + } 2086 + 2087 + async fn get_invite_code_used_by_user(&self, user_id: Uuid) -> Result<Option<String>, DbError> { 2088 + let (tx, rx) = oneshot::channel(); 2089 + self.pool.send(MetastoreRequest::Infra( 2090 + InfraRequest::GetInviteCodeUsedByUser { user_id, tx }, 2091 + ))?; 2092 + recv(rx).await 2093 + } 2094 + 2095 + async fn delete_invite_code_uses_by_user(&self, user_id: Uuid) -> Result<(), DbError> { 2096 + let (tx, rx) = oneshot::channel(); 2097 + self.pool.send(MetastoreRequest::Infra( 2098 + InfraRequest::DeleteInviteCodeUsesByUser { user_id, tx }, 2099 + ))?; 2100 + recv(rx).await 2101 + } 2102 + 2103 + async fn delete_invite_codes_by_user(&self, user_id: Uuid) -> Result<(), DbError> { 2104 + let (tx, rx) = oneshot::channel(); 2105 + self.pool.send(MetastoreRequest::Infra( 2106 + InfraRequest::DeleteInviteCodesByUser { user_id, tx }, 2107 + ))?; 2108 + recv(rx).await 2109 + } 2110 + 2111 + async fn reserve_signing_key( 2112 + &self, 2113 + did: Option<&Did>, 2114 + public_key_did_key: &str, 2115 + private_key_bytes: &[u8], 2116 + expires_at: DateTime<Utc>, 2117 + ) -> Result<Uuid, DbError> { 2118 + let (tx, rx) = oneshot::channel(); 2119 + self.pool 2120 + .send(MetastoreRequest::Infra(InfraRequest::ReserveSigningKey { 2121 + did: did.cloned(), 2122 + public_key_did_key: public_key_did_key.to_owned(), 2123 + private_key_bytes: private_key_bytes.to_vec(), 2124 + expires_at, 2125 + tx, 2126 + }))?; 2127 + recv(rx).await 2128 + } 2129 + 2130 + async fn get_reserved_signing_key( 2131 + &self, 2132 + public_key_did_key: &str, 2133 + ) -> Result<Option<ReservedSigningKey>, DbError> { 2134 + let (tx, rx) = oneshot::channel(); 2135 + self.pool.send(MetastoreRequest::Infra( 2136 + InfraRequest::GetReservedSigningKey { 2137 + public_key_did_key: public_key_did_key.to_owned(), 2138 + tx, 2139 + }, 2140 + ))?; 2141 + recv(rx).await 2142 + } 2143 + 2144 + async fn mark_signing_key_used(&self, key_id: Uuid) -> Result<(), DbError> { 2145 + let (tx, rx) = oneshot::channel(); 2146 + self.pool 2147 + .send(MetastoreRequest::Infra(InfraRequest::MarkSigningKeyUsed { 2148 + key_id, 2149 + tx, 2150 + }))?; 2151 + recv(rx).await 2152 + } 2153 + 2154 + async fn create_deletion_request( 2155 + &self, 2156 + token: &str, 2157 + did: &Did, 2158 + expires_at: DateTime<Utc>, 2159 + ) -> Result<(), DbError> { 2160 + let (tx, rx) = oneshot::channel(); 2161 + self.pool.send(MetastoreRequest::Infra( 2162 + InfraRequest::CreateDeletionRequest { 2163 + token: token.to_owned(), 2164 + did: did.clone(), 2165 + expires_at, 2166 + tx, 2167 + }, 2168 + ))?; 2169 + recv(rx).await 2170 + } 2171 + 2172 + async fn get_deletion_request(&self, token: &str) -> Result<Option<DeletionRequest>, DbError> { 2173 + let (tx, rx) = oneshot::channel(); 2174 + self.pool 2175 + .send(MetastoreRequest::Infra(InfraRequest::GetDeletionRequest { 2176 + token: token.to_owned(), 2177 + tx, 2178 + }))?; 2179 + recv(rx).await 2180 + } 2181 + 2182 + async fn delete_deletion_request(&self, token: &str) -> Result<(), DbError> { 2183 + let (tx, rx) = oneshot::channel(); 2184 + self.pool.send(MetastoreRequest::Infra( 2185 + InfraRequest::DeleteDeletionRequest { 2186 + token: token.to_owned(), 2187 + tx, 2188 + }, 2189 + ))?; 2190 + recv(rx).await 2191 + } 2192 + 2193 + async fn delete_deletion_requests_by_did(&self, did: &Did) -> Result<(), DbError> { 2194 + let (tx, rx) = oneshot::channel(); 2195 + self.pool.send(MetastoreRequest::Infra( 2196 + InfraRequest::DeleteDeletionRequestsByDid { 2197 + did: did.clone(), 2198 + tx, 2199 + }, 2200 + ))?; 2201 + recv(rx).await 2202 + } 2203 + 2204 + async fn upsert_account_preference( 2205 + &self, 2206 + user_id: Uuid, 2207 + name: &str, 2208 + value_json: serde_json::Value, 2209 + ) -> Result<(), DbError> { 2210 + let (tx, rx) = oneshot::channel(); 2211 + self.pool.send(MetastoreRequest::Infra( 2212 + InfraRequest::UpsertAccountPreference { 2213 + user_id, 2214 + name: name.to_owned(), 2215 + value_json, 2216 + tx, 2217 + }, 2218 + ))?; 2219 + recv(rx).await 2220 + } 2221 + 2222 + async fn insert_account_preference_if_not_exists( 2223 + &self, 2224 + user_id: Uuid, 2225 + name: &str, 2226 + value_json: serde_json::Value, 2227 + ) -> Result<(), DbError> { 2228 + let (tx, rx) = oneshot::channel(); 2229 + self.pool.send(MetastoreRequest::Infra( 2230 + InfraRequest::InsertAccountPreferenceIfNotExists { 2231 + user_id, 2232 + name: name.to_owned(), 2233 + value_json, 2234 + tx, 2235 + }, 2236 + ))?; 2237 + recv(rx).await 2238 + } 2239 + 2240 + async fn get_server_config(&self, key: &str) -> Result<Option<String>, DbError> { 2241 + let (tx, rx) = oneshot::channel(); 2242 + self.pool 2243 + .send(MetastoreRequest::Infra(InfraRequest::GetServerConfig { 2244 + key: key.to_owned(), 2245 + tx, 2246 + }))?; 2247 + recv(rx).await 2248 + } 2249 + 2250 + async fn health_check(&self) -> Result<bool, DbError> { 2251 + Ok(true) 2252 + } 2253 + 2254 + async fn insert_report( 2255 + &self, 2256 + id: i64, 2257 + reason_type: &str, 2258 + reason: Option<&str>, 2259 + subject_json: serde_json::Value, 2260 + reported_by_did: &Did, 2261 + created_at: DateTime<Utc>, 2262 + ) -> Result<(), DbError> { 2263 + let (tx, rx) = oneshot::channel(); 2264 + self.pool 2265 + .send(MetastoreRequest::Infra(InfraRequest::InsertReport { 2266 + id, 2267 + reason_type: reason_type.to_owned(), 2268 + reason: reason.map(str::to_owned), 2269 + subject_json, 2270 + reported_by_did: reported_by_did.clone(), 2271 + created_at, 2272 + tx, 2273 + }))?; 2274 + recv(rx).await 2275 + } 2276 + 2277 + async fn delete_plc_tokens_for_user(&self, user_id: Uuid) -> Result<(), DbError> { 2278 + let (tx, rx) = oneshot::channel(); 2279 + self.pool.send(MetastoreRequest::Infra( 2280 + InfraRequest::DeletePlcTokensForUser { user_id, tx }, 2281 + ))?; 2282 + recv(rx).await 2283 + } 2284 + 2285 + async fn insert_plc_token( 2286 + &self, 2287 + user_id: Uuid, 2288 + token: &str, 2289 + expires_at: DateTime<Utc>, 2290 + ) -> Result<(), DbError> { 2291 + let (tx, rx) = oneshot::channel(); 2292 + self.pool 2293 + .send(MetastoreRequest::Infra(InfraRequest::InsertPlcToken { 2294 + user_id, 2295 + token: token.to_owned(), 2296 + expires_at, 2297 + tx, 2298 + }))?; 2299 + recv(rx).await 2300 + } 2301 + 2302 + async fn get_plc_token_expiry( 2303 + &self, 2304 + user_id: Uuid, 2305 + token: &str, 2306 + ) -> Result<Option<DateTime<Utc>>, DbError> { 2307 + let (tx, rx) = oneshot::channel(); 2308 + self.pool 2309 + .send(MetastoreRequest::Infra(InfraRequest::GetPlcTokenExpiry { 2310 + user_id, 2311 + token: token.to_owned(), 2312 + tx, 2313 + }))?; 2314 + recv(rx).await 2315 + } 2316 + 2317 + async fn delete_plc_token(&self, user_id: Uuid, token: &str) -> Result<(), DbError> { 2318 + let (tx, rx) = oneshot::channel(); 2319 + self.pool 2320 + .send(MetastoreRequest::Infra(InfraRequest::DeletePlcToken { 2321 + user_id, 2322 + token: token.to_owned(), 2323 + tx, 2324 + }))?; 2325 + recv(rx).await 2326 + } 2327 + 2328 + async fn get_account_preferences( 2329 + &self, 2330 + user_id: Uuid, 2331 + ) -> Result<Vec<(String, serde_json::Value)>, DbError> { 2332 + let (tx, rx) = oneshot::channel(); 2333 + self.pool.send(MetastoreRequest::Infra( 2334 + InfraRequest::GetAccountPreferences { user_id, tx }, 2335 + ))?; 2336 + recv(rx).await 2337 + } 2338 + 2339 + async fn replace_namespace_preferences( 2340 + &self, 2341 + user_id: Uuid, 2342 + namespace: &str, 2343 + preferences: Vec<(String, serde_json::Value)>, 2344 + ) -> Result<(), DbError> { 2345 + let (tx, rx) = oneshot::channel(); 2346 + self.pool.send(MetastoreRequest::Infra( 2347 + InfraRequest::ReplaceNamespacePreferences { 2348 + user_id, 2349 + namespace: namespace.to_owned(), 2350 + preferences, 2351 + tx, 2352 + }, 2353 + ))?; 2354 + recv(rx).await 2355 + } 2356 + 2357 + async fn get_notification_history( 2358 + &self, 2359 + user_id: Uuid, 2360 + limit: i64, 2361 + ) -> Result<Vec<NotificationHistoryRow>, DbError> { 2362 + let (tx, rx) = oneshot::channel(); 2363 + self.pool.send(MetastoreRequest::Infra( 2364 + InfraRequest::GetNotificationHistory { user_id, limit, tx }, 2365 + ))?; 2366 + recv(rx).await 2367 + } 2368 + 2369 + async fn get_server_configs(&self, keys: &[&str]) -> Result<Vec<(String, String)>, DbError> { 2370 + let (tx, rx) = oneshot::channel(); 2371 + self.pool 2372 + .send(MetastoreRequest::Infra(InfraRequest::GetServerConfigs { 2373 + keys: keys.iter().map(|s| (*s).to_owned()).collect(), 2374 + tx, 2375 + }))?; 2376 + recv(rx).await 2377 + } 2378 + 2379 + async fn upsert_server_config(&self, key: &str, value: &str) -> Result<(), DbError> { 2380 + let (tx, rx) = oneshot::channel(); 2381 + self.pool 2382 + .send(MetastoreRequest::Infra(InfraRequest::UpsertServerConfig { 2383 + key: key.to_owned(), 2384 + value: value.to_owned(), 2385 + tx, 2386 + }))?; 2387 + recv(rx).await 2388 + } 2389 + 2390 + async fn delete_server_config(&self, key: &str) -> Result<(), DbError> { 2391 + let (tx, rx) = oneshot::channel(); 2392 + self.pool 2393 + .send(MetastoreRequest::Infra(InfraRequest::DeleteServerConfig { 2394 + key: key.to_owned(), 2395 + tx, 2396 + }))?; 2397 + recv(rx).await 2398 + } 2399 + 2400 + async fn get_blob_storage_key_by_cid(&self, cid: &CidLink) -> Result<Option<String>, DbError> { 2401 + let (tx, rx) = oneshot::channel(); 2402 + self.pool.send(MetastoreRequest::Infra( 2403 + InfraRequest::GetBlobStorageKeyByCid { 2404 + cid: cid.clone(), 2405 + tx, 2406 + }, 2407 + ))?; 2408 + recv(rx).await 2409 + } 2410 + 2411 + async fn delete_blob_by_cid(&self, cid: &CidLink) -> Result<(), DbError> { 2412 + let (tx, rx) = oneshot::channel(); 2413 + self.pool 2414 + .send(MetastoreRequest::Infra(InfraRequest::DeleteBlobByCid { 2415 + cid: cid.clone(), 2416 + tx, 2417 + }))?; 2418 + recv(rx).await 2419 + } 2420 + 2421 + async fn get_admin_account_info_by_did( 2422 + &self, 2423 + did: &Did, 2424 + ) -> Result<Option<AdminAccountInfo>, DbError> { 2425 + let (tx, rx) = oneshot::channel(); 2426 + self.pool.send(MetastoreRequest::Infra( 2427 + InfraRequest::GetAdminAccountInfoByDid { 2428 + did: did.clone(), 2429 + tx, 2430 + }, 2431 + ))?; 2432 + recv(rx).await 2433 + } 2434 + 2435 + async fn get_admin_account_infos_by_dids( 2436 + &self, 2437 + dids: &[Did], 2438 + ) -> Result<Vec<AdminAccountInfo>, DbError> { 2439 + let (tx, rx) = oneshot::channel(); 2440 + self.pool.send(MetastoreRequest::Infra( 2441 + InfraRequest::GetAdminAccountInfosByDids { 2442 + dids: dids.to_vec(), 2443 + tx, 2444 + }, 2445 + ))?; 2446 + recv(rx).await 2447 + } 2448 + 2449 + async fn get_invite_code_uses_by_users( 2450 + &self, 2451 + user_ids: &[Uuid], 2452 + ) -> Result<Vec<(Uuid, String)>, DbError> { 2453 + let (tx, rx) = oneshot::channel(); 2454 + self.pool.send(MetastoreRequest::Infra( 2455 + InfraRequest::GetInviteCodeUsesByUsers { 2456 + user_ids: user_ids.to_vec(), 2457 + tx, 2458 + }, 2459 + ))?; 2460 + recv(rx).await 2461 + } 2462 + } 2463 + 2464 + #[async_trait] 2465 + impl<S: StorageIO + 'static> tranquil_db_traits::OAuthRepository for MetastoreClient<S> { 2466 + async fn create_token(&self, data: &TokenData) -> Result<TokenFamilyId, DbError> { 2467 + let (tx, rx) = oneshot::channel(); 2468 + self.pool 2469 + .send(MetastoreRequest::OAuth(OAuthRequest::CreateToken { 2470 + data: data.clone(), 2471 + tx, 2472 + }))?; 2473 + recv(rx).await 2474 + } 2475 + 2476 + async fn get_token_by_id(&self, token_id: &TokenId) -> Result<Option<TokenData>, DbError> { 2477 + let (tx, rx) = oneshot::channel(); 2478 + self.pool 2479 + .send(MetastoreRequest::OAuth(OAuthRequest::GetTokenById { 2480 + token_id: token_id.clone(), 2481 + tx, 2482 + }))?; 2483 + recv(rx).await 2484 + } 2485 + 2486 + async fn get_token_by_refresh_token( 2487 + &self, 2488 + refresh_token: &RefreshToken, 2489 + ) -> Result<Option<(TokenFamilyId, TokenData)>, DbError> { 2490 + let (tx, rx) = oneshot::channel(); 2491 + self.pool.send(MetastoreRequest::OAuth( 2492 + OAuthRequest::GetTokenByRefreshToken { 2493 + refresh_token: refresh_token.clone(), 2494 + tx, 2495 + }, 2496 + ))?; 2497 + recv(rx).await 2498 + } 2499 + 2500 + async fn get_token_by_previous_refresh_token( 2501 + &self, 2502 + refresh_token: &RefreshToken, 2503 + ) -> Result<Option<(TokenFamilyId, TokenData)>, DbError> { 2504 + let (tx, rx) = oneshot::channel(); 2505 + self.pool.send(MetastoreRequest::OAuth( 2506 + OAuthRequest::GetTokenByPreviousRefreshToken { 2507 + refresh_token: refresh_token.clone(), 2508 + tx, 2509 + }, 2510 + ))?; 2511 + recv(rx).await 2512 + } 2513 + 2514 + async fn rotate_token( 2515 + &self, 2516 + old_db_id: TokenFamilyId, 2517 + new_refresh_token: &RefreshToken, 2518 + new_expires_at: DateTime<Utc>, 2519 + ) -> Result<(), DbError> { 2520 + let (tx, rx) = oneshot::channel(); 2521 + self.pool 2522 + .send(MetastoreRequest::OAuth(OAuthRequest::RotateToken { 2523 + old_db_id, 2524 + new_refresh_token: new_refresh_token.clone(), 2525 + new_expires_at, 2526 + tx, 2527 + }))?; 2528 + recv(rx).await 2529 + } 2530 + 2531 + async fn check_refresh_token_used( 2532 + &self, 2533 + refresh_token: &RefreshToken, 2534 + ) -> Result<Option<TokenFamilyId>, DbError> { 2535 + let (tx, rx) = oneshot::channel(); 2536 + self.pool.send(MetastoreRequest::OAuth( 2537 + OAuthRequest::CheckRefreshTokenUsed { 2538 + refresh_token: refresh_token.clone(), 2539 + tx, 2540 + }, 2541 + ))?; 2542 + recv(rx).await 2543 + } 2544 + 2545 + async fn delete_token(&self, token_id: &TokenId) -> Result<(), DbError> { 2546 + let (tx, rx) = oneshot::channel(); 2547 + self.pool 2548 + .send(MetastoreRequest::OAuth(OAuthRequest::DeleteToken { 2549 + token_id: token_id.clone(), 2550 + tx, 2551 + }))?; 2552 + recv(rx).await 2553 + } 2554 + 2555 + async fn delete_token_family(&self, db_id: TokenFamilyId) -> Result<(), DbError> { 2556 + let (tx, rx) = oneshot::channel(); 2557 + self.pool 2558 + .send(MetastoreRequest::OAuth(OAuthRequest::DeleteTokenFamily { 2559 + db_id, 2560 + tx, 2561 + }))?; 2562 + recv(rx).await 2563 + } 2564 + 2565 + async fn list_tokens_for_user(&self, did: &Did) -> Result<Vec<TokenData>, DbError> { 2566 + let (tx, rx) = oneshot::channel(); 2567 + self.pool 2568 + .send(MetastoreRequest::OAuth(OAuthRequest::ListTokensForUser { 2569 + did: did.clone(), 2570 + tx, 2571 + }))?; 2572 + recv(rx).await 2573 + } 2574 + 2575 + async fn count_tokens_for_user(&self, did: &Did) -> Result<i64, DbError> { 2576 + let (tx, rx) = oneshot::channel(); 2577 + self.pool 2578 + .send(MetastoreRequest::OAuth(OAuthRequest::CountTokensForUser { 2579 + did: did.clone(), 2580 + tx, 2581 + }))?; 2582 + recv(rx).await 2583 + } 2584 + 2585 + async fn delete_oldest_tokens_for_user( 2586 + &self, 2587 + did: &Did, 2588 + keep_count: i64, 2589 + ) -> Result<u64, DbError> { 2590 + let (tx, rx) = oneshot::channel(); 2591 + self.pool.send(MetastoreRequest::OAuth( 2592 + OAuthRequest::DeleteOldestTokensForUser { 2593 + did: did.clone(), 2594 + keep_count, 2595 + tx, 2596 + }, 2597 + ))?; 2598 + recv(rx).await 2599 + } 2600 + 2601 + async fn revoke_tokens_for_client( 2602 + &self, 2603 + did: &Did, 2604 + client_id: &ClientId, 2605 + ) -> Result<u64, DbError> { 2606 + let (tx, rx) = oneshot::channel(); 2607 + self.pool.send(MetastoreRequest::OAuth( 2608 + OAuthRequest::RevokeTokensForClient { 2609 + did: did.clone(), 2610 + client_id: client_id.clone(), 2611 + tx, 2612 + }, 2613 + ))?; 2614 + recv(rx).await 2615 + } 2616 + 2617 + async fn revoke_tokens_for_controller( 2618 + &self, 2619 + delegated_did: &Did, 2620 + controller_did: &Did, 2621 + ) -> Result<u64, DbError> { 2622 + let (tx, rx) = oneshot::channel(); 2623 + self.pool.send(MetastoreRequest::OAuth( 2624 + OAuthRequest::RevokeTokensForController { 2625 + delegated_did: delegated_did.clone(), 2626 + controller_did: controller_did.clone(), 2627 + tx, 2628 + }, 2629 + ))?; 2630 + recv(rx).await 2631 + } 2632 + 2633 + async fn create_authorization_request( 2634 + &self, 2635 + request_id: &RequestId, 2636 + data: &RequestData, 2637 + ) -> Result<(), DbError> { 2638 + let (tx, rx) = oneshot::channel(); 2639 + self.pool.send(MetastoreRequest::OAuth( 2640 + OAuthRequest::CreateAuthorizationRequest { 2641 + request_id: request_id.clone(), 2642 + data: data.clone(), 2643 + tx, 2644 + }, 2645 + ))?; 2646 + recv(rx).await 2647 + } 2648 + 2649 + async fn get_authorization_request( 2650 + &self, 2651 + request_id: &RequestId, 2652 + ) -> Result<Option<RequestData>, DbError> { 2653 + let (tx, rx) = oneshot::channel(); 2654 + self.pool.send(MetastoreRequest::OAuth( 2655 + OAuthRequest::GetAuthorizationRequest { 2656 + request_id: request_id.clone(), 2657 + tx, 2658 + }, 2659 + ))?; 2660 + recv(rx).await 2661 + } 2662 + 2663 + async fn set_authorization_did( 2664 + &self, 2665 + request_id: &RequestId, 2666 + did: &Did, 2667 + device_id: Option<&DeviceId>, 2668 + ) -> Result<(), DbError> { 2669 + let (tx, rx) = oneshot::channel(); 2670 + self.pool 2671 + .send(MetastoreRequest::OAuth(OAuthRequest::SetAuthorizationDid { 2672 + request_id: request_id.clone(), 2673 + did: did.clone(), 2674 + device_id: device_id.cloned(), 2675 + tx, 2676 + }))?; 2677 + recv(rx).await 2678 + } 2679 + 2680 + async fn update_authorization_request( 2681 + &self, 2682 + request_id: &RequestId, 2683 + did: &Did, 2684 + device_id: Option<&DeviceId>, 2685 + code: &AuthorizationCode, 2686 + ) -> Result<(), DbError> { 2687 + let (tx, rx) = oneshot::channel(); 2688 + self.pool.send(MetastoreRequest::OAuth( 2689 + OAuthRequest::UpdateAuthorizationRequest { 2690 + request_id: request_id.clone(), 2691 + did: did.clone(), 2692 + device_id: device_id.cloned(), 2693 + code: code.clone(), 2694 + tx, 2695 + }, 2696 + ))?; 2697 + recv(rx).await 2698 + } 2699 + 2700 + async fn consume_authorization_request_by_code( 2701 + &self, 2702 + code: &AuthorizationCode, 2703 + ) -> Result<Option<RequestData>, DbError> { 2704 + let (tx, rx) = oneshot::channel(); 2705 + self.pool.send(MetastoreRequest::OAuth( 2706 + OAuthRequest::ConsumeAuthorizationRequestByCode { 2707 + code: code.clone(), 2708 + tx, 2709 + }, 2710 + ))?; 2711 + recv(rx).await 2712 + } 2713 + 2714 + async fn delete_authorization_request(&self, request_id: &RequestId) -> Result<(), DbError> { 2715 + let (tx, rx) = oneshot::channel(); 2716 + self.pool.send(MetastoreRequest::OAuth( 2717 + OAuthRequest::DeleteAuthorizationRequest { 2718 + request_id: request_id.clone(), 2719 + tx, 2720 + }, 2721 + ))?; 2722 + recv(rx).await 2723 + } 2724 + 2725 + async fn delete_expired_authorization_requests(&self) -> Result<u64, DbError> { 2726 + let (tx, rx) = oneshot::channel(); 2727 + self.pool.send(MetastoreRequest::OAuth( 2728 + OAuthRequest::DeleteExpiredAuthorizationRequests { tx }, 2729 + ))?; 2730 + recv(rx).await 2731 + } 2732 + 2733 + async fn extend_authorization_request_expiry( 2734 + &self, 2735 + request_id: &RequestId, 2736 + new_expires_at: DateTime<Utc>, 2737 + ) -> Result<bool, DbError> { 2738 + let (tx, rx) = oneshot::channel(); 2739 + self.pool.send(MetastoreRequest::OAuth( 2740 + OAuthRequest::ExtendAuthorizationRequestExpiry { 2741 + request_id: request_id.clone(), 2742 + new_expires_at, 2743 + tx, 2744 + }, 2745 + ))?; 2746 + recv(rx).await 2747 + } 2748 + 2749 + async fn mark_request_authenticated( 2750 + &self, 2751 + request_id: &RequestId, 2752 + did: &Did, 2753 + device_id: Option<&DeviceId>, 2754 + ) -> Result<(), DbError> { 2755 + let (tx, rx) = oneshot::channel(); 2756 + self.pool.send(MetastoreRequest::OAuth( 2757 + OAuthRequest::MarkRequestAuthenticated { 2758 + request_id: request_id.clone(), 2759 + did: did.clone(), 2760 + device_id: device_id.cloned(), 2761 + tx, 2762 + }, 2763 + ))?; 2764 + recv(rx).await 2765 + } 2766 + 2767 + async fn update_request_scope( 2768 + &self, 2769 + request_id: &RequestId, 2770 + scope: &str, 2771 + ) -> Result<(), DbError> { 2772 + let (tx, rx) = oneshot::channel(); 2773 + self.pool 2774 + .send(MetastoreRequest::OAuth(OAuthRequest::UpdateRequestScope { 2775 + request_id: request_id.clone(), 2776 + scope: scope.to_owned(), 2777 + tx, 2778 + }))?; 2779 + recv(rx).await 2780 + } 2781 + 2782 + async fn set_controller_did( 2783 + &self, 2784 + request_id: &RequestId, 2785 + controller_did: &Did, 2786 + ) -> Result<(), DbError> { 2787 + let (tx, rx) = oneshot::channel(); 2788 + self.pool 2789 + .send(MetastoreRequest::OAuth(OAuthRequest::SetControllerDid { 2790 + request_id: request_id.clone(), 2791 + controller_did: controller_did.clone(), 2792 + tx, 2793 + }))?; 2794 + recv(rx).await 2795 + } 2796 + 2797 + async fn set_request_did(&self, request_id: &RequestId, did: &Did) -> Result<(), DbError> { 2798 + let (tx, rx) = oneshot::channel(); 2799 + self.pool 2800 + .send(MetastoreRequest::OAuth(OAuthRequest::SetRequestDid { 2801 + request_id: request_id.clone(), 2802 + did: did.clone(), 2803 + tx, 2804 + }))?; 2805 + recv(rx).await 2806 + } 2807 + 2808 + async fn create_device(&self, device_id: &DeviceId, data: &DeviceData) -> Result<(), DbError> { 2809 + let (tx, rx) = oneshot::channel(); 2810 + self.pool 2811 + .send(MetastoreRequest::OAuth(OAuthRequest::CreateDevice { 2812 + device_id: device_id.clone(), 2813 + data: data.clone(), 2814 + tx, 2815 + }))?; 2816 + recv(rx).await 2817 + } 2818 + 2819 + async fn get_device(&self, device_id: &DeviceId) -> Result<Option<DeviceData>, DbError> { 2820 + let (tx, rx) = oneshot::channel(); 2821 + self.pool 2822 + .send(MetastoreRequest::OAuth(OAuthRequest::GetDevice { 2823 + device_id: device_id.clone(), 2824 + tx, 2825 + }))?; 2826 + recv(rx).await 2827 + } 2828 + 2829 + async fn update_device_last_seen(&self, device_id: &DeviceId) -> Result<(), DbError> { 2830 + let (tx, rx) = oneshot::channel(); 2831 + self.pool.send(MetastoreRequest::OAuth( 2832 + OAuthRequest::UpdateDeviceLastSeen { 2833 + device_id: device_id.clone(), 2834 + tx, 2835 + }, 2836 + ))?; 2837 + recv(rx).await 2838 + } 2839 + 2840 + async fn delete_device(&self, device_id: &DeviceId) -> Result<(), DbError> { 2841 + let (tx, rx) = oneshot::channel(); 2842 + self.pool 2843 + .send(MetastoreRequest::OAuth(OAuthRequest::DeleteDevice { 2844 + device_id: device_id.clone(), 2845 + tx, 2846 + }))?; 2847 + recv(rx).await 2848 + } 2849 + 2850 + async fn upsert_account_device(&self, did: &Did, device_id: &DeviceId) -> Result<(), DbError> { 2851 + let (tx, rx) = oneshot::channel(); 2852 + self.pool 2853 + .send(MetastoreRequest::OAuth(OAuthRequest::UpsertAccountDevice { 2854 + did: did.clone(), 2855 + device_id: device_id.clone(), 2856 + tx, 2857 + }))?; 2858 + recv(rx).await 2859 + } 2860 + 2861 + async fn get_device_accounts( 2862 + &self, 2863 + device_id: &DeviceId, 2864 + ) -> Result<Vec<tranquil_db_traits::DeviceAccountRow>, DbError> { 2865 + let (tx, rx) = oneshot::channel(); 2866 + self.pool 2867 + .send(MetastoreRequest::OAuth(OAuthRequest::GetDeviceAccounts { 2868 + device_id: device_id.clone(), 2869 + tx, 2870 + }))?; 2871 + recv(rx).await 2872 + } 2873 + 2874 + async fn verify_account_on_device( 2875 + &self, 2876 + device_id: &DeviceId, 2877 + did: &Did, 2878 + ) -> Result<bool, DbError> { 2879 + let (tx, rx) = oneshot::channel(); 2880 + self.pool.send(MetastoreRequest::OAuth( 2881 + OAuthRequest::VerifyAccountOnDevice { 2882 + device_id: device_id.clone(), 2883 + did: did.clone(), 2884 + tx, 2885 + }, 2886 + ))?; 2887 + recv(rx).await 2888 + } 2889 + 2890 + async fn check_and_record_dpop_jti(&self, jti: &DPoPProofId) -> Result<bool, DbError> { 2891 + let (tx, rx) = oneshot::channel(); 2892 + self.pool.send(MetastoreRequest::OAuth( 2893 + OAuthRequest::CheckAndRecordDpopJti { 2894 + jti: jti.clone(), 2895 + tx, 2896 + }, 2897 + ))?; 2898 + recv(rx).await 2899 + } 2900 + 2901 + async fn cleanup_expired_dpop_jtis(&self, max_age_secs: i64) -> Result<u64, DbError> { 2902 + let (tx, rx) = oneshot::channel(); 2903 + self.pool.send(MetastoreRequest::OAuth( 2904 + OAuthRequest::CleanupExpiredDpopJtis { max_age_secs, tx }, 2905 + ))?; 2906 + recv(rx).await 2907 + } 2908 + 2909 + async fn create_2fa_challenge( 2910 + &self, 2911 + did: &Did, 2912 + request_uri: &RequestId, 2913 + ) -> Result<tranquil_db_traits::TwoFactorChallenge, DbError> { 2914 + let (tx, rx) = oneshot::channel(); 2915 + self.pool 2916 + .send(MetastoreRequest::OAuth(OAuthRequest::Create2faChallenge { 2917 + did: did.clone(), 2918 + request_uri: request_uri.clone(), 2919 + tx, 2920 + }))?; 2921 + recv(rx).await 2922 + } 2923 + 2924 + async fn get_2fa_challenge( 2925 + &self, 2926 + request_uri: &RequestId, 2927 + ) -> Result<Option<tranquil_db_traits::TwoFactorChallenge>, DbError> { 2928 + let (tx, rx) = oneshot::channel(); 2929 + self.pool 2930 + .send(MetastoreRequest::OAuth(OAuthRequest::Get2faChallenge { 2931 + request_uri: request_uri.clone(), 2932 + tx, 2933 + }))?; 2934 + recv(rx).await 2935 + } 2936 + 2937 + async fn increment_2fa_attempts(&self, id: Uuid) -> Result<i32, DbError> { 2938 + let (tx, rx) = oneshot::channel(); 2939 + self.pool.send(MetastoreRequest::OAuth( 2940 + OAuthRequest::Increment2faAttempts { id, tx }, 2941 + ))?; 2942 + recv(rx).await 2943 + } 2944 + 2945 + async fn delete_2fa_challenge(&self, id: Uuid) -> Result<(), DbError> { 2946 + let (tx, rx) = oneshot::channel(); 2947 + self.pool 2948 + .send(MetastoreRequest::OAuth(OAuthRequest::Delete2faChallenge { 2949 + id, 2950 + tx, 2951 + }))?; 2952 + recv(rx).await 2953 + } 2954 + 2955 + async fn delete_2fa_challenge_by_request_uri( 2956 + &self, 2957 + request_uri: &RequestId, 2958 + ) -> Result<(), DbError> { 2959 + let (tx, rx) = oneshot::channel(); 2960 + self.pool.send(MetastoreRequest::OAuth( 2961 + OAuthRequest::Delete2faChallengeByRequestUri { 2962 + request_uri: request_uri.clone(), 2963 + tx, 2964 + }, 2965 + ))?; 2966 + recv(rx).await 2967 + } 2968 + 2969 + async fn cleanup_expired_2fa_challenges(&self) -> Result<u64, DbError> { 2970 + let (tx, rx) = oneshot::channel(); 2971 + self.pool.send(MetastoreRequest::OAuth( 2972 + OAuthRequest::CleanupExpired2faChallenges { tx }, 2973 + ))?; 2974 + recv(rx).await 2975 + } 2976 + 2977 + async fn check_user_2fa_enabled(&self, did: &Did) -> Result<bool, DbError> { 2978 + let (tx, rx) = oneshot::channel(); 2979 + self.pool 2980 + .send(MetastoreRequest::OAuth(OAuthRequest::CheckUser2faEnabled { 2981 + did: did.clone(), 2982 + tx, 2983 + }))?; 2984 + recv(rx).await 2985 + } 2986 + 2987 + async fn get_scope_preferences( 2988 + &self, 2989 + did: &Did, 2990 + client_id: &ClientId, 2991 + ) -> Result<Vec<ScopePreference>, DbError> { 2992 + let (tx, rx) = oneshot::channel(); 2993 + self.pool 2994 + .send(MetastoreRequest::OAuth(OAuthRequest::GetScopePreferences { 2995 + did: did.clone(), 2996 + client_id: client_id.clone(), 2997 + tx, 2998 + }))?; 2999 + recv(rx).await 3000 + } 3001 + 3002 + async fn upsert_scope_preferences( 3003 + &self, 3004 + did: &Did, 3005 + client_id: &ClientId, 3006 + prefs: &[ScopePreference], 3007 + ) -> Result<(), DbError> { 3008 + let (tx, rx) = oneshot::channel(); 3009 + self.pool.send(MetastoreRequest::OAuth( 3010 + OAuthRequest::UpsertScopePreferences { 3011 + did: did.clone(), 3012 + client_id: client_id.clone(), 3013 + prefs: prefs.to_vec(), 3014 + tx, 3015 + }, 3016 + ))?; 3017 + recv(rx).await 3018 + } 3019 + 3020 + async fn delete_scope_preferences( 3021 + &self, 3022 + did: &Did, 3023 + client_id: &ClientId, 3024 + ) -> Result<(), DbError> { 3025 + let (tx, rx) = oneshot::channel(); 3026 + self.pool.send(MetastoreRequest::OAuth( 3027 + OAuthRequest::DeleteScopePreferences { 3028 + did: did.clone(), 3029 + client_id: client_id.clone(), 3030 + tx, 3031 + }, 3032 + ))?; 3033 + recv(rx).await 3034 + } 3035 + 3036 + async fn upsert_authorized_client( 3037 + &self, 3038 + did: &Did, 3039 + client_id: &ClientId, 3040 + data: &AuthorizedClientData, 3041 + ) -> Result<(), DbError> { 3042 + let (tx, rx) = oneshot::channel(); 3043 + self.pool.send(MetastoreRequest::OAuth( 3044 + OAuthRequest::UpsertAuthorizedClient { 3045 + did: did.clone(), 3046 + client_id: client_id.clone(), 3047 + data: data.clone(), 3048 + tx, 3049 + }, 3050 + ))?; 3051 + recv(rx).await 3052 + } 3053 + 3054 + async fn get_authorized_client( 3055 + &self, 3056 + did: &Did, 3057 + client_id: &ClientId, 3058 + ) -> Result<Option<AuthorizedClientData>, DbError> { 3059 + let (tx, rx) = oneshot::channel(); 3060 + self.pool 3061 + .send(MetastoreRequest::OAuth(OAuthRequest::GetAuthorizedClient { 3062 + did: did.clone(), 3063 + client_id: client_id.clone(), 3064 + tx, 3065 + }))?; 3066 + recv(rx).await 3067 + } 3068 + 3069 + async fn list_trusted_devices( 3070 + &self, 3071 + did: &Did, 3072 + ) -> Result<Vec<tranquil_db_traits::TrustedDeviceRow>, DbError> { 3073 + let (tx, rx) = oneshot::channel(); 3074 + self.pool 3075 + .send(MetastoreRequest::OAuth(OAuthRequest::ListTrustedDevices { 3076 + did: did.clone(), 3077 + tx, 3078 + }))?; 3079 + recv(rx).await 3080 + } 3081 + 3082 + async fn get_device_trust_info( 3083 + &self, 3084 + device_id: &DeviceId, 3085 + did: &Did, 3086 + ) -> Result<Option<tranquil_db_traits::DeviceTrustInfo>, DbError> { 3087 + let (tx, rx) = oneshot::channel(); 3088 + self.pool 3089 + .send(MetastoreRequest::OAuth(OAuthRequest::GetDeviceTrustInfo { 3090 + device_id: device_id.clone(), 3091 + did: did.clone(), 3092 + tx, 3093 + }))?; 3094 + recv(rx).await 3095 + } 3096 + 3097 + async fn device_belongs_to_user( 3098 + &self, 3099 + device_id: &DeviceId, 3100 + did: &Did, 3101 + ) -> Result<bool, DbError> { 3102 + let (tx, rx) = oneshot::channel(); 3103 + self.pool 3104 + .send(MetastoreRequest::OAuth(OAuthRequest::DeviceBelongsToUser { 3105 + device_id: device_id.clone(), 3106 + did: did.clone(), 3107 + tx, 3108 + }))?; 3109 + recv(rx).await 3110 + } 3111 + 3112 + async fn revoke_device_trust(&self, device_id: &DeviceId, did: &Did) -> Result<(), DbError> { 3113 + let (tx, rx) = oneshot::channel(); 3114 + self.pool 3115 + .send(MetastoreRequest::OAuth(OAuthRequest::RevokeDeviceTrust { 3116 + device_id: device_id.clone(), 3117 + did: did.clone(), 3118 + tx, 3119 + }))?; 3120 + recv(rx).await 3121 + } 3122 + 3123 + async fn update_device_friendly_name( 3124 + &self, 3125 + device_id: &DeviceId, 3126 + did: &Did, 3127 + friendly_name: Option<&str>, 3128 + ) -> Result<(), DbError> { 3129 + let (tx, rx) = oneshot::channel(); 3130 + self.pool.send(MetastoreRequest::OAuth( 3131 + OAuthRequest::UpdateDeviceFriendlyName { 3132 + device_id: device_id.clone(), 3133 + did: did.clone(), 3134 + friendly_name: friendly_name.map(str::to_owned), 3135 + tx, 3136 + }, 3137 + ))?; 3138 + recv(rx).await 3139 + } 3140 + 3141 + async fn trust_device( 3142 + &self, 3143 + device_id: &DeviceId, 3144 + did: &Did, 3145 + trusted_at: DateTime<Utc>, 3146 + trusted_until: DateTime<Utc>, 3147 + ) -> Result<(), DbError> { 3148 + let (tx, rx) = oneshot::channel(); 3149 + self.pool 3150 + .send(MetastoreRequest::OAuth(OAuthRequest::TrustDevice { 3151 + device_id: device_id.clone(), 3152 + did: did.clone(), 3153 + trusted_at, 3154 + trusted_until, 3155 + tx, 3156 + }))?; 3157 + recv(rx).await 3158 + } 3159 + 3160 + async fn extend_device_trust( 3161 + &self, 3162 + device_id: &DeviceId, 3163 + did: &Did, 3164 + trusted_until: DateTime<Utc>, 3165 + ) -> Result<(), DbError> { 3166 + let (tx, rx) = oneshot::channel(); 3167 + self.pool 3168 + .send(MetastoreRequest::OAuth(OAuthRequest::ExtendDeviceTrust { 3169 + device_id: device_id.clone(), 3170 + did: did.clone(), 3171 + trusted_until, 3172 + tx, 3173 + }))?; 3174 + recv(rx).await 3175 + } 3176 + 3177 + async fn list_sessions_by_did( 3178 + &self, 3179 + did: &Did, 3180 + ) -> Result<Vec<tranquil_db_traits::OAuthSessionListItem>, DbError> { 3181 + let (tx, rx) = oneshot::channel(); 3182 + self.pool 3183 + .send(MetastoreRequest::OAuth(OAuthRequest::ListSessionsByDid { 3184 + did: did.clone(), 3185 + tx, 3186 + }))?; 3187 + recv(rx).await 3188 + } 3189 + 3190 + async fn delete_session_by_id( 3191 + &self, 3192 + session_id: TokenFamilyId, 3193 + did: &Did, 3194 + ) -> Result<u64, DbError> { 3195 + let (tx, rx) = oneshot::channel(); 3196 + self.pool 3197 + .send(MetastoreRequest::OAuth(OAuthRequest::DeleteSessionById { 3198 + session_id, 3199 + did: did.clone(), 3200 + tx, 3201 + }))?; 3202 + recv(rx).await 3203 + } 3204 + 3205 + async fn delete_sessions_by_did(&self, did: &Did) -> Result<u64, DbError> { 3206 + let (tx, rx) = oneshot::channel(); 3207 + self.pool 3208 + .send(MetastoreRequest::OAuth(OAuthRequest::DeleteSessionsByDid { 3209 + did: did.clone(), 3210 + tx, 3211 + }))?; 3212 + recv(rx).await 3213 + } 3214 + 3215 + async fn delete_sessions_by_did_except( 3216 + &self, 3217 + did: &Did, 3218 + except_token_id: &TokenId, 3219 + ) -> Result<u64, DbError> { 3220 + let (tx, rx) = oneshot::channel(); 3221 + self.pool.send(MetastoreRequest::OAuth( 3222 + OAuthRequest::DeleteSessionsByDidExcept { 3223 + did: did.clone(), 3224 + except_token_id: except_token_id.clone(), 3225 + tx, 3226 + }, 3227 + ))?; 3228 + recv(rx).await 3229 + } 3230 + } 3231 + 3232 + #[async_trait] 3233 + impl<S: StorageIO + 'static> tranquil_db_traits::UserRepository for MetastoreClient<S> { 3234 + async fn get_by_did(&self, did: &Did) -> Result<Option<UserRow>, DbError> { 3235 + let (tx, rx) = oneshot::channel(); 3236 + self.pool 3237 + .send(MetastoreRequest::User(UserRequest::GetByDid { 3238 + did: did.clone(), 3239 + tx, 3240 + }))?; 3241 + recv(rx).await 3242 + } 3243 + 3244 + async fn get_by_handle(&self, handle: &Handle) -> Result<Option<UserRow>, DbError> { 3245 + let (tx, rx) = oneshot::channel(); 3246 + self.pool 3247 + .send(MetastoreRequest::User(UserRequest::GetByHandle { 3248 + handle: handle.clone(), 3249 + tx, 3250 + }))?; 3251 + recv(rx).await 3252 + } 3253 + 3254 + async fn get_with_key_by_did(&self, did: &Did) -> Result<Option<UserWithKey>, DbError> { 3255 + let (tx, rx) = oneshot::channel(); 3256 + self.pool 3257 + .send(MetastoreRequest::User(UserRequest::GetWithKeyByDid { 3258 + did: did.clone(), 3259 + tx, 3260 + }))?; 3261 + recv(rx).await 3262 + } 3263 + 3264 + async fn get_status_by_did(&self, did: &Did) -> Result<Option<UserStatus>, DbError> { 3265 + let (tx, rx) = oneshot::channel(); 3266 + self.pool 3267 + .send(MetastoreRequest::User(UserRequest::GetStatusByDid { 3268 + did: did.clone(), 3269 + tx, 3270 + }))?; 3271 + recv(rx).await 3272 + } 3273 + 3274 + async fn count_users(&self) -> Result<i64, DbError> { 3275 + let (tx, rx) = oneshot::channel(); 3276 + self.pool 3277 + .send(MetastoreRequest::User(UserRequest::CountUsers { tx }))?; 3278 + recv(rx).await 3279 + } 3280 + 3281 + async fn get_session_access_expiry( 3282 + &self, 3283 + did: &Did, 3284 + access_jti: &str, 3285 + ) -> Result<Option<DateTime<Utc>>, DbError> { 3286 + let (tx, rx) = oneshot::channel(); 3287 + self.pool.send(MetastoreRequest::User( 3288 + UserRequest::GetSessionAccessExpiry { 3289 + did: did.clone(), 3290 + access_jti: access_jti.to_owned(), 3291 + tx, 3292 + }, 3293 + ))?; 3294 + recv(rx).await 3295 + } 3296 + 3297 + async fn get_oauth_token_with_user( 3298 + &self, 3299 + token_id: &str, 3300 + ) -> Result<Option<OAuthTokenWithUser>, DbError> { 3301 + let (tx, rx) = oneshot::channel(); 3302 + self.pool 3303 + .send(MetastoreRequest::User(UserRequest::GetOAuthTokenWithUser { 3304 + token_id: token_id.to_owned(), 3305 + tx, 3306 + }))?; 3307 + recv(rx).await 3308 + } 3309 + 3310 + async fn get_user_info_by_did(&self, did: &Did) -> Result<Option<UserInfoForAuth>, DbError> { 3311 + let (tx, rx) = oneshot::channel(); 3312 + self.pool 3313 + .send(MetastoreRequest::User(UserRequest::GetUserInfoByDid { 3314 + did: did.clone(), 3315 + tx, 3316 + }))?; 3317 + recv(rx).await 3318 + } 3319 + 3320 + async fn get_any_admin_user_id(&self) -> Result<Option<Uuid>, DbError> { 3321 + let (tx, rx) = oneshot::channel(); 3322 + self.pool 3323 + .send(MetastoreRequest::User(UserRequest::GetAnyAdminUserId { 3324 + tx, 3325 + }))?; 3326 + recv(rx).await 3327 + } 3328 + 3329 + async fn set_invites_disabled(&self, did: &Did, disabled: bool) -> Result<bool, DbError> { 3330 + let (tx, rx) = oneshot::channel(); 3331 + self.pool 3332 + .send(MetastoreRequest::User(UserRequest::SetInvitesDisabled { 3333 + did: did.clone(), 3334 + disabled, 3335 + tx, 3336 + }))?; 3337 + recv(rx).await 3338 + } 3339 + 3340 + async fn search_accounts( 3341 + &self, 3342 + cursor_did: Option<&Did>, 3343 + email_filter: Option<&str>, 3344 + handle_filter: Option<&str>, 3345 + limit: i64, 3346 + ) -> Result<Vec<AccountSearchResult>, DbError> { 3347 + let (tx, rx) = oneshot::channel(); 3348 + self.pool 3349 + .send(MetastoreRequest::User(UserRequest::SearchAccounts { 3350 + cursor_did: cursor_did.cloned(), 3351 + email_filter: email_filter.map(str::to_owned), 3352 + handle_filter: handle_filter.map(str::to_owned), 3353 + limit, 3354 + tx, 3355 + }))?; 3356 + recv(rx).await 3357 + } 3358 + 3359 + async fn get_auth_info_by_did(&self, did: &Did) -> Result<Option<UserAuthInfo>, DbError> { 3360 + let (tx, rx) = oneshot::channel(); 3361 + self.pool 3362 + .send(MetastoreRequest::User(UserRequest::GetAuthInfoByDid { 3363 + did: did.clone(), 3364 + tx, 3365 + }))?; 3366 + recv(rx).await 3367 + } 3368 + 3369 + async fn get_by_email(&self, email: &str) -> Result<Option<UserForVerification>, DbError> { 3370 + let (tx, rx) = oneshot::channel(); 3371 + self.pool 3372 + .send(MetastoreRequest::User(UserRequest::GetByEmail { 3373 + email: email.to_owned(), 3374 + tx, 3375 + }))?; 3376 + recv(rx).await 3377 + } 3378 + 3379 + async fn get_login_check_by_handle_or_email( 3380 + &self, 3381 + identifier: &str, 3382 + ) -> Result<Option<UserLoginCheck>, DbError> { 3383 + let (tx, rx) = oneshot::channel(); 3384 + self.pool.send(MetastoreRequest::User( 3385 + UserRequest::GetLoginCheckByHandleOrEmail { 3386 + identifier: identifier.to_owned(), 3387 + tx, 3388 + }, 3389 + ))?; 3390 + recv(rx).await 3391 + } 3392 + 3393 + async fn get_login_info_by_handle_or_email( 3394 + &self, 3395 + identifier: &str, 3396 + ) -> Result<Option<UserLoginInfo>, DbError> { 3397 + let (tx, rx) = oneshot::channel(); 3398 + self.pool.send(MetastoreRequest::User( 3399 + UserRequest::GetLoginInfoByHandleOrEmail { 3400 + identifier: identifier.to_owned(), 3401 + tx, 3402 + }, 3403 + ))?; 3404 + recv(rx).await 3405 + } 3406 + 3407 + async fn get_2fa_status_by_did(&self, did: &Did) -> Result<Option<User2faStatus>, DbError> { 3408 + let (tx, rx) = oneshot::channel(); 3409 + self.pool 3410 + .send(MetastoreRequest::User(UserRequest::Get2faStatusByDid { 3411 + did: did.clone(), 3412 + tx, 3413 + }))?; 3414 + recv(rx).await 3415 + } 3416 + 3417 + async fn get_comms_prefs(&self, user_id: Uuid) -> Result<Option<UserCommsPrefs>, DbError> { 3418 + let (tx, rx) = oneshot::channel(); 3419 + self.pool 3420 + .send(MetastoreRequest::User(UserRequest::GetCommsPrefs { 3421 + user_id, 3422 + tx, 3423 + }))?; 3424 + recv(rx).await 3425 + } 3426 + 3427 + async fn get_id_by_did(&self, did: &Did) -> Result<Option<Uuid>, DbError> { 3428 + let (tx, rx) = oneshot::channel(); 3429 + self.pool 3430 + .send(MetastoreRequest::User(UserRequest::GetIdByDid { 3431 + did: did.clone(), 3432 + tx, 3433 + }))?; 3434 + recv(rx).await 3435 + } 3436 + 3437 + async fn get_user_key_by_id(&self, user_id: Uuid) -> Result<Option<UserKeyInfo>, DbError> { 3438 + let (tx, rx) = oneshot::channel(); 3439 + self.pool 3440 + .send(MetastoreRequest::User(UserRequest::GetUserKeyById { 3441 + user_id, 3442 + tx, 3443 + }))?; 3444 + recv(rx).await 3445 + } 3446 + 3447 + async fn get_id_and_handle_by_did( 3448 + &self, 3449 + did: &Did, 3450 + ) -> Result<Option<UserIdAndHandle>, DbError> { 3451 + let (tx, rx) = oneshot::channel(); 3452 + self.pool 3453 + .send(MetastoreRequest::User(UserRequest::GetIdAndHandleByDid { 3454 + did: did.clone(), 3455 + tx, 3456 + }))?; 3457 + recv(rx).await 3458 + } 3459 + 3460 + async fn get_did_web_info_by_handle( 3461 + &self, 3462 + handle: &Handle, 3463 + ) -> Result<Option<UserDidWebInfo>, DbError> { 3464 + let (tx, rx) = oneshot::channel(); 3465 + self.pool 3466 + .send(MetastoreRequest::User(UserRequest::GetDidWebInfoByHandle { 3467 + handle: handle.clone(), 3468 + tx, 3469 + }))?; 3470 + recv(rx).await 3471 + } 3472 + 3473 + async fn get_did_web_overrides( 3474 + &self, 3475 + user_id: Uuid, 3476 + ) -> Result<Option<DidWebOverrides>, DbError> { 3477 + let (tx, rx) = oneshot::channel(); 3478 + self.pool 3479 + .send(MetastoreRequest::User(UserRequest::GetDidWebOverrides { 3480 + user_id, 3481 + tx, 3482 + }))?; 3483 + recv(rx).await 3484 + } 3485 + 3486 + async fn get_handle_by_did(&self, did: &Did) -> Result<Option<Handle>, DbError> { 3487 + let (tx, rx) = oneshot::channel(); 3488 + self.pool 3489 + .send(MetastoreRequest::User(UserRequest::GetHandleByDid { 3490 + did: did.clone(), 3491 + tx, 3492 + }))?; 3493 + recv(rx).await 3494 + } 3495 + 3496 + async fn is_account_active_by_did(&self, did: &Did) -> Result<Option<bool>, DbError> { 3497 + let (tx, rx) = oneshot::channel(); 3498 + self.pool 3499 + .send(MetastoreRequest::User(UserRequest::IsAccountActiveByDid { 3500 + did: did.clone(), 3501 + tx, 3502 + }))?; 3503 + recv(rx).await 3504 + } 3505 + 3506 + async fn get_user_for_deletion(&self, did: &Did) -> Result<Option<UserForDeletion>, DbError> { 3507 + let (tx, rx) = oneshot::channel(); 3508 + self.pool 3509 + .send(MetastoreRequest::User(UserRequest::GetUserForDeletion { 3510 + did: did.clone(), 3511 + tx, 3512 + }))?; 3513 + recv(rx).await 3514 + } 3515 + 3516 + async fn check_handle_exists( 3517 + &self, 3518 + handle: &Handle, 3519 + exclude_user_id: Uuid, 3520 + ) -> Result<bool, DbError> { 3521 + let (tx, rx) = oneshot::channel(); 3522 + self.pool 3523 + .send(MetastoreRequest::User(UserRequest::CheckHandleExists { 3524 + handle: handle.clone(), 3525 + exclude_user_id, 3526 + tx, 3527 + }))?; 3528 + recv(rx).await 3529 + } 3530 + 3531 + async fn update_handle(&self, user_id: Uuid, handle: &Handle) -> Result<(), DbError> { 3532 + let (tx, rx) = oneshot::channel(); 3533 + self.pool 3534 + .send(MetastoreRequest::User(UserRequest::UpdateHandle { 3535 + user_id, 3536 + handle: handle.clone(), 3537 + tx, 3538 + }))?; 3539 + recv(rx).await 3540 + } 3541 + 3542 + async fn get_user_with_key_by_did(&self, did: &Did) -> Result<Option<UserKeyWithId>, DbError> { 3543 + let (tx, rx) = oneshot::channel(); 3544 + self.pool 3545 + .send(MetastoreRequest::User(UserRequest::GetUserWithKeyByDid { 3546 + did: did.clone(), 3547 + tx, 3548 + }))?; 3549 + recv(rx).await 3550 + } 3551 + 3552 + async fn is_account_migrated(&self, did: &Did) -> Result<bool, DbError> { 3553 + let (tx, rx) = oneshot::channel(); 3554 + self.pool 3555 + .send(MetastoreRequest::User(UserRequest::IsAccountMigrated { 3556 + did: did.clone(), 3557 + tx, 3558 + }))?; 3559 + recv(rx).await 3560 + } 3561 + 3562 + async fn has_verified_comms_channel(&self, did: &Did) -> Result<bool, DbError> { 3563 + let (tx, rx) = oneshot::channel(); 3564 + self.pool.send(MetastoreRequest::User( 3565 + UserRequest::HasVerifiedCommsChannel { 3566 + did: did.clone(), 3567 + tx, 3568 + }, 3569 + ))?; 3570 + recv(rx).await 3571 + } 3572 + 3573 + async fn get_id_by_handle(&self, handle: &Handle) -> Result<Option<Uuid>, DbError> { 3574 + let (tx, rx) = oneshot::channel(); 3575 + self.pool 3576 + .send(MetastoreRequest::User(UserRequest::GetIdByHandle { 3577 + handle: handle.clone(), 3578 + tx, 3579 + }))?; 3580 + recv(rx).await 3581 + } 3582 + 3583 + async fn get_email_info_by_did(&self, did: &Did) -> Result<Option<UserEmailInfo>, DbError> { 3584 + let (tx, rx) = oneshot::channel(); 3585 + self.pool 3586 + .send(MetastoreRequest::User(UserRequest::GetEmailInfoByDid { 3587 + did: did.clone(), 3588 + tx, 3589 + }))?; 3590 + recv(rx).await 3591 + } 3592 + 3593 + async fn check_email_exists( 3594 + &self, 3595 + email: &str, 3596 + exclude_user_id: Uuid, 3597 + ) -> Result<bool, DbError> { 3598 + let (tx, rx) = oneshot::channel(); 3599 + self.pool 3600 + .send(MetastoreRequest::User(UserRequest::CheckEmailExists { 3601 + email: email.to_owned(), 3602 + exclude_user_id, 3603 + tx, 3604 + }))?; 3605 + recv(rx).await 3606 + } 3607 + 3608 + async fn update_email(&self, user_id: Uuid, email: &str) -> Result<(), DbError> { 3609 + let (tx, rx) = oneshot::channel(); 3610 + self.pool 3611 + .send(MetastoreRequest::User(UserRequest::UpdateEmail { 3612 + user_id, 3613 + email: email.to_owned(), 3614 + tx, 3615 + }))?; 3616 + recv(rx).await 3617 + } 3618 + 3619 + async fn set_email_verified(&self, user_id: Uuid, verified: bool) -> Result<(), DbError> { 3620 + let (tx, rx) = oneshot::channel(); 3621 + self.pool 3622 + .send(MetastoreRequest::User(UserRequest::SetEmailVerified { 3623 + user_id, 3624 + verified, 3625 + tx, 3626 + }))?; 3627 + recv(rx).await 3628 + } 3629 + 3630 + async fn check_email_verified_by_identifier( 3631 + &self, 3632 + identifier: &str, 3633 + ) -> Result<Option<bool>, DbError> { 3634 + let (tx, rx) = oneshot::channel(); 3635 + self.pool.send(MetastoreRequest::User( 3636 + UserRequest::CheckEmailVerifiedByIdentifier { 3637 + identifier: identifier.to_owned(), 3638 + tx, 3639 + }, 3640 + ))?; 3641 + recv(rx).await 3642 + } 3643 + 3644 + async fn check_channel_verified_by_did( 3645 + &self, 3646 + did: &Did, 3647 + channel: CommsChannel, 3648 + ) -> Result<Option<bool>, DbError> { 3649 + let (tx, rx) = oneshot::channel(); 3650 + self.pool.send(MetastoreRequest::User( 3651 + UserRequest::CheckChannelVerifiedByDid { 3652 + did: did.clone(), 3653 + channel, 3654 + tx, 3655 + }, 3656 + ))?; 3657 + recv(rx).await 3658 + } 3659 + 3660 + async fn admin_update_email(&self, did: &Did, email: &str) -> Result<u64, DbError> { 3661 + let (tx, rx) = oneshot::channel(); 3662 + self.pool 3663 + .send(MetastoreRequest::User(UserRequest::AdminUpdateEmail { 3664 + did: did.clone(), 3665 + email: email.to_owned(), 3666 + tx, 3667 + }))?; 3668 + recv(rx).await 3669 + } 3670 + 3671 + async fn admin_update_handle(&self, did: &Did, handle: &Handle) -> Result<u64, DbError> { 3672 + let (tx, rx) = oneshot::channel(); 3673 + self.pool 3674 + .send(MetastoreRequest::User(UserRequest::AdminUpdateHandle { 3675 + did: did.clone(), 3676 + handle: handle.clone(), 3677 + tx, 3678 + }))?; 3679 + recv(rx).await 3680 + } 3681 + 3682 + async fn admin_update_password(&self, did: &Did, password_hash: &str) -> Result<u64, DbError> { 3683 + let (tx, rx) = oneshot::channel(); 3684 + self.pool 3685 + .send(MetastoreRequest::User(UserRequest::AdminUpdatePassword { 3686 + did: did.clone(), 3687 + password_hash: password_hash.to_owned(), 3688 + tx, 3689 + }))?; 3690 + recv(rx).await 3691 + } 3692 + 3693 + async fn get_notification_prefs( 3694 + &self, 3695 + did: &Did, 3696 + ) -> Result<Option<NotificationPrefs>, DbError> { 3697 + let (tx, rx) = oneshot::channel(); 3698 + self.pool 3699 + .send(MetastoreRequest::User(UserRequest::GetNotificationPrefs { 3700 + did: did.clone(), 3701 + tx, 3702 + }))?; 3703 + recv(rx).await 3704 + } 3705 + 3706 + async fn get_id_handle_email_by_did( 3707 + &self, 3708 + did: &Did, 3709 + ) -> Result<Option<UserIdHandleEmail>, DbError> { 3710 + let (tx, rx) = oneshot::channel(); 3711 + self.pool 3712 + .send(MetastoreRequest::User(UserRequest::GetIdHandleEmailByDid { 3713 + did: did.clone(), 3714 + tx, 3715 + }))?; 3716 + recv(rx).await 3717 + } 3718 + 3719 + async fn update_preferred_comms_channel( 3720 + &self, 3721 + did: &Did, 3722 + channel: CommsChannel, 3723 + ) -> Result<(), DbError> { 3724 + let (tx, rx) = oneshot::channel(); 3725 + self.pool.send(MetastoreRequest::User( 3726 + UserRequest::UpdatePreferredCommsChannel { 3727 + did: did.clone(), 3728 + channel, 3729 + tx, 3730 + }, 3731 + ))?; 3732 + recv(rx).await 3733 + } 3734 + 3735 + async fn clear_discord(&self, user_id: Uuid) -> Result<(), DbError> { 3736 + let (tx, rx) = oneshot::channel(); 3737 + self.pool 3738 + .send(MetastoreRequest::User(UserRequest::ClearDiscord { 3739 + user_id, 3740 + tx, 3741 + }))?; 3742 + recv(rx).await 3743 + } 3744 + 3745 + async fn clear_telegram(&self, user_id: Uuid) -> Result<(), DbError> { 3746 + let (tx, rx) = oneshot::channel(); 3747 + self.pool 3748 + .send(MetastoreRequest::User(UserRequest::ClearTelegram { 3749 + user_id, 3750 + tx, 3751 + }))?; 3752 + recv(rx).await 3753 + } 3754 + 3755 + async fn clear_signal(&self, user_id: Uuid) -> Result<(), DbError> { 3756 + let (tx, rx) = oneshot::channel(); 3757 + self.pool 3758 + .send(MetastoreRequest::User(UserRequest::ClearSignal { 3759 + user_id, 3760 + tx, 3761 + }))?; 3762 + recv(rx).await 3763 + } 3764 + 3765 + async fn set_unverified_signal( 3766 + &self, 3767 + user_id: Uuid, 3768 + signal_username: &str, 3769 + ) -> Result<(), DbError> { 3770 + let (tx, rx) = oneshot::channel(); 3771 + self.pool 3772 + .send(MetastoreRequest::User(UserRequest::SetUnverifiedSignal { 3773 + user_id, 3774 + signal_username: signal_username.to_owned(), 3775 + tx, 3776 + }))?; 3777 + recv(rx).await 3778 + } 3779 + 3780 + async fn set_unverified_telegram( 3781 + &self, 3782 + user_id: Uuid, 3783 + telegram_username: &str, 3784 + ) -> Result<(), DbError> { 3785 + let (tx, rx) = oneshot::channel(); 3786 + self.pool 3787 + .send(MetastoreRequest::User(UserRequest::SetUnverifiedTelegram { 3788 + user_id, 3789 + telegram_username: telegram_username.to_owned(), 3790 + tx, 3791 + }))?; 3792 + recv(rx).await 3793 + } 3794 + 3795 + async fn store_telegram_chat_id( 3796 + &self, 3797 + telegram_username: &str, 3798 + chat_id: i64, 3799 + handle: Option<&str>, 3800 + ) -> Result<Option<Uuid>, DbError> { 3801 + let (tx, rx) = oneshot::channel(); 3802 + self.pool 3803 + .send(MetastoreRequest::User(UserRequest::StoreTelegramChatId { 3804 + telegram_username: telegram_username.to_owned(), 3805 + chat_id, 3806 + handle: handle.map(str::to_owned), 3807 + tx, 3808 + }))?; 3809 + recv(rx).await 3810 + } 3811 + 3812 + async fn get_telegram_chat_id(&self, user_id: Uuid) -> Result<Option<i64>, DbError> { 3813 + let (tx, rx) = oneshot::channel(); 3814 + self.pool 3815 + .send(MetastoreRequest::User(UserRequest::GetTelegramChatId { 3816 + user_id, 3817 + tx, 3818 + }))?; 3819 + recv(rx).await 3820 + } 3821 + 3822 + async fn set_unverified_discord( 3823 + &self, 3824 + user_id: Uuid, 3825 + discord_username: &str, 3826 + ) -> Result<(), DbError> { 3827 + let (tx, rx) = oneshot::channel(); 3828 + self.pool 3829 + .send(MetastoreRequest::User(UserRequest::SetUnverifiedDiscord { 3830 + user_id, 3831 + discord_username: discord_username.to_owned(), 3832 + tx, 3833 + }))?; 3834 + recv(rx).await 3835 + } 3836 + 3837 + async fn store_discord_user_id( 3838 + &self, 3839 + discord_username: &str, 3840 + discord_id: &str, 3841 + handle: Option<&str>, 3842 + ) -> Result<Option<Uuid>, DbError> { 3843 + let (tx, rx) = oneshot::channel(); 3844 + self.pool 3845 + .send(MetastoreRequest::User(UserRequest::StoreDiscordUserId { 3846 + discord_username: discord_username.to_owned(), 3847 + discord_id: discord_id.to_owned(), 3848 + handle: handle.map(str::to_owned), 3849 + tx, 3850 + }))?; 3851 + recv(rx).await 3852 + } 3853 + 3854 + async fn get_verification_info( 3855 + &self, 3856 + did: &Did, 3857 + ) -> Result<Option<UserVerificationInfo>, DbError> { 3858 + let (tx, rx) = oneshot::channel(); 3859 + self.pool 3860 + .send(MetastoreRequest::User(UserRequest::GetVerificationInfo { 3861 + did: did.clone(), 3862 + tx, 3863 + }))?; 3864 + recv(rx).await 3865 + } 3866 + 3867 + async fn verify_email_channel(&self, user_id: Uuid, email: &str) -> Result<bool, DbError> { 3868 + let (tx, rx) = oneshot::channel(); 3869 + self.pool 3870 + .send(MetastoreRequest::User(UserRequest::VerifyEmailChannel { 3871 + user_id, 3872 + email: email.to_owned(), 3873 + tx, 3874 + }))?; 3875 + recv(rx).await 3876 + } 3877 + 3878 + async fn verify_discord_channel(&self, user_id: Uuid, discord_id: &str) -> Result<(), DbError> { 3879 + let (tx, rx) = oneshot::channel(); 3880 + self.pool 3881 + .send(MetastoreRequest::User(UserRequest::VerifyDiscordChannel { 3882 + user_id, 3883 + discord_id: discord_id.to_owned(), 3884 + tx, 3885 + }))?; 3886 + recv(rx).await 3887 + } 3888 + 3889 + async fn verify_telegram_channel( 3890 + &self, 3891 + user_id: Uuid, 3892 + telegram_username: &str, 3893 + ) -> Result<(), DbError> { 3894 + let (tx, rx) = oneshot::channel(); 3895 + self.pool 3896 + .send(MetastoreRequest::User(UserRequest::VerifyTelegramChannel { 3897 + user_id, 3898 + telegram_username: telegram_username.to_owned(), 3899 + tx, 3900 + }))?; 3901 + recv(rx).await 3902 + } 3903 + 3904 + async fn verify_signal_channel( 3905 + &self, 3906 + user_id: Uuid, 3907 + signal_username: &str, 3908 + ) -> Result<(), DbError> { 3909 + let (tx, rx) = oneshot::channel(); 3910 + self.pool 3911 + .send(MetastoreRequest::User(UserRequest::VerifySignalChannel { 3912 + user_id, 3913 + signal_username: signal_username.to_owned(), 3914 + tx, 3915 + }))?; 3916 + recv(rx).await 3917 + } 3918 + 3919 + async fn set_email_verified_flag(&self, user_id: Uuid) -> Result<(), DbError> { 3920 + let (tx, rx) = oneshot::channel(); 3921 + self.pool 3922 + .send(MetastoreRequest::User(UserRequest::SetEmailVerifiedFlag { 3923 + user_id, 3924 + tx, 3925 + }))?; 3926 + recv(rx).await 3927 + } 3928 + 3929 + async fn set_discord_verified_flag(&self, user_id: Uuid) -> Result<(), DbError> { 3930 + let (tx, rx) = oneshot::channel(); 3931 + self.pool.send(MetastoreRequest::User( 3932 + UserRequest::SetDiscordVerifiedFlag { user_id, tx }, 3933 + ))?; 3934 + recv(rx).await 3935 + } 3936 + 3937 + async fn set_telegram_verified_flag(&self, user_id: Uuid) -> Result<(), DbError> { 3938 + let (tx, rx) = oneshot::channel(); 3939 + self.pool.send(MetastoreRequest::User( 3940 + UserRequest::SetTelegramVerifiedFlag { user_id, tx }, 3941 + ))?; 3942 + recv(rx).await 3943 + } 3944 + 3945 + async fn set_signal_verified_flag(&self, user_id: Uuid) -> Result<(), DbError> { 3946 + let (tx, rx) = oneshot::channel(); 3947 + self.pool 3948 + .send(MetastoreRequest::User(UserRequest::SetSignalVerifiedFlag { 3949 + user_id, 3950 + tx, 3951 + }))?; 3952 + recv(rx).await 3953 + } 3954 + 3955 + async fn has_totp_enabled(&self, did: &Did) -> Result<bool, DbError> { 3956 + let (tx, rx) = oneshot::channel(); 3957 + self.pool 3958 + .send(MetastoreRequest::User(UserRequest::HasTotpEnabled { 3959 + did: did.clone(), 3960 + tx, 3961 + }))?; 3962 + recv(rx).await 3963 + } 3964 + 3965 + async fn has_passkeys(&self, did: &Did) -> Result<bool, DbError> { 3966 + let (tx, rx) = oneshot::channel(); 3967 + self.pool 3968 + .send(MetastoreRequest::User(UserRequest::HasPasskeys { 3969 + did: did.clone(), 3970 + tx, 3971 + }))?; 3972 + recv(rx).await 3973 + } 3974 + 3975 + async fn get_password_hash_by_did(&self, did: &Did) -> Result<Option<String>, DbError> { 3976 + let (tx, rx) = oneshot::channel(); 3977 + self.pool 3978 + .send(MetastoreRequest::User(UserRequest::GetPasswordHashByDid { 3979 + did: did.clone(), 3980 + tx, 3981 + }))?; 3982 + recv(rx).await 3983 + } 3984 + 3985 + async fn get_passkeys_for_user(&self, did: &Did) -> Result<Vec<StoredPasskey>, DbError> { 3986 + let (tx, rx) = oneshot::channel(); 3987 + self.pool 3988 + .send(MetastoreRequest::User(UserRequest::GetPasskeysForUser { 3989 + did: did.clone(), 3990 + tx, 3991 + }))?; 3992 + recv(rx).await 3993 + } 3994 + 3995 + async fn get_passkey_by_credential_id( 3996 + &self, 3997 + credential_id: &[u8], 3998 + ) -> Result<Option<StoredPasskey>, DbError> { 3999 + let (tx, rx) = oneshot::channel(); 4000 + self.pool.send(MetastoreRequest::User( 4001 + UserRequest::GetPasskeyByCredentialId { 4002 + credential_id: credential_id.to_vec(), 4003 + tx, 4004 + }, 4005 + ))?; 4006 + recv(rx).await 4007 + } 4008 + 4009 + async fn save_passkey( 4010 + &self, 4011 + did: &Did, 4012 + credential_id: &[u8], 4013 + public_key: &[u8], 4014 + friendly_name: Option<&str>, 4015 + ) -> Result<Uuid, DbError> { 4016 + let (tx, rx) = oneshot::channel(); 4017 + self.pool 4018 + .send(MetastoreRequest::User(UserRequest::SavePasskey { 4019 + did: did.clone(), 4020 + credential_id: credential_id.to_vec(), 4021 + public_key: public_key.to_vec(), 4022 + friendly_name: friendly_name.map(str::to_owned), 4023 + tx, 4024 + }))?; 4025 + recv(rx).await 4026 + } 4027 + 4028 + async fn update_passkey_counter( 4029 + &self, 4030 + credential_id: &[u8], 4031 + new_counter: i32, 4032 + ) -> Result<bool, DbError> { 4033 + let (tx, rx) = oneshot::channel(); 4034 + self.pool 4035 + .send(MetastoreRequest::User(UserRequest::UpdatePasskeyCounter { 4036 + credential_id: credential_id.to_vec(), 4037 + new_counter, 4038 + tx, 4039 + }))?; 4040 + recv(rx).await 4041 + } 4042 + 4043 + async fn delete_passkey(&self, id: Uuid, did: &Did) -> Result<bool, DbError> { 4044 + let (tx, rx) = oneshot::channel(); 4045 + self.pool 4046 + .send(MetastoreRequest::User(UserRequest::DeletePasskey { 4047 + id, 4048 + did: did.clone(), 4049 + tx, 4050 + }))?; 4051 + recv(rx).await 4052 + } 4053 + 4054 + async fn update_passkey_name(&self, id: Uuid, did: &Did, name: &str) -> Result<bool, DbError> { 4055 + let (tx, rx) = oneshot::channel(); 4056 + self.pool 4057 + .send(MetastoreRequest::User(UserRequest::UpdatePasskeyName { 4058 + id, 4059 + did: did.clone(), 4060 + name: name.to_owned(), 4061 + tx, 4062 + }))?; 4063 + recv(rx).await 4064 + } 4065 + 4066 + async fn save_webauthn_challenge( 4067 + &self, 4068 + did: &Did, 4069 + challenge_type: WebauthnChallengeType, 4070 + state_json: &str, 4071 + ) -> Result<Uuid, DbError> { 4072 + let (tx, rx) = oneshot::channel(); 4073 + self.pool 4074 + .send(MetastoreRequest::User(UserRequest::SaveWebauthnChallenge { 4075 + did: did.clone(), 4076 + challenge_type, 4077 + state_json: state_json.to_owned(), 4078 + tx, 4079 + }))?; 4080 + recv(rx).await 4081 + } 4082 + 4083 + async fn load_webauthn_challenge( 4084 + &self, 4085 + did: &Did, 4086 + challenge_type: WebauthnChallengeType, 4087 + ) -> Result<Option<String>, DbError> { 4088 + let (tx, rx) = oneshot::channel(); 4089 + self.pool 4090 + .send(MetastoreRequest::User(UserRequest::LoadWebauthnChallenge { 4091 + did: did.clone(), 4092 + challenge_type, 4093 + tx, 4094 + }))?; 4095 + recv(rx).await 4096 + } 4097 + 4098 + async fn delete_webauthn_challenge( 4099 + &self, 4100 + did: &Did, 4101 + challenge_type: WebauthnChallengeType, 4102 + ) -> Result<(), DbError> { 4103 + let (tx, rx) = oneshot::channel(); 4104 + self.pool.send(MetastoreRequest::User( 4105 + UserRequest::DeleteWebauthnChallenge { 4106 + did: did.clone(), 4107 + challenge_type, 4108 + tx, 4109 + }, 4110 + ))?; 4111 + recv(rx).await 4112 + } 4113 + 4114 + async fn get_totp_record(&self, did: &Did) -> Result<Option<TotpRecord>, DbError> { 4115 + let (tx, rx) = oneshot::channel(); 4116 + self.pool 4117 + .send(MetastoreRequest::User(UserRequest::GetTotpRecord { 4118 + did: did.clone(), 4119 + tx, 4120 + }))?; 4121 + recv(rx).await 4122 + } 4123 + 4124 + async fn get_totp_record_state(&self, did: &Did) -> Result<Option<TotpRecordState>, DbError> { 4125 + let (tx, rx) = oneshot::channel(); 4126 + self.pool 4127 + .send(MetastoreRequest::User(UserRequest::GetTotpRecordState { 4128 + did: did.clone(), 4129 + tx, 4130 + }))?; 4131 + recv(rx).await 4132 + } 4133 + 4134 + async fn upsert_totp_secret( 4135 + &self, 4136 + did: &Did, 4137 + secret_encrypted: &[u8], 4138 + encryption_version: i32, 4139 + ) -> Result<(), DbError> { 4140 + let (tx, rx) = oneshot::channel(); 4141 + self.pool 4142 + .send(MetastoreRequest::User(UserRequest::UpsertTotpSecret { 4143 + did: did.clone(), 4144 + secret_encrypted: secret_encrypted.to_vec(), 4145 + encryption_version, 4146 + tx, 4147 + }))?; 4148 + recv(rx).await 4149 + } 4150 + 4151 + async fn set_totp_verified(&self, did: &Did) -> Result<(), DbError> { 4152 + let (tx, rx) = oneshot::channel(); 4153 + self.pool 4154 + .send(MetastoreRequest::User(UserRequest::SetTotpVerified { 4155 + did: did.clone(), 4156 + tx, 4157 + }))?; 4158 + recv(rx).await 4159 + } 4160 + 4161 + async fn update_totp_last_used(&self, did: &Did) -> Result<(), DbError> { 4162 + let (tx, rx) = oneshot::channel(); 4163 + self.pool 4164 + .send(MetastoreRequest::User(UserRequest::UpdateTotpLastUsed { 4165 + did: did.clone(), 4166 + tx, 4167 + }))?; 4168 + recv(rx).await 4169 + } 4170 + 4171 + async fn delete_totp(&self, did: &Did) -> Result<(), DbError> { 4172 + let (tx, rx) = oneshot::channel(); 4173 + self.pool 4174 + .send(MetastoreRequest::User(UserRequest::DeleteTotp { 4175 + did: did.clone(), 4176 + tx, 4177 + }))?; 4178 + recv(rx).await 4179 + } 4180 + 4181 + async fn get_unused_backup_codes(&self, did: &Did) -> Result<Vec<StoredBackupCode>, DbError> { 4182 + let (tx, rx) = oneshot::channel(); 4183 + self.pool 4184 + .send(MetastoreRequest::User(UserRequest::GetUnusedBackupCodes { 4185 + did: did.clone(), 4186 + tx, 4187 + }))?; 4188 + recv(rx).await 4189 + } 4190 + 4191 + async fn mark_backup_code_used(&self, code_id: Uuid) -> Result<bool, DbError> { 4192 + let (tx, rx) = oneshot::channel(); 4193 + self.pool 4194 + .send(MetastoreRequest::User(UserRequest::MarkBackupCodeUsed { 4195 + code_id, 4196 + tx, 4197 + }))?; 4198 + recv(rx).await 4199 + } 4200 + 4201 + async fn count_unused_backup_codes(&self, did: &Did) -> Result<i64, DbError> { 4202 + let (tx, rx) = oneshot::channel(); 4203 + self.pool.send(MetastoreRequest::User( 4204 + UserRequest::CountUnusedBackupCodes { 4205 + did: did.clone(), 4206 + tx, 4207 + }, 4208 + ))?; 4209 + recv(rx).await 4210 + } 4211 + 4212 + async fn delete_backup_codes(&self, did: &Did) -> Result<u64, DbError> { 4213 + let (tx, rx) = oneshot::channel(); 4214 + self.pool 4215 + .send(MetastoreRequest::User(UserRequest::DeleteBackupCodes { 4216 + did: did.clone(), 4217 + tx, 4218 + }))?; 4219 + recv(rx).await 4220 + } 4221 + 4222 + async fn insert_backup_codes(&self, did: &Did, code_hashes: &[String]) -> Result<(), DbError> { 4223 + let (tx, rx) = oneshot::channel(); 4224 + self.pool 4225 + .send(MetastoreRequest::User(UserRequest::InsertBackupCodes { 4226 + did: did.clone(), 4227 + code_hashes: code_hashes.to_vec(), 4228 + tx, 4229 + }))?; 4230 + recv(rx).await 4231 + } 4232 + 4233 + async fn enable_totp_with_backup_codes( 4234 + &self, 4235 + did: &Did, 4236 + code_hashes: &[String], 4237 + ) -> Result<(), DbError> { 4238 + let (tx, rx) = oneshot::channel(); 4239 + self.pool.send(MetastoreRequest::User( 4240 + UserRequest::EnableTotpWithBackupCodes { 4241 + did: did.clone(), 4242 + code_hashes: code_hashes.to_vec(), 4243 + tx, 4244 + }, 4245 + ))?; 4246 + recv(rx).await 4247 + } 4248 + 4249 + async fn delete_totp_and_backup_codes(&self, did: &Did) -> Result<(), DbError> { 4250 + let (tx, rx) = oneshot::channel(); 4251 + self.pool.send(MetastoreRequest::User( 4252 + UserRequest::DeleteTotpAndBackupCodes { 4253 + did: did.clone(), 4254 + tx, 4255 + }, 4256 + ))?; 4257 + recv(rx).await 4258 + } 4259 + 4260 + async fn replace_backup_codes(&self, did: &Did, code_hashes: &[String]) -> Result<(), DbError> { 4261 + let (tx, rx) = oneshot::channel(); 4262 + self.pool 4263 + .send(MetastoreRequest::User(UserRequest::ReplaceBackupCodes { 4264 + did: did.clone(), 4265 + code_hashes: code_hashes.to_vec(), 4266 + tx, 4267 + }))?; 4268 + recv(rx).await 4269 + } 4270 + 4271 + async fn get_session_info_by_did(&self, did: &Did) -> Result<Option<UserSessionInfo>, DbError> { 4272 + let (tx, rx) = oneshot::channel(); 4273 + self.pool 4274 + .send(MetastoreRequest::User(UserRequest::GetSessionInfoByDid { 4275 + did: did.clone(), 4276 + tx, 4277 + }))?; 4278 + recv(rx).await 4279 + } 4280 + 4281 + async fn get_legacy_login_pref( 4282 + &self, 4283 + did: &Did, 4284 + ) -> Result<Option<UserLegacyLoginPref>, DbError> { 4285 + let (tx, rx) = oneshot::channel(); 4286 + self.pool 4287 + .send(MetastoreRequest::User(UserRequest::GetLegacyLoginPref { 4288 + did: did.clone(), 4289 + tx, 4290 + }))?; 4291 + recv(rx).await 4292 + } 4293 + 4294 + async fn update_legacy_login(&self, did: &Did, allow: bool) -> Result<bool, DbError> { 4295 + let (tx, rx) = oneshot::channel(); 4296 + self.pool 4297 + .send(MetastoreRequest::User(UserRequest::UpdateLegacyLogin { 4298 + did: did.clone(), 4299 + allow, 4300 + tx, 4301 + }))?; 4302 + recv(rx).await 4303 + } 4304 + 4305 + async fn update_locale(&self, did: &Did, locale: &str) -> Result<bool, DbError> { 4306 + let (tx, rx) = oneshot::channel(); 4307 + self.pool 4308 + .send(MetastoreRequest::User(UserRequest::UpdateLocale { 4309 + did: did.clone(), 4310 + locale: locale.to_owned(), 4311 + tx, 4312 + }))?; 4313 + recv(rx).await 4314 + } 4315 + 4316 + async fn get_login_full_by_identifier( 4317 + &self, 4318 + identifier: &str, 4319 + ) -> Result<Option<UserLoginFull>, DbError> { 4320 + let (tx, rx) = oneshot::channel(); 4321 + self.pool.send(MetastoreRequest::User( 4322 + UserRequest::GetLoginFullByIdentifier { 4323 + identifier: identifier.to_owned(), 4324 + tx, 4325 + }, 4326 + ))?; 4327 + recv(rx).await 4328 + } 4329 + 4330 + async fn get_confirm_signup_by_did( 4331 + &self, 4332 + did: &Did, 4333 + ) -> Result<Option<UserConfirmSignup>, DbError> { 4334 + let (tx, rx) = oneshot::channel(); 4335 + self.pool 4336 + .send(MetastoreRequest::User(UserRequest::GetConfirmSignupByDid { 4337 + did: did.clone(), 4338 + tx, 4339 + }))?; 4340 + recv(rx).await 4341 + } 4342 + 4343 + async fn get_resend_verification_by_did( 4344 + &self, 4345 + did: &Did, 4346 + ) -> Result<Option<UserResendVerification>, DbError> { 4347 + let (tx, rx) = oneshot::channel(); 4348 + self.pool.send(MetastoreRequest::User( 4349 + UserRequest::GetResendVerificationByDid { 4350 + did: did.clone(), 4351 + tx, 4352 + }, 4353 + ))?; 4354 + recv(rx).await 4355 + } 4356 + 4357 + async fn set_channel_verified(&self, did: &Did, channel: CommsChannel) -> Result<(), DbError> { 4358 + let (tx, rx) = oneshot::channel(); 4359 + self.pool 4360 + .send(MetastoreRequest::User(UserRequest::SetChannelVerified { 4361 + did: did.clone(), 4362 + channel, 4363 + tx, 4364 + }))?; 4365 + recv(rx).await 4366 + } 4367 + 4368 + async fn get_id_by_email_or_handle( 4369 + &self, 4370 + email: &str, 4371 + handle: &str, 4372 + ) -> Result<Option<Uuid>, DbError> { 4373 + let (tx, rx) = oneshot::channel(); 4374 + self.pool 4375 + .send(MetastoreRequest::User(UserRequest::GetIdByEmailOrHandle { 4376 + email: email.to_owned(), 4377 + handle: handle.to_owned(), 4378 + tx, 4379 + }))?; 4380 + recv(rx).await 4381 + } 4382 + 4383 + async fn count_accounts_by_email(&self, email: &str) -> Result<i64, DbError> { 4384 + let (tx, rx) = oneshot::channel(); 4385 + self.pool 4386 + .send(MetastoreRequest::User(UserRequest::CountAccountsByEmail { 4387 + email: email.to_owned(), 4388 + tx, 4389 + }))?; 4390 + recv(rx).await 4391 + } 4392 + 4393 + async fn get_handles_by_email(&self, email: &str) -> Result<Vec<Handle>, DbError> { 4394 + let (tx, rx) = oneshot::channel(); 4395 + self.pool 4396 + .send(MetastoreRequest::User(UserRequest::GetHandlesByEmail { 4397 + email: email.to_owned(), 4398 + tx, 4399 + }))?; 4400 + recv(rx).await 4401 + } 4402 + 4403 + async fn set_password_reset_code( 4404 + &self, 4405 + user_id: Uuid, 4406 + code: &str, 4407 + expires_at: DateTime<Utc>, 4408 + ) -> Result<(), DbError> { 4409 + let (tx, rx) = oneshot::channel(); 4410 + self.pool 4411 + .send(MetastoreRequest::User(UserRequest::SetPasswordResetCode { 4412 + user_id, 4413 + code: code.to_owned(), 4414 + expires_at, 4415 + tx, 4416 + }))?; 4417 + recv(rx).await 4418 + } 4419 + 4420 + async fn get_user_by_reset_code( 4421 + &self, 4422 + code: &str, 4423 + ) -> Result<Option<UserResetCodeInfo>, DbError> { 4424 + let (tx, rx) = oneshot::channel(); 4425 + self.pool 4426 + .send(MetastoreRequest::User(UserRequest::GetUserByResetCode { 4427 + code: code.to_owned(), 4428 + tx, 4429 + }))?; 4430 + recv(rx).await 4431 + } 4432 + 4433 + async fn clear_password_reset_code(&self, user_id: Uuid) -> Result<(), DbError> { 4434 + let (tx, rx) = oneshot::channel(); 4435 + self.pool.send(MetastoreRequest::User( 4436 + UserRequest::ClearPasswordResetCode { user_id, tx }, 4437 + ))?; 4438 + recv(rx).await 4439 + } 4440 + 4441 + async fn get_id_and_password_hash_by_did( 4442 + &self, 4443 + did: &Did, 4444 + ) -> Result<Option<UserIdAndPasswordHash>, DbError> { 4445 + let (tx, rx) = oneshot::channel(); 4446 + self.pool.send(MetastoreRequest::User( 4447 + UserRequest::GetIdAndPasswordHashByDid { 4448 + did: did.clone(), 4449 + tx, 4450 + }, 4451 + ))?; 4452 + recv(rx).await 4453 + } 4454 + 4455 + async fn update_password_hash( 4456 + &self, 4457 + user_id: Uuid, 4458 + password_hash: &str, 4459 + ) -> Result<(), DbError> { 4460 + let (tx, rx) = oneshot::channel(); 4461 + self.pool 4462 + .send(MetastoreRequest::User(UserRequest::UpdatePasswordHash { 4463 + user_id, 4464 + password_hash: password_hash.to_owned(), 4465 + tx, 4466 + }))?; 4467 + recv(rx).await 4468 + } 4469 + 4470 + async fn reset_password_with_sessions( 4471 + &self, 4472 + user_id: Uuid, 4473 + password_hash: &str, 4474 + ) -> Result<PasswordResetResult, DbError> { 4475 + let (tx, rx) = oneshot::channel(); 4476 + self.pool.send(MetastoreRequest::User( 4477 + UserRequest::ResetPasswordWithSessions { 4478 + user_id, 4479 + password_hash: password_hash.to_owned(), 4480 + tx, 4481 + }, 4482 + ))?; 4483 + recv(rx).await 4484 + } 4485 + 4486 + async fn activate_account(&self, did: &Did) -> Result<bool, DbError> { 4487 + let (tx, rx) = oneshot::channel(); 4488 + self.pool 4489 + .send(MetastoreRequest::User(UserRequest::ActivateAccount { 4490 + did: did.clone(), 4491 + tx, 4492 + }))?; 4493 + recv(rx).await 4494 + } 4495 + 4496 + async fn deactivate_account( 4497 + &self, 4498 + did: &Did, 4499 + delete_after: Option<DateTime<Utc>>, 4500 + ) -> Result<bool, DbError> { 4501 + let (tx, rx) = oneshot::channel(); 4502 + self.pool 4503 + .send(MetastoreRequest::User(UserRequest::DeactivateAccount { 4504 + did: did.clone(), 4505 + delete_after, 4506 + tx, 4507 + }))?; 4508 + recv(rx).await 4509 + } 4510 + 4511 + async fn has_password_by_did(&self, did: &Did) -> Result<Option<bool>, DbError> { 4512 + let (tx, rx) = oneshot::channel(); 4513 + self.pool 4514 + .send(MetastoreRequest::User(UserRequest::HasPasswordByDid { 4515 + did: did.clone(), 4516 + tx, 4517 + }))?; 4518 + recv(rx).await 4519 + } 4520 + 4521 + async fn get_password_info_by_did( 4522 + &self, 4523 + did: &Did, 4524 + ) -> Result<Option<UserPasswordInfo>, DbError> { 4525 + let (tx, rx) = oneshot::channel(); 4526 + self.pool 4527 + .send(MetastoreRequest::User(UserRequest::GetPasswordInfoByDid { 4528 + did: did.clone(), 4529 + tx, 4530 + }))?; 4531 + recv(rx).await 4532 + } 4533 + 4534 + async fn remove_user_password(&self, user_id: Uuid) -> Result<(), DbError> { 4535 + let (tx, rx) = oneshot::channel(); 4536 + self.pool 4537 + .send(MetastoreRequest::User(UserRequest::RemoveUserPassword { 4538 + user_id, 4539 + tx, 4540 + }))?; 4541 + recv(rx).await 4542 + } 4543 + 4544 + async fn set_new_user_password( 4545 + &self, 4546 + user_id: Uuid, 4547 + password_hash: &str, 4548 + ) -> Result<(), DbError> { 4549 + let (tx, rx) = oneshot::channel(); 4550 + self.pool 4551 + .send(MetastoreRequest::User(UserRequest::SetNewUserPassword { 4552 + user_id, 4553 + password_hash: password_hash.to_owned(), 4554 + tx, 4555 + }))?; 4556 + recv(rx).await 4557 + } 4558 + 4559 + async fn get_user_key_by_did(&self, did: &Did) -> Result<Option<UserKeyInfo>, DbError> { 4560 + let (tx, rx) = oneshot::channel(); 4561 + self.pool 4562 + .send(MetastoreRequest::User(UserRequest::GetUserKeyByDid { 4563 + did: did.clone(), 4564 + tx, 4565 + }))?; 4566 + recv(rx).await 4567 + } 4568 + 4569 + async fn delete_account_complete(&self, user_id: Uuid, did: &Did) -> Result<(), DbError> { 4570 + let (tx, rx) = oneshot::channel(); 4571 + self.pool 4572 + .send(MetastoreRequest::User(UserRequest::DeleteAccountComplete { 4573 + user_id, 4574 + did: did.clone(), 4575 + tx, 4576 + }))?; 4577 + recv(rx).await 4578 + } 4579 + 4580 + async fn set_user_takedown( 4581 + &self, 4582 + did: &Did, 4583 + takedown_ref: Option<&str>, 4584 + ) -> Result<bool, DbError> { 4585 + let (tx, rx) = oneshot::channel(); 4586 + self.pool 4587 + .send(MetastoreRequest::User(UserRequest::SetUserTakedown { 4588 + did: did.clone(), 4589 + takedown_ref: takedown_ref.map(str::to_owned), 4590 + tx, 4591 + }))?; 4592 + recv(rx).await 4593 + } 4594 + 4595 + async fn admin_delete_account_complete(&self, user_id: Uuid, did: &Did) -> Result<(), DbError> { 4596 + let (tx, rx) = oneshot::channel(); 4597 + self.pool.send(MetastoreRequest::User( 4598 + UserRequest::AdminDeleteAccountComplete { 4599 + user_id, 4600 + did: did.clone(), 4601 + tx, 4602 + }, 4603 + ))?; 4604 + recv(rx).await 4605 + } 4606 + 4607 + async fn get_user_for_did_doc(&self, did: &Did) -> Result<Option<UserForDidDoc>, DbError> { 4608 + let (tx, rx) = oneshot::channel(); 4609 + self.pool 4610 + .send(MetastoreRequest::User(UserRequest::GetUserForDidDoc { 4611 + did: did.clone(), 4612 + tx, 4613 + }))?; 4614 + recv(rx).await 4615 + } 4616 + 4617 + async fn get_user_for_did_doc_build( 4618 + &self, 4619 + did: &Did, 4620 + ) -> Result<Option<UserForDidDocBuild>, DbError> { 4621 + let (tx, rx) = oneshot::channel(); 4622 + self.pool 4623 + .send(MetastoreRequest::User(UserRequest::GetUserForDidDocBuild { 4624 + did: did.clone(), 4625 + tx, 4626 + }))?; 4627 + recv(rx).await 4628 + } 4629 + 4630 + async fn upsert_did_web_overrides( 4631 + &self, 4632 + user_id: Uuid, 4633 + verification_methods: Option<serde_json::Value>, 4634 + also_known_as: Option<Vec<String>>, 4635 + ) -> Result<(), DbError> { 4636 + let (tx, rx) = oneshot::channel(); 4637 + self.pool 4638 + .send(MetastoreRequest::User(UserRequest::UpsertDidWebOverrides { 4639 + user_id, 4640 + verification_methods, 4641 + also_known_as, 4642 + tx, 4643 + }))?; 4644 + recv(rx).await 4645 + } 4646 + 4647 + async fn update_migrated_to_pds(&self, did: &Did, endpoint: &str) -> Result<(), DbError> { 4648 + let (tx, rx) = oneshot::channel(); 4649 + self.pool 4650 + .send(MetastoreRequest::User(UserRequest::UpdateMigratedToPds { 4651 + did: did.clone(), 4652 + endpoint: endpoint.to_owned(), 4653 + tx, 4654 + }))?; 4655 + recv(rx).await 4656 + } 4657 + 4658 + async fn get_user_for_passkey_setup( 4659 + &self, 4660 + did: &Did, 4661 + ) -> Result<Option<UserForPasskeySetup>, DbError> { 4662 + let (tx, rx) = oneshot::channel(); 4663 + self.pool.send(MetastoreRequest::User( 4664 + UserRequest::GetUserForPasskeySetup { 4665 + did: did.clone(), 4666 + tx, 4667 + }, 4668 + ))?; 4669 + recv(rx).await 4670 + } 4671 + 4672 + async fn get_user_for_passkey_recovery( 4673 + &self, 4674 + identifier: &str, 4675 + normalized_handle: &str, 4676 + ) -> Result<Option<UserForPasskeyRecovery>, DbError> { 4677 + let (tx, rx) = oneshot::channel(); 4678 + self.pool.send(MetastoreRequest::User( 4679 + UserRequest::GetUserForPasskeyRecovery { 4680 + identifier: identifier.to_owned(), 4681 + normalized_handle: normalized_handle.to_owned(), 4682 + tx, 4683 + }, 4684 + ))?; 4685 + recv(rx).await 4686 + } 4687 + 4688 + async fn set_recovery_token( 4689 + &self, 4690 + did: &Did, 4691 + token_hash: &str, 4692 + expires_at: DateTime<Utc>, 4693 + ) -> Result<(), DbError> { 4694 + let (tx, rx) = oneshot::channel(); 4695 + self.pool 4696 + .send(MetastoreRequest::User(UserRequest::SetRecoveryToken { 4697 + did: did.clone(), 4698 + token_hash: token_hash.to_owned(), 4699 + expires_at, 4700 + tx, 4701 + }))?; 4702 + recv(rx).await 4703 + } 4704 + 4705 + async fn get_user_for_recovery(&self, did: &Did) -> Result<Option<UserForRecovery>, DbError> { 4706 + let (tx, rx) = oneshot::channel(); 4707 + self.pool 4708 + .send(MetastoreRequest::User(UserRequest::GetUserForRecovery { 4709 + did: did.clone(), 4710 + tx, 4711 + }))?; 4712 + recv(rx).await 4713 + } 4714 + 4715 + async fn get_accounts_scheduled_for_deletion( 4716 + &self, 4717 + limit: i64, 4718 + ) -> Result<Vec<ScheduledDeletionAccount>, DbError> { 4719 + let (tx, rx) = oneshot::channel(); 4720 + self.pool.send(MetastoreRequest::User( 4721 + UserRequest::GetAccountsScheduledForDeletion { limit, tx }, 4722 + ))?; 4723 + recv(rx).await 4724 + } 4725 + 4726 + async fn delete_account_with_firehose(&self, user_id: Uuid, did: &Did) -> Result<i64, DbError> { 4727 + let (tx, rx) = oneshot::channel(); 4728 + self.pool.send(MetastoreRequest::User( 4729 + UserRequest::DeleteAccountWithFirehose { 4730 + user_id, 4731 + did: did.clone(), 4732 + tx, 4733 + }, 4734 + ))?; 4735 + recv(rx).await 4736 + } 4737 + 4738 + async fn create_password_account( 4739 + &self, 4740 + input: &CreatePasswordAccountInput, 4741 + ) -> Result<CreatePasswordAccountResult, CreateAccountError> { 4742 + let (tx, rx) = oneshot::channel(); 4743 + self.pool 4744 + .send(MetastoreRequest::User(UserRequest::CreatePasswordAccount { 4745 + input: input.clone(), 4746 + tx, 4747 + })) 4748 + .map_err(|e| CreateAccountError::Database(e.to_string()))?; 4749 + recv_create_account(rx).await 4750 + } 4751 + 4752 + async fn create_delegated_account( 4753 + &self, 4754 + input: &CreateDelegatedAccountInput, 4755 + ) -> Result<Uuid, CreateAccountError> { 4756 + let (tx, rx) = oneshot::channel(); 4757 + self.pool 4758 + .send(MetastoreRequest::User( 4759 + UserRequest::CreateDelegatedAccount { 4760 + input: input.clone(), 4761 + tx, 4762 + }, 4763 + )) 4764 + .map_err(|e| CreateAccountError::Database(e.to_string()))?; 4765 + recv_create_account(rx).await 4766 + } 4767 + 4768 + async fn create_passkey_account( 4769 + &self, 4770 + input: &CreatePasskeyAccountInput, 4771 + ) -> Result<CreatePasswordAccountResult, CreateAccountError> { 4772 + let (tx, rx) = oneshot::channel(); 4773 + self.pool 4774 + .send(MetastoreRequest::User(UserRequest::CreatePasskeyAccount { 4775 + input: input.clone(), 4776 + tx, 4777 + })) 4778 + .map_err(|e| CreateAccountError::Database(e.to_string()))?; 4779 + recv_create_account(rx).await 4780 + } 4781 + 4782 + async fn create_sso_account( 4783 + &self, 4784 + input: &CreateSsoAccountInput, 4785 + ) -> Result<CreatePasswordAccountResult, CreateAccountError> { 4786 + let (tx, rx) = oneshot::channel(); 4787 + self.pool 4788 + .send(MetastoreRequest::User(UserRequest::CreateSsoAccount { 4789 + input: input.clone(), 4790 + tx, 4791 + })) 4792 + .map_err(|e| CreateAccountError::Database(e.to_string()))?; 4793 + recv_create_account(rx).await 4794 + } 4795 + 4796 + async fn reactivate_migration_account( 4797 + &self, 4798 + input: &MigrationReactivationInput, 4799 + ) -> Result<ReactivatedAccountInfo, MigrationReactivationError> { 4800 + let (tx, rx) = oneshot::channel(); 4801 + self.pool 4802 + .send(MetastoreRequest::User( 4803 + UserRequest::ReactivateMigrationAccount { 4804 + input: input.clone(), 4805 + tx, 4806 + }, 4807 + )) 4808 + .map_err(|e| MigrationReactivationError::Database(e.to_string()))?; 4809 + recv_migration_reactivation(rx).await 4810 + } 4811 + 4812 + async fn check_handle_available_for_new_account( 4813 + &self, 4814 + handle: &Handle, 4815 + ) -> Result<bool, DbError> { 4816 + let (tx, rx) = oneshot::channel(); 4817 + self.pool.send(MetastoreRequest::User( 4818 + UserRequest::CheckHandleAvailableForNewAccount { 4819 + handle: handle.clone(), 4820 + tx, 4821 + }, 4822 + ))?; 4823 + recv(rx).await 4824 + } 4825 + 4826 + async fn reserve_handle(&self, handle: &Handle, reserved_by: &str) -> Result<bool, DbError> { 4827 + let (tx, rx) = oneshot::channel(); 4828 + self.pool 4829 + .send(MetastoreRequest::User(UserRequest::ReserveHandle { 4830 + handle: handle.clone(), 4831 + reserved_by: reserved_by.to_owned(), 4832 + tx, 4833 + }))?; 4834 + recv(rx).await 4835 + } 4836 + 4837 + async fn release_handle_reservation(&self, handle: &Handle) -> Result<(), DbError> { 4838 + let (tx, rx) = oneshot::channel(); 4839 + self.pool.send(MetastoreRequest::User( 4840 + UserRequest::ReleaseHandleReservation { 4841 + handle: handle.clone(), 4842 + tx, 4843 + }, 4844 + ))?; 4845 + recv(rx).await 4846 + } 4847 + 4848 + async fn cleanup_expired_handle_reservations(&self) -> Result<u64, DbError> { 4849 + let (tx, rx) = oneshot::channel(); 4850 + self.pool.send(MetastoreRequest::User( 4851 + UserRequest::CleanupExpiredHandleReservations { tx }, 4852 + ))?; 4853 + recv(rx).await 4854 + } 4855 + 4856 + async fn check_and_consume_invite_code(&self, code: &str) -> Result<bool, DbError> { 4857 + let (tx, rx) = oneshot::channel(); 4858 + self.pool.send(MetastoreRequest::User( 4859 + UserRequest::CheckAndConsumeInviteCode { 4860 + code: code.to_owned(), 4861 + tx, 4862 + }, 4863 + ))?; 4864 + recv(rx).await 4865 + } 4866 + 4867 + async fn complete_passkey_setup( 4868 + &self, 4869 + input: &CompletePasskeySetupInput, 4870 + ) -> Result<(), DbError> { 4871 + let (tx, rx) = oneshot::channel(); 4872 + self.pool 4873 + .send(MetastoreRequest::User(UserRequest::CompletePasskeySetup { 4874 + input: input.clone(), 4875 + tx, 4876 + }))?; 4877 + recv(rx).await 4878 + } 4879 + 4880 + async fn recover_passkey_account( 4881 + &self, 4882 + input: &RecoverPasskeyAccountInput, 4883 + ) -> Result<RecoverPasskeyAccountResult, DbError> { 4884 + let (tx, rx) = oneshot::channel(); 4885 + self.pool 4886 + .send(MetastoreRequest::User(UserRequest::RecoverPasskeyAccount { 4887 + input: input.clone(), 4888 + tx, 4889 + }))?; 4890 + recv(rx).await 4891 + } 4892 + }
+1 -1
crates/tranquil-store/src/metastore/commit_ops.rs
··· 367 367 &self, 368 368 limit: i64, 369 369 ) -> Result<Vec<UserNeedingRecordBlobsBackfill>, MetastoreError> { 370 - let limit_usize = usize::try_from(limit).unwrap_or(usize::MAX); 370 + let limit_usize = usize::try_from(limit).unwrap_or(0); 371 371 372 372 self.scan_users_missing_prefix( 373 373 record_blobs_user_prefix,
+412
crates/tranquil-store/src/metastore/delegation_ops.rs
··· 1 + use std::sync::Arc; 2 + 3 + use chrono::{DateTime, Utc}; 4 + use fjall::{Database, Keyspace}; 5 + use uuid::Uuid; 6 + 7 + use super::MetastoreError; 8 + use super::delegations::{ 9 + AuditLogValue, DelegationGrantValue, action_type_to_u8, audit_log_key, audit_log_prefix, 10 + by_controller_key, by_controller_prefix, grant_key, grant_prefix, u8_to_action_type, 11 + }; 12 + use super::keys::UserHash; 13 + use super::scan::{count_prefix, point_lookup}; 14 + use super::user_hash::UserHashMap; 15 + 16 + use tranquil_db_traits::DbScope; 17 + use tranquil_db_traits::{ 18 + AuditLogEntry, ControllerInfo, DelegatedAccountInfo, DelegationActionType, DelegationGrant, 19 + }; 20 + use tranquil_types::{Did, Handle}; 21 + 22 + pub struct DelegationOps { 23 + db: Database, 24 + indexes: Keyspace, 25 + users: Keyspace, 26 + user_hashes: Arc<UserHashMap>, 27 + } 28 + 29 + impl DelegationOps { 30 + pub fn new( 31 + db: Database, 32 + indexes: Keyspace, 33 + users: Keyspace, 34 + user_hashes: Arc<UserHashMap>, 35 + ) -> Self { 36 + Self { 37 + db, 38 + indexes, 39 + users, 40 + user_hashes, 41 + } 42 + } 43 + 44 + fn resolve_handle_for_did(&self, did_str: &str) -> Option<Handle> { 45 + let user_hash = UserHash::from_did(did_str); 46 + let key = super::encoding::KeyBuilder::new() 47 + .tag(super::keys::KeyTag::USER_PRIMARY) 48 + .u64(user_hash.raw()) 49 + .build(); 50 + self.users 51 + .get(key.as_slice()) 52 + .ok() 53 + .flatten() 54 + .and_then(|raw| super::users::UserValue::deserialize(&raw)) 55 + .and_then(|u| Handle::new(u.handle).ok()) 56 + } 57 + 58 + fn value_to_grant(&self, v: &DelegationGrantValue) -> Result<DelegationGrant, MetastoreError> { 59 + Ok(DelegationGrant { 60 + id: v.id, 61 + delegated_did: Did::new(v.delegated_did.clone()) 62 + .map_err(|_| MetastoreError::CorruptData("invalid delegated_did"))?, 63 + controller_did: Did::new(v.controller_did.clone()) 64 + .map_err(|_| MetastoreError::CorruptData("invalid controller_did"))?, 65 + granted_scopes: DbScope::new(&v.granted_scopes).unwrap_or_else(|_| DbScope::empty()), 66 + granted_at: DateTime::from_timestamp_millis(v.granted_at_ms).unwrap_or_default(), 67 + granted_by: Did::new(v.granted_by.clone()) 68 + .map_err(|_| MetastoreError::CorruptData("invalid granted_by"))?, 69 + revoked_at: v.revoked_at_ms.and_then(DateTime::from_timestamp_millis), 70 + revoked_by: v.revoked_by.as_ref().and_then(|d| Did::new(d.clone()).ok()), 71 + }) 72 + } 73 + 74 + pub fn is_delegated_account(&self, did: &Did) -> Result<bool, MetastoreError> { 75 + let delegated_hash = UserHash::from_did(did.as_str()); 76 + let prefix = grant_prefix(delegated_hash); 77 + let found = self 78 + .indexes 79 + .prefix(prefix.as_slice()) 80 + .map(|guard| { 81 + let (_, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 82 + DelegationGrantValue::deserialize(&val_bytes) 83 + .ok_or(MetastoreError::CorruptData("corrupt delegation grant")) 84 + .map(|val| val.revoked_at_ms.is_none()) 85 + }) 86 + .find(|result| !matches!(result, Ok(false))); 87 + match found { 88 + Some(Ok(true)) => Ok(true), 89 + Some(Err(e)) => Err(e), 90 + _ => Ok(false), 91 + } 92 + } 93 + 94 + pub fn create_delegation( 95 + &self, 96 + delegated_did: &Did, 97 + controller_did: &Did, 98 + granted_scopes: &DbScope, 99 + granted_by: &Did, 100 + ) -> Result<Uuid, MetastoreError> { 101 + let delegated_hash = UserHash::from_did(delegated_did.as_str()); 102 + let controller_hash = UserHash::from_did(controller_did.as_str()); 103 + let id = Uuid::new_v4(); 104 + let now_ms = Utc::now().timestamp_millis(); 105 + 106 + let value = DelegationGrantValue { 107 + id, 108 + delegated_did: delegated_did.to_string(), 109 + controller_did: controller_did.to_string(), 110 + granted_scopes: granted_scopes.as_str().to_owned(), 111 + granted_at_ms: now_ms, 112 + granted_by: granted_by.to_string(), 113 + revoked_at_ms: None, 114 + revoked_by: None, 115 + }; 116 + 117 + let primary = grant_key(delegated_hash, controller_hash); 118 + let reverse = by_controller_key(controller_hash, delegated_hash); 119 + 120 + let mut batch = self.db.batch(); 121 + batch.insert(&self.indexes, primary.as_slice(), value.serialize()); 122 + batch.insert(&self.indexes, reverse.as_slice(), []); 123 + batch.commit().map_err(MetastoreError::Fjall)?; 124 + 125 + Ok(id) 126 + } 127 + 128 + pub fn revoke_delegation( 129 + &self, 130 + delegated_did: &Did, 131 + controller_did: &Did, 132 + revoked_by: &Did, 133 + ) -> Result<bool, MetastoreError> { 134 + let delegated_hash = UserHash::from_did(delegated_did.as_str()); 135 + let controller_hash = UserHash::from_did(controller_did.as_str()); 136 + let primary = grant_key(delegated_hash, controller_hash); 137 + 138 + let existing: Option<DelegationGrantValue> = point_lookup( 139 + &self.indexes, 140 + primary.as_slice(), 141 + DelegationGrantValue::deserialize, 142 + "corrupt delegation grant", 143 + )?; 144 + 145 + match existing { 146 + Some(mut val) if val.revoked_at_ms.is_none() => { 147 + val.revoked_at_ms = Some(Utc::now().timestamp_millis()); 148 + val.revoked_by = Some(revoked_by.to_string()); 149 + 150 + let reverse = by_controller_key(controller_hash, delegated_hash); 151 + let mut batch = self.db.batch(); 152 + batch.insert(&self.indexes, primary.as_slice(), val.serialize()); 153 + batch.remove(&self.indexes, reverse.as_slice()); 154 + batch.commit().map_err(MetastoreError::Fjall)?; 155 + Ok(true) 156 + } 157 + _ => Ok(false), 158 + } 159 + } 160 + 161 + pub fn update_delegation_scopes( 162 + &self, 163 + delegated_did: &Did, 164 + controller_did: &Did, 165 + new_scopes: &DbScope, 166 + ) -> Result<bool, MetastoreError> { 167 + let delegated_hash = UserHash::from_did(delegated_did.as_str()); 168 + let controller_hash = UserHash::from_did(controller_did.as_str()); 169 + let primary = grant_key(delegated_hash, controller_hash); 170 + 171 + let existing: Option<DelegationGrantValue> = point_lookup( 172 + &self.indexes, 173 + primary.as_slice(), 174 + DelegationGrantValue::deserialize, 175 + "corrupt delegation grant", 176 + )?; 177 + 178 + match existing { 179 + Some(mut val) if val.revoked_at_ms.is_none() => { 180 + val.granted_scopes = new_scopes.as_str().to_owned(); 181 + self.indexes 182 + .insert(primary.as_slice(), val.serialize()) 183 + .map_err(MetastoreError::Fjall)?; 184 + Ok(true) 185 + } 186 + _ => Ok(false), 187 + } 188 + } 189 + 190 + pub fn get_delegation( 191 + &self, 192 + delegated_did: &Did, 193 + controller_did: &Did, 194 + ) -> Result<Option<DelegationGrant>, MetastoreError> { 195 + let delegated_hash = UserHash::from_did(delegated_did.as_str()); 196 + let controller_hash = UserHash::from_did(controller_did.as_str()); 197 + let primary = grant_key(delegated_hash, controller_hash); 198 + 199 + let val: Option<DelegationGrantValue> = point_lookup( 200 + &self.indexes, 201 + primary.as_slice(), 202 + DelegationGrantValue::deserialize, 203 + "corrupt delegation grant", 204 + )?; 205 + 206 + val.map(|v| self.value_to_grant(&v)).transpose() 207 + } 208 + 209 + pub fn get_delegations_for_account( 210 + &self, 211 + delegated_did: &Did, 212 + ) -> Result<Vec<ControllerInfo>, MetastoreError> { 213 + let delegated_hash = UserHash::from_did(delegated_did.as_str()); 214 + let prefix = grant_prefix(delegated_hash); 215 + 216 + self.indexes 217 + .prefix(prefix.as_slice()) 218 + .try_fold(Vec::new(), |mut acc, guard| { 219 + let (_, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 220 + let val = DelegationGrantValue::deserialize(&val_bytes) 221 + .ok_or(MetastoreError::CorruptData("corrupt delegation grant"))?; 222 + let is_active = val.revoked_at_ms.is_none(); 223 + let controller_did_parsed = Did::new(val.controller_did.clone()) 224 + .map_err(|_| MetastoreError::CorruptData("invalid controller_did"))?; 225 + let handle = self.resolve_handle_for_did(&val.controller_did); 226 + let is_local = self 227 + .user_hashes 228 + .get_uuid(&UserHash::from_did(&val.controller_did)) 229 + .is_some(); 230 + 231 + acc.push(ControllerInfo { 232 + did: controller_did_parsed, 233 + handle, 234 + granted_scopes: DbScope::new(&val.granted_scopes) 235 + .unwrap_or_else(|_| DbScope::empty()), 236 + granted_at: DateTime::from_timestamp_millis(val.granted_at_ms) 237 + .unwrap_or_default(), 238 + is_active, 239 + is_local, 240 + }); 241 + Ok::<_, MetastoreError>(acc) 242 + }) 243 + } 244 + 245 + pub fn get_accounts_controlled_by( 246 + &self, 247 + controller_did: &Did, 248 + ) -> Result<Vec<DelegatedAccountInfo>, MetastoreError> { 249 + let controller_hash = UserHash::from_did(controller_did.as_str()); 250 + let prefix = by_controller_prefix(controller_hash); 251 + 252 + self.indexes 253 + .prefix(prefix.as_slice()) 254 + .try_fold(Vec::new(), |mut acc, guard| { 255 + let (key_bytes, _) = guard.into_inner().map_err(MetastoreError::Fjall)?; 256 + let mut reader = super::encoding::KeyReader::new(&key_bytes); 257 + let _tag = reader.tag(); 258 + let _ctrl_hash = reader.u64(); 259 + let deleg_hash_raw = reader 260 + .u64() 261 + .ok_or(MetastoreError::CorruptData("corrupt by_controller key"))?; 262 + let deleg_hash = UserHash::from_raw(deleg_hash_raw); 263 + 264 + let grant_pfx = grant_key(deleg_hash, controller_hash); 265 + let grant_val: Option<DelegationGrantValue> = point_lookup( 266 + &self.indexes, 267 + grant_pfx.as_slice(), 268 + DelegationGrantValue::deserialize, 269 + "corrupt delegation grant", 270 + )?; 271 + 272 + if let Some(val) = grant_val.filter(|v| v.revoked_at_ms.is_none()) { 273 + let delegated_did = Did::new(val.delegated_did.clone()) 274 + .map_err(|_| MetastoreError::CorruptData("invalid delegated_did"))?; 275 + let handle = self 276 + .resolve_handle_for_did(&val.delegated_did) 277 + .unwrap_or_else(|| Handle::new("unknown.invalid").unwrap()); 278 + acc.push(DelegatedAccountInfo { 279 + did: delegated_did, 280 + handle, 281 + granted_scopes: DbScope::new(&val.granted_scopes) 282 + .unwrap_or_else(|_| DbScope::empty()), 283 + granted_at: DateTime::from_timestamp_millis(val.granted_at_ms) 284 + .unwrap_or_default(), 285 + }); 286 + } 287 + 288 + Ok::<_, MetastoreError>(acc) 289 + }) 290 + } 291 + 292 + pub fn count_active_controllers(&self, delegated_did: &Did) -> Result<i64, MetastoreError> { 293 + let delegated_hash = UserHash::from_did(delegated_did.as_str()); 294 + let prefix = grant_prefix(delegated_hash); 295 + 296 + self.indexes 297 + .prefix(prefix.as_slice()) 298 + .try_fold(0i64, |acc, guard| { 299 + let (_, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 300 + let val = DelegationGrantValue::deserialize(&val_bytes) 301 + .ok_or(MetastoreError::CorruptData("corrupt delegation grant"))?; 302 + Ok::<_, MetastoreError>(match val.revoked_at_ms.is_none() { 303 + true => acc.saturating_add(1), 304 + false => acc, 305 + }) 306 + }) 307 + } 308 + 309 + pub fn controls_any_accounts(&self, did: &Did) -> Result<bool, MetastoreError> { 310 + let controller_hash = UserHash::from_did(did.as_str()); 311 + let prefix = by_controller_prefix(controller_hash); 312 + match self.indexes.prefix(prefix.as_slice()).next() { 313 + Some(guard) => { 314 + guard.into_inner().map_err(MetastoreError::Fjall)?; 315 + Ok(true) 316 + } 317 + None => Ok(false), 318 + } 319 + } 320 + 321 + #[allow(clippy::too_many_arguments)] 322 + pub fn log_delegation_action( 323 + &self, 324 + delegated_did: &Did, 325 + actor_did: &Did, 326 + controller_did: Option<&Did>, 327 + action_type: DelegationActionType, 328 + action_details: Option<serde_json::Value>, 329 + ip_address: Option<&str>, 330 + user_agent: Option<&str>, 331 + ) -> Result<Uuid, MetastoreError> { 332 + let delegated_hash = UserHash::from_did(delegated_did.as_str()); 333 + let id = Uuid::new_v4(); 334 + let now_ms = Utc::now().timestamp_millis(); 335 + 336 + let value = AuditLogValue { 337 + id, 338 + delegated_did: delegated_did.to_string(), 339 + actor_did: actor_did.to_string(), 340 + controller_did: controller_did.map(|d| d.to_string()), 341 + action_type: action_type_to_u8(action_type), 342 + action_details: action_details.map(|v| serde_json::to_vec(&v).unwrap_or_default()), 343 + ip_address: ip_address.map(str::to_owned), 344 + user_agent: user_agent.map(str::to_owned), 345 + created_at_ms: now_ms, 346 + }; 347 + 348 + let key = audit_log_key(delegated_hash, now_ms, id); 349 + self.indexes 350 + .insert(key.as_slice(), value.serialize()) 351 + .map_err(MetastoreError::Fjall)?; 352 + 353 + Ok(id) 354 + } 355 + 356 + pub fn get_audit_log_for_account( 357 + &self, 358 + delegated_did: &Did, 359 + limit: i64, 360 + offset: i64, 361 + ) -> Result<Vec<AuditLogEntry>, MetastoreError> { 362 + let delegated_hash = UserHash::from_did(delegated_did.as_str()); 363 + let prefix = audit_log_prefix(delegated_hash); 364 + 365 + let limit = usize::try_from(limit).unwrap_or(0); 366 + let offset = usize::try_from(offset).unwrap_or(0); 367 + 368 + self.indexes 369 + .prefix(prefix.as_slice()) 370 + .skip(offset) 371 + .take(limit) 372 + .try_fold(Vec::new(), |mut acc, guard| { 373 + let (_, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 374 + let val = AuditLogValue::deserialize(&val_bytes) 375 + .ok_or(MetastoreError::CorruptData("corrupt audit log entry"))?; 376 + acc.push(self.value_to_audit_entry(&val)?); 377 + Ok::<_, MetastoreError>(acc) 378 + }) 379 + } 380 + 381 + pub fn count_audit_log_entries(&self, delegated_did: &Did) -> Result<i64, MetastoreError> { 382 + let delegated_hash = UserHash::from_did(delegated_did.as_str()); 383 + let prefix = audit_log_prefix(delegated_hash); 384 + count_prefix(&self.indexes, prefix.as_slice()) 385 + } 386 + 387 + fn value_to_audit_entry(&self, v: &AuditLogValue) -> Result<AuditLogEntry, MetastoreError> { 388 + Ok(AuditLogEntry { 389 + id: v.id, 390 + delegated_did: Did::new(v.delegated_did.clone()) 391 + .map_err(|_| MetastoreError::CorruptData("invalid delegated_did"))?, 392 + actor_did: Did::new(v.actor_did.clone()) 393 + .map_err(|_| MetastoreError::CorruptData("invalid actor_did"))?, 394 + controller_did: v 395 + .controller_did 396 + .as_ref() 397 + .map(|d| Did::new(d.clone())) 398 + .transpose() 399 + .map_err(|_| MetastoreError::CorruptData("invalid controller_did"))?, 400 + action_type: u8_to_action_type(v.action_type).ok_or(MetastoreError::CorruptData( 401 + "unknown delegation action type", 402 + ))?, 403 + action_details: v 404 + .action_details 405 + .as_ref() 406 + .and_then(|b| serde_json::from_slice(b).ok()), 407 + ip_address: v.ip_address.clone(), 408 + user_agent: v.user_agent.clone(), 409 + created_at: DateTime::from_timestamp_millis(v.created_at_ms).unwrap_or_default(), 410 + }) 411 + } 412 + }
+201
crates/tranquil-store/src/metastore/delegations.rs
··· 1 + use serde::{Deserialize, Serialize}; 2 + use smallvec::SmallVec; 3 + 4 + use super::encoding::KeyBuilder; 5 + use super::keys::{KeyTag, UserHash}; 6 + 7 + const GRANT_SCHEMA_VERSION: u8 = 1; 8 + const AUDIT_LOG_SCHEMA_VERSION: u8 = 1; 9 + 10 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 11 + pub struct DelegationGrantValue { 12 + pub id: uuid::Uuid, 13 + pub delegated_did: String, 14 + pub controller_did: String, 15 + pub granted_scopes: String, 16 + pub granted_at_ms: i64, 17 + pub granted_by: String, 18 + pub revoked_at_ms: Option<i64>, 19 + pub revoked_by: Option<String>, 20 + } 21 + 22 + impl DelegationGrantValue { 23 + pub fn serialize(&self) -> Vec<u8> { 24 + let payload = 25 + postcard::to_allocvec(self).expect("DelegationGrantValue serialization cannot fail"); 26 + let mut buf = Vec::with_capacity(1 + payload.len()); 27 + buf.push(GRANT_SCHEMA_VERSION); 28 + buf.extend_from_slice(&payload); 29 + buf 30 + } 31 + 32 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 33 + let (&version, payload) = bytes.split_first()?; 34 + match version { 35 + GRANT_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 36 + _ => None, 37 + } 38 + } 39 + } 40 + 41 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 42 + pub struct AuditLogValue { 43 + pub id: uuid::Uuid, 44 + pub delegated_did: String, 45 + pub actor_did: String, 46 + pub controller_did: Option<String>, 47 + pub action_type: u8, 48 + pub action_details: Option<Vec<u8>>, 49 + pub ip_address: Option<String>, 50 + pub user_agent: Option<String>, 51 + pub created_at_ms: i64, 52 + } 53 + 54 + impl AuditLogValue { 55 + pub fn serialize(&self) -> Vec<u8> { 56 + let payload = postcard::to_allocvec(self).expect("AuditLogValue serialization cannot fail"); 57 + let mut buf = Vec::with_capacity(1 + payload.len()); 58 + buf.push(AUDIT_LOG_SCHEMA_VERSION); 59 + buf.extend_from_slice(&payload); 60 + buf 61 + } 62 + 63 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 64 + let (&version, payload) = bytes.split_first()?; 65 + match version { 66 + AUDIT_LOG_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 67 + _ => None, 68 + } 69 + } 70 + } 71 + 72 + pub fn action_type_to_u8(t: tranquil_db_traits::DelegationActionType) -> u8 { 73 + match t { 74 + tranquil_db_traits::DelegationActionType::GrantCreated => 0, 75 + tranquil_db_traits::DelegationActionType::GrantRevoked => 1, 76 + tranquil_db_traits::DelegationActionType::ScopesModified => 2, 77 + tranquil_db_traits::DelegationActionType::TokenIssued => 3, 78 + tranquil_db_traits::DelegationActionType::RepoWrite => 4, 79 + tranquil_db_traits::DelegationActionType::BlobUpload => 5, 80 + tranquil_db_traits::DelegationActionType::AccountAction => 6, 81 + } 82 + } 83 + 84 + pub fn u8_to_action_type(v: u8) -> Option<tranquil_db_traits::DelegationActionType> { 85 + match v { 86 + 0 => Some(tranquil_db_traits::DelegationActionType::GrantCreated), 87 + 1 => Some(tranquil_db_traits::DelegationActionType::GrantRevoked), 88 + 2 => Some(tranquil_db_traits::DelegationActionType::ScopesModified), 89 + 3 => Some(tranquil_db_traits::DelegationActionType::TokenIssued), 90 + 4 => Some(tranquil_db_traits::DelegationActionType::RepoWrite), 91 + 5 => Some(tranquil_db_traits::DelegationActionType::BlobUpload), 92 + 6 => Some(tranquil_db_traits::DelegationActionType::AccountAction), 93 + _ => None, 94 + } 95 + } 96 + 97 + pub fn grant_key(delegated_hash: UserHash, controller_hash: UserHash) -> SmallVec<[u8; 128]> { 98 + KeyBuilder::new() 99 + .tag(KeyTag::DELEG_GRANT) 100 + .u64(delegated_hash.raw()) 101 + .u64(controller_hash.raw()) 102 + .build() 103 + } 104 + 105 + pub fn grant_prefix(delegated_hash: UserHash) -> SmallVec<[u8; 128]> { 106 + KeyBuilder::new() 107 + .tag(KeyTag::DELEG_GRANT) 108 + .u64(delegated_hash.raw()) 109 + .build() 110 + } 111 + 112 + pub fn by_controller_key( 113 + controller_hash: UserHash, 114 + delegated_hash: UserHash, 115 + ) -> SmallVec<[u8; 128]> { 116 + KeyBuilder::new() 117 + .tag(KeyTag::DELEG_BY_CONTROLLER) 118 + .u64(controller_hash.raw()) 119 + .u64(delegated_hash.raw()) 120 + .build() 121 + } 122 + 123 + pub fn by_controller_prefix(controller_hash: UserHash) -> SmallVec<[u8; 128]> { 124 + KeyBuilder::new() 125 + .tag(KeyTag::DELEG_BY_CONTROLLER) 126 + .u64(controller_hash.raw()) 127 + .build() 128 + } 129 + 130 + pub fn audit_log_key( 131 + delegated_hash: UserHash, 132 + created_at_ms: i64, 133 + id: uuid::Uuid, 134 + ) -> SmallVec<[u8; 128]> { 135 + let reversed_ts = i64::MAX.saturating_sub(created_at_ms); 136 + KeyBuilder::new() 137 + .tag(KeyTag::DELEG_AUDIT_LOG) 138 + .u64(delegated_hash.raw()) 139 + .i64(reversed_ts) 140 + .bytes(id.as_bytes()) 141 + .build() 142 + } 143 + 144 + pub fn audit_log_prefix(delegated_hash: UserHash) -> SmallVec<[u8; 128]> { 145 + KeyBuilder::new() 146 + .tag(KeyTag::DELEG_AUDIT_LOG) 147 + .u64(delegated_hash.raw()) 148 + .build() 149 + } 150 + 151 + #[cfg(test)] 152 + mod tests { 153 + use super::*; 154 + 155 + #[test] 156 + fn grant_value_roundtrip() { 157 + let val = DelegationGrantValue { 158 + id: uuid::Uuid::new_v4(), 159 + delegated_did: "did:plc:deleg".to_owned(), 160 + controller_did: "did:plc:ctrl".to_owned(), 161 + granted_scopes: "atproto".to_owned(), 162 + granted_at_ms: 1700000000000, 163 + granted_by: "did:plc:grantor".to_owned(), 164 + revoked_at_ms: None, 165 + revoked_by: None, 166 + }; 167 + let bytes = val.serialize(); 168 + assert_eq!(bytes[0], GRANT_SCHEMA_VERSION); 169 + let decoded = DelegationGrantValue::deserialize(&bytes).unwrap(); 170 + assert_eq!(val, decoded); 171 + } 172 + 173 + #[test] 174 + fn audit_log_value_roundtrip() { 175 + let val = AuditLogValue { 176 + id: uuid::Uuid::new_v4(), 177 + delegated_did: "did:plc:deleg".to_owned(), 178 + actor_did: "did:plc:actor".to_owned(), 179 + controller_did: Some("did:plc:ctrl".to_owned()), 180 + action_type: 0, 181 + action_details: None, 182 + ip_address: Some("127.0.0.1".to_owned()), 183 + user_agent: None, 184 + created_at_ms: 1700000000000, 185 + }; 186 + let bytes = val.serialize(); 187 + assert_eq!(bytes[0], AUDIT_LOG_SCHEMA_VERSION); 188 + let decoded = AuditLogValue::deserialize(&bytes).unwrap(); 189 + assert_eq!(val, decoded); 190 + } 191 + 192 + #[test] 193 + fn grant_key_ordering_by_controller() { 194 + let deleg = UserHash::from_did("did:plc:deleg"); 195 + let ctrl_a = UserHash::from_raw(100); 196 + let ctrl_b = UserHash::from_raw(200); 197 + let key_a = grant_key(deleg, ctrl_a); 198 + let key_b = grant_key(deleg, ctrl_b); 199 + assert!(key_a.as_slice() < key_b.as_slice()); 200 + } 201 + }
+4215 -25
crates/tranquil-store/src/metastore/handler.rs
··· 4 4 5 5 use chrono::{DateTime, Utc}; 6 6 use tokio::sync::oneshot; 7 + use tranquil_db_traits::DbScope; 7 8 use tranquil_db_traits::{ 8 - AccountStatus, ApplyCommitError, ApplyCommitInput, ApplyCommitResult, Backlink, 9 - BrokenGenesisCommit, CommitEventData, DbError, EventBlocksCids, ImportBlock, ImportRecord, 10 - ImportRepoError, SequenceNumber, SequencedEvent, UserNeedingRecordBlobsBackfill, 11 - UserWithoutBlocks, 9 + AccountSearchResult, AccountStatus, AdminAccountInfo, ApplyCommitError, ApplyCommitInput, 10 + ApplyCommitResult, Backlink, BrokenGenesisCommit, CommitEventData, CommsChannel, CommsType, 11 + CompletePasskeySetupInput, CreateAccountError, CreateDelegatedAccountInput, 12 + CreatePasskeyAccountInput, CreatePasswordAccountInput, CreatePasswordAccountResult, 13 + CreateSsoAccountInput, DbError, DelegationActionType, DeletionRequest, DidWebOverrides, 14 + EventBlocksCids, ImportBlock, ImportRecord, ImportRepoError, InviteCodeError, InviteCodeInfo, 15 + InviteCodeRow, InviteCodeSortOrder, InviteCodeUse, MigrationReactivationError, 16 + MigrationReactivationInput, NotificationHistoryRow, NotificationPrefs, OAuthTokenWithUser, 17 + PasswordResetResult, QueuedComms, ReactivatedAccountInfo, RecoverPasskeyAccountInput, 18 + RecoverPasskeyAccountResult, RefreshSessionResult, ReservedSigningKey, 19 + ScheduledDeletionAccount, ScopePreference, SequenceNumber, SequencedEvent, SessionId, 20 + StoredBackupCode, StoredPasskey, TokenFamilyId, TotpRecord, TotpRecordState, User2faStatus, 21 + UserAuthInfo, UserCommsPrefs, UserConfirmSignup, UserDidWebInfo, UserEmailInfo, 22 + UserForDeletion, UserForDidDoc, UserForDidDocBuild, UserForPasskeyRecovery, 23 + UserForPasskeySetup, UserForRecovery, UserForVerification, UserIdAndHandle, 24 + UserIdAndPasswordHash, UserIdHandleEmail, UserInfoForAuth, UserKeyInfo, UserKeyWithId, 25 + UserLegacyLoginPref, UserLoginCheck, UserLoginFull, UserLoginInfo, 26 + UserNeedingRecordBlobsBackfill, UserPasswordInfo, UserResendVerification, UserResetCodeInfo, 27 + UserRow, UserSessionInfo, UserStatus, UserVerificationInfo, UserWithKey, UserWithoutBlocks, 28 + ValidatedInviteCode, WebauthnChallengeType, 29 + }; 30 + use tranquil_oauth::{AuthorizedClientData, DeviceData, RequestData, TokenData}; 31 + use tranquil_types::{ 32 + AtUri, AuthorizationCode, CidLink, ClientId, DPoPProofId, DeviceId, Did, Handle, Nsid, 33 + RefreshToken, RequestId, Rkey, TokenId, 12 34 }; 13 - use tranquil_types::{AtUri, CidLink, Did, Handle, Nsid, Rkey}; 14 35 use uuid::Uuid; 15 36 16 37 use super::MetastoreError; ··· 77 98 Commit(Box<CommitRequest>), 78 99 Backlink(BacklinkRequest), 79 100 Blob(BlobRequest), 101 + Delegation(DelegationRequest), 102 + Sso(SsoRequest), 103 + Session(SessionRequest), 104 + Infra(InfraRequest), 105 + OAuth(OAuthRequest), 106 + User(UserRequest), 80 107 } 81 108 82 109 impl MetastoreRequest { ··· 89 116 Self::Commit(r) => r.routing(user_hashes), 90 117 Self::Backlink(r) => r.routing(user_hashes), 91 118 Self::Blob(r) => r.routing(user_hashes), 119 + Self::Delegation(r) => r.routing(), 120 + Self::Sso(r) => r.routing(), 121 + Self::Session(r) => r.routing(user_hashes), 122 + Self::Infra(r) => r.routing(user_hashes), 123 + Self::OAuth(r) => r.routing(), 124 + Self::User(r) => r.routing(user_hashes), 92 125 } 93 126 } 94 127 } ··· 580 613 } 581 614 } 582 615 616 + pub enum DelegationRequest { 617 + IsDelegatedAccount { 618 + did: Did, 619 + tx: Tx<bool>, 620 + }, 621 + CreateDelegation { 622 + delegated_did: Did, 623 + controller_did: Did, 624 + granted_scopes: DbScope, 625 + granted_by: Did, 626 + tx: Tx<Uuid>, 627 + }, 628 + RevokeDelegation { 629 + delegated_did: Did, 630 + controller_did: Did, 631 + revoked_by: Did, 632 + tx: Tx<bool>, 633 + }, 634 + UpdateDelegationScopes { 635 + delegated_did: Did, 636 + controller_did: Did, 637 + new_scopes: DbScope, 638 + tx: Tx<bool>, 639 + }, 640 + GetDelegation { 641 + delegated_did: Did, 642 + controller_did: Did, 643 + tx: Tx<Option<tranquil_db_traits::DelegationGrant>>, 644 + }, 645 + GetDelegationsForAccount { 646 + delegated_did: Did, 647 + tx: Tx<Vec<tranquil_db_traits::ControllerInfo>>, 648 + }, 649 + GetAccountsControlledBy { 650 + controller_did: Did, 651 + tx: Tx<Vec<tranquil_db_traits::DelegatedAccountInfo>>, 652 + }, 653 + CountActiveControllers { 654 + delegated_did: Did, 655 + tx: Tx<i64>, 656 + }, 657 + ControlsAnyAccounts { 658 + did: Did, 659 + tx: Tx<bool>, 660 + }, 661 + LogDelegationAction { 662 + delegated_did: Did, 663 + actor_did: Did, 664 + controller_did: Option<Did>, 665 + action_type: DelegationActionType, 666 + action_details: Option<serde_json::Value>, 667 + ip_address: Option<String>, 668 + user_agent: Option<String>, 669 + tx: Tx<Uuid>, 670 + }, 671 + GetAuditLogForAccount { 672 + delegated_did: Did, 673 + limit: i64, 674 + offset: i64, 675 + tx: Tx<Vec<tranquil_db_traits::AuditLogEntry>>, 676 + }, 677 + CountAuditLogEntries { 678 + delegated_did: Did, 679 + tx: Tx<i64>, 680 + }, 681 + } 682 + 683 + impl DelegationRequest { 684 + fn routing(&self) -> Routing { 685 + match self { 686 + Self::IsDelegatedAccount { did, .. } 687 + | Self::GetDelegationsForAccount { 688 + delegated_did: did, .. 689 + } 690 + | Self::CountActiveControllers { 691 + delegated_did: did, .. 692 + } 693 + | Self::GetAuditLogForAccount { 694 + delegated_did: did, .. 695 + } 696 + | Self::CountAuditLogEntries { 697 + delegated_did: did, .. 698 + } => did_to_routing(did.as_str()), 699 + Self::CreateDelegation { delegated_did, .. } 700 + | Self::RevokeDelegation { delegated_did, .. } 701 + | Self::UpdateDelegationScopes { delegated_did, .. } 702 + | Self::GetDelegation { delegated_did, .. } 703 + | Self::LogDelegationAction { delegated_did, .. } => { 704 + did_to_routing(delegated_did.as_str()) 705 + } 706 + Self::GetAccountsControlledBy { controller_did, .. } 707 + | Self::ControlsAnyAccounts { 708 + did: controller_did, 709 + .. 710 + } => did_to_routing(controller_did.as_str()), 711 + } 712 + } 713 + } 714 + 715 + pub enum SsoRequest { 716 + CreateExternalIdentity { 717 + did: Did, 718 + provider: tranquil_db_traits::SsoProviderType, 719 + provider_user_id: String, 720 + provider_username: Option<String>, 721 + provider_email: Option<String>, 722 + tx: Tx<Uuid>, 723 + }, 724 + GetExternalIdentityByProvider { 725 + provider: tranquil_db_traits::SsoProviderType, 726 + provider_user_id: String, 727 + tx: Tx<Option<tranquil_db_traits::ExternalIdentity>>, 728 + }, 729 + GetExternalIdentitiesByDid { 730 + did: Did, 731 + tx: Tx<Vec<tranquil_db_traits::ExternalIdentity>>, 732 + }, 733 + UpdateExternalIdentityLogin { 734 + id: Uuid, 735 + provider_username: Option<String>, 736 + provider_email: Option<String>, 737 + tx: Tx<()>, 738 + }, 739 + DeleteExternalIdentity { 740 + id: Uuid, 741 + did: Did, 742 + tx: Tx<bool>, 743 + }, 744 + CreateSsoAuthState { 745 + state: String, 746 + request_uri: String, 747 + provider: tranquil_db_traits::SsoProviderType, 748 + action: tranquil_db_traits::SsoAction, 749 + nonce: Option<String>, 750 + code_verifier: Option<String>, 751 + did: Option<Did>, 752 + tx: Tx<()>, 753 + }, 754 + ConsumeSsoAuthState { 755 + state: String, 756 + tx: Tx<Option<tranquil_db_traits::SsoAuthState>>, 757 + }, 758 + CleanupExpiredSsoAuthStates { 759 + tx: Tx<u64>, 760 + }, 761 + CreatePendingRegistration { 762 + token: String, 763 + request_uri: String, 764 + provider: tranquil_db_traits::SsoProviderType, 765 + provider_user_id: String, 766 + provider_username: Option<String>, 767 + provider_email: Option<String>, 768 + provider_email_verified: bool, 769 + tx: Tx<()>, 770 + }, 771 + GetPendingRegistration { 772 + token: String, 773 + tx: Tx<Option<tranquil_db_traits::SsoPendingRegistration>>, 774 + }, 775 + ConsumePendingRegistration { 776 + token: String, 777 + tx: Tx<Option<tranquil_db_traits::SsoPendingRegistration>>, 778 + }, 779 + CleanupExpiredPendingRegistrations { 780 + tx: Tx<u64>, 781 + }, 782 + } 783 + 784 + impl SsoRequest { 785 + fn routing(&self) -> Routing { 786 + match self { 787 + Self::CreateExternalIdentity { did, .. } 788 + | Self::GetExternalIdentitiesByDid { did, .. } 789 + | Self::DeleteExternalIdentity { did, .. } => did_to_routing(did.as_str()), 790 + Self::GetExternalIdentityByProvider { .. } 791 + | Self::UpdateExternalIdentityLogin { .. } 792 + | Self::ConsumeSsoAuthState { .. } 793 + | Self::CreateSsoAuthState { .. } 794 + | Self::CleanupExpiredSsoAuthStates { .. } 795 + | Self::CreatePendingRegistration { .. } 796 + | Self::GetPendingRegistration { .. } 797 + | Self::ConsumePendingRegistration { .. } 798 + | Self::CleanupExpiredPendingRegistrations { .. } => Routing::Global, 799 + } 800 + } 801 + } 802 + 803 + pub enum SessionRequest { 804 + CreateSession { 805 + data: tranquil_db_traits::SessionTokenCreate, 806 + tx: Tx<SessionId>, 807 + }, 808 + GetSessionByAccessJti { 809 + access_jti: String, 810 + tx: Tx<Option<tranquil_db_traits::SessionToken>>, 811 + }, 812 + GetSessionForRefresh { 813 + refresh_jti: String, 814 + tx: Tx<Option<tranquil_db_traits::SessionForRefresh>>, 815 + }, 816 + UpdateSessionTokens { 817 + session_id: SessionId, 818 + new_access_jti: String, 819 + new_refresh_jti: String, 820 + new_access_expires_at: DateTime<Utc>, 821 + new_refresh_expires_at: DateTime<Utc>, 822 + tx: Tx<()>, 823 + }, 824 + DeleteSessionByAccessJti { 825 + access_jti: String, 826 + tx: Tx<u64>, 827 + }, 828 + DeleteSessionById { 829 + session_id: SessionId, 830 + tx: Tx<u64>, 831 + }, 832 + DeleteSessionsByDid { 833 + did: Did, 834 + tx: Tx<u64>, 835 + }, 836 + DeleteSessionsByDidExceptJti { 837 + did: Did, 838 + except_jti: String, 839 + tx: Tx<u64>, 840 + }, 841 + ListSessionsByDid { 842 + did: Did, 843 + tx: Tx<Vec<tranquil_db_traits::SessionListItem>>, 844 + }, 845 + GetSessionAccessJtiById { 846 + session_id: SessionId, 847 + did: Did, 848 + tx: Tx<Option<String>>, 849 + }, 850 + DeleteSessionsByAppPassword { 851 + did: Did, 852 + app_password_name: String, 853 + tx: Tx<u64>, 854 + }, 855 + GetSessionJtisByAppPassword { 856 + did: Did, 857 + app_password_name: String, 858 + tx: Tx<Vec<String>>, 859 + }, 860 + CheckRefreshTokenUsed { 861 + refresh_jti: String, 862 + tx: Tx<Option<SessionId>>, 863 + }, 864 + MarkRefreshTokenUsed { 865 + refresh_jti: String, 866 + session_id: SessionId, 867 + tx: Tx<bool>, 868 + }, 869 + ListAppPasswords { 870 + user_id: Uuid, 871 + tx: Tx<Vec<tranquil_db_traits::AppPasswordRecord>>, 872 + }, 873 + GetAppPasswordsForLogin { 874 + user_id: Uuid, 875 + tx: Tx<Vec<tranquil_db_traits::AppPasswordRecord>>, 876 + }, 877 + GetAppPasswordByName { 878 + user_id: Uuid, 879 + name: String, 880 + tx: Tx<Option<tranquil_db_traits::AppPasswordRecord>>, 881 + }, 882 + CreateAppPassword { 883 + data: tranquil_db_traits::AppPasswordCreate, 884 + tx: Tx<Uuid>, 885 + }, 886 + DeleteAppPassword { 887 + user_id: Uuid, 888 + name: String, 889 + tx: Tx<u64>, 890 + }, 891 + DeleteAppPasswordsByController { 892 + did: Did, 893 + controller_did: Did, 894 + tx: Tx<u64>, 895 + }, 896 + GetLastReauthAt { 897 + did: Did, 898 + tx: Tx<Option<DateTime<Utc>>>, 899 + }, 900 + UpdateLastReauth { 901 + did: Did, 902 + tx: Tx<DateTime<Utc>>, 903 + }, 904 + GetSessionMfaStatus { 905 + did: Did, 906 + tx: Tx<Option<tranquil_db_traits::SessionMfaStatus>>, 907 + }, 908 + UpdateMfaVerified { 909 + did: Did, 910 + tx: Tx<()>, 911 + }, 912 + GetAppPasswordHashesByDid { 913 + did: Did, 914 + tx: Tx<Vec<String>>, 915 + }, 916 + RefreshSessionAtomic { 917 + data: tranquil_db_traits::SessionRefreshData, 918 + tx: Tx<RefreshSessionResult>, 919 + }, 920 + } 921 + 922 + impl SessionRequest { 923 + fn routing(&self, user_hashes: &UserHashMap) -> Routing { 924 + match self { 925 + Self::CreateSession { .. } 926 + | Self::GetSessionByAccessJti { .. } 927 + | Self::GetSessionForRefresh { .. } 928 + | Self::CheckRefreshTokenUsed { .. } 929 + | Self::MarkRefreshTokenUsed { .. } 930 + | Self::DeleteSessionByAccessJti { .. } 931 + | Self::DeleteSessionById { .. } => Routing::Global, 932 + Self::DeleteSessionsByDid { did, .. } 933 + | Self::DeleteSessionsByDidExceptJti { did, .. } 934 + | Self::ListSessionsByDid { did, .. } 935 + | Self::GetSessionAccessJtiById { did, .. } 936 + | Self::DeleteSessionsByAppPassword { did, .. } 937 + | Self::GetSessionJtisByAppPassword { did, .. } 938 + | Self::DeleteAppPasswordsByController { did, .. } 939 + | Self::GetLastReauthAt { did, .. } 940 + | Self::UpdateLastReauth { did, .. } 941 + | Self::GetSessionMfaStatus { did, .. } 942 + | Self::UpdateMfaVerified { did, .. } 943 + | Self::GetAppPasswordHashesByDid { did, .. } => did_to_routing(did.as_str()), 944 + Self::UpdateSessionTokens { .. } | Self::RefreshSessionAtomic { .. } => Routing::Global, 945 + Self::ListAppPasswords { user_id, .. } 946 + | Self::GetAppPasswordsForLogin { user_id, .. } 947 + | Self::GetAppPasswordByName { user_id, .. } 948 + | Self::CreateAppPassword { 949 + data: tranquil_db_traits::AppPasswordCreate { user_id, .. }, 950 + .. 951 + } 952 + | Self::DeleteAppPassword { user_id, .. } => uuid_to_routing(user_hashes, user_id), 953 + } 954 + } 955 + } 956 + 957 + pub enum UserRequest { 958 + GetByDid { 959 + did: Did, 960 + tx: Tx<Option<UserRow>>, 961 + }, 962 + GetByHandle { 963 + handle: Handle, 964 + tx: Tx<Option<UserRow>>, 965 + }, 966 + GetWithKeyByDid { 967 + did: Did, 968 + tx: Tx<Option<UserWithKey>>, 969 + }, 970 + GetStatusByDid { 971 + did: Did, 972 + tx: Tx<Option<UserStatus>>, 973 + }, 974 + CountUsers { 975 + tx: Tx<i64>, 976 + }, 977 + GetSessionAccessExpiry { 978 + did: Did, 979 + access_jti: String, 980 + tx: Tx<Option<DateTime<Utc>>>, 981 + }, 982 + GetOAuthTokenWithUser { 983 + token_id: String, 984 + tx: Tx<Option<OAuthTokenWithUser>>, 985 + }, 986 + GetUserInfoByDid { 987 + did: Did, 988 + tx: Tx<Option<UserInfoForAuth>>, 989 + }, 990 + GetAnyAdminUserId { 991 + tx: Tx<Option<Uuid>>, 992 + }, 993 + SetInvitesDisabled { 994 + did: Did, 995 + disabled: bool, 996 + tx: Tx<bool>, 997 + }, 998 + SearchAccounts { 999 + cursor_did: Option<Did>, 1000 + email_filter: Option<String>, 1001 + handle_filter: Option<String>, 1002 + limit: i64, 1003 + tx: Tx<Vec<AccountSearchResult>>, 1004 + }, 1005 + GetAuthInfoByDid { 1006 + did: Did, 1007 + tx: Tx<Option<UserAuthInfo>>, 1008 + }, 1009 + GetByEmail { 1010 + email: String, 1011 + tx: Tx<Option<UserForVerification>>, 1012 + }, 1013 + GetLoginCheckByHandleOrEmail { 1014 + identifier: String, 1015 + tx: Tx<Option<UserLoginCheck>>, 1016 + }, 1017 + GetLoginInfoByHandleOrEmail { 1018 + identifier: String, 1019 + tx: Tx<Option<UserLoginInfo>>, 1020 + }, 1021 + Get2faStatusByDid { 1022 + did: Did, 1023 + tx: Tx<Option<User2faStatus>>, 1024 + }, 1025 + GetCommsPrefs { 1026 + user_id: Uuid, 1027 + tx: Tx<Option<UserCommsPrefs>>, 1028 + }, 1029 + GetIdByDid { 1030 + did: Did, 1031 + tx: Tx<Option<Uuid>>, 1032 + }, 1033 + GetUserKeyById { 1034 + user_id: Uuid, 1035 + tx: Tx<Option<UserKeyInfo>>, 1036 + }, 1037 + GetIdAndHandleByDid { 1038 + did: Did, 1039 + tx: Tx<Option<UserIdAndHandle>>, 1040 + }, 1041 + GetDidWebInfoByHandle { 1042 + handle: Handle, 1043 + tx: Tx<Option<UserDidWebInfo>>, 1044 + }, 1045 + GetDidWebOverrides { 1046 + user_id: Uuid, 1047 + tx: Tx<Option<DidWebOverrides>>, 1048 + }, 1049 + GetHandleByDid { 1050 + did: Did, 1051 + tx: Tx<Option<Handle>>, 1052 + }, 1053 + IsAccountActiveByDid { 1054 + did: Did, 1055 + tx: Tx<Option<bool>>, 1056 + }, 1057 + GetUserForDeletion { 1058 + did: Did, 1059 + tx: Tx<Option<UserForDeletion>>, 1060 + }, 1061 + CheckHandleExists { 1062 + handle: Handle, 1063 + exclude_user_id: Uuid, 1064 + tx: Tx<bool>, 1065 + }, 1066 + UpdateHandle { 1067 + user_id: Uuid, 1068 + handle: Handle, 1069 + tx: Tx<()>, 1070 + }, 1071 + GetUserWithKeyByDid { 1072 + did: Did, 1073 + tx: Tx<Option<UserKeyWithId>>, 1074 + }, 1075 + IsAccountMigrated { 1076 + did: Did, 1077 + tx: Tx<bool>, 1078 + }, 1079 + HasVerifiedCommsChannel { 1080 + did: Did, 1081 + tx: Tx<bool>, 1082 + }, 1083 + GetIdByHandle { 1084 + handle: Handle, 1085 + tx: Tx<Option<Uuid>>, 1086 + }, 1087 + GetEmailInfoByDid { 1088 + did: Did, 1089 + tx: Tx<Option<UserEmailInfo>>, 1090 + }, 1091 + CheckEmailExists { 1092 + email: String, 1093 + exclude_user_id: Uuid, 1094 + tx: Tx<bool>, 1095 + }, 1096 + UpdateEmail { 1097 + user_id: Uuid, 1098 + email: String, 1099 + tx: Tx<()>, 1100 + }, 1101 + SetEmailVerified { 1102 + user_id: Uuid, 1103 + verified: bool, 1104 + tx: Tx<()>, 1105 + }, 1106 + CheckEmailVerifiedByIdentifier { 1107 + identifier: String, 1108 + tx: Tx<Option<bool>>, 1109 + }, 1110 + CheckChannelVerifiedByDid { 1111 + did: Did, 1112 + channel: CommsChannel, 1113 + tx: Tx<Option<bool>>, 1114 + }, 1115 + AdminUpdateEmail { 1116 + did: Did, 1117 + email: String, 1118 + tx: Tx<u64>, 1119 + }, 1120 + AdminUpdateHandle { 1121 + did: Did, 1122 + handle: Handle, 1123 + tx: Tx<u64>, 1124 + }, 1125 + AdminUpdatePassword { 1126 + did: Did, 1127 + password_hash: String, 1128 + tx: Tx<u64>, 1129 + }, 1130 + GetNotificationPrefs { 1131 + did: Did, 1132 + tx: Tx<Option<NotificationPrefs>>, 1133 + }, 1134 + GetIdHandleEmailByDid { 1135 + did: Did, 1136 + tx: Tx<Option<UserIdHandleEmail>>, 1137 + }, 1138 + UpdatePreferredCommsChannel { 1139 + did: Did, 1140 + channel: CommsChannel, 1141 + tx: Tx<()>, 1142 + }, 1143 + ClearDiscord { 1144 + user_id: Uuid, 1145 + tx: Tx<()>, 1146 + }, 1147 + ClearTelegram { 1148 + user_id: Uuid, 1149 + tx: Tx<()>, 1150 + }, 1151 + ClearSignal { 1152 + user_id: Uuid, 1153 + tx: Tx<()>, 1154 + }, 1155 + SetUnverifiedSignal { 1156 + user_id: Uuid, 1157 + signal_username: String, 1158 + tx: Tx<()>, 1159 + }, 1160 + SetUnverifiedTelegram { 1161 + user_id: Uuid, 1162 + telegram_username: String, 1163 + tx: Tx<()>, 1164 + }, 1165 + StoreTelegramChatId { 1166 + telegram_username: String, 1167 + chat_id: i64, 1168 + handle: Option<String>, 1169 + tx: Tx<Option<Uuid>>, 1170 + }, 1171 + GetTelegramChatId { 1172 + user_id: Uuid, 1173 + tx: Tx<Option<i64>>, 1174 + }, 1175 + SetUnverifiedDiscord { 1176 + user_id: Uuid, 1177 + discord_username: String, 1178 + tx: Tx<()>, 1179 + }, 1180 + StoreDiscordUserId { 1181 + discord_username: String, 1182 + discord_id: String, 1183 + handle: Option<String>, 1184 + tx: Tx<Option<Uuid>>, 1185 + }, 1186 + GetVerificationInfo { 1187 + did: Did, 1188 + tx: Tx<Option<UserVerificationInfo>>, 1189 + }, 1190 + VerifyEmailChannel { 1191 + user_id: Uuid, 1192 + email: String, 1193 + tx: Tx<bool>, 1194 + }, 1195 + VerifyDiscordChannel { 1196 + user_id: Uuid, 1197 + discord_id: String, 1198 + tx: Tx<()>, 1199 + }, 1200 + VerifyTelegramChannel { 1201 + user_id: Uuid, 1202 + telegram_username: String, 1203 + tx: Tx<()>, 1204 + }, 1205 + VerifySignalChannel { 1206 + user_id: Uuid, 1207 + signal_username: String, 1208 + tx: Tx<()>, 1209 + }, 1210 + SetEmailVerifiedFlag { 1211 + user_id: Uuid, 1212 + tx: Tx<()>, 1213 + }, 1214 + SetDiscordVerifiedFlag { 1215 + user_id: Uuid, 1216 + tx: Tx<()>, 1217 + }, 1218 + SetTelegramVerifiedFlag { 1219 + user_id: Uuid, 1220 + tx: Tx<()>, 1221 + }, 1222 + SetSignalVerifiedFlag { 1223 + user_id: Uuid, 1224 + tx: Tx<()>, 1225 + }, 1226 + HasTotpEnabled { 1227 + did: Did, 1228 + tx: Tx<bool>, 1229 + }, 1230 + HasPasskeys { 1231 + did: Did, 1232 + tx: Tx<bool>, 1233 + }, 1234 + GetPasswordHashByDid { 1235 + did: Did, 1236 + tx: Tx<Option<String>>, 1237 + }, 1238 + GetPasskeysForUser { 1239 + did: Did, 1240 + tx: Tx<Vec<StoredPasskey>>, 1241 + }, 1242 + GetPasskeyByCredentialId { 1243 + credential_id: Vec<u8>, 1244 + tx: Tx<Option<StoredPasskey>>, 1245 + }, 1246 + SavePasskey { 1247 + did: Did, 1248 + credential_id: Vec<u8>, 1249 + public_key: Vec<u8>, 1250 + friendly_name: Option<String>, 1251 + tx: Tx<Uuid>, 1252 + }, 1253 + UpdatePasskeyCounter { 1254 + credential_id: Vec<u8>, 1255 + new_counter: i32, 1256 + tx: Tx<bool>, 1257 + }, 1258 + DeletePasskey { 1259 + id: Uuid, 1260 + did: Did, 1261 + tx: Tx<bool>, 1262 + }, 1263 + UpdatePasskeyName { 1264 + id: Uuid, 1265 + did: Did, 1266 + name: String, 1267 + tx: Tx<bool>, 1268 + }, 1269 + SaveWebauthnChallenge { 1270 + did: Did, 1271 + challenge_type: WebauthnChallengeType, 1272 + state_json: String, 1273 + tx: Tx<Uuid>, 1274 + }, 1275 + LoadWebauthnChallenge { 1276 + did: Did, 1277 + challenge_type: WebauthnChallengeType, 1278 + tx: Tx<Option<String>>, 1279 + }, 1280 + DeleteWebauthnChallenge { 1281 + did: Did, 1282 + challenge_type: WebauthnChallengeType, 1283 + tx: Tx<()>, 1284 + }, 1285 + GetTotpRecord { 1286 + did: Did, 1287 + tx: Tx<Option<TotpRecord>>, 1288 + }, 1289 + GetTotpRecordState { 1290 + did: Did, 1291 + tx: Tx<Option<TotpRecordState>>, 1292 + }, 1293 + UpsertTotpSecret { 1294 + did: Did, 1295 + secret_encrypted: Vec<u8>, 1296 + encryption_version: i32, 1297 + tx: Tx<()>, 1298 + }, 1299 + SetTotpVerified { 1300 + did: Did, 1301 + tx: Tx<()>, 1302 + }, 1303 + UpdateTotpLastUsed { 1304 + did: Did, 1305 + tx: Tx<()>, 1306 + }, 1307 + DeleteTotp { 1308 + did: Did, 1309 + tx: Tx<()>, 1310 + }, 1311 + GetUnusedBackupCodes { 1312 + did: Did, 1313 + tx: Tx<Vec<StoredBackupCode>>, 1314 + }, 1315 + MarkBackupCodeUsed { 1316 + code_id: Uuid, 1317 + tx: Tx<bool>, 1318 + }, 1319 + CountUnusedBackupCodes { 1320 + did: Did, 1321 + tx: Tx<i64>, 1322 + }, 1323 + DeleteBackupCodes { 1324 + did: Did, 1325 + tx: Tx<u64>, 1326 + }, 1327 + InsertBackupCodes { 1328 + did: Did, 1329 + code_hashes: Vec<String>, 1330 + tx: Tx<()>, 1331 + }, 1332 + EnableTotpWithBackupCodes { 1333 + did: Did, 1334 + code_hashes: Vec<String>, 1335 + tx: Tx<()>, 1336 + }, 1337 + DeleteTotpAndBackupCodes { 1338 + did: Did, 1339 + tx: Tx<()>, 1340 + }, 1341 + ReplaceBackupCodes { 1342 + did: Did, 1343 + code_hashes: Vec<String>, 1344 + tx: Tx<()>, 1345 + }, 1346 + GetSessionInfoByDid { 1347 + did: Did, 1348 + tx: Tx<Option<UserSessionInfo>>, 1349 + }, 1350 + GetLegacyLoginPref { 1351 + did: Did, 1352 + tx: Tx<Option<UserLegacyLoginPref>>, 1353 + }, 1354 + UpdateLegacyLogin { 1355 + did: Did, 1356 + allow: bool, 1357 + tx: Tx<bool>, 1358 + }, 1359 + UpdateLocale { 1360 + did: Did, 1361 + locale: String, 1362 + tx: Tx<bool>, 1363 + }, 1364 + GetLoginFullByIdentifier { 1365 + identifier: String, 1366 + tx: Tx<Option<UserLoginFull>>, 1367 + }, 1368 + GetConfirmSignupByDid { 1369 + did: Did, 1370 + tx: Tx<Option<UserConfirmSignup>>, 1371 + }, 1372 + GetResendVerificationByDid { 1373 + did: Did, 1374 + tx: Tx<Option<UserResendVerification>>, 1375 + }, 1376 + SetChannelVerified { 1377 + did: Did, 1378 + channel: CommsChannel, 1379 + tx: Tx<()>, 1380 + }, 1381 + GetIdByEmailOrHandle { 1382 + email: String, 1383 + handle: String, 1384 + tx: Tx<Option<Uuid>>, 1385 + }, 1386 + CountAccountsByEmail { 1387 + email: String, 1388 + tx: Tx<i64>, 1389 + }, 1390 + GetHandlesByEmail { 1391 + email: String, 1392 + tx: Tx<Vec<Handle>>, 1393 + }, 1394 + SetPasswordResetCode { 1395 + user_id: Uuid, 1396 + code: String, 1397 + expires_at: DateTime<Utc>, 1398 + tx: Tx<()>, 1399 + }, 1400 + GetUserByResetCode { 1401 + code: String, 1402 + tx: Tx<Option<UserResetCodeInfo>>, 1403 + }, 1404 + ClearPasswordResetCode { 1405 + user_id: Uuid, 1406 + tx: Tx<()>, 1407 + }, 1408 + GetIdAndPasswordHashByDid { 1409 + did: Did, 1410 + tx: Tx<Option<UserIdAndPasswordHash>>, 1411 + }, 1412 + UpdatePasswordHash { 1413 + user_id: Uuid, 1414 + password_hash: String, 1415 + tx: Tx<()>, 1416 + }, 1417 + ResetPasswordWithSessions { 1418 + user_id: Uuid, 1419 + password_hash: String, 1420 + tx: Tx<PasswordResetResult>, 1421 + }, 1422 + ActivateAccount { 1423 + did: Did, 1424 + tx: Tx<bool>, 1425 + }, 1426 + DeactivateAccount { 1427 + did: Did, 1428 + delete_after: Option<DateTime<Utc>>, 1429 + tx: Tx<bool>, 1430 + }, 1431 + HasPasswordByDid { 1432 + did: Did, 1433 + tx: Tx<Option<bool>>, 1434 + }, 1435 + GetPasswordInfoByDid { 1436 + did: Did, 1437 + tx: Tx<Option<UserPasswordInfo>>, 1438 + }, 1439 + RemoveUserPassword { 1440 + user_id: Uuid, 1441 + tx: Tx<()>, 1442 + }, 1443 + SetNewUserPassword { 1444 + user_id: Uuid, 1445 + password_hash: String, 1446 + tx: Tx<()>, 1447 + }, 1448 + GetUserKeyByDid { 1449 + did: Did, 1450 + tx: Tx<Option<UserKeyInfo>>, 1451 + }, 1452 + DeleteAccountComplete { 1453 + user_id: Uuid, 1454 + did: Did, 1455 + tx: Tx<()>, 1456 + }, 1457 + SetUserTakedown { 1458 + did: Did, 1459 + takedown_ref: Option<String>, 1460 + tx: Tx<bool>, 1461 + }, 1462 + AdminDeleteAccountComplete { 1463 + user_id: Uuid, 1464 + did: Did, 1465 + tx: Tx<()>, 1466 + }, 1467 + GetUserForDidDoc { 1468 + did: Did, 1469 + tx: Tx<Option<UserForDidDoc>>, 1470 + }, 1471 + GetUserForDidDocBuild { 1472 + did: Did, 1473 + tx: Tx<Option<UserForDidDocBuild>>, 1474 + }, 1475 + UpsertDidWebOverrides { 1476 + user_id: Uuid, 1477 + verification_methods: Option<serde_json::Value>, 1478 + also_known_as: Option<Vec<String>>, 1479 + tx: Tx<()>, 1480 + }, 1481 + UpdateMigratedToPds { 1482 + did: Did, 1483 + endpoint: String, 1484 + tx: Tx<()>, 1485 + }, 1486 + GetUserForPasskeySetup { 1487 + did: Did, 1488 + tx: Tx<Option<UserForPasskeySetup>>, 1489 + }, 1490 + GetUserForPasskeyRecovery { 1491 + identifier: String, 1492 + normalized_handle: String, 1493 + tx: Tx<Option<UserForPasskeyRecovery>>, 1494 + }, 1495 + SetRecoveryToken { 1496 + did: Did, 1497 + token_hash: String, 1498 + expires_at: DateTime<Utc>, 1499 + tx: Tx<()>, 1500 + }, 1501 + GetUserForRecovery { 1502 + did: Did, 1503 + tx: Tx<Option<UserForRecovery>>, 1504 + }, 1505 + GetAccountsScheduledForDeletion { 1506 + limit: i64, 1507 + tx: Tx<Vec<ScheduledDeletionAccount>>, 1508 + }, 1509 + DeleteAccountWithFirehose { 1510 + user_id: Uuid, 1511 + did: Did, 1512 + tx: Tx<i64>, 1513 + }, 1514 + CreatePasswordAccount { 1515 + input: CreatePasswordAccountInput, 1516 + tx: oneshot::Sender<Result<CreatePasswordAccountResult, CreateAccountError>>, 1517 + }, 1518 + CreateDelegatedAccount { 1519 + input: CreateDelegatedAccountInput, 1520 + tx: oneshot::Sender<Result<Uuid, CreateAccountError>>, 1521 + }, 1522 + CreatePasskeyAccount { 1523 + input: CreatePasskeyAccountInput, 1524 + tx: oneshot::Sender<Result<CreatePasswordAccountResult, CreateAccountError>>, 1525 + }, 1526 + CreateSsoAccount { 1527 + input: CreateSsoAccountInput, 1528 + tx: oneshot::Sender<Result<CreatePasswordAccountResult, CreateAccountError>>, 1529 + }, 1530 + ReactivateMigrationAccount { 1531 + input: MigrationReactivationInput, 1532 + tx: oneshot::Sender<Result<ReactivatedAccountInfo, MigrationReactivationError>>, 1533 + }, 1534 + CheckHandleAvailableForNewAccount { 1535 + handle: Handle, 1536 + tx: Tx<bool>, 1537 + }, 1538 + ReserveHandle { 1539 + handle: Handle, 1540 + reserved_by: String, 1541 + tx: Tx<bool>, 1542 + }, 1543 + ReleaseHandleReservation { 1544 + handle: Handle, 1545 + tx: Tx<()>, 1546 + }, 1547 + CleanupExpiredHandleReservations { 1548 + tx: Tx<u64>, 1549 + }, 1550 + CheckAndConsumeInviteCode { 1551 + code: String, 1552 + tx: Tx<bool>, 1553 + }, 1554 + CompletePasskeySetup { 1555 + input: CompletePasskeySetupInput, 1556 + tx: Tx<()>, 1557 + }, 1558 + RecoverPasskeyAccount { 1559 + input: RecoverPasskeyAccountInput, 1560 + tx: Tx<RecoverPasskeyAccountResult>, 1561 + }, 1562 + } 1563 + 1564 + impl UserRequest { 1565 + fn routing(&self, user_hashes: &UserHashMap) -> Routing { 1566 + match self { 1567 + Self::GetByDid { did, .. } 1568 + | Self::GetWithKeyByDid { did, .. } 1569 + | Self::GetStatusByDid { did, .. } 1570 + | Self::GetSessionAccessExpiry { did, .. } 1571 + | Self::GetUserInfoByDid { did, .. } 1572 + | Self::SetInvitesDisabled { did, .. } 1573 + | Self::GetAuthInfoByDid { did, .. } 1574 + | Self::Get2faStatusByDid { did, .. } 1575 + | Self::GetIdByDid { did, .. } 1576 + | Self::GetIdAndHandleByDid { did, .. } 1577 + | Self::GetHandleByDid { did, .. } 1578 + | Self::IsAccountActiveByDid { did, .. } 1579 + | Self::GetUserForDeletion { did, .. } 1580 + | Self::GetUserWithKeyByDid { did, .. } 1581 + | Self::IsAccountMigrated { did, .. } 1582 + | Self::HasVerifiedCommsChannel { did, .. } 1583 + | Self::GetEmailInfoByDid { did, .. } 1584 + | Self::CheckChannelVerifiedByDid { did, .. } 1585 + | Self::AdminUpdateEmail { did, .. } 1586 + | Self::AdminUpdateHandle { did, .. } 1587 + | Self::AdminUpdatePassword { did, .. } 1588 + | Self::GetNotificationPrefs { did, .. } 1589 + | Self::GetIdHandleEmailByDid { did, .. } 1590 + | Self::UpdatePreferredCommsChannel { did, .. } 1591 + | Self::GetVerificationInfo { did, .. } 1592 + | Self::HasTotpEnabled { did, .. } 1593 + | Self::HasPasskeys { did, .. } 1594 + | Self::GetPasswordHashByDid { did, .. } 1595 + | Self::GetPasskeysForUser { did, .. } 1596 + | Self::SavePasskey { did, .. } 1597 + | Self::DeletePasskey { did, .. } 1598 + | Self::UpdatePasskeyName { did, .. } 1599 + | Self::SaveWebauthnChallenge { did, .. } 1600 + | Self::LoadWebauthnChallenge { did, .. } 1601 + | Self::DeleteWebauthnChallenge { did, .. } 1602 + | Self::GetTotpRecord { did, .. } 1603 + | Self::GetTotpRecordState { did, .. } 1604 + | Self::UpsertTotpSecret { did, .. } 1605 + | Self::SetTotpVerified { did, .. } 1606 + | Self::UpdateTotpLastUsed { did, .. } 1607 + | Self::DeleteTotp { did, .. } 1608 + | Self::GetUnusedBackupCodes { did, .. } 1609 + | Self::CountUnusedBackupCodes { did, .. } 1610 + | Self::DeleteBackupCodes { did, .. } 1611 + | Self::InsertBackupCodes { did, .. } 1612 + | Self::EnableTotpWithBackupCodes { did, .. } 1613 + | Self::DeleteTotpAndBackupCodes { did, .. } 1614 + | Self::ReplaceBackupCodes { did, .. } 1615 + | Self::GetSessionInfoByDid { did, .. } 1616 + | Self::GetLegacyLoginPref { did, .. } 1617 + | Self::UpdateLegacyLogin { did, .. } 1618 + | Self::UpdateLocale { did, .. } 1619 + | Self::GetConfirmSignupByDid { did, .. } 1620 + | Self::GetResendVerificationByDid { did, .. } 1621 + | Self::SetChannelVerified { did, .. } 1622 + | Self::GetIdAndPasswordHashByDid { did, .. } 1623 + | Self::ActivateAccount { did, .. } 1624 + | Self::DeactivateAccount { did, .. } 1625 + | Self::HasPasswordByDid { did, .. } 1626 + | Self::GetPasswordInfoByDid { did, .. } 1627 + | Self::GetUserKeyByDid { did, .. } 1628 + | Self::DeleteAccountComplete { did, .. } 1629 + | Self::SetUserTakedown { did, .. } 1630 + | Self::AdminDeleteAccountComplete { did, .. } 1631 + | Self::GetUserForDidDoc { did, .. } 1632 + | Self::GetUserForDidDocBuild { did, .. } 1633 + | Self::UpdateMigratedToPds { did, .. } 1634 + | Self::GetUserForPasskeySetup { did, .. } 1635 + | Self::GetUserForRecovery { did, .. } 1636 + | Self::DeleteAccountWithFirehose { did, .. } 1637 + | Self::CreatePasswordAccount { 1638 + input: CreatePasswordAccountInput { did, .. }, 1639 + .. 1640 + } 1641 + | Self::CreateDelegatedAccount { 1642 + input: CreateDelegatedAccountInput { did, .. }, 1643 + .. 1644 + } 1645 + | Self::CreatePasskeyAccount { 1646 + input: CreatePasskeyAccountInput { did, .. }, 1647 + .. 1648 + } 1649 + | Self::CreateSsoAccount { 1650 + input: CreateSsoAccountInput { did, .. }, 1651 + .. 1652 + } 1653 + | Self::ReactivateMigrationAccount { 1654 + input: MigrationReactivationInput { did, .. }, 1655 + .. 1656 + } 1657 + | Self::CompletePasskeySetup { 1658 + input: CompletePasskeySetupInput { did, .. }, 1659 + .. 1660 + } 1661 + | Self::RecoverPasskeyAccount { 1662 + input: RecoverPasskeyAccountInput { did, .. }, 1663 + .. 1664 + } 1665 + | Self::SetRecoveryToken { did, .. } => did_to_routing(did.as_str()), 1666 + 1667 + Self::GetCommsPrefs { user_id, .. } 1668 + | Self::GetUserKeyById { user_id, .. } 1669 + | Self::GetDidWebOverrides { user_id, .. } 1670 + | Self::UpdateHandle { user_id, .. } 1671 + | Self::UpdateEmail { user_id, .. } 1672 + | Self::SetEmailVerified { user_id, .. } 1673 + | Self::ClearDiscord { user_id, .. } 1674 + | Self::ClearTelegram { user_id, .. } 1675 + | Self::ClearSignal { user_id, .. } 1676 + | Self::SetUnverifiedSignal { user_id, .. } 1677 + | Self::SetUnverifiedTelegram { user_id, .. } 1678 + | Self::GetTelegramChatId { user_id, .. } 1679 + | Self::SetUnverifiedDiscord { user_id, .. } 1680 + | Self::VerifyEmailChannel { user_id, .. } 1681 + | Self::VerifyDiscordChannel { user_id, .. } 1682 + | Self::VerifyTelegramChannel { user_id, .. } 1683 + | Self::VerifySignalChannel { user_id, .. } 1684 + | Self::SetEmailVerifiedFlag { user_id, .. } 1685 + | Self::SetDiscordVerifiedFlag { user_id, .. } 1686 + | Self::SetTelegramVerifiedFlag { user_id, .. } 1687 + | Self::SetSignalVerifiedFlag { user_id, .. } 1688 + | Self::SetPasswordResetCode { user_id, .. } 1689 + | Self::ClearPasswordResetCode { user_id, .. } 1690 + | Self::UpdatePasswordHash { user_id, .. } 1691 + | Self::ResetPasswordWithSessions { user_id, .. } 1692 + | Self::RemoveUserPassword { user_id, .. } 1693 + | Self::SetNewUserPassword { user_id, .. } 1694 + | Self::UpsertDidWebOverrides { user_id, .. } => uuid_to_routing(user_hashes, user_id), 1695 + 1696 + Self::CheckHandleExists { 1697 + exclude_user_id, .. 1698 + } 1699 + | Self::CheckEmailExists { 1700 + exclude_user_id, .. 1701 + } => uuid_to_routing(user_hashes, exclude_user_id), 1702 + 1703 + Self::MarkBackupCodeUsed { .. } => Routing::Global, 1704 + 1705 + Self::GetByHandle { .. } 1706 + | Self::GetDidWebInfoByHandle { .. } 1707 + | Self::GetIdByHandle { .. } 1708 + | Self::CheckHandleAvailableForNewAccount { .. } 1709 + | Self::ReserveHandle { .. } 1710 + | Self::ReleaseHandleReservation { .. } => Routing::Global, 1711 + 1712 + Self::CountUsers { .. } 1713 + | Self::GetOAuthTokenWithUser { .. } 1714 + | Self::GetAnyAdminUserId { .. } 1715 + | Self::SearchAccounts { .. } 1716 + | Self::GetByEmail { .. } 1717 + | Self::GetLoginCheckByHandleOrEmail { .. } 1718 + | Self::GetLoginInfoByHandleOrEmail { .. } 1719 + | Self::CheckEmailVerifiedByIdentifier { .. } 1720 + | Self::StoreTelegramChatId { .. } 1721 + | Self::StoreDiscordUserId { .. } 1722 + | Self::GetPasskeyByCredentialId { .. } 1723 + | Self::UpdatePasskeyCounter { .. } 1724 + | Self::GetLoginFullByIdentifier { .. } 1725 + | Self::GetIdByEmailOrHandle { .. } 1726 + | Self::CountAccountsByEmail { .. } 1727 + | Self::GetHandlesByEmail { .. } 1728 + | Self::GetUserByResetCode { .. } 1729 + | Self::GetUserForPasskeyRecovery { .. } 1730 + | Self::GetAccountsScheduledForDeletion { .. } 1731 + | Self::CleanupExpiredHandleReservations { .. } 1732 + | Self::CheckAndConsumeInviteCode { .. } => Routing::Global, 1733 + } 1734 + } 1735 + } 1736 + 1737 + pub enum InfraRequest { 1738 + EnqueueComms { 1739 + user_id: Option<Uuid>, 1740 + channel: CommsChannel, 1741 + comms_type: CommsType, 1742 + recipient: String, 1743 + subject: Option<String>, 1744 + body: String, 1745 + metadata: Option<serde_json::Value>, 1746 + tx: Tx<Uuid>, 1747 + }, 1748 + FetchPendingComms { 1749 + now: DateTime<Utc>, 1750 + batch_size: i64, 1751 + tx: Tx<Vec<QueuedComms>>, 1752 + }, 1753 + MarkCommsSent { 1754 + id: Uuid, 1755 + tx: Tx<()>, 1756 + }, 1757 + MarkCommsFailed { 1758 + id: Uuid, 1759 + error: String, 1760 + tx: Tx<()>, 1761 + }, 1762 + CreateInviteCode { 1763 + code: String, 1764 + use_count: i32, 1765 + for_account: Option<Did>, 1766 + tx: Tx<bool>, 1767 + }, 1768 + CreateInviteCodesBatch { 1769 + codes: Vec<String>, 1770 + use_count: i32, 1771 + created_by_user: Uuid, 1772 + for_account: Option<Did>, 1773 + tx: Tx<()>, 1774 + }, 1775 + GetInviteCodeAvailableUses { 1776 + code: String, 1777 + tx: Tx<Option<i32>>, 1778 + }, 1779 + ValidateInviteCode { 1780 + code: String, 1781 + tx: oneshot::Sender<Result<(), InviteCodeError>>, 1782 + }, 1783 + DecrementInviteCodeUses { 1784 + code: String, 1785 + tx: Tx<()>, 1786 + }, 1787 + RecordInviteCodeUse { 1788 + code: String, 1789 + used_by_user: Uuid, 1790 + tx: Tx<()>, 1791 + }, 1792 + GetInviteCodesForAccount { 1793 + for_account: Did, 1794 + tx: Tx<Vec<InviteCodeInfo>>, 1795 + }, 1796 + GetInviteCodeUses { 1797 + code: String, 1798 + tx: Tx<Vec<InviteCodeUse>>, 1799 + }, 1800 + DisableInviteCodesByCode { 1801 + codes: Vec<String>, 1802 + tx: Tx<()>, 1803 + }, 1804 + DisableInviteCodesByAccount { 1805 + accounts: Vec<Did>, 1806 + tx: Tx<()>, 1807 + }, 1808 + ListInviteCodes { 1809 + cursor: Option<String>, 1810 + limit: i64, 1811 + sort: InviteCodeSortOrder, 1812 + tx: Tx<Vec<InviteCodeRow>>, 1813 + }, 1814 + GetUserDidsByIds { 1815 + user_ids: Vec<Uuid>, 1816 + tx: Tx<Vec<(Uuid, Did)>>, 1817 + }, 1818 + GetInviteCodeUsesBatch { 1819 + codes: Vec<String>, 1820 + tx: Tx<Vec<InviteCodeUse>>, 1821 + }, 1822 + GetInvitesCreatedByUser { 1823 + user_id: Uuid, 1824 + tx: Tx<Vec<InviteCodeInfo>>, 1825 + }, 1826 + GetInviteCodeInfo { 1827 + code: String, 1828 + tx: Tx<Option<InviteCodeInfo>>, 1829 + }, 1830 + GetInviteCodesByUsers { 1831 + user_ids: Vec<Uuid>, 1832 + tx: Tx<Vec<(Uuid, InviteCodeInfo)>>, 1833 + }, 1834 + GetInviteCodeUsedByUser { 1835 + user_id: Uuid, 1836 + tx: Tx<Option<String>>, 1837 + }, 1838 + DeleteInviteCodeUsesByUser { 1839 + user_id: Uuid, 1840 + tx: Tx<()>, 1841 + }, 1842 + DeleteInviteCodesByUser { 1843 + user_id: Uuid, 1844 + tx: Tx<()>, 1845 + }, 1846 + ReserveSigningKey { 1847 + did: Option<Did>, 1848 + public_key_did_key: String, 1849 + private_key_bytes: Vec<u8>, 1850 + expires_at: DateTime<Utc>, 1851 + tx: Tx<Uuid>, 1852 + }, 1853 + GetReservedSigningKey { 1854 + public_key_did_key: String, 1855 + tx: Tx<Option<ReservedSigningKey>>, 1856 + }, 1857 + MarkSigningKeyUsed { 1858 + key_id: Uuid, 1859 + tx: Tx<()>, 1860 + }, 1861 + CreateDeletionRequest { 1862 + token: String, 1863 + did: Did, 1864 + expires_at: DateTime<Utc>, 1865 + tx: Tx<()>, 1866 + }, 1867 + GetDeletionRequest { 1868 + token: String, 1869 + tx: Tx<Option<DeletionRequest>>, 1870 + }, 1871 + DeleteDeletionRequest { 1872 + token: String, 1873 + tx: Tx<()>, 1874 + }, 1875 + DeleteDeletionRequestsByDid { 1876 + did: Did, 1877 + tx: Tx<()>, 1878 + }, 1879 + UpsertAccountPreference { 1880 + user_id: Uuid, 1881 + name: String, 1882 + value_json: serde_json::Value, 1883 + tx: Tx<()>, 1884 + }, 1885 + InsertAccountPreferenceIfNotExists { 1886 + user_id: Uuid, 1887 + name: String, 1888 + value_json: serde_json::Value, 1889 + tx: Tx<()>, 1890 + }, 1891 + GetServerConfig { 1892 + key: String, 1893 + tx: Tx<Option<String>>, 1894 + }, 1895 + InsertReport { 1896 + id: i64, 1897 + reason_type: String, 1898 + reason: Option<String>, 1899 + subject_json: serde_json::Value, 1900 + reported_by_did: Did, 1901 + created_at: DateTime<Utc>, 1902 + tx: Tx<()>, 1903 + }, 1904 + DeletePlcTokensForUser { 1905 + user_id: Uuid, 1906 + tx: Tx<()>, 1907 + }, 1908 + InsertPlcToken { 1909 + user_id: Uuid, 1910 + token: String, 1911 + expires_at: DateTime<Utc>, 1912 + tx: Tx<()>, 1913 + }, 1914 + GetPlcTokenExpiry { 1915 + user_id: Uuid, 1916 + token: String, 1917 + tx: Tx<Option<DateTime<Utc>>>, 1918 + }, 1919 + DeletePlcToken { 1920 + user_id: Uuid, 1921 + token: String, 1922 + tx: Tx<()>, 1923 + }, 1924 + GetAccountPreferences { 1925 + user_id: Uuid, 1926 + tx: Tx<Vec<(String, serde_json::Value)>>, 1927 + }, 1928 + ReplaceNamespacePreferences { 1929 + user_id: Uuid, 1930 + namespace: String, 1931 + preferences: Vec<(String, serde_json::Value)>, 1932 + tx: Tx<()>, 1933 + }, 1934 + GetNotificationHistory { 1935 + user_id: Uuid, 1936 + limit: i64, 1937 + tx: Tx<Vec<NotificationHistoryRow>>, 1938 + }, 1939 + GetServerConfigs { 1940 + keys: Vec<String>, 1941 + tx: Tx<Vec<(String, String)>>, 1942 + }, 1943 + UpsertServerConfig { 1944 + key: String, 1945 + value: String, 1946 + tx: Tx<()>, 1947 + }, 1948 + DeleteServerConfig { 1949 + key: String, 1950 + tx: Tx<()>, 1951 + }, 1952 + GetBlobStorageKeyByCid { 1953 + cid: CidLink, 1954 + tx: Tx<Option<String>>, 1955 + }, 1956 + DeleteBlobByCid { 1957 + cid: CidLink, 1958 + tx: Tx<()>, 1959 + }, 1960 + GetAdminAccountInfoByDid { 1961 + did: Did, 1962 + tx: Tx<Option<AdminAccountInfo>>, 1963 + }, 1964 + GetAdminAccountInfosByDids { 1965 + dids: Vec<Did>, 1966 + tx: Tx<Vec<AdminAccountInfo>>, 1967 + }, 1968 + GetInviteCodeUsesByUsers { 1969 + user_ids: Vec<Uuid>, 1970 + tx: Tx<Vec<(Uuid, String)>>, 1971 + }, 1972 + } 1973 + 1974 + impl InfraRequest { 1975 + fn routing(&self, user_hashes: &UserHashMap) -> Routing { 1976 + match self { 1977 + Self::UpsertAccountPreference { user_id, .. } 1978 + | Self::InsertAccountPreferenceIfNotExists { user_id, .. } 1979 + | Self::GetAccountPreferences { user_id, .. } 1980 + | Self::ReplaceNamespacePreferences { user_id, .. } 1981 + | Self::GetNotificationHistory { user_id, .. } 1982 + | Self::DeletePlcTokensForUser { user_id, .. } 1983 + | Self::InsertPlcToken { user_id, .. } 1984 + | Self::GetPlcTokenExpiry { user_id, .. } 1985 + | Self::DeletePlcToken { user_id, .. } 1986 + | Self::GetInvitesCreatedByUser { user_id, .. } 1987 + | Self::GetInviteCodeUsedByUser { user_id, .. } 1988 + | Self::DeleteInviteCodeUsesByUser { user_id, .. } 1989 + | Self::DeleteInviteCodesByUser { user_id, .. } => { 1990 + uuid_to_routing(user_hashes, user_id) 1991 + } 1992 + Self::CreateInviteCodesBatch { 1993 + created_by_user, .. 1994 + } => uuid_to_routing(user_hashes, created_by_user), 1995 + Self::RecordInviteCodeUse { used_by_user, .. } => { 1996 + uuid_to_routing(user_hashes, used_by_user) 1997 + } 1998 + Self::EnqueueComms { 1999 + user_id: Some(uid), .. 2000 + } => uuid_to_routing(user_hashes, uid), 2001 + Self::GetInviteCodesForAccount { for_account, .. } => { 2002 + did_to_routing(for_account.as_str()) 2003 + } 2004 + Self::DeleteDeletionRequestsByDid { did, .. } 2005 + | Self::CreateDeletionRequest { did, .. } 2006 + | Self::GetAdminAccountInfoByDid { did, .. } => did_to_routing(did.as_str()), 2007 + Self::GetBlobStorageKeyByCid { cid, .. } | Self::DeleteBlobByCid { cid, .. } => { 2008 + cid_to_routing(cid) 2009 + } 2010 + _ => Routing::Global, 2011 + } 2012 + } 2013 + } 2014 + 2015 + pub enum OAuthRequest { 2016 + CreateToken { 2017 + data: TokenData, 2018 + tx: Tx<TokenFamilyId>, 2019 + }, 2020 + GetTokenById { 2021 + token_id: TokenId, 2022 + tx: Tx<Option<TokenData>>, 2023 + }, 2024 + GetTokenByRefreshToken { 2025 + refresh_token: RefreshToken, 2026 + tx: Tx<Option<(TokenFamilyId, TokenData)>>, 2027 + }, 2028 + GetTokenByPreviousRefreshToken { 2029 + refresh_token: RefreshToken, 2030 + tx: Tx<Option<(TokenFamilyId, TokenData)>>, 2031 + }, 2032 + RotateToken { 2033 + old_db_id: TokenFamilyId, 2034 + new_refresh_token: RefreshToken, 2035 + new_expires_at: DateTime<Utc>, 2036 + tx: Tx<()>, 2037 + }, 2038 + CheckRefreshTokenUsed { 2039 + refresh_token: RefreshToken, 2040 + tx: Tx<Option<TokenFamilyId>>, 2041 + }, 2042 + DeleteToken { 2043 + token_id: TokenId, 2044 + tx: Tx<()>, 2045 + }, 2046 + DeleteTokenFamily { 2047 + db_id: TokenFamilyId, 2048 + tx: Tx<()>, 2049 + }, 2050 + ListTokensForUser { 2051 + did: Did, 2052 + tx: Tx<Vec<TokenData>>, 2053 + }, 2054 + CountTokensForUser { 2055 + did: Did, 2056 + tx: Tx<i64>, 2057 + }, 2058 + DeleteOldestTokensForUser { 2059 + did: Did, 2060 + keep_count: i64, 2061 + tx: Tx<u64>, 2062 + }, 2063 + RevokeTokensForClient { 2064 + did: Did, 2065 + client_id: ClientId, 2066 + tx: Tx<u64>, 2067 + }, 2068 + RevokeTokensForController { 2069 + delegated_did: Did, 2070 + controller_did: Did, 2071 + tx: Tx<u64>, 2072 + }, 2073 + CreateAuthorizationRequest { 2074 + request_id: RequestId, 2075 + data: RequestData, 2076 + tx: Tx<()>, 2077 + }, 2078 + GetAuthorizationRequest { 2079 + request_id: RequestId, 2080 + tx: Tx<Option<RequestData>>, 2081 + }, 2082 + SetAuthorizationDid { 2083 + request_id: RequestId, 2084 + did: Did, 2085 + device_id: Option<DeviceId>, 2086 + tx: Tx<()>, 2087 + }, 2088 + UpdateAuthorizationRequest { 2089 + request_id: RequestId, 2090 + did: Did, 2091 + device_id: Option<DeviceId>, 2092 + code: AuthorizationCode, 2093 + tx: Tx<()>, 2094 + }, 2095 + ConsumeAuthorizationRequestByCode { 2096 + code: AuthorizationCode, 2097 + tx: Tx<Option<RequestData>>, 2098 + }, 2099 + DeleteAuthorizationRequest { 2100 + request_id: RequestId, 2101 + tx: Tx<()>, 2102 + }, 2103 + DeleteExpiredAuthorizationRequests { 2104 + tx: Tx<u64>, 2105 + }, 2106 + ExtendAuthorizationRequestExpiry { 2107 + request_id: RequestId, 2108 + new_expires_at: DateTime<Utc>, 2109 + tx: Tx<bool>, 2110 + }, 2111 + MarkRequestAuthenticated { 2112 + request_id: RequestId, 2113 + did: Did, 2114 + device_id: Option<DeviceId>, 2115 + tx: Tx<()>, 2116 + }, 2117 + UpdateRequestScope { 2118 + request_id: RequestId, 2119 + scope: String, 2120 + tx: Tx<()>, 2121 + }, 2122 + SetControllerDid { 2123 + request_id: RequestId, 2124 + controller_did: Did, 2125 + tx: Tx<()>, 2126 + }, 2127 + SetRequestDid { 2128 + request_id: RequestId, 2129 + did: Did, 2130 + tx: Tx<()>, 2131 + }, 2132 + CreateDevice { 2133 + device_id: DeviceId, 2134 + data: DeviceData, 2135 + tx: Tx<()>, 2136 + }, 2137 + GetDevice { 2138 + device_id: DeviceId, 2139 + tx: Tx<Option<DeviceData>>, 2140 + }, 2141 + UpdateDeviceLastSeen { 2142 + device_id: DeviceId, 2143 + tx: Tx<()>, 2144 + }, 2145 + DeleteDevice { 2146 + device_id: DeviceId, 2147 + tx: Tx<()>, 2148 + }, 2149 + UpsertAccountDevice { 2150 + did: Did, 2151 + device_id: DeviceId, 2152 + tx: Tx<()>, 2153 + }, 2154 + GetDeviceAccounts { 2155 + device_id: DeviceId, 2156 + tx: Tx<Vec<tranquil_db_traits::DeviceAccountRow>>, 2157 + }, 2158 + VerifyAccountOnDevice { 2159 + device_id: DeviceId, 2160 + did: Did, 2161 + tx: Tx<bool>, 2162 + }, 2163 + CheckAndRecordDpopJti { 2164 + jti: DPoPProofId, 2165 + tx: Tx<bool>, 2166 + }, 2167 + CleanupExpiredDpopJtis { 2168 + max_age_secs: i64, 2169 + tx: Tx<u64>, 2170 + }, 2171 + Create2faChallenge { 2172 + did: Did, 2173 + request_uri: RequestId, 2174 + tx: Tx<tranquil_db_traits::TwoFactorChallenge>, 2175 + }, 2176 + Get2faChallenge { 2177 + request_uri: RequestId, 2178 + tx: Tx<Option<tranquil_db_traits::TwoFactorChallenge>>, 2179 + }, 2180 + Increment2faAttempts { 2181 + id: Uuid, 2182 + tx: Tx<i32>, 2183 + }, 2184 + Delete2faChallenge { 2185 + id: Uuid, 2186 + tx: Tx<()>, 2187 + }, 2188 + Delete2faChallengeByRequestUri { 2189 + request_uri: RequestId, 2190 + tx: Tx<()>, 2191 + }, 2192 + CleanupExpired2faChallenges { 2193 + tx: Tx<u64>, 2194 + }, 2195 + CheckUser2faEnabled { 2196 + did: Did, 2197 + tx: Tx<bool>, 2198 + }, 2199 + GetScopePreferences { 2200 + did: Did, 2201 + client_id: ClientId, 2202 + tx: Tx<Vec<ScopePreference>>, 2203 + }, 2204 + UpsertScopePreferences { 2205 + did: Did, 2206 + client_id: ClientId, 2207 + prefs: Vec<ScopePreference>, 2208 + tx: Tx<()>, 2209 + }, 2210 + DeleteScopePreferences { 2211 + did: Did, 2212 + client_id: ClientId, 2213 + tx: Tx<()>, 2214 + }, 2215 + UpsertAuthorizedClient { 2216 + did: Did, 2217 + client_id: ClientId, 2218 + data: AuthorizedClientData, 2219 + tx: Tx<()>, 2220 + }, 2221 + GetAuthorizedClient { 2222 + did: Did, 2223 + client_id: ClientId, 2224 + tx: Tx<Option<AuthorizedClientData>>, 2225 + }, 2226 + ListTrustedDevices { 2227 + did: Did, 2228 + tx: Tx<Vec<tranquil_db_traits::TrustedDeviceRow>>, 2229 + }, 2230 + GetDeviceTrustInfo { 2231 + device_id: DeviceId, 2232 + did: Did, 2233 + tx: Tx<Option<tranquil_db_traits::DeviceTrustInfo>>, 2234 + }, 2235 + DeviceBelongsToUser { 2236 + device_id: DeviceId, 2237 + did: Did, 2238 + tx: Tx<bool>, 2239 + }, 2240 + RevokeDeviceTrust { 2241 + device_id: DeviceId, 2242 + did: Did, 2243 + tx: Tx<()>, 2244 + }, 2245 + UpdateDeviceFriendlyName { 2246 + device_id: DeviceId, 2247 + did: Did, 2248 + friendly_name: Option<String>, 2249 + tx: Tx<()>, 2250 + }, 2251 + TrustDevice { 2252 + device_id: DeviceId, 2253 + did: Did, 2254 + trusted_at: DateTime<Utc>, 2255 + trusted_until: DateTime<Utc>, 2256 + tx: Tx<()>, 2257 + }, 2258 + ExtendDeviceTrust { 2259 + device_id: DeviceId, 2260 + did: Did, 2261 + trusted_until: DateTime<Utc>, 2262 + tx: Tx<()>, 2263 + }, 2264 + ListSessionsByDid { 2265 + did: Did, 2266 + tx: Tx<Vec<tranquil_db_traits::OAuthSessionListItem>>, 2267 + }, 2268 + DeleteSessionById { 2269 + session_id: TokenFamilyId, 2270 + did: Did, 2271 + tx: Tx<u64>, 2272 + }, 2273 + DeleteSessionsByDid { 2274 + did: Did, 2275 + tx: Tx<u64>, 2276 + }, 2277 + DeleteSessionsByDidExcept { 2278 + did: Did, 2279 + except_token_id: TokenId, 2280 + tx: Tx<u64>, 2281 + }, 2282 + } 2283 + 2284 + impl OAuthRequest { 2285 + fn routing(&self) -> Routing { 2286 + match self { 2287 + Self::ListTokensForUser { did, .. } 2288 + | Self::CountTokensForUser { did, .. } 2289 + | Self::DeleteOldestTokensForUser { did, .. } 2290 + | Self::RevokeTokensForClient { did, .. } 2291 + | Self::Create2faChallenge { did, .. } 2292 + | Self::CheckUser2faEnabled { did, .. } 2293 + | Self::GetScopePreferences { did, .. } 2294 + | Self::UpsertScopePreferences { did, .. } 2295 + | Self::DeleteScopePreferences { did, .. } 2296 + | Self::UpsertAuthorizedClient { did, .. } 2297 + | Self::GetAuthorizedClient { did, .. } 2298 + | Self::ListTrustedDevices { did, .. } 2299 + | Self::GetDeviceTrustInfo { did, .. } 2300 + | Self::DeviceBelongsToUser { did, .. } 2301 + | Self::UpsertAccountDevice { did, .. } 2302 + | Self::VerifyAccountOnDevice { did, .. } 2303 + | Self::ListSessionsByDid { did, .. } 2304 + | Self::DeleteSessionsByDid { did, .. } 2305 + | Self::DeleteSessionsByDidExcept { did, .. } 2306 + | Self::DeleteSessionById { did, .. } => did_to_routing(did.as_str()), 2307 + Self::RevokeTokensForController { delegated_did, .. } => { 2308 + did_to_routing(delegated_did.as_str()) 2309 + } 2310 + Self::SetAuthorizationDid { did, .. } 2311 + | Self::UpdateAuthorizationRequest { did, .. } 2312 + | Self::MarkRequestAuthenticated { did, .. } 2313 + | Self::SetRequestDid { did, .. } => did_to_routing(did.as_str()), 2314 + Self::CreateToken { .. } 2315 + | Self::GetTokenById { .. } 2316 + | Self::GetTokenByRefreshToken { .. } 2317 + | Self::GetTokenByPreviousRefreshToken { .. } 2318 + | Self::RotateToken { .. } 2319 + | Self::CheckRefreshTokenUsed { .. } 2320 + | Self::DeleteToken { .. } 2321 + | Self::DeleteTokenFamily { .. } 2322 + | Self::CreateAuthorizationRequest { .. } 2323 + | Self::GetAuthorizationRequest { .. } 2324 + | Self::ConsumeAuthorizationRequestByCode { .. } 2325 + | Self::DeleteAuthorizationRequest { .. } 2326 + | Self::DeleteExpiredAuthorizationRequests { .. } 2327 + | Self::ExtendAuthorizationRequestExpiry { .. } 2328 + | Self::UpdateRequestScope { .. } 2329 + | Self::SetControllerDid { .. } 2330 + | Self::CreateDevice { .. } 2331 + | Self::GetDevice { .. } 2332 + | Self::UpdateDeviceLastSeen { .. } 2333 + | Self::DeleteDevice { .. } 2334 + | Self::GetDeviceAccounts { .. } 2335 + | Self::CheckAndRecordDpopJti { .. } 2336 + | Self::CleanupExpiredDpopJtis { .. } 2337 + | Self::Get2faChallenge { .. } 2338 + | Self::Increment2faAttempts { .. } 2339 + | Self::Delete2faChallenge { .. } 2340 + | Self::Delete2faChallengeByRequestUri { .. } 2341 + | Self::CleanupExpired2faChallenges { .. } 2342 + | Self::RevokeDeviceTrust { .. } 2343 + | Self::UpdateDeviceFriendlyName { .. } 2344 + | Self::TrustDevice { .. } 2345 + | Self::ExtendDeviceTrust { .. } => Routing::Global, 2346 + } 2347 + } 2348 + } 2349 + 583 2350 fn convert_repo_info(r: super::repo_ops::RepoInfo) -> tranquil_db_traits::RepoInfo { 584 2351 tranquil_db_traits::RepoInfo { 585 2352 user_id: r.user_id, ··· 928 2695 user_id: repo_id, 929 2696 collection: &collection, 930 2697 cursor: cursor.as_ref(), 931 - limit: usize::try_from(limit).unwrap_or(usize::MAX), 2698 + limit: usize::try_from(limit).unwrap_or(0), 932 2699 reverse, 933 2700 rkey_start: rkey_start.as_ref(), 934 2701 rkey_end: rkey_end.as_ref(), ··· 1348 3115 .list_blobs_by_user( 1349 3116 user_id, 1350 3117 cursor.as_deref(), 1351 - usize::try_from(limit).unwrap_or(usize::MAX), 3118 + usize::try_from(limit).unwrap_or(0), 1352 3119 ) 1353 3120 .map_err(metastore_to_db); 1354 3121 let _ = tx.send(result); ··· 1421 3188 .list_missing_blobs( 1422 3189 repo_id, 1423 3190 cursor.as_deref(), 1424 - usize::try_from(limit).unwrap_or(usize::MAX), 3191 + usize::try_from(limit).unwrap_or(0), 1425 3192 ) 1426 3193 .map_err(metastore_to_db); 1427 3194 let _ = tx.send(result); ··· 1445 3212 } 1446 3213 } 1447 3214 3215 + fn dispatch_delegation<S: StorageIO>(state: &HandlerState<S>, req: DelegationRequest) { 3216 + match req { 3217 + DelegationRequest::IsDelegatedAccount { did, tx } => { 3218 + let result = state 3219 + .metastore 3220 + .delegation_ops() 3221 + .is_delegated_account(&did) 3222 + .map_err(metastore_to_db); 3223 + let _ = tx.send(result); 3224 + } 3225 + DelegationRequest::CreateDelegation { 3226 + delegated_did, 3227 + controller_did, 3228 + granted_scopes, 3229 + granted_by, 3230 + tx, 3231 + } => { 3232 + let result = state 3233 + .metastore 3234 + .delegation_ops() 3235 + .create_delegation( 3236 + &delegated_did, 3237 + &controller_did, 3238 + &granted_scopes, 3239 + &granted_by, 3240 + ) 3241 + .map_err(metastore_to_db); 3242 + let _ = tx.send(result); 3243 + } 3244 + DelegationRequest::RevokeDelegation { 3245 + delegated_did, 3246 + controller_did, 3247 + revoked_by, 3248 + tx, 3249 + } => { 3250 + let result = state 3251 + .metastore 3252 + .delegation_ops() 3253 + .revoke_delegation(&delegated_did, &controller_did, &revoked_by) 3254 + .map_err(metastore_to_db); 3255 + let _ = tx.send(result); 3256 + } 3257 + DelegationRequest::UpdateDelegationScopes { 3258 + delegated_did, 3259 + controller_did, 3260 + new_scopes, 3261 + tx, 3262 + } => { 3263 + let result = state 3264 + .metastore 3265 + .delegation_ops() 3266 + .update_delegation_scopes(&delegated_did, &controller_did, &new_scopes) 3267 + .map_err(metastore_to_db); 3268 + let _ = tx.send(result); 3269 + } 3270 + DelegationRequest::GetDelegation { 3271 + delegated_did, 3272 + controller_did, 3273 + tx, 3274 + } => { 3275 + let result = state 3276 + .metastore 3277 + .delegation_ops() 3278 + .get_delegation(&delegated_did, &controller_did) 3279 + .map_err(metastore_to_db); 3280 + let _ = tx.send(result); 3281 + } 3282 + DelegationRequest::GetDelegationsForAccount { delegated_did, tx } => { 3283 + let result = state 3284 + .metastore 3285 + .delegation_ops() 3286 + .get_delegations_for_account(&delegated_did) 3287 + .map_err(metastore_to_db); 3288 + let _ = tx.send(result); 3289 + } 3290 + DelegationRequest::GetAccountsControlledBy { controller_did, tx } => { 3291 + let result = state 3292 + .metastore 3293 + .delegation_ops() 3294 + .get_accounts_controlled_by(&controller_did) 3295 + .map_err(metastore_to_db); 3296 + let _ = tx.send(result); 3297 + } 3298 + DelegationRequest::CountActiveControllers { delegated_did, tx } => { 3299 + let result = state 3300 + .metastore 3301 + .delegation_ops() 3302 + .count_active_controllers(&delegated_did) 3303 + .map_err(metastore_to_db); 3304 + let _ = tx.send(result); 3305 + } 3306 + DelegationRequest::ControlsAnyAccounts { did, tx } => { 3307 + let result = state 3308 + .metastore 3309 + .delegation_ops() 3310 + .controls_any_accounts(&did) 3311 + .map_err(metastore_to_db); 3312 + let _ = tx.send(result); 3313 + } 3314 + DelegationRequest::LogDelegationAction { 3315 + delegated_did, 3316 + actor_did, 3317 + controller_did, 3318 + action_type, 3319 + action_details, 3320 + ip_address, 3321 + user_agent, 3322 + tx, 3323 + } => { 3324 + let result = state 3325 + .metastore 3326 + .delegation_ops() 3327 + .log_delegation_action( 3328 + &delegated_did, 3329 + &actor_did, 3330 + controller_did.as_ref(), 3331 + action_type, 3332 + action_details, 3333 + ip_address.as_deref(), 3334 + user_agent.as_deref(), 3335 + ) 3336 + .map_err(metastore_to_db); 3337 + let _ = tx.send(result); 3338 + } 3339 + DelegationRequest::GetAuditLogForAccount { 3340 + delegated_did, 3341 + limit, 3342 + offset, 3343 + tx, 3344 + } => { 3345 + let result = state 3346 + .metastore 3347 + .delegation_ops() 3348 + .get_audit_log_for_account(&delegated_did, limit, offset) 3349 + .map_err(metastore_to_db); 3350 + let _ = tx.send(result); 3351 + } 3352 + DelegationRequest::CountAuditLogEntries { delegated_did, tx } => { 3353 + let result = state 3354 + .metastore 3355 + .delegation_ops() 3356 + .count_audit_log_entries(&delegated_did) 3357 + .map_err(metastore_to_db); 3358 + let _ = tx.send(result); 3359 + } 3360 + } 3361 + } 3362 + 3363 + fn dispatch_sso<S: StorageIO>(state: &HandlerState<S>, req: SsoRequest) { 3364 + match req { 3365 + SsoRequest::CreateExternalIdentity { 3366 + did, 3367 + provider, 3368 + provider_user_id, 3369 + provider_username, 3370 + provider_email, 3371 + tx, 3372 + } => { 3373 + let result = state 3374 + .metastore 3375 + .sso_ops() 3376 + .create_external_identity( 3377 + &did, 3378 + provider, 3379 + &provider_user_id, 3380 + provider_username.as_deref(), 3381 + provider_email.as_deref(), 3382 + ) 3383 + .map_err(metastore_to_db); 3384 + let _ = tx.send(result); 3385 + } 3386 + SsoRequest::GetExternalIdentityByProvider { 3387 + provider, 3388 + provider_user_id, 3389 + tx, 3390 + } => { 3391 + let result = state 3392 + .metastore 3393 + .sso_ops() 3394 + .get_external_identity_by_provider(provider, &provider_user_id) 3395 + .map_err(metastore_to_db); 3396 + let _ = tx.send(result); 3397 + } 3398 + SsoRequest::GetExternalIdentitiesByDid { did, tx } => { 3399 + let result = state 3400 + .metastore 3401 + .sso_ops() 3402 + .get_external_identities_by_did(&did) 3403 + .map_err(metastore_to_db); 3404 + let _ = tx.send(result); 3405 + } 3406 + SsoRequest::UpdateExternalIdentityLogin { 3407 + id, 3408 + provider_username, 3409 + provider_email, 3410 + tx, 3411 + } => { 3412 + let result = state 3413 + .metastore 3414 + .sso_ops() 3415 + .update_external_identity_login( 3416 + id, 3417 + provider_username.as_deref(), 3418 + provider_email.as_deref(), 3419 + ) 3420 + .map_err(metastore_to_db); 3421 + let _ = tx.send(result); 3422 + } 3423 + SsoRequest::DeleteExternalIdentity { id, did, tx } => { 3424 + let result = state 3425 + .metastore 3426 + .sso_ops() 3427 + .delete_external_identity(id, &did) 3428 + .map_err(metastore_to_db); 3429 + let _ = tx.send(result); 3430 + } 3431 + SsoRequest::CreateSsoAuthState { 3432 + state: sso_state, 3433 + request_uri, 3434 + provider, 3435 + action, 3436 + nonce, 3437 + code_verifier, 3438 + did, 3439 + tx, 3440 + } => { 3441 + let result = state 3442 + .metastore 3443 + .sso_ops() 3444 + .create_sso_auth_state( 3445 + &sso_state, 3446 + &request_uri, 3447 + provider, 3448 + action, 3449 + nonce.as_deref(), 3450 + code_verifier.as_deref(), 3451 + did.as_ref(), 3452 + ) 3453 + .map_err(metastore_to_db); 3454 + let _ = tx.send(result); 3455 + } 3456 + SsoRequest::ConsumeSsoAuthState { 3457 + state: sso_state, 3458 + tx, 3459 + } => { 3460 + let result = state 3461 + .metastore 3462 + .sso_ops() 3463 + .consume_sso_auth_state(&sso_state) 3464 + .map_err(metastore_to_db); 3465 + let _ = tx.send(result); 3466 + } 3467 + SsoRequest::CleanupExpiredSsoAuthStates { tx } => { 3468 + let result = state 3469 + .metastore 3470 + .sso_ops() 3471 + .cleanup_expired_sso_auth_states() 3472 + .map_err(metastore_to_db); 3473 + let _ = tx.send(result); 3474 + } 3475 + SsoRequest::CreatePendingRegistration { 3476 + token, 3477 + request_uri, 3478 + provider, 3479 + provider_user_id, 3480 + provider_username, 3481 + provider_email, 3482 + provider_email_verified, 3483 + tx, 3484 + } => { 3485 + let result = state 3486 + .metastore 3487 + .sso_ops() 3488 + .create_pending_registration( 3489 + &token, 3490 + &request_uri, 3491 + provider, 3492 + &provider_user_id, 3493 + provider_username.as_deref(), 3494 + provider_email.as_deref(), 3495 + provider_email_verified, 3496 + ) 3497 + .map_err(metastore_to_db); 3498 + let _ = tx.send(result); 3499 + } 3500 + SsoRequest::GetPendingRegistration { token, tx } => { 3501 + let result = state 3502 + .metastore 3503 + .sso_ops() 3504 + .get_pending_registration(&token) 3505 + .map_err(metastore_to_db); 3506 + let _ = tx.send(result); 3507 + } 3508 + SsoRequest::ConsumePendingRegistration { token, tx } => { 3509 + let result = state 3510 + .metastore 3511 + .sso_ops() 3512 + .consume_pending_registration(&token) 3513 + .map_err(metastore_to_db); 3514 + let _ = tx.send(result); 3515 + } 3516 + SsoRequest::CleanupExpiredPendingRegistrations { tx } => { 3517 + let result = state 3518 + .metastore 3519 + .sso_ops() 3520 + .cleanup_expired_pending_registrations() 3521 + .map_err(metastore_to_db); 3522 + let _ = tx.send(result); 3523 + } 3524 + } 3525 + } 3526 + 3527 + fn dispatch_session<S: StorageIO>(state: &HandlerState<S>, req: SessionRequest) { 3528 + match req { 3529 + SessionRequest::CreateSession { data, tx } => { 3530 + let result = state 3531 + .metastore 3532 + .session_ops() 3533 + .create_session(&data) 3534 + .map_err(metastore_to_db); 3535 + let _ = tx.send(result); 3536 + } 3537 + SessionRequest::GetSessionByAccessJti { access_jti, tx } => { 3538 + let result = state 3539 + .metastore 3540 + .session_ops() 3541 + .get_session_by_access_jti(&access_jti) 3542 + .map_err(metastore_to_db); 3543 + let _ = tx.send(result); 3544 + } 3545 + SessionRequest::GetSessionForRefresh { refresh_jti, tx } => { 3546 + let result = state 3547 + .metastore 3548 + .session_ops() 3549 + .get_session_for_refresh(&refresh_jti) 3550 + .map_err(metastore_to_db); 3551 + let _ = tx.send(result); 3552 + } 3553 + SessionRequest::UpdateSessionTokens { 3554 + session_id, 3555 + new_access_jti, 3556 + new_refresh_jti, 3557 + new_access_expires_at, 3558 + new_refresh_expires_at, 3559 + tx, 3560 + } => { 3561 + let result = state 3562 + .metastore 3563 + .session_ops() 3564 + .update_session_tokens( 3565 + session_id, 3566 + &new_access_jti, 3567 + &new_refresh_jti, 3568 + new_access_expires_at, 3569 + new_refresh_expires_at, 3570 + ) 3571 + .map_err(metastore_to_db); 3572 + let _ = tx.send(result); 3573 + } 3574 + SessionRequest::DeleteSessionByAccessJti { access_jti, tx } => { 3575 + let result = state 3576 + .metastore 3577 + .session_ops() 3578 + .delete_session_by_access_jti(&access_jti) 3579 + .map_err(metastore_to_db); 3580 + let _ = tx.send(result); 3581 + } 3582 + SessionRequest::DeleteSessionById { session_id, tx } => { 3583 + let result = state 3584 + .metastore 3585 + .session_ops() 3586 + .delete_session_by_id(session_id) 3587 + .map_err(metastore_to_db); 3588 + let _ = tx.send(result); 3589 + } 3590 + SessionRequest::DeleteSessionsByDid { did, tx } => { 3591 + let result = state 3592 + .metastore 3593 + .session_ops() 3594 + .delete_sessions_by_did(&did) 3595 + .map_err(metastore_to_db); 3596 + let _ = tx.send(result); 3597 + } 3598 + SessionRequest::DeleteSessionsByDidExceptJti { 3599 + did, 3600 + except_jti, 3601 + tx, 3602 + } => { 3603 + let result = state 3604 + .metastore 3605 + .session_ops() 3606 + .delete_sessions_by_did_except_jti(&did, &except_jti) 3607 + .map_err(metastore_to_db); 3608 + let _ = tx.send(result); 3609 + } 3610 + SessionRequest::ListSessionsByDid { did, tx } => { 3611 + let result = state 3612 + .metastore 3613 + .session_ops() 3614 + .list_sessions_by_did(&did) 3615 + .map_err(metastore_to_db); 3616 + let _ = tx.send(result); 3617 + } 3618 + SessionRequest::GetSessionAccessJtiById { 3619 + session_id, 3620 + did, 3621 + tx, 3622 + } => { 3623 + let result = state 3624 + .metastore 3625 + .session_ops() 3626 + .get_session_access_jti_by_id(session_id, &did) 3627 + .map_err(metastore_to_db); 3628 + let _ = tx.send(result); 3629 + } 3630 + SessionRequest::DeleteSessionsByAppPassword { 3631 + did, 3632 + app_password_name, 3633 + tx, 3634 + } => { 3635 + let result = state 3636 + .metastore 3637 + .session_ops() 3638 + .delete_sessions_by_app_password(&did, &app_password_name) 3639 + .map_err(metastore_to_db); 3640 + let _ = tx.send(result); 3641 + } 3642 + SessionRequest::GetSessionJtisByAppPassword { 3643 + did, 3644 + app_password_name, 3645 + tx, 3646 + } => { 3647 + let result = state 3648 + .metastore 3649 + .session_ops() 3650 + .get_session_jtis_by_app_password(&did, &app_password_name) 3651 + .map_err(metastore_to_db); 3652 + let _ = tx.send(result); 3653 + } 3654 + SessionRequest::CheckRefreshTokenUsed { refresh_jti, tx } => { 3655 + let result = state 3656 + .metastore 3657 + .session_ops() 3658 + .check_refresh_token_used(&refresh_jti) 3659 + .map_err(metastore_to_db); 3660 + let _ = tx.send(result); 3661 + } 3662 + SessionRequest::MarkRefreshTokenUsed { 3663 + refresh_jti, 3664 + session_id, 3665 + tx, 3666 + } => { 3667 + let result = state 3668 + .metastore 3669 + .session_ops() 3670 + .mark_refresh_token_used(&refresh_jti, session_id) 3671 + .map_err(metastore_to_db); 3672 + let _ = tx.send(result); 3673 + } 3674 + SessionRequest::ListAppPasswords { user_id, tx } => { 3675 + let result = state 3676 + .metastore 3677 + .session_ops() 3678 + .list_app_passwords(user_id) 3679 + .map_err(metastore_to_db); 3680 + let _ = tx.send(result); 3681 + } 3682 + SessionRequest::GetAppPasswordsForLogin { user_id, tx } => { 3683 + let result = state 3684 + .metastore 3685 + .session_ops() 3686 + .get_app_passwords_for_login(user_id) 3687 + .map_err(metastore_to_db); 3688 + let _ = tx.send(result); 3689 + } 3690 + SessionRequest::GetAppPasswordByName { user_id, name, tx } => { 3691 + let result = state 3692 + .metastore 3693 + .session_ops() 3694 + .get_app_password_by_name(user_id, &name) 3695 + .map_err(metastore_to_db); 3696 + let _ = tx.send(result); 3697 + } 3698 + SessionRequest::CreateAppPassword { data, tx } => { 3699 + let result = state 3700 + .metastore 3701 + .session_ops() 3702 + .create_app_password(&data) 3703 + .map_err(metastore_to_db); 3704 + let _ = tx.send(result); 3705 + } 3706 + SessionRequest::DeleteAppPassword { user_id, name, tx } => { 3707 + let result = state 3708 + .metastore 3709 + .session_ops() 3710 + .delete_app_password(user_id, &name) 3711 + .map_err(metastore_to_db); 3712 + let _ = tx.send(result); 3713 + } 3714 + SessionRequest::DeleteAppPasswordsByController { 3715 + did, 3716 + controller_did, 3717 + tx, 3718 + } => { 3719 + let result = state 3720 + .metastore 3721 + .session_ops() 3722 + .delete_app_passwords_by_controller(&did, &controller_did) 3723 + .map_err(metastore_to_db); 3724 + let _ = tx.send(result); 3725 + } 3726 + SessionRequest::GetLastReauthAt { did, tx } => { 3727 + let result = state 3728 + .metastore 3729 + .session_ops() 3730 + .get_last_reauth_at(&did) 3731 + .map_err(metastore_to_db); 3732 + let _ = tx.send(result); 3733 + } 3734 + SessionRequest::UpdateLastReauth { did, tx } => { 3735 + let result = state 3736 + .metastore 3737 + .session_ops() 3738 + .update_last_reauth(&did) 3739 + .map_err(metastore_to_db); 3740 + let _ = tx.send(result); 3741 + } 3742 + SessionRequest::GetSessionMfaStatus { did, tx } => { 3743 + let result = state 3744 + .metastore 3745 + .session_ops() 3746 + .get_session_mfa_status(&did) 3747 + .map_err(metastore_to_db); 3748 + let _ = tx.send(result); 3749 + } 3750 + SessionRequest::UpdateMfaVerified { did, tx } => { 3751 + let result = state 3752 + .metastore 3753 + .session_ops() 3754 + .update_mfa_verified(&did) 3755 + .map_err(metastore_to_db); 3756 + let _ = tx.send(result); 3757 + } 3758 + SessionRequest::GetAppPasswordHashesByDid { did, tx } => { 3759 + let result = state 3760 + .metastore 3761 + .session_ops() 3762 + .get_app_password_hashes_by_did(&did) 3763 + .map_err(metastore_to_db); 3764 + let _ = tx.send(result); 3765 + } 3766 + SessionRequest::RefreshSessionAtomic { data, tx } => { 3767 + let result = state 3768 + .metastore 3769 + .session_ops() 3770 + .refresh_session_atomic(&data) 3771 + .map_err(metastore_to_db); 3772 + let _ = tx.send(result); 3773 + } 3774 + } 3775 + } 3776 + 3777 + fn dispatch_infra<S: StorageIO>(state: &HandlerState<S>, req: InfraRequest) { 3778 + match req { 3779 + InfraRequest::EnqueueComms { 3780 + user_id, 3781 + channel, 3782 + comms_type, 3783 + recipient, 3784 + subject, 3785 + body, 3786 + metadata, 3787 + tx, 3788 + } => { 3789 + let result = state 3790 + .metastore 3791 + .infra_ops() 3792 + .enqueue_comms( 3793 + user_id, 3794 + channel, 3795 + comms_type, 3796 + &recipient, 3797 + subject.as_deref(), 3798 + &body, 3799 + metadata, 3800 + ) 3801 + .map_err(metastore_to_db); 3802 + let _ = tx.send(result); 3803 + } 3804 + InfraRequest::FetchPendingComms { 3805 + now, 3806 + batch_size, 3807 + tx, 3808 + } => { 3809 + let result = state 3810 + .metastore 3811 + .infra_ops() 3812 + .fetch_pending_comms(now, batch_size) 3813 + .map_err(metastore_to_db); 3814 + let _ = tx.send(result); 3815 + } 3816 + InfraRequest::MarkCommsSent { id, tx } => { 3817 + let result = state 3818 + .metastore 3819 + .infra_ops() 3820 + .mark_comms_sent(id) 3821 + .map_err(metastore_to_db); 3822 + let _ = tx.send(result); 3823 + } 3824 + InfraRequest::MarkCommsFailed { id, error, tx } => { 3825 + let result = state 3826 + .metastore 3827 + .infra_ops() 3828 + .mark_comms_failed(id, &error) 3829 + .map_err(metastore_to_db); 3830 + let _ = tx.send(result); 3831 + } 3832 + InfraRequest::CreateInviteCode { 3833 + code, 3834 + use_count, 3835 + for_account, 3836 + tx, 3837 + } => { 3838 + let result = state 3839 + .metastore 3840 + .infra_ops() 3841 + .create_invite_code(&code, use_count, for_account.as_ref()) 3842 + .map_err(metastore_to_db); 3843 + let _ = tx.send(result); 3844 + } 3845 + InfraRequest::CreateInviteCodesBatch { 3846 + codes, 3847 + use_count, 3848 + created_by_user, 3849 + for_account, 3850 + tx, 3851 + } => { 3852 + let result = state 3853 + .metastore 3854 + .infra_ops() 3855 + .create_invite_codes_batch(&codes, use_count, created_by_user, for_account.as_ref()) 3856 + .map_err(metastore_to_db); 3857 + let _ = tx.send(result); 3858 + } 3859 + InfraRequest::GetInviteCodeAvailableUses { code, tx } => { 3860 + let result = state 3861 + .metastore 3862 + .infra_ops() 3863 + .get_invite_code_available_uses(&code) 3864 + .map_err(metastore_to_db); 3865 + let _ = tx.send(result); 3866 + } 3867 + InfraRequest::ValidateInviteCode { code, tx } => { 3868 + let result = state 3869 + .metastore 3870 + .infra_ops() 3871 + .validate_invite_code(&code) 3872 + .map(|_| ()); 3873 + let _ = tx.send(result); 3874 + } 3875 + InfraRequest::DecrementInviteCodeUses { code, tx } => { 3876 + let validated = ValidatedInviteCode::new_validated(&code); 3877 + let result = state 3878 + .metastore 3879 + .infra_ops() 3880 + .decrement_invite_code_uses(&validated) 3881 + .map_err(metastore_to_db); 3882 + let _ = tx.send(result); 3883 + } 3884 + InfraRequest::RecordInviteCodeUse { 3885 + code, 3886 + used_by_user, 3887 + tx, 3888 + } => { 3889 + let validated = ValidatedInviteCode::new_validated(&code); 3890 + let result = state 3891 + .metastore 3892 + .infra_ops() 3893 + .record_invite_code_use(&validated, used_by_user) 3894 + .map_err(metastore_to_db); 3895 + let _ = tx.send(result); 3896 + } 3897 + InfraRequest::GetInviteCodesForAccount { for_account, tx } => { 3898 + let result = state 3899 + .metastore 3900 + .infra_ops() 3901 + .get_invite_codes_for_account(&for_account) 3902 + .map_err(metastore_to_db); 3903 + let _ = tx.send(result); 3904 + } 3905 + InfraRequest::GetInviteCodeUses { code, tx } => { 3906 + let result = state 3907 + .metastore 3908 + .infra_ops() 3909 + .get_invite_code_uses(&code) 3910 + .map_err(metastore_to_db); 3911 + let _ = tx.send(result); 3912 + } 3913 + InfraRequest::DisableInviteCodesByCode { codes, tx } => { 3914 + let result = state 3915 + .metastore 3916 + .infra_ops() 3917 + .disable_invite_codes_by_code(&codes) 3918 + .map_err(metastore_to_db); 3919 + let _ = tx.send(result); 3920 + } 3921 + InfraRequest::DisableInviteCodesByAccount { accounts, tx } => { 3922 + let result = state 3923 + .metastore 3924 + .infra_ops() 3925 + .disable_invite_codes_by_account(&accounts) 3926 + .map_err(metastore_to_db); 3927 + let _ = tx.send(result); 3928 + } 3929 + InfraRequest::ListInviteCodes { 3930 + cursor, 3931 + limit, 3932 + sort, 3933 + tx, 3934 + } => { 3935 + let result = state 3936 + .metastore 3937 + .infra_ops() 3938 + .list_invite_codes(cursor.as_deref(), limit, sort) 3939 + .map_err(metastore_to_db); 3940 + let _ = tx.send(result); 3941 + } 3942 + InfraRequest::GetUserDidsByIds { user_ids, tx } => { 3943 + let result = state 3944 + .metastore 3945 + .infra_ops() 3946 + .get_user_dids_by_ids(&user_ids) 3947 + .map_err(metastore_to_db); 3948 + let _ = tx.send(result); 3949 + } 3950 + InfraRequest::GetInviteCodeUsesBatch { codes, tx } => { 3951 + let result = state 3952 + .metastore 3953 + .infra_ops() 3954 + .get_invite_code_uses_batch(&codes) 3955 + .map_err(metastore_to_db); 3956 + let _ = tx.send(result); 3957 + } 3958 + InfraRequest::GetInvitesCreatedByUser { user_id, tx } => { 3959 + let result = state 3960 + .metastore 3961 + .infra_ops() 3962 + .get_invites_created_by_user(user_id) 3963 + .map_err(metastore_to_db); 3964 + let _ = tx.send(result); 3965 + } 3966 + InfraRequest::GetInviteCodeInfo { code, tx } => { 3967 + let result = state 3968 + .metastore 3969 + .infra_ops() 3970 + .get_invite_code_info(&code) 3971 + .map_err(metastore_to_db); 3972 + let _ = tx.send(result); 3973 + } 3974 + InfraRequest::GetInviteCodesByUsers { user_ids, tx } => { 3975 + let result = state 3976 + .metastore 3977 + .infra_ops() 3978 + .get_invite_codes_by_users(&user_ids) 3979 + .map_err(metastore_to_db); 3980 + let _ = tx.send(result); 3981 + } 3982 + InfraRequest::GetInviteCodeUsedByUser { user_id, tx } => { 3983 + let result = state 3984 + .metastore 3985 + .infra_ops() 3986 + .get_invite_code_used_by_user(user_id) 3987 + .map_err(metastore_to_db); 3988 + let _ = tx.send(result); 3989 + } 3990 + InfraRequest::DeleteInviteCodeUsesByUser { user_id, tx } => { 3991 + let result = state 3992 + .metastore 3993 + .infra_ops() 3994 + .delete_invite_code_uses_by_user(user_id) 3995 + .map_err(metastore_to_db); 3996 + let _ = tx.send(result); 3997 + } 3998 + InfraRequest::DeleteInviteCodesByUser { user_id, tx } => { 3999 + let result = state 4000 + .metastore 4001 + .infra_ops() 4002 + .delete_invite_codes_by_user(user_id) 4003 + .map_err(metastore_to_db); 4004 + let _ = tx.send(result); 4005 + } 4006 + InfraRequest::ReserveSigningKey { 4007 + did, 4008 + public_key_did_key, 4009 + private_key_bytes, 4010 + expires_at, 4011 + tx, 4012 + } => { 4013 + let result = state 4014 + .metastore 4015 + .infra_ops() 4016 + .reserve_signing_key( 4017 + did.as_ref(), 4018 + &public_key_did_key, 4019 + &private_key_bytes, 4020 + expires_at, 4021 + ) 4022 + .map_err(metastore_to_db); 4023 + let _ = tx.send(result); 4024 + } 4025 + InfraRequest::GetReservedSigningKey { 4026 + public_key_did_key, 4027 + tx, 4028 + } => { 4029 + let result = state 4030 + .metastore 4031 + .infra_ops() 4032 + .get_reserved_signing_key(&public_key_did_key) 4033 + .map_err(metastore_to_db); 4034 + let _ = tx.send(result); 4035 + } 4036 + InfraRequest::MarkSigningKeyUsed { key_id, tx } => { 4037 + let result = state 4038 + .metastore 4039 + .infra_ops() 4040 + .mark_signing_key_used(key_id) 4041 + .map_err(metastore_to_db); 4042 + let _ = tx.send(result); 4043 + } 4044 + InfraRequest::CreateDeletionRequest { 4045 + token, 4046 + did, 4047 + expires_at, 4048 + tx, 4049 + } => { 4050 + let result = state 4051 + .metastore 4052 + .infra_ops() 4053 + .create_deletion_request(&token, &did, expires_at) 4054 + .map_err(metastore_to_db); 4055 + let _ = tx.send(result); 4056 + } 4057 + InfraRequest::GetDeletionRequest { token, tx } => { 4058 + let result = state 4059 + .metastore 4060 + .infra_ops() 4061 + .get_deletion_request(&token) 4062 + .map_err(metastore_to_db); 4063 + let _ = tx.send(result); 4064 + } 4065 + InfraRequest::DeleteDeletionRequest { token, tx } => { 4066 + let result = state 4067 + .metastore 4068 + .infra_ops() 4069 + .delete_deletion_request(&token) 4070 + .map_err(metastore_to_db); 4071 + let _ = tx.send(result); 4072 + } 4073 + InfraRequest::DeleteDeletionRequestsByDid { did, tx } => { 4074 + let result = state 4075 + .metastore 4076 + .infra_ops() 4077 + .delete_deletion_requests_by_did(&did) 4078 + .map_err(metastore_to_db); 4079 + let _ = tx.send(result); 4080 + } 4081 + InfraRequest::UpsertAccountPreference { 4082 + user_id, 4083 + name, 4084 + value_json, 4085 + tx, 4086 + } => { 4087 + let result = state 4088 + .metastore 4089 + .infra_ops() 4090 + .upsert_account_preference(user_id, &name, value_json) 4091 + .map_err(metastore_to_db); 4092 + let _ = tx.send(result); 4093 + } 4094 + InfraRequest::InsertAccountPreferenceIfNotExists { 4095 + user_id, 4096 + name, 4097 + value_json, 4098 + tx, 4099 + } => { 4100 + let result = state 4101 + .metastore 4102 + .infra_ops() 4103 + .insert_account_preference_if_not_exists(user_id, &name, value_json) 4104 + .map_err(metastore_to_db); 4105 + let _ = tx.send(result); 4106 + } 4107 + InfraRequest::GetServerConfig { key, tx } => { 4108 + let result = state 4109 + .metastore 4110 + .infra_ops() 4111 + .get_server_config(&key) 4112 + .map_err(metastore_to_db); 4113 + let _ = tx.send(result); 4114 + } 4115 + InfraRequest::InsertReport { 4116 + id, 4117 + reason_type, 4118 + reason, 4119 + subject_json, 4120 + reported_by_did, 4121 + created_at, 4122 + tx, 4123 + } => { 4124 + let result = state 4125 + .metastore 4126 + .infra_ops() 4127 + .insert_report( 4128 + id, 4129 + &reason_type, 4130 + reason.as_deref(), 4131 + subject_json, 4132 + &reported_by_did, 4133 + created_at, 4134 + ) 4135 + .map_err(metastore_to_db); 4136 + let _ = tx.send(result); 4137 + } 4138 + InfraRequest::DeletePlcTokensForUser { user_id, tx } => { 4139 + let result = state 4140 + .metastore 4141 + .infra_ops() 4142 + .delete_plc_tokens_for_user(user_id) 4143 + .map_err(metastore_to_db); 4144 + let _ = tx.send(result); 4145 + } 4146 + InfraRequest::InsertPlcToken { 4147 + user_id, 4148 + token, 4149 + expires_at, 4150 + tx, 4151 + } => { 4152 + let result = state 4153 + .metastore 4154 + .infra_ops() 4155 + .insert_plc_token(user_id, &token, expires_at) 4156 + .map_err(metastore_to_db); 4157 + let _ = tx.send(result); 4158 + } 4159 + InfraRequest::GetPlcTokenExpiry { user_id, token, tx } => { 4160 + let result = state 4161 + .metastore 4162 + .infra_ops() 4163 + .get_plc_token_expiry(user_id, &token) 4164 + .map_err(metastore_to_db); 4165 + let _ = tx.send(result); 4166 + } 4167 + InfraRequest::DeletePlcToken { user_id, token, tx } => { 4168 + let result = state 4169 + .metastore 4170 + .infra_ops() 4171 + .delete_plc_token(user_id, &token) 4172 + .map_err(metastore_to_db); 4173 + let _ = tx.send(result); 4174 + } 4175 + InfraRequest::GetAccountPreferences { user_id, tx } => { 4176 + let result = state 4177 + .metastore 4178 + .infra_ops() 4179 + .get_account_preferences(user_id) 4180 + .map_err(metastore_to_db); 4181 + let _ = tx.send(result); 4182 + } 4183 + InfraRequest::ReplaceNamespacePreferences { 4184 + user_id, 4185 + namespace, 4186 + preferences, 4187 + tx, 4188 + } => { 4189 + let result = state 4190 + .metastore 4191 + .infra_ops() 4192 + .replace_namespace_preferences(user_id, &namespace, preferences) 4193 + .map_err(metastore_to_db); 4194 + let _ = tx.send(result); 4195 + } 4196 + InfraRequest::GetNotificationHistory { user_id, limit, tx } => { 4197 + let result = state 4198 + .metastore 4199 + .infra_ops() 4200 + .get_notification_history(user_id, limit) 4201 + .map_err(metastore_to_db); 4202 + let _ = tx.send(result); 4203 + } 4204 + InfraRequest::GetServerConfigs { keys, tx } => { 4205 + let key_refs: Vec<&str> = keys.iter().map(String::as_str).collect(); 4206 + let result = state 4207 + .metastore 4208 + .infra_ops() 4209 + .get_server_configs(&key_refs) 4210 + .map_err(metastore_to_db); 4211 + let _ = tx.send(result); 4212 + } 4213 + InfraRequest::UpsertServerConfig { key, value, tx } => { 4214 + let result = state 4215 + .metastore 4216 + .infra_ops() 4217 + .upsert_server_config(&key, &value) 4218 + .map_err(metastore_to_db); 4219 + let _ = tx.send(result); 4220 + } 4221 + InfraRequest::DeleteServerConfig { key, tx } => { 4222 + let result = state 4223 + .metastore 4224 + .infra_ops() 4225 + .delete_server_config(&key) 4226 + .map_err(metastore_to_db); 4227 + let _ = tx.send(result); 4228 + } 4229 + InfraRequest::GetBlobStorageKeyByCid { cid, tx } => { 4230 + let result = state 4231 + .metastore 4232 + .infra_ops() 4233 + .get_blob_storage_key_by_cid(&cid) 4234 + .map_err(metastore_to_db); 4235 + let _ = tx.send(result); 4236 + } 4237 + InfraRequest::DeleteBlobByCid { cid, tx } => { 4238 + let result = state 4239 + .metastore 4240 + .infra_ops() 4241 + .delete_blob_by_cid(&cid) 4242 + .map_err(metastore_to_db); 4243 + let _ = tx.send(result); 4244 + } 4245 + InfraRequest::GetAdminAccountInfoByDid { did, tx } => { 4246 + let result = state 4247 + .metastore 4248 + .infra_ops() 4249 + .get_admin_account_info_by_did(&did) 4250 + .map_err(metastore_to_db); 4251 + let _ = tx.send(result); 4252 + } 4253 + InfraRequest::GetAdminAccountInfosByDids { dids, tx } => { 4254 + let result = state 4255 + .metastore 4256 + .infra_ops() 4257 + .get_admin_account_infos_by_dids(&dids) 4258 + .map_err(metastore_to_db); 4259 + let _ = tx.send(result); 4260 + } 4261 + InfraRequest::GetInviteCodeUsesByUsers { user_ids, tx } => { 4262 + let result = state 4263 + .metastore 4264 + .infra_ops() 4265 + .get_invite_code_uses_by_users(&user_ids) 4266 + .map_err(metastore_to_db); 4267 + let _ = tx.send(result); 4268 + } 4269 + } 4270 + } 4271 + 4272 + fn dispatch_oauth<S: StorageIO>(state: &HandlerState<S>, req: OAuthRequest) { 4273 + match req { 4274 + OAuthRequest::CreateToken { data, tx } => { 4275 + let result = state 4276 + .metastore 4277 + .oauth_ops() 4278 + .create_token(&data) 4279 + .map_err(metastore_to_db); 4280 + let _ = tx.send(result); 4281 + } 4282 + OAuthRequest::GetTokenById { token_id, tx } => { 4283 + let result = state 4284 + .metastore 4285 + .oauth_ops() 4286 + .get_token_by_id(&token_id) 4287 + .map_err(metastore_to_db); 4288 + let _ = tx.send(result); 4289 + } 4290 + OAuthRequest::GetTokenByRefreshToken { refresh_token, tx } => { 4291 + let result = state 4292 + .metastore 4293 + .oauth_ops() 4294 + .get_token_by_refresh_token(&refresh_token) 4295 + .map_err(metastore_to_db); 4296 + let _ = tx.send(result); 4297 + } 4298 + OAuthRequest::GetTokenByPreviousRefreshToken { refresh_token, tx } => { 4299 + let result = state 4300 + .metastore 4301 + .oauth_ops() 4302 + .get_token_by_previous_refresh_token(&refresh_token) 4303 + .map_err(metastore_to_db); 4304 + let _ = tx.send(result); 4305 + } 4306 + OAuthRequest::RotateToken { 4307 + old_db_id, 4308 + new_refresh_token, 4309 + new_expires_at, 4310 + tx, 4311 + } => { 4312 + let result = state 4313 + .metastore 4314 + .oauth_ops() 4315 + .rotate_token(old_db_id, &new_refresh_token, new_expires_at) 4316 + .map_err(metastore_to_db); 4317 + let _ = tx.send(result); 4318 + } 4319 + OAuthRequest::CheckRefreshTokenUsed { refresh_token, tx } => { 4320 + let result = state 4321 + .metastore 4322 + .oauth_ops() 4323 + .check_refresh_token_used(&refresh_token) 4324 + .map_err(metastore_to_db); 4325 + let _ = tx.send(result); 4326 + } 4327 + OAuthRequest::DeleteToken { token_id, tx } => { 4328 + let result = state 4329 + .metastore 4330 + .oauth_ops() 4331 + .delete_token(&token_id) 4332 + .map_err(metastore_to_db); 4333 + let _ = tx.send(result); 4334 + } 4335 + OAuthRequest::DeleteTokenFamily { db_id, tx } => { 4336 + let result = state 4337 + .metastore 4338 + .oauth_ops() 4339 + .delete_token_family(db_id) 4340 + .map_err(metastore_to_db); 4341 + let _ = tx.send(result); 4342 + } 4343 + OAuthRequest::ListTokensForUser { did, tx } => { 4344 + let result = state 4345 + .metastore 4346 + .oauth_ops() 4347 + .list_tokens_for_user(&did) 4348 + .map_err(metastore_to_db); 4349 + let _ = tx.send(result); 4350 + } 4351 + OAuthRequest::CountTokensForUser { did, tx } => { 4352 + let result = state 4353 + .metastore 4354 + .oauth_ops() 4355 + .count_tokens_for_user(&did) 4356 + .map_err(metastore_to_db); 4357 + let _ = tx.send(result); 4358 + } 4359 + OAuthRequest::DeleteOldestTokensForUser { 4360 + did, 4361 + keep_count, 4362 + tx, 4363 + } => { 4364 + let result = state 4365 + .metastore 4366 + .oauth_ops() 4367 + .delete_oldest_tokens_for_user(&did, keep_count) 4368 + .map_err(metastore_to_db); 4369 + let _ = tx.send(result); 4370 + } 4371 + OAuthRequest::RevokeTokensForClient { did, client_id, tx } => { 4372 + let result = state 4373 + .metastore 4374 + .oauth_ops() 4375 + .revoke_tokens_for_client(&did, &client_id) 4376 + .map_err(metastore_to_db); 4377 + let _ = tx.send(result); 4378 + } 4379 + OAuthRequest::RevokeTokensForController { 4380 + delegated_did, 4381 + controller_did, 4382 + tx, 4383 + } => { 4384 + let result = state 4385 + .metastore 4386 + .oauth_ops() 4387 + .revoke_tokens_for_controller(&delegated_did, &controller_did) 4388 + .map_err(metastore_to_db); 4389 + let _ = tx.send(result); 4390 + } 4391 + OAuthRequest::CreateAuthorizationRequest { 4392 + request_id, 4393 + data, 4394 + tx, 4395 + } => { 4396 + let result = state 4397 + .metastore 4398 + .oauth_ops() 4399 + .create_authorization_request(&request_id, &data) 4400 + .map_err(metastore_to_db); 4401 + let _ = tx.send(result); 4402 + } 4403 + OAuthRequest::GetAuthorizationRequest { request_id, tx } => { 4404 + let result = state 4405 + .metastore 4406 + .oauth_ops() 4407 + .get_authorization_request(&request_id) 4408 + .map_err(metastore_to_db); 4409 + let _ = tx.send(result); 4410 + } 4411 + OAuthRequest::SetAuthorizationDid { 4412 + request_id, 4413 + did, 4414 + device_id, 4415 + tx, 4416 + } => { 4417 + let result = state 4418 + .metastore 4419 + .oauth_ops() 4420 + .set_authorization_did(&request_id, &did, device_id.as_ref()) 4421 + .map_err(metastore_to_db); 4422 + let _ = tx.send(result); 4423 + } 4424 + OAuthRequest::UpdateAuthorizationRequest { 4425 + request_id, 4426 + did, 4427 + device_id, 4428 + code, 4429 + tx, 4430 + } => { 4431 + let result = state 4432 + .metastore 4433 + .oauth_ops() 4434 + .update_authorization_request(&request_id, &did, device_id.as_ref(), &code) 4435 + .map_err(metastore_to_db); 4436 + let _ = tx.send(result); 4437 + } 4438 + OAuthRequest::ConsumeAuthorizationRequestByCode { code, tx } => { 4439 + let result = state 4440 + .metastore 4441 + .oauth_ops() 4442 + .consume_authorization_request_by_code(&code) 4443 + .map_err(metastore_to_db); 4444 + let _ = tx.send(result); 4445 + } 4446 + OAuthRequest::DeleteAuthorizationRequest { request_id, tx } => { 4447 + let result = state 4448 + .metastore 4449 + .oauth_ops() 4450 + .delete_authorization_request(&request_id) 4451 + .map_err(metastore_to_db); 4452 + let _ = tx.send(result); 4453 + } 4454 + OAuthRequest::DeleteExpiredAuthorizationRequests { tx } => { 4455 + let result = state 4456 + .metastore 4457 + .oauth_ops() 4458 + .delete_expired_authorization_requests() 4459 + .map_err(metastore_to_db); 4460 + let _ = tx.send(result); 4461 + } 4462 + OAuthRequest::ExtendAuthorizationRequestExpiry { 4463 + request_id, 4464 + new_expires_at, 4465 + tx, 4466 + } => { 4467 + let result = state 4468 + .metastore 4469 + .oauth_ops() 4470 + .extend_authorization_request_expiry(&request_id, new_expires_at) 4471 + .map_err(metastore_to_db); 4472 + let _ = tx.send(result); 4473 + } 4474 + OAuthRequest::MarkRequestAuthenticated { 4475 + request_id, 4476 + did, 4477 + device_id, 4478 + tx, 4479 + } => { 4480 + let result = state 4481 + .metastore 4482 + .oauth_ops() 4483 + .mark_request_authenticated(&request_id, &did, device_id.as_ref()) 4484 + .map_err(metastore_to_db); 4485 + let _ = tx.send(result); 4486 + } 4487 + OAuthRequest::UpdateRequestScope { 4488 + request_id, 4489 + scope, 4490 + tx, 4491 + } => { 4492 + let result = state 4493 + .metastore 4494 + .oauth_ops() 4495 + .update_request_scope(&request_id, &scope) 4496 + .map_err(metastore_to_db); 4497 + let _ = tx.send(result); 4498 + } 4499 + OAuthRequest::SetControllerDid { 4500 + request_id, 4501 + controller_did, 4502 + tx, 4503 + } => { 4504 + let result = state 4505 + .metastore 4506 + .oauth_ops() 4507 + .set_controller_did(&request_id, &controller_did) 4508 + .map_err(metastore_to_db); 4509 + let _ = tx.send(result); 4510 + } 4511 + OAuthRequest::SetRequestDid { 4512 + request_id, 4513 + did, 4514 + tx, 4515 + } => { 4516 + let result = state 4517 + .metastore 4518 + .oauth_ops() 4519 + .set_request_did(&request_id, &did) 4520 + .map_err(metastore_to_db); 4521 + let _ = tx.send(result); 4522 + } 4523 + OAuthRequest::CreateDevice { 4524 + device_id, 4525 + data, 4526 + tx, 4527 + } => { 4528 + let result = state 4529 + .metastore 4530 + .oauth_ops() 4531 + .create_device(&device_id, &data) 4532 + .map_err(metastore_to_db); 4533 + let _ = tx.send(result); 4534 + } 4535 + OAuthRequest::GetDevice { device_id, tx } => { 4536 + let result = state 4537 + .metastore 4538 + .oauth_ops() 4539 + .get_device(&device_id) 4540 + .map_err(metastore_to_db); 4541 + let _ = tx.send(result); 4542 + } 4543 + OAuthRequest::UpdateDeviceLastSeen { device_id, tx } => { 4544 + let result = state 4545 + .metastore 4546 + .oauth_ops() 4547 + .update_device_last_seen(&device_id) 4548 + .map_err(metastore_to_db); 4549 + let _ = tx.send(result); 4550 + } 4551 + OAuthRequest::DeleteDevice { device_id, tx } => { 4552 + let result = state 4553 + .metastore 4554 + .oauth_ops() 4555 + .delete_device(&device_id) 4556 + .map_err(metastore_to_db); 4557 + let _ = tx.send(result); 4558 + } 4559 + OAuthRequest::UpsertAccountDevice { did, device_id, tx } => { 4560 + let result = state 4561 + .metastore 4562 + .oauth_ops() 4563 + .upsert_account_device(&did, &device_id) 4564 + .map_err(metastore_to_db); 4565 + let _ = tx.send(result); 4566 + } 4567 + OAuthRequest::GetDeviceAccounts { device_id, tx } => { 4568 + let result = state 4569 + .metastore 4570 + .oauth_ops() 4571 + .get_device_accounts(&device_id) 4572 + .map_err(metastore_to_db); 4573 + let _ = tx.send(result); 4574 + } 4575 + OAuthRequest::VerifyAccountOnDevice { device_id, did, tx } => { 4576 + let result = state 4577 + .metastore 4578 + .oauth_ops() 4579 + .verify_account_on_device(&device_id, &did) 4580 + .map_err(metastore_to_db); 4581 + let _ = tx.send(result); 4582 + } 4583 + OAuthRequest::CheckAndRecordDpopJti { jti, tx } => { 4584 + let result = state 4585 + .metastore 4586 + .oauth_ops() 4587 + .check_and_record_dpop_jti(&jti) 4588 + .map_err(metastore_to_db); 4589 + let _ = tx.send(result); 4590 + } 4591 + OAuthRequest::CleanupExpiredDpopJtis { max_age_secs, tx } => { 4592 + let result = state 4593 + .metastore 4594 + .oauth_ops() 4595 + .cleanup_expired_dpop_jtis(max_age_secs) 4596 + .map_err(metastore_to_db); 4597 + let _ = tx.send(result); 4598 + } 4599 + OAuthRequest::Create2faChallenge { 4600 + did, 4601 + request_uri, 4602 + tx, 4603 + } => { 4604 + let result = state 4605 + .metastore 4606 + .oauth_ops() 4607 + .create_2fa_challenge(&did, &request_uri) 4608 + .map_err(metastore_to_db); 4609 + let _ = tx.send(result); 4610 + } 4611 + OAuthRequest::Get2faChallenge { request_uri, tx } => { 4612 + let result = state 4613 + .metastore 4614 + .oauth_ops() 4615 + .get_2fa_challenge(&request_uri) 4616 + .map_err(metastore_to_db); 4617 + let _ = tx.send(result); 4618 + } 4619 + OAuthRequest::Increment2faAttempts { id, tx } => { 4620 + let result = state 4621 + .metastore 4622 + .oauth_ops() 4623 + .increment_2fa_attempts(id) 4624 + .map_err(metastore_to_db); 4625 + let _ = tx.send(result); 4626 + } 4627 + OAuthRequest::Delete2faChallenge { id, tx } => { 4628 + let result = state 4629 + .metastore 4630 + .oauth_ops() 4631 + .delete_2fa_challenge(id) 4632 + .map_err(metastore_to_db); 4633 + let _ = tx.send(result); 4634 + } 4635 + OAuthRequest::Delete2faChallengeByRequestUri { request_uri, tx } => { 4636 + let result = state 4637 + .metastore 4638 + .oauth_ops() 4639 + .delete_2fa_challenge_by_request_uri(&request_uri) 4640 + .map_err(metastore_to_db); 4641 + let _ = tx.send(result); 4642 + } 4643 + OAuthRequest::CleanupExpired2faChallenges { tx } => { 4644 + let result = state 4645 + .metastore 4646 + .oauth_ops() 4647 + .cleanup_expired_2fa_challenges() 4648 + .map_err(metastore_to_db); 4649 + let _ = tx.send(result); 4650 + } 4651 + OAuthRequest::CheckUser2faEnabled { did, tx } => { 4652 + let result = state 4653 + .metastore 4654 + .oauth_ops() 4655 + .check_user_2fa_enabled(&did) 4656 + .map_err(metastore_to_db); 4657 + let _ = tx.send(result); 4658 + } 4659 + OAuthRequest::GetScopePreferences { did, client_id, tx } => { 4660 + let result = state 4661 + .metastore 4662 + .oauth_ops() 4663 + .get_scope_preferences(&did, &client_id) 4664 + .map_err(metastore_to_db); 4665 + let _ = tx.send(result); 4666 + } 4667 + OAuthRequest::UpsertScopePreferences { 4668 + did, 4669 + client_id, 4670 + prefs, 4671 + tx, 4672 + } => { 4673 + let result = state 4674 + .metastore 4675 + .oauth_ops() 4676 + .upsert_scope_preferences(&did, &client_id, &prefs) 4677 + .map_err(metastore_to_db); 4678 + let _ = tx.send(result); 4679 + } 4680 + OAuthRequest::DeleteScopePreferences { did, client_id, tx } => { 4681 + let result = state 4682 + .metastore 4683 + .oauth_ops() 4684 + .delete_scope_preferences(&did, &client_id) 4685 + .map_err(metastore_to_db); 4686 + let _ = tx.send(result); 4687 + } 4688 + OAuthRequest::UpsertAuthorizedClient { 4689 + did, 4690 + client_id, 4691 + data, 4692 + tx, 4693 + } => { 4694 + let result = state 4695 + .metastore 4696 + .oauth_ops() 4697 + .upsert_authorized_client(&did, &client_id, &data) 4698 + .map_err(metastore_to_db); 4699 + let _ = tx.send(result); 4700 + } 4701 + OAuthRequest::GetAuthorizedClient { did, client_id, tx } => { 4702 + let result = state 4703 + .metastore 4704 + .oauth_ops() 4705 + .get_authorized_client(&did, &client_id) 4706 + .map_err(metastore_to_db); 4707 + let _ = tx.send(result); 4708 + } 4709 + OAuthRequest::ListTrustedDevices { did, tx } => { 4710 + let result = state 4711 + .metastore 4712 + .oauth_ops() 4713 + .list_trusted_devices(&did) 4714 + .map_err(metastore_to_db); 4715 + let _ = tx.send(result); 4716 + } 4717 + OAuthRequest::GetDeviceTrustInfo { device_id, did, tx } => { 4718 + let result = state 4719 + .metastore 4720 + .oauth_ops() 4721 + .get_device_trust_info(&device_id, &did) 4722 + .map_err(metastore_to_db); 4723 + let _ = tx.send(result); 4724 + } 4725 + OAuthRequest::DeviceBelongsToUser { device_id, did, tx } => { 4726 + let result = state 4727 + .metastore 4728 + .oauth_ops() 4729 + .device_belongs_to_user(&device_id, &did) 4730 + .map_err(metastore_to_db); 4731 + let _ = tx.send(result); 4732 + } 4733 + OAuthRequest::RevokeDeviceTrust { device_id, did, tx } => { 4734 + let result = state 4735 + .metastore 4736 + .oauth_ops() 4737 + .revoke_device_trust(&device_id, &did) 4738 + .map_err(metastore_to_db); 4739 + let _ = tx.send(result); 4740 + } 4741 + OAuthRequest::UpdateDeviceFriendlyName { 4742 + device_id, 4743 + did, 4744 + friendly_name, 4745 + tx, 4746 + } => { 4747 + let result = state 4748 + .metastore 4749 + .oauth_ops() 4750 + .update_device_friendly_name(&device_id, &did, friendly_name.as_deref()) 4751 + .map_err(metastore_to_db); 4752 + let _ = tx.send(result); 4753 + } 4754 + OAuthRequest::TrustDevice { 4755 + device_id, 4756 + did, 4757 + trusted_at, 4758 + trusted_until, 4759 + tx, 4760 + } => { 4761 + let result = state 4762 + .metastore 4763 + .oauth_ops() 4764 + .trust_device(&device_id, &did, trusted_at, trusted_until) 4765 + .map_err(metastore_to_db); 4766 + let _ = tx.send(result); 4767 + } 4768 + OAuthRequest::ExtendDeviceTrust { 4769 + device_id, 4770 + did, 4771 + trusted_until, 4772 + tx, 4773 + } => { 4774 + let result = state 4775 + .metastore 4776 + .oauth_ops() 4777 + .extend_device_trust(&device_id, &did, trusted_until) 4778 + .map_err(metastore_to_db); 4779 + let _ = tx.send(result); 4780 + } 4781 + OAuthRequest::ListSessionsByDid { did, tx } => { 4782 + let result = state 4783 + .metastore 4784 + .oauth_ops() 4785 + .list_sessions_by_did(&did) 4786 + .map_err(metastore_to_db); 4787 + let _ = tx.send(result); 4788 + } 4789 + OAuthRequest::DeleteSessionById { 4790 + session_id, 4791 + did, 4792 + tx, 4793 + } => { 4794 + let result = state 4795 + .metastore 4796 + .oauth_ops() 4797 + .delete_session_by_id(session_id, &did) 4798 + .map_err(metastore_to_db); 4799 + let _ = tx.send(result); 4800 + } 4801 + OAuthRequest::DeleteSessionsByDid { did, tx } => { 4802 + let result = state 4803 + .metastore 4804 + .oauth_ops() 4805 + .delete_sessions_by_did(&did) 4806 + .map_err(metastore_to_db); 4807 + let _ = tx.send(result); 4808 + } 4809 + OAuthRequest::DeleteSessionsByDidExcept { 4810 + did, 4811 + except_token_id, 4812 + tx, 4813 + } => { 4814 + let result = state 4815 + .metastore 4816 + .oauth_ops() 4817 + .delete_sessions_by_did_except(&did, &except_token_id) 4818 + .map_err(metastore_to_db); 4819 + let _ = tx.send(result); 4820 + } 4821 + } 4822 + } 4823 + 1448 4824 fn dispatch<S: StorageIO>(state: &HandlerState<S>, request: MetastoreRequest) { 1449 4825 match request { 1450 4826 MetastoreRequest::Repo(r) => dispatch_repo(state, r), ··· 1454 4830 MetastoreRequest::Commit(r) => dispatch_commit(state, *r), 1455 4831 MetastoreRequest::Backlink(r) => dispatch_backlink(state, r), 1456 4832 MetastoreRequest::Blob(r) => dispatch_blob(state, r), 4833 + MetastoreRequest::Delegation(r) => dispatch_delegation(state, r), 4834 + MetastoreRequest::Sso(r) => dispatch_sso(state, r), 4835 + MetastoreRequest::Session(r) => dispatch_session(state, r), 4836 + MetastoreRequest::Infra(r) => dispatch_infra(state, r), 4837 + MetastoreRequest::OAuth(r) => dispatch_oauth(state, r), 4838 + MetastoreRequest::User(r) => dispatch_user(state, r), 4839 + } 4840 + } 4841 + 4842 + fn dispatch_user<S: StorageIO>(state: &HandlerState<S>, req: UserRequest) { 4843 + let user = state.metastore.user_ops(); 4844 + match req { 4845 + UserRequest::GetByDid { did, tx } => { 4846 + let _ = tx.send(user.get_by_did(&did).map_err(metastore_to_db)); 4847 + } 4848 + UserRequest::GetByHandle { handle, tx } => { 4849 + let _ = tx.send(user.get_by_handle(&handle).map_err(metastore_to_db)); 4850 + } 4851 + UserRequest::GetWithKeyByDid { did, tx } => { 4852 + let _ = tx.send(user.get_with_key_by_did(&did).map_err(metastore_to_db)); 4853 + } 4854 + UserRequest::GetStatusByDid { did, tx } => { 4855 + let _ = tx.send(user.get_status_by_did(&did).map_err(metastore_to_db)); 4856 + } 4857 + UserRequest::CountUsers { tx } => { 4858 + let _ = tx.send(user.count_users().map_err(metastore_to_db)); 4859 + } 4860 + UserRequest::GetSessionAccessExpiry { 4861 + did, 4862 + access_jti, 4863 + tx, 4864 + } => { 4865 + let _ = tx.send( 4866 + user.get_session_access_expiry(&did, &access_jti) 4867 + .map_err(metastore_to_db), 4868 + ); 4869 + } 4870 + UserRequest::GetOAuthTokenWithUser { token_id, tx } => { 4871 + let _ = tx.send( 4872 + user.get_oauth_token_with_user(&token_id) 4873 + .map_err(metastore_to_db), 4874 + ); 4875 + } 4876 + UserRequest::GetUserInfoByDid { did, tx } => { 4877 + let _ = tx.send(user.get_user_info_by_did(&did).map_err(metastore_to_db)); 4878 + } 4879 + UserRequest::GetAnyAdminUserId { tx } => { 4880 + let _ = tx.send(user.get_any_admin_user_id().map_err(metastore_to_db)); 4881 + } 4882 + UserRequest::SetInvitesDisabled { did, disabled, tx } => { 4883 + let _ = tx.send( 4884 + user.set_invites_disabled(&did, disabled) 4885 + .map_err(metastore_to_db), 4886 + ); 4887 + } 4888 + UserRequest::SearchAccounts { 4889 + cursor_did, 4890 + email_filter, 4891 + handle_filter, 4892 + limit, 4893 + tx, 4894 + } => { 4895 + let _ = tx.send( 4896 + user.search_accounts( 4897 + cursor_did.as_ref(), 4898 + email_filter.as_deref(), 4899 + handle_filter.as_deref(), 4900 + limit, 4901 + ) 4902 + .map_err(metastore_to_db), 4903 + ); 4904 + } 4905 + UserRequest::GetAuthInfoByDid { did, tx } => { 4906 + let _ = tx.send(user.get_auth_info_by_did(&did).map_err(metastore_to_db)); 4907 + } 4908 + UserRequest::GetByEmail { email, tx } => { 4909 + let _ = tx.send(user.get_by_email(&email).map_err(metastore_to_db)); 4910 + } 4911 + UserRequest::GetLoginCheckByHandleOrEmail { identifier, tx } => { 4912 + let _ = tx.send( 4913 + user.get_login_check_by_handle_or_email(&identifier) 4914 + .map_err(metastore_to_db), 4915 + ); 4916 + } 4917 + UserRequest::GetLoginInfoByHandleOrEmail { identifier, tx } => { 4918 + let _ = tx.send( 4919 + user.get_login_info_by_handle_or_email(&identifier) 4920 + .map_err(metastore_to_db), 4921 + ); 4922 + } 4923 + UserRequest::Get2faStatusByDid { did, tx } => { 4924 + let _ = tx.send(user.get_2fa_status_by_did(&did).map_err(metastore_to_db)); 4925 + } 4926 + UserRequest::GetCommsPrefs { user_id, tx } => { 4927 + let _ = tx.send(user.get_comms_prefs(user_id).map_err(metastore_to_db)); 4928 + } 4929 + UserRequest::GetIdByDid { did, tx } => { 4930 + let _ = tx.send(user.get_id_by_did(&did).map_err(metastore_to_db)); 4931 + } 4932 + UserRequest::GetUserKeyById { user_id, tx } => { 4933 + let _ = tx.send(user.get_user_key_by_id(user_id).map_err(metastore_to_db)); 4934 + } 4935 + UserRequest::GetIdAndHandleByDid { did, tx } => { 4936 + let _ = tx.send(user.get_id_and_handle_by_did(&did).map_err(metastore_to_db)); 4937 + } 4938 + UserRequest::GetDidWebInfoByHandle { handle, tx } => { 4939 + let _ = tx.send( 4940 + user.get_did_web_info_by_handle(&handle) 4941 + .map_err(metastore_to_db), 4942 + ); 4943 + } 4944 + UserRequest::GetDidWebOverrides { user_id, tx } => { 4945 + let _ = tx.send(user.get_did_web_overrides(user_id).map_err(metastore_to_db)); 4946 + } 4947 + UserRequest::GetHandleByDid { did, tx } => { 4948 + let _ = tx.send(user.get_handle_by_did(&did).map_err(metastore_to_db)); 4949 + } 4950 + UserRequest::IsAccountActiveByDid { did, tx } => { 4951 + let _ = tx.send(user.is_account_active_by_did(&did).map_err(metastore_to_db)); 4952 + } 4953 + UserRequest::GetUserForDeletion { did, tx } => { 4954 + let _ = tx.send(user.get_user_for_deletion(&did).map_err(metastore_to_db)); 4955 + } 4956 + UserRequest::CheckHandleExists { 4957 + handle, 4958 + exclude_user_id, 4959 + tx, 4960 + } => { 4961 + let _ = tx.send( 4962 + user.check_handle_exists(&handle, exclude_user_id) 4963 + .map_err(metastore_to_db), 4964 + ); 4965 + } 4966 + UserRequest::UpdateHandle { 4967 + user_id, 4968 + handle, 4969 + tx, 4970 + } => { 4971 + let _ = tx.send( 4972 + user.update_handle(user_id, &handle) 4973 + .map_err(metastore_to_db), 4974 + ); 4975 + } 4976 + UserRequest::GetUserWithKeyByDid { did, tx } => { 4977 + let _ = tx.send(user.get_user_with_key_by_did(&did).map_err(metastore_to_db)); 4978 + } 4979 + UserRequest::IsAccountMigrated { did, tx } => { 4980 + let _ = tx.send(user.is_account_migrated(&did).map_err(metastore_to_db)); 4981 + } 4982 + UserRequest::HasVerifiedCommsChannel { did, tx } => { 4983 + let _ = tx.send( 4984 + user.has_verified_comms_channel(&did) 4985 + .map_err(metastore_to_db), 4986 + ); 4987 + } 4988 + UserRequest::GetIdByHandle { handle, tx } => { 4989 + let _ = tx.send(user.get_id_by_handle(&handle).map_err(metastore_to_db)); 4990 + } 4991 + UserRequest::GetEmailInfoByDid { did, tx } => { 4992 + let _ = tx.send(user.get_email_info_by_did(&did).map_err(metastore_to_db)); 4993 + } 4994 + UserRequest::CheckEmailExists { 4995 + email, 4996 + exclude_user_id, 4997 + tx, 4998 + } => { 4999 + let _ = tx.send( 5000 + user.check_email_exists(&email, exclude_user_id) 5001 + .map_err(metastore_to_db), 5002 + ); 5003 + } 5004 + UserRequest::UpdateEmail { user_id, email, tx } => { 5005 + let _ = tx.send(user.update_email(user_id, &email).map_err(metastore_to_db)); 5006 + } 5007 + UserRequest::SetEmailVerified { 5008 + user_id, 5009 + verified, 5010 + tx, 5011 + } => { 5012 + let _ = tx.send( 5013 + user.set_email_verified(user_id, verified) 5014 + .map_err(metastore_to_db), 5015 + ); 5016 + } 5017 + UserRequest::CheckEmailVerifiedByIdentifier { identifier, tx } => { 5018 + let _ = tx.send( 5019 + user.check_email_verified_by_identifier(&identifier) 5020 + .map_err(metastore_to_db), 5021 + ); 5022 + } 5023 + UserRequest::CheckChannelVerifiedByDid { did, channel, tx } => { 5024 + let _ = tx.send( 5025 + user.check_channel_verified_by_did(&did, channel) 5026 + .map_err(metastore_to_db), 5027 + ); 5028 + } 5029 + UserRequest::AdminUpdateEmail { did, email, tx } => { 5030 + let _ = tx.send( 5031 + user.admin_update_email(&did, &email) 5032 + .map_err(metastore_to_db), 5033 + ); 5034 + } 5035 + UserRequest::AdminUpdateHandle { did, handle, tx } => { 5036 + let _ = tx.send( 5037 + user.admin_update_handle(&did, &handle) 5038 + .map_err(metastore_to_db), 5039 + ); 5040 + } 5041 + UserRequest::AdminUpdatePassword { 5042 + did, 5043 + password_hash, 5044 + tx, 5045 + } => { 5046 + let _ = tx.send( 5047 + user.admin_update_password(&did, &password_hash) 5048 + .map_err(metastore_to_db), 5049 + ); 5050 + } 5051 + UserRequest::GetNotificationPrefs { did, tx } => { 5052 + let _ = tx.send(user.get_notification_prefs(&did).map_err(metastore_to_db)); 5053 + } 5054 + UserRequest::GetIdHandleEmailByDid { did, tx } => { 5055 + let _ = tx.send( 5056 + user.get_id_handle_email_by_did(&did) 5057 + .map_err(metastore_to_db), 5058 + ); 5059 + } 5060 + UserRequest::UpdatePreferredCommsChannel { did, channel, tx } => { 5061 + let _ = tx.send( 5062 + user.update_preferred_comms_channel(&did, channel) 5063 + .map_err(metastore_to_db), 5064 + ); 5065 + } 5066 + UserRequest::ClearDiscord { user_id, tx } => { 5067 + let _ = tx.send(user.clear_discord(user_id).map_err(metastore_to_db)); 5068 + } 5069 + UserRequest::ClearTelegram { user_id, tx } => { 5070 + let _ = tx.send(user.clear_telegram(user_id).map_err(metastore_to_db)); 5071 + } 5072 + UserRequest::ClearSignal { user_id, tx } => { 5073 + let _ = tx.send(user.clear_signal(user_id).map_err(metastore_to_db)); 5074 + } 5075 + UserRequest::SetUnverifiedSignal { 5076 + user_id, 5077 + signal_username, 5078 + tx, 5079 + } => { 5080 + let _ = tx.send( 5081 + user.set_unverified_signal(user_id, &signal_username) 5082 + .map_err(metastore_to_db), 5083 + ); 5084 + } 5085 + UserRequest::SetUnverifiedTelegram { 5086 + user_id, 5087 + telegram_username, 5088 + tx, 5089 + } => { 5090 + let _ = tx.send( 5091 + user.set_unverified_telegram(user_id, &telegram_username) 5092 + .map_err(metastore_to_db), 5093 + ); 5094 + } 5095 + UserRequest::StoreTelegramChatId { 5096 + telegram_username, 5097 + chat_id, 5098 + handle, 5099 + tx, 5100 + } => { 5101 + let _ = tx.send( 5102 + user.store_telegram_chat_id(&telegram_username, chat_id, handle.as_deref()) 5103 + .map_err(metastore_to_db), 5104 + ); 5105 + } 5106 + UserRequest::GetTelegramChatId { user_id, tx } => { 5107 + let _ = tx.send(user.get_telegram_chat_id(user_id).map_err(metastore_to_db)); 5108 + } 5109 + UserRequest::SetUnverifiedDiscord { 5110 + user_id, 5111 + discord_username, 5112 + tx, 5113 + } => { 5114 + let _ = tx.send( 5115 + user.set_unverified_discord(user_id, &discord_username) 5116 + .map_err(metastore_to_db), 5117 + ); 5118 + } 5119 + UserRequest::StoreDiscordUserId { 5120 + discord_username, 5121 + discord_id, 5122 + handle, 5123 + tx, 5124 + } => { 5125 + let _ = tx.send( 5126 + user.store_discord_user_id(&discord_username, &discord_id, handle.as_deref()) 5127 + .map_err(metastore_to_db), 5128 + ); 5129 + } 5130 + UserRequest::GetVerificationInfo { did, tx } => { 5131 + let _ = tx.send(user.get_verification_info(&did).map_err(metastore_to_db)); 5132 + } 5133 + UserRequest::VerifyEmailChannel { user_id, email, tx } => { 5134 + let _ = tx.send( 5135 + user.verify_email_channel(user_id, &email) 5136 + .map_err(metastore_to_db), 5137 + ); 5138 + } 5139 + UserRequest::VerifyDiscordChannel { 5140 + user_id, 5141 + discord_id, 5142 + tx, 5143 + } => { 5144 + let _ = tx.send( 5145 + user.verify_discord_channel(user_id, &discord_id) 5146 + .map_err(metastore_to_db), 5147 + ); 5148 + } 5149 + UserRequest::VerifyTelegramChannel { 5150 + user_id, 5151 + telegram_username, 5152 + tx, 5153 + } => { 5154 + let _ = tx.send( 5155 + user.verify_telegram_channel(user_id, &telegram_username) 5156 + .map_err(metastore_to_db), 5157 + ); 5158 + } 5159 + UserRequest::VerifySignalChannel { 5160 + user_id, 5161 + signal_username, 5162 + tx, 5163 + } => { 5164 + let _ = tx.send( 5165 + user.verify_signal_channel(user_id, &signal_username) 5166 + .map_err(metastore_to_db), 5167 + ); 5168 + } 5169 + UserRequest::SetEmailVerifiedFlag { user_id, tx } => { 5170 + let _ = tx.send( 5171 + user.set_email_verified_flag(user_id) 5172 + .map_err(metastore_to_db), 5173 + ); 5174 + } 5175 + UserRequest::SetDiscordVerifiedFlag { user_id, tx } => { 5176 + let _ = tx.send( 5177 + user.set_discord_verified_flag(user_id) 5178 + .map_err(metastore_to_db), 5179 + ); 5180 + } 5181 + UserRequest::SetTelegramVerifiedFlag { user_id, tx } => { 5182 + let _ = tx.send( 5183 + user.set_telegram_verified_flag(user_id) 5184 + .map_err(metastore_to_db), 5185 + ); 5186 + } 5187 + UserRequest::SetSignalVerifiedFlag { user_id, tx } => { 5188 + let _ = tx.send( 5189 + user.set_signal_verified_flag(user_id) 5190 + .map_err(metastore_to_db), 5191 + ); 5192 + } 5193 + UserRequest::HasTotpEnabled { did, tx } => { 5194 + let _ = tx.send(user.has_totp_enabled(&did).map_err(metastore_to_db)); 5195 + } 5196 + UserRequest::HasPasskeys { did, tx } => { 5197 + let _ = tx.send(user.has_passkeys(&did).map_err(metastore_to_db)); 5198 + } 5199 + UserRequest::GetPasswordHashByDid { did, tx } => { 5200 + let _ = tx.send(user.get_password_hash_by_did(&did).map_err(metastore_to_db)); 5201 + } 5202 + UserRequest::GetPasskeysForUser { did, tx } => { 5203 + let _ = tx.send(user.get_passkeys_for_user(&did).map_err(metastore_to_db)); 5204 + } 5205 + UserRequest::GetPasskeyByCredentialId { credential_id, tx } => { 5206 + let _ = tx.send( 5207 + user.get_passkey_by_credential_id(&credential_id) 5208 + .map_err(metastore_to_db), 5209 + ); 5210 + } 5211 + UserRequest::SavePasskey { 5212 + did, 5213 + credential_id, 5214 + public_key, 5215 + friendly_name, 5216 + tx, 5217 + } => { 5218 + let _ = tx.send( 5219 + user.save_passkey(&did, &credential_id, &public_key, friendly_name.as_deref()) 5220 + .map_err(metastore_to_db), 5221 + ); 5222 + } 5223 + UserRequest::UpdatePasskeyCounter { 5224 + credential_id, 5225 + new_counter, 5226 + tx, 5227 + } => { 5228 + let _ = tx.send( 5229 + user.update_passkey_counter(&credential_id, new_counter) 5230 + .map_err(metastore_to_db), 5231 + ); 5232 + } 5233 + UserRequest::DeletePasskey { id, did, tx } => { 5234 + let _ = tx.send(user.delete_passkey(id, &did).map_err(metastore_to_db)); 5235 + } 5236 + UserRequest::UpdatePasskeyName { id, did, name, tx } => { 5237 + let _ = tx.send( 5238 + user.update_passkey_name(id, &did, &name) 5239 + .map_err(metastore_to_db), 5240 + ); 5241 + } 5242 + UserRequest::SaveWebauthnChallenge { 5243 + did, 5244 + challenge_type, 5245 + state_json, 5246 + tx, 5247 + } => { 5248 + let _ = tx.send( 5249 + user.save_webauthn_challenge(&did, challenge_type, &state_json) 5250 + .map_err(metastore_to_db), 5251 + ); 5252 + } 5253 + UserRequest::LoadWebauthnChallenge { 5254 + did, 5255 + challenge_type, 5256 + tx, 5257 + } => { 5258 + let _ = tx.send( 5259 + user.load_webauthn_challenge(&did, challenge_type) 5260 + .map_err(metastore_to_db), 5261 + ); 5262 + } 5263 + UserRequest::DeleteWebauthnChallenge { 5264 + did, 5265 + challenge_type, 5266 + tx, 5267 + } => { 5268 + let _ = tx.send( 5269 + user.delete_webauthn_challenge(&did, challenge_type) 5270 + .map_err(metastore_to_db), 5271 + ); 5272 + } 5273 + UserRequest::GetTotpRecord { did, tx } => { 5274 + let _ = tx.send(user.get_totp_record(&did).map_err(metastore_to_db)); 5275 + } 5276 + UserRequest::GetTotpRecordState { did, tx } => { 5277 + let _ = tx.send(user.get_totp_record_state(&did).map_err(metastore_to_db)); 5278 + } 5279 + UserRequest::UpsertTotpSecret { 5280 + did, 5281 + secret_encrypted, 5282 + encryption_version, 5283 + tx, 5284 + } => { 5285 + let _ = tx.send( 5286 + user.upsert_totp_secret(&did, &secret_encrypted, encryption_version) 5287 + .map_err(metastore_to_db), 5288 + ); 5289 + } 5290 + UserRequest::SetTotpVerified { did, tx } => { 5291 + let _ = tx.send(user.set_totp_verified(&did).map_err(metastore_to_db)); 5292 + } 5293 + UserRequest::UpdateTotpLastUsed { did, tx } => { 5294 + let _ = tx.send(user.update_totp_last_used(&did).map_err(metastore_to_db)); 5295 + } 5296 + UserRequest::DeleteTotp { did, tx } => { 5297 + let _ = tx.send(user.delete_totp(&did).map_err(metastore_to_db)); 5298 + } 5299 + UserRequest::GetUnusedBackupCodes { did, tx } => { 5300 + let _ = tx.send(user.get_unused_backup_codes(&did).map_err(metastore_to_db)); 5301 + } 5302 + UserRequest::MarkBackupCodeUsed { code_id, tx } => { 5303 + let _ = tx.send(user.mark_backup_code_used(code_id).map_err(metastore_to_db)); 5304 + } 5305 + UserRequest::CountUnusedBackupCodes { did, tx } => { 5306 + let _ = tx.send( 5307 + user.count_unused_backup_codes(&did) 5308 + .map_err(metastore_to_db), 5309 + ); 5310 + } 5311 + UserRequest::DeleteBackupCodes { did, tx } => { 5312 + let _ = tx.send(user.delete_backup_codes(&did).map_err(metastore_to_db)); 5313 + } 5314 + UserRequest::InsertBackupCodes { 5315 + did, 5316 + code_hashes, 5317 + tx, 5318 + } => { 5319 + let _ = tx.send( 5320 + user.insert_backup_codes(&did, &code_hashes) 5321 + .map_err(metastore_to_db), 5322 + ); 5323 + } 5324 + UserRequest::EnableTotpWithBackupCodes { 5325 + did, 5326 + code_hashes, 5327 + tx, 5328 + } => { 5329 + let _ = tx.send( 5330 + user.enable_totp_with_backup_codes(&did, &code_hashes) 5331 + .map_err(metastore_to_db), 5332 + ); 5333 + } 5334 + UserRequest::DeleteTotpAndBackupCodes { did, tx } => { 5335 + let _ = tx.send( 5336 + user.delete_totp_and_backup_codes(&did) 5337 + .map_err(metastore_to_db), 5338 + ); 5339 + } 5340 + UserRequest::ReplaceBackupCodes { 5341 + did, 5342 + code_hashes, 5343 + tx, 5344 + } => { 5345 + let _ = tx.send( 5346 + user.replace_backup_codes(&did, &code_hashes) 5347 + .map_err(metastore_to_db), 5348 + ); 5349 + } 5350 + UserRequest::GetSessionInfoByDid { did, tx } => { 5351 + let _ = tx.send(user.get_session_info_by_did(&did).map_err(metastore_to_db)); 5352 + } 5353 + UserRequest::GetLegacyLoginPref { did, tx } => { 5354 + let _ = tx.send(user.get_legacy_login_pref(&did).map_err(metastore_to_db)); 5355 + } 5356 + UserRequest::UpdateLegacyLogin { did, allow, tx } => { 5357 + let _ = tx.send( 5358 + user.update_legacy_login(&did, allow) 5359 + .map_err(metastore_to_db), 5360 + ); 5361 + } 5362 + UserRequest::UpdateLocale { did, locale, tx } => { 5363 + let _ = tx.send(user.update_locale(&did, &locale).map_err(metastore_to_db)); 5364 + } 5365 + UserRequest::GetLoginFullByIdentifier { identifier, tx } => { 5366 + let _ = tx.send( 5367 + user.get_login_full_by_identifier(&identifier) 5368 + .map_err(metastore_to_db), 5369 + ); 5370 + } 5371 + UserRequest::GetConfirmSignupByDid { did, tx } => { 5372 + let _ = tx.send( 5373 + user.get_confirm_signup_by_did(&did) 5374 + .map_err(metastore_to_db), 5375 + ); 5376 + } 5377 + UserRequest::GetResendVerificationByDid { did, tx } => { 5378 + let _ = tx.send( 5379 + user.get_resend_verification_by_did(&did) 5380 + .map_err(metastore_to_db), 5381 + ); 5382 + } 5383 + UserRequest::SetChannelVerified { did, channel, tx } => { 5384 + let _ = tx.send( 5385 + user.set_channel_verified(&did, channel) 5386 + .map_err(metastore_to_db), 5387 + ); 5388 + } 5389 + UserRequest::GetIdByEmailOrHandle { email, handle, tx } => { 5390 + let _ = tx.send( 5391 + user.get_id_by_email_or_handle(&email, &handle) 5392 + .map_err(metastore_to_db), 5393 + ); 5394 + } 5395 + UserRequest::CountAccountsByEmail { email, tx } => { 5396 + let _ = tx.send( 5397 + user.count_accounts_by_email(&email) 5398 + .map_err(metastore_to_db), 5399 + ); 5400 + } 5401 + UserRequest::GetHandlesByEmail { email, tx } => { 5402 + let _ = tx.send(user.get_handles_by_email(&email).map_err(metastore_to_db)); 5403 + } 5404 + UserRequest::SetPasswordResetCode { 5405 + user_id, 5406 + code, 5407 + expires_at, 5408 + tx, 5409 + } => { 5410 + let _ = tx.send( 5411 + user.set_password_reset_code(user_id, &code, expires_at) 5412 + .map_err(metastore_to_db), 5413 + ); 5414 + } 5415 + UserRequest::GetUserByResetCode { code, tx } => { 5416 + let _ = tx.send(user.get_user_by_reset_code(&code).map_err(metastore_to_db)); 5417 + } 5418 + UserRequest::ClearPasswordResetCode { user_id, tx } => { 5419 + let _ = tx.send( 5420 + user.clear_password_reset_code(user_id) 5421 + .map_err(metastore_to_db), 5422 + ); 5423 + } 5424 + UserRequest::GetIdAndPasswordHashByDid { did, tx } => { 5425 + let _ = tx.send( 5426 + user.get_id_and_password_hash_by_did(&did) 5427 + .map_err(metastore_to_db), 5428 + ); 5429 + } 5430 + UserRequest::UpdatePasswordHash { 5431 + user_id, 5432 + password_hash, 5433 + tx, 5434 + } => { 5435 + let _ = tx.send( 5436 + user.update_password_hash(user_id, &password_hash) 5437 + .map_err(metastore_to_db), 5438 + ); 5439 + } 5440 + UserRequest::ResetPasswordWithSessions { 5441 + user_id, 5442 + password_hash, 5443 + tx, 5444 + } => { 5445 + let _ = tx.send( 5446 + user.reset_password_with_sessions(user_id, &password_hash) 5447 + .map_err(metastore_to_db), 5448 + ); 5449 + } 5450 + UserRequest::ActivateAccount { did, tx } => { 5451 + let _ = tx.send(user.activate_account(&did).map_err(metastore_to_db)); 5452 + } 5453 + UserRequest::DeactivateAccount { 5454 + did, 5455 + delete_after, 5456 + tx, 5457 + } => { 5458 + let _ = tx.send( 5459 + user.deactivate_account(&did, delete_after) 5460 + .map_err(metastore_to_db), 5461 + ); 5462 + } 5463 + UserRequest::HasPasswordByDid { did, tx } => { 5464 + let _ = tx.send(user.has_password_by_did(&did).map_err(metastore_to_db)); 5465 + } 5466 + UserRequest::GetPasswordInfoByDid { did, tx } => { 5467 + let _ = tx.send(user.get_password_info_by_did(&did).map_err(metastore_to_db)); 5468 + } 5469 + UserRequest::RemoveUserPassword { user_id, tx } => { 5470 + let _ = tx.send(user.remove_user_password(user_id).map_err(metastore_to_db)); 5471 + } 5472 + UserRequest::SetNewUserPassword { 5473 + user_id, 5474 + password_hash, 5475 + tx, 5476 + } => { 5477 + let _ = tx.send( 5478 + user.set_new_user_password(user_id, &password_hash) 5479 + .map_err(metastore_to_db), 5480 + ); 5481 + } 5482 + UserRequest::GetUserKeyByDid { did, tx } => { 5483 + let _ = tx.send(user.get_user_key_by_did(&did).map_err(metastore_to_db)); 5484 + } 5485 + UserRequest::DeleteAccountComplete { user_id, did, tx } => { 5486 + let _ = tx.send( 5487 + user.delete_account_complete(user_id, &did) 5488 + .map_err(metastore_to_db), 5489 + ); 5490 + } 5491 + UserRequest::SetUserTakedown { 5492 + did, 5493 + takedown_ref, 5494 + tx, 5495 + } => { 5496 + let _ = tx.send( 5497 + user.set_user_takedown(&did, takedown_ref.as_deref()) 5498 + .map_err(metastore_to_db), 5499 + ); 5500 + } 5501 + UserRequest::AdminDeleteAccountComplete { user_id, did, tx } => { 5502 + let _ = tx.send( 5503 + user.admin_delete_account_complete(user_id, &did) 5504 + .map_err(metastore_to_db), 5505 + ); 5506 + } 5507 + UserRequest::GetUserForDidDoc { did, tx } => { 5508 + let _ = tx.send(user.get_user_for_did_doc(&did).map_err(metastore_to_db)); 5509 + } 5510 + UserRequest::GetUserForDidDocBuild { did, tx } => { 5511 + let _ = tx.send( 5512 + user.get_user_for_did_doc_build(&did) 5513 + .map_err(metastore_to_db), 5514 + ); 5515 + } 5516 + UserRequest::UpsertDidWebOverrides { 5517 + user_id, 5518 + verification_methods, 5519 + also_known_as, 5520 + tx, 5521 + } => { 5522 + let _ = tx.send( 5523 + user.upsert_did_web_overrides(user_id, verification_methods, also_known_as) 5524 + .map_err(metastore_to_db), 5525 + ); 5526 + } 5527 + UserRequest::UpdateMigratedToPds { did, endpoint, tx } => { 5528 + let _ = tx.send( 5529 + user.update_migrated_to_pds(&did, &endpoint) 5530 + .map_err(metastore_to_db), 5531 + ); 5532 + } 5533 + UserRequest::GetUserForPasskeySetup { did, tx } => { 5534 + let _ = tx.send( 5535 + user.get_user_for_passkey_setup(&did) 5536 + .map_err(metastore_to_db), 5537 + ); 5538 + } 5539 + UserRequest::GetUserForPasskeyRecovery { 5540 + identifier, 5541 + normalized_handle, 5542 + tx, 5543 + } => { 5544 + let _ = tx.send( 5545 + user.get_user_for_passkey_recovery(&identifier, &normalized_handle) 5546 + .map_err(metastore_to_db), 5547 + ); 5548 + } 5549 + UserRequest::SetRecoveryToken { 5550 + did, 5551 + token_hash, 5552 + expires_at, 5553 + tx, 5554 + } => { 5555 + let _ = tx.send( 5556 + user.set_recovery_token(&did, &token_hash, expires_at) 5557 + .map_err(metastore_to_db), 5558 + ); 5559 + } 5560 + UserRequest::GetUserForRecovery { did, tx } => { 5561 + let _ = tx.send(user.get_user_for_recovery(&did).map_err(metastore_to_db)); 5562 + } 5563 + UserRequest::GetAccountsScheduledForDeletion { limit, tx } => { 5564 + let _ = tx.send( 5565 + user.get_accounts_scheduled_for_deletion(limit) 5566 + .map_err(metastore_to_db), 5567 + ); 5568 + } 5569 + UserRequest::DeleteAccountWithFirehose { user_id, did, tx } => { 5570 + let _ = tx.send( 5571 + user.delete_account_with_firehose(user_id, &did) 5572 + .map_err(metastore_to_db), 5573 + ); 5574 + } 5575 + UserRequest::CreatePasswordAccount { input, tx } => { 5576 + let _ = tx.send(user.create_password_account(&input)); 5577 + } 5578 + UserRequest::CreateDelegatedAccount { input, tx } => { 5579 + let _ = tx.send(user.create_delegated_account(&input)); 5580 + } 5581 + UserRequest::CreatePasskeyAccount { input, tx } => { 5582 + let _ = tx.send(user.create_passkey_account(&input)); 5583 + } 5584 + UserRequest::CreateSsoAccount { input, tx } => { 5585 + let _ = tx.send(user.create_sso_account(&input)); 5586 + } 5587 + UserRequest::ReactivateMigrationAccount { input, tx } => { 5588 + let _ = tx.send(user.reactivate_migration_account(&input)); 5589 + } 5590 + UserRequest::CheckHandleAvailableForNewAccount { handle, tx } => { 5591 + let _ = tx.send( 5592 + user.check_handle_available_for_new_account(&handle) 5593 + .map_err(metastore_to_db), 5594 + ); 5595 + } 5596 + UserRequest::ReserveHandle { 5597 + handle, 5598 + reserved_by, 5599 + tx, 5600 + } => { 5601 + let _ = tx.send( 5602 + user.reserve_handle(&handle, &reserved_by) 5603 + .map_err(metastore_to_db), 5604 + ); 5605 + } 5606 + UserRequest::ReleaseHandleReservation { handle, tx } => { 5607 + let _ = tx.send( 5608 + user.release_handle_reservation(&handle) 5609 + .map_err(metastore_to_db), 5610 + ); 5611 + } 5612 + UserRequest::CleanupExpiredHandleReservations { tx } => { 5613 + let _ = tx.send( 5614 + user.cleanup_expired_handle_reservations() 5615 + .map_err(metastore_to_db), 5616 + ); 5617 + } 5618 + UserRequest::CheckAndConsumeInviteCode { code, tx } => { 5619 + let infra = state.metastore.infra_ops(); 5620 + let result = match infra.validate_invite_code(&code) { 5621 + Ok(validated) => infra 5622 + .decrement_invite_code_uses(&validated) 5623 + .map(|()| true) 5624 + .map_err(metastore_to_db), 5625 + Err(_) => Ok(false), 5626 + }; 5627 + let _ = tx.send(result); 5628 + } 5629 + UserRequest::CompletePasskeySetup { input, tx } => { 5630 + let _ = tx.send(user.complete_passkey_setup(&input).map_err(metastore_to_db)); 5631 + } 5632 + UserRequest::RecoverPasskeyAccount { input, tx } => { 5633 + let _ = tx.send( 5634 + user.recover_passkey_account(&input) 5635 + .map_err(metastore_to_db), 5636 + ); 5637 + } 1457 5638 } 1458 5639 } 1459 5640 ··· 1497 5678 const MAX_REPOS_WITHOUT_REV: usize = 10_000; 1498 5679 1499 5680 pub struct HandlerPool { 1500 - senders: Vec<flume::Sender<MetastoreRequest>>, 1501 - handles: Option<Vec<JoinHandle<()>>>, 5681 + senders: parking_lot::Mutex<Vec<flume::Sender<MetastoreRequest>>>, 5682 + handles: parking_lot::Mutex<Option<Vec<JoinHandle<()>>>>, 5683 + sender_count: usize, 1502 5684 user_hashes: Arc<UserHashMap>, 1503 5685 round_robin: AtomicUsize, 1504 5686 } ··· 1535 5717 .unzip(); 1536 5718 1537 5719 Self { 1538 - senders, 1539 - handles: Some(handles), 5720 + sender_count: senders.len(), 5721 + senders: parking_lot::Mutex::new(senders), 5722 + handles: parking_lot::Mutex::new(Some(handles)), 1540 5723 user_hashes, 1541 5724 round_robin: AtomicUsize::new(0), 1542 5725 } 1543 5726 } 1544 5727 1545 5728 pub fn send(&self, request: MetastoreRequest) -> Result<(), DbError> { 5729 + let senders = self.senders.lock(); 5730 + if senders.is_empty() { 5731 + return Err(DbError::Connection( 5732 + "metastore handler pool shut down".to_string(), 5733 + )); 5734 + } 1546 5735 let index = match request.routing(&self.user_hashes) { 1547 - Routing::Sharded(bits) => (bits as usize) % self.senders.len(), 1548 - Routing::Global => { 1549 - self.round_robin.fetch_add(1, Ordering::Relaxed) % self.senders.len() 1550 - } 5736 + Routing::Sharded(bits) => (bits as usize) % senders.len(), 5737 + Routing::Global => self.round_robin.fetch_add(1, Ordering::Relaxed) % senders.len(), 1551 5738 }; 1552 - self.senders[index].try_send(request).map_err(|e| match e { 5739 + senders[index].try_send(request).map_err(|e| match e { 1553 5740 flume::TrySendError::Full(_) => { 1554 5741 DbError::Query("metastore handler backpressure".to_string()) 1555 5742 } ··· 1560 5747 } 1561 5748 1562 5749 pub fn thread_count(&self) -> usize { 1563 - self.senders.len() 5750 + self.sender_count 1564 5751 } 1565 5752 1566 - pub async fn shutdown(&mut self) { 1567 - self.senders.clear(); 1568 - if let Some(handles) = self.handles.take() { 5753 + pub async fn close(&self) { 5754 + { 5755 + self.senders.lock().clear(); 5756 + } 5757 + let handles = { self.handles.lock().take() }; 5758 + if let Some(handles) = handles { 1569 5759 let join_fut = tokio::task::spawn_blocking(move || { 1570 5760 handles.into_iter().for_each(|h| { 1571 5761 if let Err(e) = h.join() { ··· 1583 5773 1584 5774 impl Drop for HandlerPool { 1585 5775 fn drop(&mut self) { 1586 - self.senders.clear(); 1587 - if let Some(handles) = self.handles.take() { 5776 + self.senders.get_mut().clear(); 5777 + if let Some(handles) = self.handles.get_mut().take() { 1588 5778 tracing::warn!( 1589 5779 "HandlerPool dropped without calling shutdown(); blocking on thread join" 1590 5780 ); ··· 1705 5895 1706 5896 #[tokio::test] 1707 5897 async fn shutdown_completes_inflight() { 1708 - let mut h = setup(); 5898 + let h = setup(); 1709 5899 let user_id = Uuid::new_v4(); 1710 5900 let did = Did::from("did:plc:shutdown_test".to_string()); 1711 5901 let handle = Handle::from("shutdown.test.invalid".to_string()); ··· 1724 5914 .unwrap(); 1725 5915 rx.await.unwrap().unwrap(); 1726 5916 1727 - h.pool.shutdown().await; 5917 + h.pool.close().await; 1728 5918 } 1729 5919 }
+1209
crates/tranquil-store/src/metastore/infra_ops.rs
··· 1 + use std::sync::Arc; 2 + 3 + use chrono::{DateTime, Utc}; 4 + use fjall::{Database, Keyspace}; 5 + use uuid::Uuid; 6 + 7 + use super::MetastoreError; 8 + use super::blobs::{BlobMetaValue, blob_by_cid_key, blob_meta_key}; 9 + use super::infra_schema::{ 10 + DeletionRequestValue, InviteCodeUseValue, InviteCodeValue, NotificationHistoryValue, 11 + QueuedCommsValue, ReportValue, SigningKeyValue, account_pref_key, account_pref_prefix, 12 + channel_to_u8, comms_history_key, comms_history_prefix, comms_queue_key, comms_queue_prefix, 13 + comms_type_to_u8, deletion_by_did_key, deletion_request_key, invite_by_user_key, 14 + invite_code_key, invite_code_prefix, invite_code_used_by_key, invite_use_key, 15 + invite_use_prefix, plc_token_key, plc_token_prefix, report_key, server_config_key, 16 + signing_key_by_id_key, signing_key_key, status_to_u8, u8_to_channel, u8_to_comms_type, 17 + u8_to_status, 18 + }; 19 + use super::keys::UserHash; 20 + use super::scan::{delete_all_by_prefix, point_lookup}; 21 + use super::user_hash::UserHashMap; 22 + use super::users::UserValue; 23 + 24 + use tranquil_db_traits::{ 25 + AdminAccountInfo, CommsChannel, CommsStatus, CommsType, DeletionRequest, InviteCodeError, 26 + InviteCodeInfo, InviteCodeRow, InviteCodeSortOrder, InviteCodeState, InviteCodeUse, 27 + NotificationHistoryRow, QueuedComms, ReservedSigningKey, ValidatedInviteCode, 28 + }; 29 + use tranquil_types::{CidLink, Did, Handle}; 30 + 31 + pub struct InfraOps { 32 + db: Database, 33 + infra: Keyspace, 34 + repo_data: Keyspace, 35 + users: Keyspace, 36 + user_hashes: Arc<UserHashMap>, 37 + } 38 + 39 + impl InfraOps { 40 + pub fn new( 41 + db: Database, 42 + infra: Keyspace, 43 + repo_data: Keyspace, 44 + users: Keyspace, 45 + user_hashes: Arc<UserHashMap>, 46 + ) -> Self { 47 + Self { 48 + db, 49 + infra, 50 + repo_data, 51 + users, 52 + user_hashes, 53 + } 54 + } 55 + 56 + fn resolve_user_value(&self, user_hash: UserHash) -> Option<UserValue> { 57 + let key = super::encoding::KeyBuilder::new() 58 + .tag(super::keys::KeyTag::USER_PRIMARY) 59 + .u64(user_hash.raw()) 60 + .build(); 61 + self.users 62 + .get(key.as_slice()) 63 + .ok() 64 + .flatten() 65 + .and_then(|raw| UserValue::deserialize(&raw)) 66 + } 67 + 68 + fn resolve_did_for_uuid(&self, user_id: Uuid) -> Option<Did> { 69 + let user_hash = self.user_hashes.get(&user_id)?; 70 + self.resolve_user_value(user_hash) 71 + .and_then(|u| Did::new(u.did).ok()) 72 + } 73 + 74 + fn resolve_handle_for_uuid(&self, user_id: Uuid) -> Option<Handle> { 75 + let user_hash = self.user_hashes.get(&user_id)?; 76 + self.resolve_user_value(user_hash) 77 + .and_then(|u| Handle::new(u.handle).ok()) 78 + } 79 + 80 + fn value_to_queued_comms(&self, v: &QueuedCommsValue) -> Result<QueuedComms, MetastoreError> { 81 + let channel = 82 + u8_to_channel(v.channel).ok_or(MetastoreError::CorruptData("invalid comms channel"))?; 83 + let comms_type = u8_to_comms_type(v.comms_type) 84 + .ok_or(MetastoreError::CorruptData("invalid comms type"))?; 85 + let status = 86 + u8_to_status(v.status).ok_or(MetastoreError::CorruptData("invalid comms status"))?; 87 + Ok(QueuedComms { 88 + id: v.id, 89 + user_id: v.user_id, 90 + channel, 91 + comms_type, 92 + status, 93 + recipient: v.recipient.clone(), 94 + subject: v.subject.clone(), 95 + body: v.body.clone(), 96 + metadata: v 97 + .metadata 98 + .as_ref() 99 + .and_then(|b| serde_json::from_slice(b).ok()), 100 + attempts: v.attempts, 101 + max_attempts: v.max_attempts, 102 + last_error: v.error_message.clone(), 103 + created_at: DateTime::from_timestamp_millis(v.created_at_ms).unwrap_or_default(), 104 + updated_at: DateTime::from_timestamp_millis(v.sent_at_ms.unwrap_or(v.created_at_ms)) 105 + .unwrap_or_default(), 106 + scheduled_for: DateTime::from_timestamp_millis(v.scheduled_for_ms).unwrap_or_default(), 107 + processed_at: v.sent_at_ms.and_then(DateTime::from_timestamp_millis), 108 + }) 109 + } 110 + 111 + fn value_to_invite_info(&self, v: &InviteCodeValue) -> Result<InviteCodeInfo, MetastoreError> { 112 + Ok(InviteCodeInfo { 113 + code: v.code.clone(), 114 + available_uses: v.available_uses, 115 + state: InviteCodeState::from_disabled_flag(v.disabled), 116 + for_account: v 117 + .for_account 118 + .as_ref() 119 + .and_then(|d| Did::new(d.clone()).ok()), 120 + created_at: DateTime::from_timestamp_millis(v.created_at_ms).unwrap_or_default(), 121 + created_by: v.created_by.and_then(|uid| self.resolve_did_for_uuid(uid)), 122 + }) 123 + } 124 + 125 + fn user_to_admin_info(&self, u: &UserValue) -> Result<AdminAccountInfo, MetastoreError> { 126 + let did = Did::new(u.did.clone()) 127 + .map_err(|_| MetastoreError::CorruptData("invalid did in user record"))?; 128 + let handle = Handle::new(u.handle.clone()) 129 + .map_err(|_| MetastoreError::CorruptData("invalid handle in user record"))?; 130 + Ok(AdminAccountInfo { 131 + id: u.id, 132 + did, 133 + handle, 134 + email: u.email.clone(), 135 + created_at: DateTime::from_timestamp_millis(u.created_at_ms).unwrap_or_default(), 136 + invites_disabled: u.invites_disabled, 137 + email_verified: u.email_verified, 138 + deactivated_at: u 139 + .deactivated_at_ms 140 + .and_then(DateTime::from_timestamp_millis), 141 + }) 142 + } 143 + 144 + #[allow(clippy::too_many_arguments)] 145 + pub fn enqueue_comms( 146 + &self, 147 + user_id: Option<Uuid>, 148 + channel: CommsChannel, 149 + comms_type: CommsType, 150 + recipient: &str, 151 + subject: Option<&str>, 152 + body: &str, 153 + metadata: Option<serde_json::Value>, 154 + ) -> Result<Uuid, MetastoreError> { 155 + let id = Uuid::new_v4(); 156 + let now_ms = Utc::now().timestamp_millis(); 157 + 158 + let value = QueuedCommsValue { 159 + id, 160 + user_id, 161 + channel: channel_to_u8(channel), 162 + comms_type: comms_type_to_u8(comms_type), 163 + recipient: recipient.to_owned(), 164 + subject: subject.map(str::to_owned), 165 + body: body.to_owned(), 166 + metadata: metadata.map(|v| serde_json::to_vec(&v).unwrap_or_default()), 167 + status: status_to_u8(CommsStatus::Pending), 168 + error_message: None, 169 + attempts: 0, 170 + max_attempts: 3, 171 + created_at_ms: now_ms, 172 + scheduled_for_ms: now_ms, 173 + sent_at_ms: None, 174 + }; 175 + 176 + let queue_key = comms_queue_key(id); 177 + let mut batch = self.db.batch(); 178 + batch.insert(&self.infra, queue_key.as_slice(), value.serialize()); 179 + 180 + if let Some(uid) = user_id { 181 + let history_value = NotificationHistoryValue { 182 + id, 183 + channel: channel_to_u8(channel), 184 + comms_type: comms_type_to_u8(comms_type), 185 + recipient: recipient.to_owned(), 186 + subject: subject.map(str::to_owned), 187 + body: body.to_owned(), 188 + status: status_to_u8(CommsStatus::Pending), 189 + created_at_ms: now_ms, 190 + }; 191 + let history_key = comms_history_key(uid, now_ms, id); 192 + batch.insert( 193 + &self.infra, 194 + history_key.as_slice(), 195 + history_value.serialize(), 196 + ); 197 + } 198 + 199 + batch.commit().map_err(MetastoreError::Fjall)?; 200 + Ok(id) 201 + } 202 + 203 + pub fn fetch_pending_comms( 204 + &self, 205 + now: DateTime<Utc>, 206 + batch_size: i64, 207 + ) -> Result<Vec<QueuedComms>, MetastoreError> { 208 + let now_ms = now.timestamp_millis(); 209 + let limit = usize::try_from(batch_size).unwrap_or(0); 210 + let prefix = comms_queue_prefix(); 211 + 212 + self.infra 213 + .prefix(prefix.as_slice()) 214 + .map(|guard| -> Result<Option<QueuedComms>, MetastoreError> { 215 + let (_, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 216 + let val = QueuedCommsValue::deserialize(&val_bytes) 217 + .ok_or(MetastoreError::CorruptData("corrupt comms queue entry"))?; 218 + let is_pending = val.status == status_to_u8(CommsStatus::Pending); 219 + let is_scheduled = val.scheduled_for_ms <= now_ms; 220 + match is_pending && is_scheduled { 221 + true => Ok(Some(self.value_to_queued_comms(&val)?)), 222 + false => Ok(None), 223 + } 224 + }) 225 + .filter_map(Result::transpose) 226 + .take(limit) 227 + .collect() 228 + } 229 + 230 + pub fn mark_comms_sent(&self, id: Uuid) -> Result<(), MetastoreError> { 231 + let key = comms_queue_key(id); 232 + let mut val: QueuedCommsValue = point_lookup( 233 + &self.infra, 234 + key.as_slice(), 235 + QueuedCommsValue::deserialize, 236 + "corrupt comms queue entry", 237 + )? 238 + .ok_or(MetastoreError::InvalidInput("comms entry not found"))?; 239 + 240 + val.status = status_to_u8(CommsStatus::Sent); 241 + val.sent_at_ms = Some(Utc::now().timestamp_millis()); 242 + val.attempts = val.attempts.saturating_add(1); 243 + 244 + let mut batch = self.db.batch(); 245 + batch.insert(&self.infra, key.as_slice(), val.serialize()); 246 + 247 + let history_key = comms_history_key( 248 + val.user_id.unwrap_or(Uuid::nil()), 249 + val.created_at_ms, 250 + val.id, 251 + ); 252 + if let Some(mut history_val) = point_lookup( 253 + &self.infra, 254 + history_key.as_slice(), 255 + NotificationHistoryValue::deserialize, 256 + "corrupt notification history", 257 + )? { 258 + history_val.status = status_to_u8(CommsStatus::Sent); 259 + batch.insert(&self.infra, history_key.as_slice(), history_val.serialize()); 260 + } 261 + 262 + batch.commit().map_err(MetastoreError::Fjall) 263 + } 264 + 265 + pub fn mark_comms_failed(&self, id: Uuid, error: &str) -> Result<(), MetastoreError> { 266 + let key = comms_queue_key(id); 267 + let mut val: QueuedCommsValue = point_lookup( 268 + &self.infra, 269 + key.as_slice(), 270 + QueuedCommsValue::deserialize, 271 + "corrupt comms queue entry", 272 + )? 273 + .ok_or(MetastoreError::InvalidInput("comms entry not found"))?; 274 + 275 + val.status = status_to_u8(CommsStatus::Failed); 276 + val.error_message = Some(error.to_owned()); 277 + val.attempts = val.attempts.saturating_add(1); 278 + 279 + let mut batch = self.db.batch(); 280 + batch.insert(&self.infra, key.as_slice(), val.serialize()); 281 + 282 + let history_key = comms_history_key( 283 + val.user_id.unwrap_or(Uuid::nil()), 284 + val.created_at_ms, 285 + val.id, 286 + ); 287 + if let Some(mut history_val) = point_lookup( 288 + &self.infra, 289 + history_key.as_slice(), 290 + NotificationHistoryValue::deserialize, 291 + "corrupt notification history", 292 + )? { 293 + history_val.status = status_to_u8(CommsStatus::Failed); 294 + batch.insert(&self.infra, history_key.as_slice(), history_val.serialize()); 295 + } 296 + 297 + batch.commit().map_err(MetastoreError::Fjall) 298 + } 299 + 300 + pub fn create_invite_code( 301 + &self, 302 + code: &str, 303 + use_count: i32, 304 + for_account: Option<&Did>, 305 + ) -> Result<bool, MetastoreError> { 306 + let key = invite_code_key(code); 307 + let existing = self 308 + .infra 309 + .get(key.as_slice()) 310 + .map_err(MetastoreError::Fjall)?; 311 + if existing.is_some() { 312 + return Ok(false); 313 + } 314 + 315 + let value = InviteCodeValue { 316 + code: code.to_owned(), 317 + available_uses: use_count, 318 + disabled: false, 319 + for_account: for_account.map(|d| d.to_string()), 320 + created_by: None, 321 + created_at_ms: Utc::now().timestamp_millis(), 322 + }; 323 + 324 + self.infra 325 + .insert(key.as_slice(), value.serialize()) 326 + .map_err(MetastoreError::Fjall)?; 327 + Ok(true) 328 + } 329 + 330 + pub fn create_invite_codes_batch( 331 + &self, 332 + codes: &[String], 333 + use_count: i32, 334 + created_by_user: Uuid, 335 + for_account: Option<&Did>, 336 + ) -> Result<(), MetastoreError> { 337 + let now_ms = Utc::now().timestamp_millis(); 338 + let mut batch = self.db.batch(); 339 + 340 + codes.iter().try_for_each(|code| { 341 + let value = InviteCodeValue { 342 + code: code.clone(), 343 + available_uses: use_count, 344 + disabled: false, 345 + for_account: for_account.map(|d| d.to_string()), 346 + created_by: Some(created_by_user), 347 + created_at_ms: now_ms, 348 + }; 349 + let key = invite_code_key(code); 350 + batch.insert(&self.infra, key.as_slice(), value.serialize()); 351 + 352 + Ok::<(), MetastoreError>(()) 353 + })?; 354 + 355 + batch.commit().map_err(MetastoreError::Fjall) 356 + } 357 + 358 + pub fn get_invite_code_available_uses( 359 + &self, 360 + code: &str, 361 + ) -> Result<Option<i32>, MetastoreError> { 362 + let key = invite_code_key(code); 363 + let val: Option<InviteCodeValue> = point_lookup( 364 + &self.infra, 365 + key.as_slice(), 366 + InviteCodeValue::deserialize, 367 + "corrupt invite code", 368 + )?; 369 + Ok(val.map(|v| v.available_uses)) 370 + } 371 + 372 + pub fn validate_invite_code<'a>( 373 + &self, 374 + code: &'a str, 375 + ) -> Result<ValidatedInviteCode<'a>, InviteCodeError> { 376 + let key = invite_code_key(code); 377 + let val: Option<InviteCodeValue> = point_lookup( 378 + &self.infra, 379 + key.as_slice(), 380 + InviteCodeValue::deserialize, 381 + "corrupt invite code", 382 + ) 383 + .map_err(|e| { 384 + InviteCodeError::DatabaseError(tranquil_db_traits::DbError::Query(e.to_string())) 385 + })?; 386 + 387 + match val { 388 + None => Err(InviteCodeError::NotFound), 389 + Some(v) if v.disabled => Err(InviteCodeError::Disabled), 390 + Some(v) if v.available_uses <= 0 => Err(InviteCodeError::ExhaustedUses), 391 + Some(_) => Ok(ValidatedInviteCode::new_validated(code)), 392 + } 393 + } 394 + 395 + pub fn decrement_invite_code_uses( 396 + &self, 397 + code: &ValidatedInviteCode<'_>, 398 + ) -> Result<(), MetastoreError> { 399 + let key = invite_code_key(code.code()); 400 + let mut val: InviteCodeValue = point_lookup( 401 + &self.infra, 402 + key.as_slice(), 403 + InviteCodeValue::deserialize, 404 + "corrupt invite code", 405 + )? 406 + .ok_or(MetastoreError::InvalidInput("invite code not found"))?; 407 + 408 + val.available_uses = val.available_uses.saturating_sub(1); 409 + self.infra 410 + .insert(key.as_slice(), val.serialize()) 411 + .map_err(MetastoreError::Fjall) 412 + } 413 + 414 + pub fn record_invite_code_use( 415 + &self, 416 + code: &ValidatedInviteCode<'_>, 417 + used_by_user: Uuid, 418 + ) -> Result<(), MetastoreError> { 419 + let now_ms = Utc::now().timestamp_millis(); 420 + let use_value = InviteCodeUseValue { 421 + used_by: used_by_user, 422 + used_at_ms: now_ms, 423 + }; 424 + 425 + let use_key = invite_use_key(code.code(), used_by_user); 426 + let used_by_key = invite_code_used_by_key(used_by_user); 427 + 428 + let mut batch = self.db.batch(); 429 + batch.insert(&self.infra, use_key.as_slice(), use_value.serialize()); 430 + batch.insert(&self.infra, used_by_key.as_slice(), code.code().as_bytes()); 431 + batch.commit().map_err(MetastoreError::Fjall) 432 + } 433 + 434 + pub fn get_invite_codes_for_account( 435 + &self, 436 + for_account: &Did, 437 + ) -> Result<Vec<InviteCodeInfo>, MetastoreError> { 438 + let did_str = for_account.to_string(); 439 + let prefix = invite_code_prefix(); 440 + 441 + self.infra 442 + .prefix(prefix.as_slice()) 443 + .try_fold(Vec::new(), |mut acc, guard| { 444 + let (_, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 445 + let val = InviteCodeValue::deserialize(&val_bytes) 446 + .ok_or(MetastoreError::CorruptData("corrupt invite code"))?; 447 + let matches = val.for_account.as_ref().is_some_and(|d| *d == did_str); 448 + if matches { 449 + acc.push(self.value_to_invite_info(&val)?); 450 + } 451 + Ok(acc) 452 + }) 453 + } 454 + 455 + pub fn get_invite_code_uses(&self, code: &str) -> Result<Vec<InviteCodeUse>, MetastoreError> { 456 + let prefix = invite_use_prefix(code); 457 + 458 + self.infra 459 + .prefix(prefix.as_slice()) 460 + .try_fold(Vec::new(), |mut acc, guard| { 461 + let (_, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 462 + let val = InviteCodeUseValue::deserialize(&val_bytes) 463 + .ok_or(MetastoreError::CorruptData("corrupt invite use"))?; 464 + let used_by_did = self 465 + .resolve_did_for_uuid(val.used_by) 466 + .unwrap_or_else(|| Did::new("did:plc:unknown".to_owned()).unwrap()); 467 + let used_by_handle = self.resolve_handle_for_uuid(val.used_by); 468 + acc.push(InviteCodeUse { 469 + code: code.to_owned(), 470 + used_by_did, 471 + used_by_handle, 472 + used_at: DateTime::from_timestamp_millis(val.used_at_ms).unwrap_or_default(), 473 + }); 474 + Ok(acc) 475 + }) 476 + } 477 + 478 + pub fn disable_invite_codes_by_code(&self, codes: &[String]) -> Result<(), MetastoreError> { 479 + let mut batch = self.db.batch(); 480 + 481 + codes.iter().try_for_each(|code| { 482 + let key = invite_code_key(code); 483 + let val: Option<InviteCodeValue> = point_lookup( 484 + &self.infra, 485 + key.as_slice(), 486 + InviteCodeValue::deserialize, 487 + "corrupt invite code", 488 + )?; 489 + if let Some(mut v) = val { 490 + v.disabled = true; 491 + batch.insert(&self.infra, key.as_slice(), v.serialize()); 492 + } 493 + Ok::<(), MetastoreError>(()) 494 + })?; 495 + 496 + batch.commit().map_err(MetastoreError::Fjall) 497 + } 498 + 499 + pub fn disable_invite_codes_by_account(&self, accounts: &[Did]) -> Result<(), MetastoreError> { 500 + let account_strs: Vec<String> = accounts.iter().map(|d| d.to_string()).collect(); 501 + let prefix = invite_code_prefix(); 502 + let mut batch = self.db.batch(); 503 + 504 + self.infra.prefix(prefix.as_slice()).try_for_each(|guard| { 505 + let (key_bytes, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 506 + let mut val = InviteCodeValue::deserialize(&val_bytes) 507 + .ok_or(MetastoreError::CorruptData("corrupt invite code"))?; 508 + let matches = val 509 + .for_account 510 + .as_ref() 511 + .is_some_and(|d| account_strs.iter().any(|a| a == d)); 512 + if matches { 513 + val.disabled = true; 514 + batch.insert(&self.infra, key_bytes.as_ref(), val.serialize()); 515 + } 516 + Ok::<(), MetastoreError>(()) 517 + })?; 518 + 519 + batch.commit().map_err(MetastoreError::Fjall) 520 + } 521 + 522 + pub fn list_invite_codes( 523 + &self, 524 + cursor: Option<&str>, 525 + limit: i64, 526 + sort: InviteCodeSortOrder, 527 + ) -> Result<Vec<InviteCodeRow>, MetastoreError> { 528 + let prefix = invite_code_prefix(); 529 + let limit = usize::try_from(limit).unwrap_or(0); 530 + 531 + let mut rows: Vec<InviteCodeRow> = 532 + self.infra 533 + .prefix(prefix.as_slice()) 534 + .try_fold(Vec::new(), |mut acc, guard| { 535 + let (_, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 536 + let val = InviteCodeValue::deserialize(&val_bytes) 537 + .ok_or(MetastoreError::CorruptData("corrupt invite code"))?; 538 + let created_by_user = val.created_by.unwrap_or(Uuid::nil()); 539 + acc.push(InviteCodeRow { 540 + code: val.code, 541 + available_uses: val.available_uses, 542 + disabled: Some(val.disabled), 543 + created_by_user, 544 + created_at: DateTime::from_timestamp_millis(val.created_at_ms) 545 + .unwrap_or_default(), 546 + }); 547 + Ok::<_, MetastoreError>(acc) 548 + })?; 549 + 550 + match sort { 551 + InviteCodeSortOrder::Recent => rows.sort_by_key(|r| std::cmp::Reverse(r.created_at)), 552 + InviteCodeSortOrder::Usage => rows.sort_by_key(|r| r.available_uses), 553 + } 554 + 555 + let result = match cursor { 556 + Some(c) => rows 557 + .into_iter() 558 + .skip_while(|r| r.code != c) 559 + .skip(1) 560 + .take(limit) 561 + .collect(), 562 + None => rows.into_iter().take(limit).collect(), 563 + }; 564 + 565 + Ok(result) 566 + } 567 + 568 + pub fn get_user_dids_by_ids( 569 + &self, 570 + user_ids: &[Uuid], 571 + ) -> Result<Vec<(Uuid, Did)>, MetastoreError> { 572 + user_ids 573 + .iter() 574 + .filter_map(|&uid| self.resolve_did_for_uuid(uid).map(|did| Ok((uid, did)))) 575 + .collect() 576 + } 577 + 578 + pub fn get_invite_code_uses_batch( 579 + &self, 580 + codes: &[String], 581 + ) -> Result<Vec<InviteCodeUse>, MetastoreError> { 582 + codes.iter().try_fold(Vec::new(), |mut acc, code| { 583 + let uses = self.get_invite_code_uses(code)?; 584 + acc.extend(uses); 585 + Ok(acc) 586 + }) 587 + } 588 + 589 + pub fn get_invites_created_by_user( 590 + &self, 591 + user_id: Uuid, 592 + ) -> Result<Vec<InviteCodeInfo>, MetastoreError> { 593 + let prefix = invite_code_prefix(); 594 + 595 + self.infra 596 + .prefix(prefix.as_slice()) 597 + .try_fold(Vec::new(), |mut acc, guard| { 598 + let (_, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 599 + let val = InviteCodeValue::deserialize(&val_bytes) 600 + .ok_or(MetastoreError::CorruptData("corrupt invite code"))?; 601 + if val.created_by == Some(user_id) { 602 + acc.push(self.value_to_invite_info(&val)?); 603 + } 604 + Ok(acc) 605 + }) 606 + } 607 + 608 + pub fn get_invite_code_info( 609 + &self, 610 + code: &str, 611 + ) -> Result<Option<InviteCodeInfo>, MetastoreError> { 612 + let key = invite_code_key(code); 613 + let val: Option<InviteCodeValue> = point_lookup( 614 + &self.infra, 615 + key.as_slice(), 616 + InviteCodeValue::deserialize, 617 + "corrupt invite code", 618 + )?; 619 + val.map(|v| self.value_to_invite_info(&v)).transpose() 620 + } 621 + 622 + pub fn get_invite_codes_by_users( 623 + &self, 624 + user_ids: &[Uuid], 625 + ) -> Result<Vec<(Uuid, InviteCodeInfo)>, MetastoreError> { 626 + let prefix = invite_code_prefix(); 627 + 628 + self.infra 629 + .prefix(prefix.as_slice()) 630 + .try_fold(Vec::new(), |mut acc, guard| { 631 + let (_, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 632 + let val = InviteCodeValue::deserialize(&val_bytes) 633 + .ok_or(MetastoreError::CorruptData("corrupt invite code"))?; 634 + if let Some(uid) = val.created_by.filter(|u| user_ids.contains(u)) { 635 + acc.push((uid, self.value_to_invite_info(&val)?)); 636 + } 637 + Ok(acc) 638 + }) 639 + } 640 + 641 + pub fn get_invite_code_used_by_user( 642 + &self, 643 + user_id: Uuid, 644 + ) -> Result<Option<String>, MetastoreError> { 645 + let key = invite_code_used_by_key(user_id); 646 + match self 647 + .infra 648 + .get(key.as_slice()) 649 + .map_err(MetastoreError::Fjall)? 650 + { 651 + Some(raw) => String::from_utf8(raw.to_vec()) 652 + .map(Some) 653 + .map_err(|_| MetastoreError::CorruptData("invite code used_by not valid utf8")), 654 + None => Ok(None), 655 + } 656 + } 657 + 658 + pub fn delete_invite_code_uses_by_user(&self, user_id: Uuid) -> Result<(), MetastoreError> { 659 + let used_by_key = invite_code_used_by_key(user_id); 660 + let code = match self 661 + .infra 662 + .get(used_by_key.as_slice()) 663 + .map_err(MetastoreError::Fjall)? 664 + { 665 + Some(raw) => String::from_utf8(raw.to_vec()) 666 + .map_err(|_| MetastoreError::CorruptData("invite code used_by not valid utf8"))?, 667 + None => return Ok(()), 668 + }; 669 + 670 + let use_key = invite_use_key(&code, user_id); 671 + let mut batch = self.db.batch(); 672 + batch.remove(&self.infra, use_key.as_slice()); 673 + batch.remove(&self.infra, used_by_key.as_slice()); 674 + batch.commit().map_err(MetastoreError::Fjall) 675 + } 676 + 677 + pub fn delete_invite_codes_by_user(&self, user_id: Uuid) -> Result<(), MetastoreError> { 678 + let prefix = invite_code_prefix(); 679 + let mut batch = self.db.batch(); 680 + 681 + self.infra.prefix(prefix.as_slice()).try_for_each(|guard| { 682 + let (key_bytes, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 683 + let val = InviteCodeValue::deserialize(&val_bytes) 684 + .ok_or(MetastoreError::CorruptData("corrupt invite code"))?; 685 + if val.created_by == Some(user_id) { 686 + batch.remove(&self.infra, key_bytes.as_ref()); 687 + let use_pfx = invite_use_prefix(&val.code); 688 + delete_all_by_prefix(&self.infra, &mut batch, use_pfx.as_slice())?; 689 + } 690 + Ok::<(), MetastoreError>(()) 691 + })?; 692 + 693 + let user_key = invite_by_user_key(user_id); 694 + batch.remove(&self.infra, user_key.as_slice()); 695 + batch.commit().map_err(MetastoreError::Fjall) 696 + } 697 + 698 + pub fn reserve_signing_key( 699 + &self, 700 + did: Option<&Did>, 701 + public_key_did_key: &str, 702 + private_key_bytes: &[u8], 703 + expires_at: DateTime<Utc>, 704 + ) -> Result<Uuid, MetastoreError> { 705 + let id = Uuid::new_v4(); 706 + let now_ms = Utc::now().timestamp_millis(); 707 + 708 + let value = SigningKeyValue { 709 + id, 710 + did: did.map(|d| d.to_string()), 711 + public_key_did_key: public_key_did_key.to_owned(), 712 + private_key_bytes: private_key_bytes.to_vec(), 713 + used: false, 714 + created_at_ms: now_ms, 715 + expires_at_ms: expires_at.timestamp_millis(), 716 + }; 717 + 718 + let primary_key = signing_key_key(public_key_did_key); 719 + let id_index_key = signing_key_by_id_key(id); 720 + 721 + let mut batch = self.db.batch(); 722 + batch.insert(&self.infra, primary_key.as_slice(), value.serialize()); 723 + batch.insert( 724 + &self.infra, 725 + id_index_key.as_slice(), 726 + public_key_did_key.as_bytes(), 727 + ); 728 + batch.commit().map_err(MetastoreError::Fjall)?; 729 + 730 + Ok(id) 731 + } 732 + 733 + pub fn get_reserved_signing_key( 734 + &self, 735 + public_key_did_key: &str, 736 + ) -> Result<Option<ReservedSigningKey>, MetastoreError> { 737 + let key = signing_key_key(public_key_did_key); 738 + let val: Option<SigningKeyValue> = point_lookup( 739 + &self.infra, 740 + key.as_slice(), 741 + SigningKeyValue::deserialize, 742 + "corrupt signing key", 743 + )?; 744 + Ok(val.map(|v| ReservedSigningKey { 745 + id: v.id, 746 + private_key_bytes: v.private_key_bytes, 747 + })) 748 + } 749 + 750 + pub fn mark_signing_key_used(&self, key_id: Uuid) -> Result<(), MetastoreError> { 751 + let id_key = signing_key_by_id_key(key_id); 752 + let pub_key_str = match self 753 + .infra 754 + .get(id_key.as_slice()) 755 + .map_err(MetastoreError::Fjall)? 756 + { 757 + Some(raw) => String::from_utf8(raw.to_vec()) 758 + .map_err(|_| MetastoreError::CorruptData("signing key index not valid utf8"))?, 759 + None => return Ok(()), 760 + }; 761 + 762 + let primary_key = signing_key_key(&pub_key_str); 763 + let mut val: SigningKeyValue = point_lookup( 764 + &self.infra, 765 + primary_key.as_slice(), 766 + SigningKeyValue::deserialize, 767 + "corrupt signing key", 768 + )? 769 + .ok_or(MetastoreError::CorruptData( 770 + "signing key missing from primary", 771 + ))?; 772 + 773 + val.used = true; 774 + self.infra 775 + .insert(primary_key.as_slice(), val.serialize()) 776 + .map_err(MetastoreError::Fjall) 777 + } 778 + 779 + pub fn create_deletion_request( 780 + &self, 781 + token: &str, 782 + did: &Did, 783 + expires_at: DateTime<Utc>, 784 + ) -> Result<(), MetastoreError> { 785 + let now_ms = Utc::now().timestamp_millis(); 786 + let value = DeletionRequestValue { 787 + token: token.to_owned(), 788 + did: did.to_string(), 789 + created_at_ms: now_ms, 790 + expires_at_ms: expires_at.timestamp_millis(), 791 + }; 792 + 793 + let primary_key = deletion_request_key(token); 794 + let did_key = deletion_by_did_key(did.as_str()); 795 + 796 + let mut batch = self.db.batch(); 797 + batch.insert(&self.infra, primary_key.as_slice(), value.serialize()); 798 + batch.insert(&self.infra, did_key.as_slice(), token.as_bytes()); 799 + batch.commit().map_err(MetastoreError::Fjall) 800 + } 801 + 802 + pub fn get_deletion_request( 803 + &self, 804 + token: &str, 805 + ) -> Result<Option<DeletionRequest>, MetastoreError> { 806 + let key = deletion_request_key(token); 807 + let val: Option<DeletionRequestValue> = point_lookup( 808 + &self.infra, 809 + key.as_slice(), 810 + DeletionRequestValue::deserialize, 811 + "corrupt deletion request", 812 + )?; 813 + Ok(val.and_then(|v| { 814 + Did::new(v.did).ok().map(|did| DeletionRequest { 815 + did, 816 + expires_at: DateTime::from_timestamp_millis(v.expires_at_ms).unwrap_or_default(), 817 + }) 818 + })) 819 + } 820 + 821 + pub fn delete_deletion_request(&self, token: &str) -> Result<(), MetastoreError> { 822 + let primary_key = deletion_request_key(token); 823 + 824 + let val: Option<DeletionRequestValue> = point_lookup( 825 + &self.infra, 826 + primary_key.as_slice(), 827 + DeletionRequestValue::deserialize, 828 + "corrupt deletion request", 829 + )?; 830 + 831 + let mut batch = self.db.batch(); 832 + batch.remove(&self.infra, primary_key.as_slice()); 833 + 834 + if let Some(v) = val { 835 + let did_key = deletion_by_did_key(&v.did); 836 + batch.remove(&self.infra, did_key.as_slice()); 837 + } 838 + 839 + batch.commit().map_err(MetastoreError::Fjall) 840 + } 841 + 842 + pub fn delete_deletion_requests_by_did(&self, did: &Did) -> Result<(), MetastoreError> { 843 + let did_key = deletion_by_did_key(did.as_str()); 844 + let token = match self 845 + .infra 846 + .get(did_key.as_slice()) 847 + .map_err(MetastoreError::Fjall)? 848 + { 849 + Some(raw) => String::from_utf8(raw.to_vec()) 850 + .map_err(|_| MetastoreError::CorruptData("deletion by_did not valid utf8"))?, 851 + None => return Ok(()), 852 + }; 853 + 854 + let primary_key = deletion_request_key(&token); 855 + let mut batch = self.db.batch(); 856 + batch.remove(&self.infra, primary_key.as_slice()); 857 + batch.remove(&self.infra, did_key.as_slice()); 858 + batch.commit().map_err(MetastoreError::Fjall) 859 + } 860 + 861 + pub fn upsert_account_preference( 862 + &self, 863 + user_id: Uuid, 864 + name: &str, 865 + value_json: serde_json::Value, 866 + ) -> Result<(), MetastoreError> { 867 + let key = account_pref_key(user_id, name); 868 + let bytes = serde_json::to_vec(&value_json) 869 + .map_err(|_| MetastoreError::InvalidInput("invalid json for account preference"))?; 870 + self.infra 871 + .insert(key.as_slice(), bytes) 872 + .map_err(MetastoreError::Fjall) 873 + } 874 + 875 + pub fn insert_account_preference_if_not_exists( 876 + &self, 877 + user_id: Uuid, 878 + name: &str, 879 + value_json: serde_json::Value, 880 + ) -> Result<(), MetastoreError> { 881 + let key = account_pref_key(user_id, name); 882 + let existing = self 883 + .infra 884 + .get(key.as_slice()) 885 + .map_err(MetastoreError::Fjall)?; 886 + if existing.is_some() { 887 + return Ok(()); 888 + } 889 + let bytes = serde_json::to_vec(&value_json) 890 + .map_err(|_| MetastoreError::InvalidInput("invalid json for account preference"))?; 891 + self.infra 892 + .insert(key.as_slice(), bytes) 893 + .map_err(MetastoreError::Fjall) 894 + } 895 + 896 + pub fn get_account_preferences( 897 + &self, 898 + user_id: Uuid, 899 + ) -> Result<Vec<(String, serde_json::Value)>, MetastoreError> { 900 + let prefix = account_pref_prefix(user_id); 901 + 902 + self.infra 903 + .prefix(prefix.as_slice()) 904 + .try_fold(Vec::new(), |mut acc, guard| { 905 + let (key_bytes, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 906 + let mut reader = super::encoding::KeyReader::new(&key_bytes); 907 + reader.tag(); 908 + reader.bytes(); 909 + let name = reader 910 + .string() 911 + .ok_or(MetastoreError::CorruptData("corrupt account pref key"))?; 912 + let value: serde_json::Value = serde_json::from_slice(&val_bytes) 913 + .map_err(|_| MetastoreError::CorruptData("corrupt account pref json"))?; 914 + acc.push((name, value)); 915 + Ok(acc) 916 + }) 917 + } 918 + 919 + pub fn replace_namespace_preferences( 920 + &self, 921 + user_id: Uuid, 922 + namespace: &str, 923 + preferences: Vec<(String, serde_json::Value)>, 924 + ) -> Result<(), MetastoreError> { 925 + let prefix = account_pref_prefix(user_id); 926 + let mut batch = self.db.batch(); 927 + 928 + self.infra.prefix(prefix.as_slice()).try_for_each(|guard| { 929 + let (key_bytes, _) = guard.into_inner().map_err(MetastoreError::Fjall)?; 930 + let mut reader = super::encoding::KeyReader::new(&key_bytes); 931 + reader.tag(); 932 + reader.bytes(); 933 + let name = reader 934 + .string() 935 + .ok_or(MetastoreError::CorruptData("corrupt account pref key"))?; 936 + if name.starts_with(namespace) { 937 + batch.remove(&self.infra, key_bytes.as_ref()); 938 + } 939 + Ok::<(), MetastoreError>(()) 940 + })?; 941 + 942 + preferences.iter().try_for_each(|(name, value)| { 943 + let key = account_pref_key(user_id, name); 944 + let bytes = serde_json::to_vec(value) 945 + .map_err(|_| MetastoreError::InvalidInput("invalid json for account preference"))?; 946 + batch.insert(&self.infra, key.as_slice(), bytes); 947 + Ok::<(), MetastoreError>(()) 948 + })?; 949 + 950 + batch.commit().map_err(MetastoreError::Fjall) 951 + } 952 + 953 + pub fn get_server_config(&self, key: &str) -> Result<Option<String>, MetastoreError> { 954 + let k = server_config_key(key); 955 + match self 956 + .infra 957 + .get(k.as_slice()) 958 + .map_err(MetastoreError::Fjall)? 959 + { 960 + Some(raw) => String::from_utf8(raw.to_vec()) 961 + .map(Some) 962 + .map_err(|_| MetastoreError::CorruptData("server config not valid utf8")), 963 + None => Ok(None), 964 + } 965 + } 966 + 967 + pub fn get_server_configs( 968 + &self, 969 + keys: &[&str], 970 + ) -> Result<Vec<(String, String)>, MetastoreError> { 971 + keys.iter() 972 + .filter_map(|&key| { 973 + let k = server_config_key(key); 974 + match self.infra.get(k.as_slice()) { 975 + Ok(Some(raw)) => String::from_utf8(raw.to_vec()) 976 + .ok() 977 + .map(|v| Ok((key.to_owned(), v))), 978 + Ok(None) => None, 979 + Err(e) => Some(Err(MetastoreError::Fjall(e))), 980 + } 981 + }) 982 + .collect() 983 + } 984 + 985 + pub fn upsert_server_config(&self, key: &str, value: &str) -> Result<(), MetastoreError> { 986 + let k = server_config_key(key); 987 + self.infra 988 + .insert(k.as_slice(), value.as_bytes()) 989 + .map_err(MetastoreError::Fjall) 990 + } 991 + 992 + pub fn delete_server_config(&self, key: &str) -> Result<(), MetastoreError> { 993 + let k = server_config_key(key); 994 + self.infra 995 + .remove(k.as_slice()) 996 + .map_err(MetastoreError::Fjall) 997 + } 998 + 999 + pub fn health_check(&self) -> Result<bool, MetastoreError> { 1000 + Ok(true) 1001 + } 1002 + 1003 + pub fn insert_report( 1004 + &self, 1005 + id: i64, 1006 + reason_type: &str, 1007 + reason: Option<&str>, 1008 + subject_json: serde_json::Value, 1009 + reported_by_did: &Did, 1010 + created_at: DateTime<Utc>, 1011 + ) -> Result<(), MetastoreError> { 1012 + let value = ReportValue { 1013 + id, 1014 + reason_type: reason_type.to_owned(), 1015 + reason: reason.map(str::to_owned), 1016 + subject_json: serde_json::to_vec(&subject_json).unwrap_or_default(), 1017 + reported_by_did: reported_by_did.to_string(), 1018 + created_at_ms: created_at.timestamp_millis(), 1019 + }; 1020 + 1021 + let key = report_key(id); 1022 + self.infra 1023 + .insert(key.as_slice(), value.serialize()) 1024 + .map_err(MetastoreError::Fjall) 1025 + } 1026 + 1027 + pub fn delete_plc_tokens_for_user(&self, user_id: Uuid) -> Result<(), MetastoreError> { 1028 + let prefix = plc_token_prefix(user_id); 1029 + let mut batch = self.db.batch(); 1030 + delete_all_by_prefix(&self.infra, &mut batch, prefix.as_slice())?; 1031 + batch.commit().map_err(MetastoreError::Fjall) 1032 + } 1033 + 1034 + pub fn insert_plc_token( 1035 + &self, 1036 + user_id: Uuid, 1037 + token: &str, 1038 + expires_at: DateTime<Utc>, 1039 + ) -> Result<(), MetastoreError> { 1040 + let key = plc_token_key(user_id, token); 1041 + let expires_at_ms = expires_at.timestamp_millis(); 1042 + self.infra 1043 + .insert(key.as_slice(), expires_at_ms.to_be_bytes()) 1044 + .map_err(MetastoreError::Fjall) 1045 + } 1046 + 1047 + pub fn get_plc_token_expiry( 1048 + &self, 1049 + user_id: Uuid, 1050 + token: &str, 1051 + ) -> Result<Option<DateTime<Utc>>, MetastoreError> { 1052 + let key = plc_token_key(user_id, token); 1053 + match self 1054 + .infra 1055 + .get(key.as_slice()) 1056 + .map_err(MetastoreError::Fjall)? 1057 + { 1058 + Some(raw) => { 1059 + let arr: [u8; 8] = raw 1060 + .as_ref() 1061 + .try_into() 1062 + .map_err(|_| MetastoreError::CorruptData("plc token expiry not 8 bytes"))?; 1063 + let ms = i64::from_be_bytes(arr); 1064 + Ok(DateTime::from_timestamp_millis(ms)) 1065 + } 1066 + None => Ok(None), 1067 + } 1068 + } 1069 + 1070 + pub fn delete_plc_token(&self, user_id: Uuid, token: &str) -> Result<(), MetastoreError> { 1071 + let key = plc_token_key(user_id, token); 1072 + self.infra 1073 + .remove(key.as_slice()) 1074 + .map_err(MetastoreError::Fjall) 1075 + } 1076 + 1077 + pub fn get_notification_history( 1078 + &self, 1079 + user_id: Uuid, 1080 + limit: i64, 1081 + ) -> Result<Vec<NotificationHistoryRow>, MetastoreError> { 1082 + let prefix = comms_history_prefix(user_id); 1083 + let limit = usize::try_from(limit).unwrap_or(0); 1084 + 1085 + self.infra 1086 + .prefix(prefix.as_slice()) 1087 + .take(limit) 1088 + .try_fold(Vec::new(), |mut acc, guard| { 1089 + let (_, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 1090 + let val = NotificationHistoryValue::deserialize(&val_bytes) 1091 + .ok_or(MetastoreError::CorruptData("corrupt notification history"))?; 1092 + let channel = u8_to_channel(val.channel) 1093 + .ok_or(MetastoreError::CorruptData("invalid history channel"))?; 1094 + let comms_type = u8_to_comms_type(val.comms_type) 1095 + .ok_or(MetastoreError::CorruptData("invalid history comms type"))?; 1096 + let status = u8_to_status(val.status) 1097 + .ok_or(MetastoreError::CorruptData("invalid history status"))?; 1098 + acc.push(NotificationHistoryRow { 1099 + created_at: DateTime::from_timestamp_millis(val.created_at_ms) 1100 + .unwrap_or_default(), 1101 + channel, 1102 + comms_type, 1103 + status, 1104 + subject: val.subject, 1105 + body: val.body, 1106 + }); 1107 + Ok(acc) 1108 + }) 1109 + } 1110 + 1111 + pub fn get_blob_storage_key_by_cid( 1112 + &self, 1113 + cid: &CidLink, 1114 + ) -> Result<Option<String>, MetastoreError> { 1115 + let cid_str = cid.as_str(); 1116 + let cid_index_key = blob_by_cid_key(cid_str); 1117 + let user_hash_raw = match self 1118 + .repo_data 1119 + .get(cid_index_key.as_slice()) 1120 + .map_err(MetastoreError::Fjall)? 1121 + { 1122 + Some(raw) => { 1123 + let arr: [u8; 8] = raw 1124 + .as_ref() 1125 + .try_into() 1126 + .map_err(|_| MetastoreError::CorruptData("blob_by_cid value not 8 bytes"))?; 1127 + u64::from_be_bytes(arr) 1128 + } 1129 + None => return Ok(None), 1130 + }; 1131 + let user_hash = UserHash::from_raw(user_hash_raw); 1132 + let key = blob_meta_key(user_hash, cid_str); 1133 + let val: Option<BlobMetaValue> = point_lookup( 1134 + &self.repo_data, 1135 + key.as_slice(), 1136 + BlobMetaValue::deserialize, 1137 + "corrupt blob_meta value", 1138 + )?; 1139 + Ok(val.map(|v| v.storage_key)) 1140 + } 1141 + 1142 + pub fn delete_blob_by_cid(&self, cid: &CidLink) -> Result<(), MetastoreError> { 1143 + let cid_str = cid.as_str(); 1144 + let cid_index_key = blob_by_cid_key(cid_str); 1145 + let user_hash_raw = match self 1146 + .repo_data 1147 + .get(cid_index_key.as_slice()) 1148 + .map_err(MetastoreError::Fjall)? 1149 + { 1150 + Some(raw) => { 1151 + let arr: [u8; 8] = raw 1152 + .as_ref() 1153 + .try_into() 1154 + .map_err(|_| MetastoreError::CorruptData("blob_by_cid value not 8 bytes"))?; 1155 + u64::from_be_bytes(arr) 1156 + } 1157 + None => return Ok(()), 1158 + }; 1159 + let user_hash = UserHash::from_raw(user_hash_raw); 1160 + let primary_key = blob_meta_key(user_hash, cid_str); 1161 + 1162 + let mut batch = self.db.batch(); 1163 + batch.remove(&self.repo_data, primary_key.as_slice()); 1164 + batch.remove(&self.repo_data, cid_index_key.as_slice()); 1165 + batch.commit().map_err(MetastoreError::Fjall) 1166 + } 1167 + 1168 + pub fn get_admin_account_info_by_did( 1169 + &self, 1170 + did: &Did, 1171 + ) -> Result<Option<AdminAccountInfo>, MetastoreError> { 1172 + let user_hash = UserHash::from_did(did.as_str()); 1173 + self.resolve_user_value(user_hash) 1174 + .map(|u| self.user_to_admin_info(&u)) 1175 + .transpose() 1176 + } 1177 + 1178 + pub fn get_admin_account_infos_by_dids( 1179 + &self, 1180 + dids: &[Did], 1181 + ) -> Result<Vec<AdminAccountInfo>, MetastoreError> { 1182 + dids.iter() 1183 + .filter_map(|did| { 1184 + let user_hash = UserHash::from_did(did.as_str()); 1185 + self.resolve_user_value(user_hash) 1186 + .map(|u| self.user_to_admin_info(&u)) 1187 + }) 1188 + .collect() 1189 + } 1190 + 1191 + pub fn get_invite_code_uses_by_users( 1192 + &self, 1193 + user_ids: &[Uuid], 1194 + ) -> Result<Vec<(Uuid, String)>, MetastoreError> { 1195 + user_ids 1196 + .iter() 1197 + .filter_map(|&uid| { 1198 + let key = invite_code_used_by_key(uid); 1199 + match self.infra.get(key.as_slice()) { 1200 + Ok(Some(raw)) => String::from_utf8(raw.to_vec()) 1201 + .ok() 1202 + .map(|code| Ok((uid, code))), 1203 + Ok(None) => None, 1204 + Err(e) => Some(Err(MetastoreError::Fjall(e))), 1205 + } 1206 + }) 1207 + .collect() 1208 + } 1209 + }
+730
crates/tranquil-store/src/metastore/infra_schema.rs
··· 1 + use serde::{Deserialize, Serialize}; 2 + use smallvec::SmallVec; 3 + 4 + use super::encoding::KeyBuilder; 5 + use super::keys::KeyTag; 6 + 7 + const COMMS_SCHEMA_VERSION: u8 = 1; 8 + const INVITE_CODE_SCHEMA_VERSION: u8 = 1; 9 + const INVITE_USE_SCHEMA_VERSION: u8 = 1; 10 + const SIGNING_KEY_SCHEMA_VERSION: u8 = 1; 11 + const DELETION_REQUEST_SCHEMA_VERSION: u8 = 1; 12 + const REPORT_SCHEMA_VERSION: u8 = 1; 13 + const NOTIFICATION_HISTORY_SCHEMA_VERSION: u8 = 1; 14 + 15 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 16 + pub struct QueuedCommsValue { 17 + pub id: uuid::Uuid, 18 + pub user_id: Option<uuid::Uuid>, 19 + pub channel: u8, 20 + pub comms_type: u8, 21 + pub recipient: String, 22 + pub subject: Option<String>, 23 + pub body: String, 24 + pub metadata: Option<Vec<u8>>, 25 + pub status: u8, 26 + pub error_message: Option<String>, 27 + pub attempts: i32, 28 + pub max_attempts: i32, 29 + pub created_at_ms: i64, 30 + pub scheduled_for_ms: i64, 31 + pub sent_at_ms: Option<i64>, 32 + } 33 + 34 + impl QueuedCommsValue { 35 + pub fn serialize(&self) -> Vec<u8> { 36 + let payload = 37 + postcard::to_allocvec(self).expect("QueuedCommsValue serialization cannot fail"); 38 + let mut buf = Vec::with_capacity(1 + payload.len()); 39 + buf.push(COMMS_SCHEMA_VERSION); 40 + buf.extend_from_slice(&payload); 41 + buf 42 + } 43 + 44 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 45 + let (&version, payload) = bytes.split_first()?; 46 + match version { 47 + COMMS_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 48 + _ => None, 49 + } 50 + } 51 + } 52 + 53 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 54 + pub struct InviteCodeValue { 55 + pub code: String, 56 + pub available_uses: i32, 57 + pub disabled: bool, 58 + pub for_account: Option<String>, 59 + pub created_by: Option<uuid::Uuid>, 60 + pub created_at_ms: i64, 61 + } 62 + 63 + impl InviteCodeValue { 64 + pub fn serialize(&self) -> Vec<u8> { 65 + let payload = 66 + postcard::to_allocvec(self).expect("InviteCodeValue serialization cannot fail"); 67 + let mut buf = Vec::with_capacity(1 + payload.len()); 68 + buf.push(INVITE_CODE_SCHEMA_VERSION); 69 + buf.extend_from_slice(&payload); 70 + buf 71 + } 72 + 73 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 74 + let (&version, payload) = bytes.split_first()?; 75 + match version { 76 + INVITE_CODE_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 77 + _ => None, 78 + } 79 + } 80 + } 81 + 82 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 83 + pub struct InviteCodeUseValue { 84 + pub used_by: uuid::Uuid, 85 + pub used_at_ms: i64, 86 + } 87 + 88 + impl InviteCodeUseValue { 89 + pub fn serialize(&self) -> Vec<u8> { 90 + let payload = 91 + postcard::to_allocvec(self).expect("InviteCodeUseValue serialization cannot fail"); 92 + let mut buf = Vec::with_capacity(1 + payload.len()); 93 + buf.push(INVITE_USE_SCHEMA_VERSION); 94 + buf.extend_from_slice(&payload); 95 + buf 96 + } 97 + 98 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 99 + let (&version, payload) = bytes.split_first()?; 100 + match version { 101 + INVITE_USE_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 102 + _ => None, 103 + } 104 + } 105 + } 106 + 107 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 108 + pub struct SigningKeyValue { 109 + pub id: uuid::Uuid, 110 + pub did: Option<String>, 111 + pub public_key_did_key: String, 112 + pub private_key_bytes: Vec<u8>, 113 + pub used: bool, 114 + pub created_at_ms: i64, 115 + pub expires_at_ms: i64, 116 + } 117 + 118 + impl SigningKeyValue { 119 + pub fn serialize(&self) -> Vec<u8> { 120 + let payload = 121 + postcard::to_allocvec(self).expect("SigningKeyValue serialization cannot fail"); 122 + let mut buf = Vec::with_capacity(1 + payload.len()); 123 + buf.push(SIGNING_KEY_SCHEMA_VERSION); 124 + buf.extend_from_slice(&payload); 125 + buf 126 + } 127 + 128 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 129 + let (&version, payload) = bytes.split_first()?; 130 + match version { 131 + SIGNING_KEY_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 132 + _ => None, 133 + } 134 + } 135 + } 136 + 137 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 138 + pub struct DeletionRequestValue { 139 + pub token: String, 140 + pub did: String, 141 + pub created_at_ms: i64, 142 + pub expires_at_ms: i64, 143 + } 144 + 145 + impl DeletionRequestValue { 146 + pub fn serialize(&self) -> Vec<u8> { 147 + let payload = 148 + postcard::to_allocvec(self).expect("DeletionRequestValue serialization cannot fail"); 149 + let mut buf = Vec::with_capacity(1 + payload.len()); 150 + buf.push(DELETION_REQUEST_SCHEMA_VERSION); 151 + buf.extend_from_slice(&payload); 152 + buf 153 + } 154 + 155 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 156 + let (&version, payload) = bytes.split_first()?; 157 + match version { 158 + DELETION_REQUEST_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 159 + _ => None, 160 + } 161 + } 162 + } 163 + 164 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 165 + pub struct ReportValue { 166 + pub id: i64, 167 + pub reason_type: String, 168 + pub reason: Option<String>, 169 + pub subject_json: Vec<u8>, 170 + pub reported_by_did: String, 171 + pub created_at_ms: i64, 172 + } 173 + 174 + impl ReportValue { 175 + pub fn serialize(&self) -> Vec<u8> { 176 + let payload = postcard::to_allocvec(self).expect("ReportValue serialization cannot fail"); 177 + let mut buf = Vec::with_capacity(1 + payload.len()); 178 + buf.push(REPORT_SCHEMA_VERSION); 179 + buf.extend_from_slice(&payload); 180 + buf 181 + } 182 + 183 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 184 + let (&version, payload) = bytes.split_first()?; 185 + match version { 186 + REPORT_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 187 + _ => None, 188 + } 189 + } 190 + } 191 + 192 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 193 + pub struct NotificationHistoryValue { 194 + pub id: uuid::Uuid, 195 + pub channel: u8, 196 + pub comms_type: u8, 197 + pub recipient: String, 198 + pub subject: Option<String>, 199 + pub body: String, 200 + pub status: u8, 201 + pub created_at_ms: i64, 202 + } 203 + 204 + impl NotificationHistoryValue { 205 + pub fn serialize(&self) -> Vec<u8> { 206 + let payload = postcard::to_allocvec(self) 207 + .expect("NotificationHistoryValue serialization cannot fail"); 208 + let mut buf = Vec::with_capacity(1 + payload.len()); 209 + buf.push(NOTIFICATION_HISTORY_SCHEMA_VERSION); 210 + buf.extend_from_slice(&payload); 211 + buf 212 + } 213 + 214 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 215 + let (&version, payload) = bytes.split_first()?; 216 + match version { 217 + NOTIFICATION_HISTORY_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 218 + _ => None, 219 + } 220 + } 221 + } 222 + 223 + pub fn channel_to_u8(ch: tranquil_db_traits::CommsChannel) -> u8 { 224 + match ch { 225 + tranquil_db_traits::CommsChannel::Email => 0, 226 + tranquil_db_traits::CommsChannel::Discord => 1, 227 + tranquil_db_traits::CommsChannel::Telegram => 2, 228 + tranquil_db_traits::CommsChannel::Signal => 3, 229 + } 230 + } 231 + 232 + pub fn u8_to_channel(v: u8) -> Option<tranquil_db_traits::CommsChannel> { 233 + match v { 234 + 0 => Some(tranquil_db_traits::CommsChannel::Email), 235 + 1 => Some(tranquil_db_traits::CommsChannel::Discord), 236 + 2 => Some(tranquil_db_traits::CommsChannel::Telegram), 237 + 3 => Some(tranquil_db_traits::CommsChannel::Signal), 238 + _ => None, 239 + } 240 + } 241 + 242 + pub fn comms_type_to_u8(ct: tranquil_db_traits::CommsType) -> u8 { 243 + match ct { 244 + tranquil_db_traits::CommsType::Welcome => 0, 245 + tranquil_db_traits::CommsType::EmailVerification => 1, 246 + tranquil_db_traits::CommsType::PasswordReset => 2, 247 + tranquil_db_traits::CommsType::EmailUpdate => 3, 248 + tranquil_db_traits::CommsType::AccountDeletion => 4, 249 + tranquil_db_traits::CommsType::AdminEmail => 5, 250 + tranquil_db_traits::CommsType::PlcOperation => 6, 251 + tranquil_db_traits::CommsType::TwoFactorCode => 7, 252 + tranquil_db_traits::CommsType::PasskeyRecovery => 8, 253 + tranquil_db_traits::CommsType::LegacyLoginAlert => 9, 254 + tranquil_db_traits::CommsType::MigrationVerification => 10, 255 + tranquil_db_traits::CommsType::ChannelVerification => 11, 256 + tranquil_db_traits::CommsType::ChannelVerified => 12, 257 + } 258 + } 259 + 260 + pub fn u8_to_comms_type(v: u8) -> Option<tranquil_db_traits::CommsType> { 261 + match v { 262 + 0 => Some(tranquil_db_traits::CommsType::Welcome), 263 + 1 => Some(tranquil_db_traits::CommsType::EmailVerification), 264 + 2 => Some(tranquil_db_traits::CommsType::PasswordReset), 265 + 3 => Some(tranquil_db_traits::CommsType::EmailUpdate), 266 + 4 => Some(tranquil_db_traits::CommsType::AccountDeletion), 267 + 5 => Some(tranquil_db_traits::CommsType::AdminEmail), 268 + 6 => Some(tranquil_db_traits::CommsType::PlcOperation), 269 + 7 => Some(tranquil_db_traits::CommsType::TwoFactorCode), 270 + 8 => Some(tranquil_db_traits::CommsType::PasskeyRecovery), 271 + 9 => Some(tranquil_db_traits::CommsType::LegacyLoginAlert), 272 + 10 => Some(tranquil_db_traits::CommsType::MigrationVerification), 273 + 11 => Some(tranquil_db_traits::CommsType::ChannelVerification), 274 + 12 => Some(tranquil_db_traits::CommsType::ChannelVerified), 275 + _ => None, 276 + } 277 + } 278 + 279 + pub fn status_to_u8(s: tranquil_db_traits::CommsStatus) -> u8 { 280 + match s { 281 + tranquil_db_traits::CommsStatus::Pending => 0, 282 + tranquil_db_traits::CommsStatus::Processing => 1, 283 + tranquil_db_traits::CommsStatus::Sent => 2, 284 + tranquil_db_traits::CommsStatus::Failed => 3, 285 + } 286 + } 287 + 288 + pub fn u8_to_status(v: u8) -> Option<tranquil_db_traits::CommsStatus> { 289 + match v { 290 + 0 => Some(tranquil_db_traits::CommsStatus::Pending), 291 + 1 => Some(tranquil_db_traits::CommsStatus::Processing), 292 + 2 => Some(tranquil_db_traits::CommsStatus::Sent), 293 + 3 => Some(tranquil_db_traits::CommsStatus::Failed), 294 + _ => None, 295 + } 296 + } 297 + 298 + pub fn comms_queue_key(id: uuid::Uuid) -> SmallVec<[u8; 128]> { 299 + KeyBuilder::new() 300 + .tag(KeyTag::INFRA_COMMS_QUEUE) 301 + .bytes(id.as_bytes()) 302 + .build() 303 + } 304 + 305 + pub fn comms_queue_prefix() -> SmallVec<[u8; 128]> { 306 + KeyBuilder::new().tag(KeyTag::INFRA_COMMS_QUEUE).build() 307 + } 308 + 309 + pub fn invite_code_key(code: &str) -> SmallVec<[u8; 128]> { 310 + KeyBuilder::new() 311 + .tag(KeyTag::INFRA_INVITE_CODE) 312 + .string(code) 313 + .build() 314 + } 315 + 316 + pub fn invite_code_prefix() -> SmallVec<[u8; 128]> { 317 + KeyBuilder::new().tag(KeyTag::INFRA_INVITE_CODE).build() 318 + } 319 + 320 + pub fn invite_use_key(code: &str, used_by: uuid::Uuid) -> SmallVec<[u8; 128]> { 321 + KeyBuilder::new() 322 + .tag(KeyTag::INFRA_INVITE_USE) 323 + .string(code) 324 + .bytes(used_by.as_bytes()) 325 + .build() 326 + } 327 + 328 + pub fn invite_use_prefix(code: &str) -> SmallVec<[u8; 128]> { 329 + KeyBuilder::new() 330 + .tag(KeyTag::INFRA_INVITE_USE) 331 + .string(code) 332 + .build() 333 + } 334 + 335 + pub fn invite_by_account_key(did: &str) -> SmallVec<[u8; 128]> { 336 + KeyBuilder::new() 337 + .tag(KeyTag::INFRA_INVITE_BY_ACCOUNT) 338 + .string(did) 339 + .build() 340 + } 341 + 342 + pub fn invite_by_user_key(user_id: uuid::Uuid) -> SmallVec<[u8; 128]> { 343 + KeyBuilder::new() 344 + .tag(KeyTag::INFRA_INVITE_BY_USER) 345 + .bytes(user_id.as_bytes()) 346 + .build() 347 + } 348 + 349 + pub fn invite_by_user_prefix() -> SmallVec<[u8; 128]> { 350 + KeyBuilder::new().tag(KeyTag::INFRA_INVITE_BY_USER).build() 351 + } 352 + 353 + pub fn signing_key_key(public_key_did_key: &str) -> SmallVec<[u8; 128]> { 354 + KeyBuilder::new() 355 + .tag(KeyTag::INFRA_SIGNING_KEY) 356 + .string(public_key_did_key) 357 + .build() 358 + } 359 + 360 + pub fn signing_key_by_id_key(key_id: uuid::Uuid) -> SmallVec<[u8; 128]> { 361 + KeyBuilder::new() 362 + .tag(KeyTag::INFRA_SIGNING_KEY_BY_ID) 363 + .bytes(key_id.as_bytes()) 364 + .build() 365 + } 366 + 367 + pub fn deletion_request_key(token: &str) -> SmallVec<[u8; 128]> { 368 + KeyBuilder::new() 369 + .tag(KeyTag::INFRA_DELETION_REQUEST) 370 + .string(token) 371 + .build() 372 + } 373 + 374 + pub fn deletion_by_did_key(did: &str) -> SmallVec<[u8; 128]> { 375 + KeyBuilder::new() 376 + .tag(KeyTag::INFRA_DELETION_BY_DID) 377 + .string(did) 378 + .build() 379 + } 380 + 381 + pub fn deletion_by_did_prefix() -> SmallVec<[u8; 128]> { 382 + KeyBuilder::new().tag(KeyTag::INFRA_DELETION_BY_DID).build() 383 + } 384 + 385 + pub fn account_pref_key(user_id: uuid::Uuid, name: &str) -> SmallVec<[u8; 128]> { 386 + KeyBuilder::new() 387 + .tag(KeyTag::INFRA_ACCOUNT_PREF) 388 + .bytes(user_id.as_bytes()) 389 + .string(name) 390 + .build() 391 + } 392 + 393 + pub fn account_pref_prefix(user_id: uuid::Uuid) -> SmallVec<[u8; 128]> { 394 + KeyBuilder::new() 395 + .tag(KeyTag::INFRA_ACCOUNT_PREF) 396 + .bytes(user_id.as_bytes()) 397 + .build() 398 + } 399 + 400 + pub fn server_config_key(key: &str) -> SmallVec<[u8; 128]> { 401 + KeyBuilder::new() 402 + .tag(KeyTag::INFRA_SERVER_CONFIG) 403 + .string(key) 404 + .build() 405 + } 406 + 407 + pub fn report_key(id: i64) -> SmallVec<[u8; 128]> { 408 + KeyBuilder::new().tag(KeyTag::INFRA_REPORT).i64(id).build() 409 + } 410 + 411 + pub fn plc_token_key(user_id: uuid::Uuid, token: &str) -> SmallVec<[u8; 128]> { 412 + KeyBuilder::new() 413 + .tag(KeyTag::INFRA_PLC_TOKEN) 414 + .bytes(user_id.as_bytes()) 415 + .string(token) 416 + .build() 417 + } 418 + 419 + pub fn plc_token_prefix(user_id: uuid::Uuid) -> SmallVec<[u8; 128]> { 420 + KeyBuilder::new() 421 + .tag(KeyTag::INFRA_PLC_TOKEN) 422 + .bytes(user_id.as_bytes()) 423 + .build() 424 + } 425 + 426 + pub fn comms_history_key( 427 + user_id: uuid::Uuid, 428 + created_at_ms: i64, 429 + id: uuid::Uuid, 430 + ) -> SmallVec<[u8; 128]> { 431 + let reversed_ts = i64::MAX.saturating_sub(created_at_ms); 432 + KeyBuilder::new() 433 + .tag(KeyTag::INFRA_COMMS_HISTORY) 434 + .bytes(user_id.as_bytes()) 435 + .i64(reversed_ts) 436 + .bytes(id.as_bytes()) 437 + .build() 438 + } 439 + 440 + pub fn comms_history_prefix(user_id: uuid::Uuid) -> SmallVec<[u8; 128]> { 441 + KeyBuilder::new() 442 + .tag(KeyTag::INFRA_COMMS_HISTORY) 443 + .bytes(user_id.as_bytes()) 444 + .build() 445 + } 446 + 447 + pub fn invite_code_used_by_key(user_id: uuid::Uuid) -> SmallVec<[u8; 128]> { 448 + KeyBuilder::new() 449 + .tag(KeyTag::INFRA_INVITE_CODE_USED_BY) 450 + .bytes(user_id.as_bytes()) 451 + .build() 452 + } 453 + 454 + #[cfg(test)] 455 + mod tests { 456 + use super::*; 457 + use crate::metastore::encoding::KeyReader; 458 + 459 + #[test] 460 + fn queued_comms_value_roundtrip() { 461 + let val = QueuedCommsValue { 462 + id: uuid::Uuid::new_v4(), 463 + user_id: Some(uuid::Uuid::new_v4()), 464 + channel: 0, 465 + comms_type: 1, 466 + recipient: "user@example.com".to_owned(), 467 + subject: Some("test".to_owned()), 468 + body: "body text".to_owned(), 469 + metadata: None, 470 + status: 0, 471 + error_message: None, 472 + attempts: 0, 473 + max_attempts: 3, 474 + created_at_ms: 1700000000000, 475 + scheduled_for_ms: 1700000000000, 476 + sent_at_ms: None, 477 + }; 478 + let bytes = val.serialize(); 479 + assert_eq!(bytes[0], COMMS_SCHEMA_VERSION); 480 + let decoded = QueuedCommsValue::deserialize(&bytes).unwrap(); 481 + assert_eq!(val, decoded); 482 + } 483 + 484 + #[test] 485 + fn invite_code_value_roundtrip() { 486 + let val = InviteCodeValue { 487 + code: "abc-def-ghi".to_owned(), 488 + available_uses: 5, 489 + disabled: false, 490 + for_account: Some("did:plc:test".to_owned()), 491 + created_by: Some(uuid::Uuid::new_v4()), 492 + created_at_ms: 1700000000000, 493 + }; 494 + let bytes = val.serialize(); 495 + assert_eq!(bytes[0], INVITE_CODE_SCHEMA_VERSION); 496 + let decoded = InviteCodeValue::deserialize(&bytes).unwrap(); 497 + assert_eq!(val, decoded); 498 + } 499 + 500 + #[test] 501 + fn invite_use_value_roundtrip() { 502 + let val = InviteCodeUseValue { 503 + used_by: uuid::Uuid::new_v4(), 504 + used_at_ms: 1700000000000, 505 + }; 506 + let bytes = val.serialize(); 507 + let decoded = InviteCodeUseValue::deserialize(&bytes).unwrap(); 508 + assert_eq!(val, decoded); 509 + } 510 + 511 + #[test] 512 + fn signing_key_value_roundtrip() { 513 + let val = SigningKeyValue { 514 + id: uuid::Uuid::new_v4(), 515 + did: Some("did:plc:test".to_owned()), 516 + public_key_did_key: "did:key:z123".to_owned(), 517 + private_key_bytes: vec![1, 2, 3, 4], 518 + used: false, 519 + created_at_ms: 1700000000000, 520 + expires_at_ms: 1700000600000, 521 + }; 522 + let bytes = val.serialize(); 523 + let decoded = SigningKeyValue::deserialize(&bytes).unwrap(); 524 + assert_eq!(val, decoded); 525 + } 526 + 527 + #[test] 528 + fn deletion_request_value_roundtrip() { 529 + let val = DeletionRequestValue { 530 + token: "tok-abc".to_owned(), 531 + did: "did:plc:test".to_owned(), 532 + created_at_ms: 1700000000000, 533 + expires_at_ms: 1700000600000, 534 + }; 535 + let bytes = val.serialize(); 536 + let decoded = DeletionRequestValue::deserialize(&bytes).unwrap(); 537 + assert_eq!(val, decoded); 538 + } 539 + 540 + #[test] 541 + fn report_value_roundtrip() { 542 + let val = ReportValue { 543 + id: 42, 544 + reason_type: "spam".to_owned(), 545 + reason: Some("bad content".to_owned()), 546 + subject_json: b"{}".to_vec(), 547 + reported_by_did: "did:plc:reporter".to_owned(), 548 + created_at_ms: 1700000000000, 549 + }; 550 + let bytes = val.serialize(); 551 + let decoded = ReportValue::deserialize(&bytes).unwrap(); 552 + assert_eq!(val, decoded); 553 + } 554 + 555 + #[test] 556 + fn notification_history_value_roundtrip() { 557 + let val = NotificationHistoryValue { 558 + id: uuid::Uuid::new_v4(), 559 + channel: 0, 560 + comms_type: 1, 561 + recipient: "user@example.com".to_owned(), 562 + subject: None, 563 + body: "notification body".to_owned(), 564 + status: 2, 565 + created_at_ms: 1700000000000, 566 + }; 567 + let bytes = val.serialize(); 568 + let decoded = NotificationHistoryValue::deserialize(&bytes).unwrap(); 569 + assert_eq!(val, decoded); 570 + } 571 + 572 + #[test] 573 + fn comms_queue_key_roundtrip() { 574 + let id = uuid::Uuid::new_v4(); 575 + let key = comms_queue_key(id); 576 + let mut reader = KeyReader::new(&key); 577 + assert_eq!(reader.tag(), Some(KeyTag::INFRA_COMMS_QUEUE.raw())); 578 + let id_bytes = reader.bytes().unwrap(); 579 + assert_eq!(uuid::Uuid::from_slice(&id_bytes).unwrap(), id); 580 + assert!(reader.is_empty()); 581 + } 582 + 583 + #[test] 584 + fn invite_code_key_roundtrip() { 585 + let key = invite_code_key("abc-def"); 586 + let mut reader = KeyReader::new(&key); 587 + assert_eq!(reader.tag(), Some(KeyTag::INFRA_INVITE_CODE.raw())); 588 + assert_eq!(reader.string(), Some("abc-def".to_owned())); 589 + assert!(reader.is_empty()); 590 + } 591 + 592 + #[test] 593 + fn invite_use_key_roundtrip() { 594 + let user_id = uuid::Uuid::new_v4(); 595 + let key = invite_use_key("code1", user_id); 596 + let mut reader = KeyReader::new(&key); 597 + assert_eq!(reader.tag(), Some(KeyTag::INFRA_INVITE_USE.raw())); 598 + assert_eq!(reader.string(), Some("code1".to_owned())); 599 + let id_bytes = reader.bytes().unwrap(); 600 + assert_eq!(uuid::Uuid::from_slice(&id_bytes).unwrap(), user_id); 601 + assert!(reader.is_empty()); 602 + } 603 + 604 + #[test] 605 + fn comms_history_newest_first_ordering() { 606 + let user_id = uuid::Uuid::new_v4(); 607 + let id_a = uuid::Uuid::new_v4(); 608 + let id_b = uuid::Uuid::new_v4(); 609 + let key_old = comms_history_key(user_id, 1000, id_a); 610 + let key_new = comms_history_key(user_id, 2000, id_b); 611 + assert!(key_new.as_slice() < key_old.as_slice()); 612 + } 613 + 614 + #[test] 615 + fn account_pref_key_roundtrip() { 616 + let user_id = uuid::Uuid::new_v4(); 617 + let key = account_pref_key(user_id, "app.bsky.actor.profile"); 618 + let mut reader = KeyReader::new(&key); 619 + assert_eq!(reader.tag(), Some(KeyTag::INFRA_ACCOUNT_PREF.raw())); 620 + let id_bytes = reader.bytes().unwrap(); 621 + assert_eq!(uuid::Uuid::from_slice(&id_bytes).unwrap(), user_id); 622 + assert_eq!(reader.string(), Some("app.bsky.actor.profile".to_owned())); 623 + assert!(reader.is_empty()); 624 + } 625 + 626 + #[test] 627 + fn plc_token_key_roundtrip() { 628 + let user_id = uuid::Uuid::new_v4(); 629 + let key = plc_token_key(user_id, "tok123"); 630 + let mut reader = KeyReader::new(&key); 631 + assert_eq!(reader.tag(), Some(KeyTag::INFRA_PLC_TOKEN.raw())); 632 + let id_bytes = reader.bytes().unwrap(); 633 + assert_eq!(uuid::Uuid::from_slice(&id_bytes).unwrap(), user_id); 634 + assert_eq!(reader.string(), Some("tok123".to_owned())); 635 + assert!(reader.is_empty()); 636 + } 637 + 638 + #[test] 639 + fn deserialize_unknown_version_returns_none() { 640 + let val = QueuedCommsValue { 641 + id: uuid::Uuid::new_v4(), 642 + user_id: None, 643 + channel: 0, 644 + comms_type: 0, 645 + recipient: String::new(), 646 + subject: None, 647 + body: String::new(), 648 + metadata: None, 649 + status: 0, 650 + error_message: None, 651 + attempts: 0, 652 + max_attempts: 3, 653 + created_at_ms: 0, 654 + scheduled_for_ms: 0, 655 + sent_at_ms: None, 656 + }; 657 + let mut bytes = val.serialize(); 658 + bytes[0] = 99; 659 + assert!(QueuedCommsValue::deserialize(&bytes).is_none()); 660 + } 661 + 662 + #[test] 663 + fn channel_u8_roundtrip() { 664 + use tranquil_db_traits::CommsChannel; 665 + [ 666 + CommsChannel::Email, 667 + CommsChannel::Discord, 668 + CommsChannel::Telegram, 669 + CommsChannel::Signal, 670 + ] 671 + .iter() 672 + .for_each(|&ch| { 673 + assert_eq!(u8_to_channel(channel_to_u8(ch)), Some(ch)); 674 + }); 675 + } 676 + 677 + #[test] 678 + fn comms_type_u8_roundtrip() { 679 + use tranquil_db_traits::CommsType; 680 + [ 681 + CommsType::Welcome, 682 + CommsType::EmailVerification, 683 + CommsType::PasswordReset, 684 + CommsType::EmailUpdate, 685 + CommsType::AccountDeletion, 686 + CommsType::AdminEmail, 687 + CommsType::PlcOperation, 688 + CommsType::TwoFactorCode, 689 + CommsType::PasskeyRecovery, 690 + CommsType::LegacyLoginAlert, 691 + CommsType::MigrationVerification, 692 + CommsType::ChannelVerification, 693 + CommsType::ChannelVerified, 694 + ] 695 + .iter() 696 + .for_each(|&ct| { 697 + assert_eq!(u8_to_comms_type(comms_type_to_u8(ct)), Some(ct)); 698 + }); 699 + } 700 + 701 + #[test] 702 + fn status_u8_roundtrip() { 703 + use tranquil_db_traits::CommsStatus; 704 + [ 705 + CommsStatus::Pending, 706 + CommsStatus::Processing, 707 + CommsStatus::Sent, 708 + CommsStatus::Failed, 709 + ] 710 + .iter() 711 + .for_each(|&s| { 712 + assert_eq!(u8_to_status(status_to_u8(s)), Some(s)); 713 + }); 714 + } 715 + 716 + #[test] 717 + fn u8_to_channel_invalid_returns_none() { 718 + assert!(u8_to_channel(255).is_none()); 719 + } 720 + 721 + #[test] 722 + fn u8_to_comms_type_invalid_returns_none() { 723 + assert!(u8_to_comms_type(255).is_none()); 724 + } 725 + 726 + #[test] 727 + fn u8_to_status_invalid_returns_none() { 728 + assert!(u8_to_status(255).is_none()); 729 + } 730 + }
+133
crates/tranquil-store/src/metastore/keys.rs
··· 55 55 pub const RECORD_BLOBS: Self = Self(0x30); 56 56 pub const BACKLINK_BY_USER: Self = Self(0x31); 57 57 58 + pub const USER_PRIMARY: Self = Self(0x40); 59 + pub const USER_BY_HANDLE: Self = Self(0x41); 60 + pub const USER_BY_EMAIL: Self = Self(0x42); 61 + pub const USER_PASSKEYS: Self = Self(0x43); 62 + pub const USER_PASSKEY_BY_CRED: Self = Self(0x44); 63 + pub const USER_TOTP: Self = Self(0x45); 64 + pub const USER_BACKUP_CODES: Self = Self(0x46); 65 + pub const USER_WEBAUTHN_CHALLENGE: Self = Self(0x47); 66 + pub const USER_RESET_CODE: Self = Self(0x48); 67 + pub const USER_RECOVERY_TOKEN: Self = Self(0x49); 68 + pub const USER_DID_WEB_OVERRIDES: Self = Self(0x4A); 69 + pub const USER_HANDLE_RESERVATION: Self = Self(0x4B); 70 + pub const USER_COMMS_CHANNEL: Self = Self(0x4C); 71 + pub const USER_TELEGRAM_LOOKUP: Self = Self(0x4D); 72 + pub const USER_DISCORD_LOOKUP: Self = Self(0x4E); 73 + 74 + pub const SESSION_PRIMARY: Self = Self(0x50); 75 + pub const SESSION_BY_ACCESS: Self = Self(0x51); 76 + pub const SESSION_BY_REFRESH: Self = Self(0x52); 77 + pub const SESSION_USED_REFRESH: Self = Self(0x53); 78 + pub const SESSION_APP_PASSWORD: Self = Self(0x54); 79 + pub const SESSION_BY_DID: Self = Self(0x55); 80 + pub const SESSION_LAST_REAUTH: Self = Self(0x56); 81 + pub const SESSION_ID_COUNTER: Self = Self(0x57); 82 + 83 + pub const OAUTH_TOKEN: Self = Self(0x60); 84 + pub const OAUTH_TOKEN_BY_ID: Self = Self(0x61); 85 + pub const OAUTH_TOKEN_BY_REFRESH: Self = Self(0x62); 86 + pub const OAUTH_TOKEN_BY_PREV_REFRESH: Self = Self(0x63); 87 + pub const OAUTH_USED_REFRESH: Self = Self(0x64); 88 + pub const OAUTH_AUTH_REQUEST: Self = Self(0x65); 89 + pub const OAUTH_AUTH_BY_CODE: Self = Self(0x66); 90 + pub const OAUTH_DEVICE: Self = Self(0x67); 91 + pub const OAUTH_ACCOUNT_DEVICE: Self = Self(0x68); 92 + pub const OAUTH_DPOP_JTI: Self = Self(0x69); 93 + pub const OAUTH_2FA_CHALLENGE: Self = Self(0x6A); 94 + pub const OAUTH_2FA_BY_REQUEST: Self = Self(0x6B); 95 + pub const OAUTH_SCOPE_PREFS: Self = Self(0x6C); 96 + pub const OAUTH_AUTH_CLIENT: Self = Self(0x6D); 97 + pub const OAUTH_DEVICE_TRUST: Self = Self(0x6E); 98 + pub const OAUTH_TOKEN_FAMILY_COUNTER: Self = Self(0x6F); 99 + 100 + pub const INFRA_COMMS_QUEUE: Self = Self(0x70); 101 + pub const INFRA_INVITE_CODE: Self = Self(0x71); 102 + pub const INFRA_INVITE_USE: Self = Self(0x72); 103 + pub const INFRA_INVITE_BY_ACCOUNT: Self = Self(0x73); 104 + pub const INFRA_INVITE_BY_USER: Self = Self(0x74); 105 + pub const INFRA_SIGNING_KEY: Self = Self(0x75); 106 + pub const INFRA_SIGNING_KEY_BY_ID: Self = Self(0x76); 107 + pub const INFRA_DELETION_REQUEST: Self = Self(0x77); 108 + pub const INFRA_DELETION_BY_DID: Self = Self(0x78); 109 + pub const INFRA_ACCOUNT_PREF: Self = Self(0x79); 110 + pub const INFRA_SERVER_CONFIG: Self = Self(0x7A); 111 + pub const INFRA_REPORT: Self = Self(0x7B); 112 + pub const INFRA_PLC_TOKEN: Self = Self(0x7C); 113 + pub const INFRA_COMMS_HISTORY: Self = Self(0x7D); 114 + pub const INFRA_INVITE_CODE_USED_BY: Self = Self(0x7E); 115 + 116 + pub const DELEG_GRANT: Self = Self(0x80); 117 + pub const DELEG_BY_CONTROLLER: Self = Self(0x81); 118 + pub const DELEG_AUDIT_LOG: Self = Self(0x82); 119 + 120 + pub const SSO_IDENTITY: Self = Self(0x90); 121 + pub const SSO_BY_PROVIDER: Self = Self(0x91); 122 + pub const SSO_AUTH_STATE: Self = Self(0x92); 123 + pub const SSO_PENDING_REG: Self = Self(0x93); 124 + pub const SSO_BY_ID: Self = Self(0x94); 125 + 126 + pub const OAUTH_TOKEN_BY_FAMILY: Self = Self(0xA0); 127 + 58 128 pub const FORMAT_VERSION: Self = Self(0xFF); 59 129 60 130 pub const fn raw(self) -> u8 { ··· 117 187 KeyTag::DID_EVENTS, 118 188 KeyTag::RECORD_BLOBS, 119 189 KeyTag::BACKLINK_BY_USER, 190 + KeyTag::USER_PRIMARY, 191 + KeyTag::USER_BY_HANDLE, 192 + KeyTag::USER_BY_EMAIL, 193 + KeyTag::USER_PASSKEYS, 194 + KeyTag::USER_PASSKEY_BY_CRED, 195 + KeyTag::USER_TOTP, 196 + KeyTag::USER_BACKUP_CODES, 197 + KeyTag::USER_WEBAUTHN_CHALLENGE, 198 + KeyTag::USER_RESET_CODE, 199 + KeyTag::USER_RECOVERY_TOKEN, 200 + KeyTag::USER_DID_WEB_OVERRIDES, 201 + KeyTag::USER_HANDLE_RESERVATION, 202 + KeyTag::USER_COMMS_CHANNEL, 203 + KeyTag::USER_TELEGRAM_LOOKUP, 204 + KeyTag::USER_DISCORD_LOOKUP, 205 + KeyTag::SESSION_PRIMARY, 206 + KeyTag::SESSION_BY_ACCESS, 207 + KeyTag::SESSION_BY_REFRESH, 208 + KeyTag::SESSION_USED_REFRESH, 209 + KeyTag::SESSION_APP_PASSWORD, 210 + KeyTag::SESSION_BY_DID, 211 + KeyTag::SESSION_LAST_REAUTH, 212 + KeyTag::SESSION_ID_COUNTER, 213 + KeyTag::OAUTH_TOKEN, 214 + KeyTag::OAUTH_TOKEN_BY_ID, 215 + KeyTag::OAUTH_TOKEN_BY_REFRESH, 216 + KeyTag::OAUTH_TOKEN_BY_PREV_REFRESH, 217 + KeyTag::OAUTH_USED_REFRESH, 218 + KeyTag::OAUTH_AUTH_REQUEST, 219 + KeyTag::OAUTH_AUTH_BY_CODE, 220 + KeyTag::OAUTH_DEVICE, 221 + KeyTag::OAUTH_ACCOUNT_DEVICE, 222 + KeyTag::OAUTH_DPOP_JTI, 223 + KeyTag::OAUTH_2FA_CHALLENGE, 224 + KeyTag::OAUTH_2FA_BY_REQUEST, 225 + KeyTag::OAUTH_SCOPE_PREFS, 226 + KeyTag::OAUTH_AUTH_CLIENT, 227 + KeyTag::OAUTH_DEVICE_TRUST, 228 + KeyTag::OAUTH_TOKEN_FAMILY_COUNTER, 229 + KeyTag::INFRA_COMMS_QUEUE, 230 + KeyTag::INFRA_INVITE_CODE, 231 + KeyTag::INFRA_INVITE_USE, 232 + KeyTag::INFRA_INVITE_BY_ACCOUNT, 233 + KeyTag::INFRA_INVITE_BY_USER, 234 + KeyTag::INFRA_SIGNING_KEY, 235 + KeyTag::INFRA_SIGNING_KEY_BY_ID, 236 + KeyTag::INFRA_DELETION_REQUEST, 237 + KeyTag::INFRA_DELETION_BY_DID, 238 + KeyTag::INFRA_ACCOUNT_PREF, 239 + KeyTag::INFRA_SERVER_CONFIG, 240 + KeyTag::INFRA_REPORT, 241 + KeyTag::INFRA_PLC_TOKEN, 242 + KeyTag::INFRA_COMMS_HISTORY, 243 + KeyTag::INFRA_INVITE_CODE_USED_BY, 244 + KeyTag::DELEG_GRANT, 245 + KeyTag::DELEG_BY_CONTROLLER, 246 + KeyTag::DELEG_AUDIT_LOG, 247 + KeyTag::SSO_IDENTITY, 248 + KeyTag::SSO_BY_PROVIDER, 249 + KeyTag::SSO_AUTH_STATE, 250 + KeyTag::SSO_PENDING_REG, 251 + KeyTag::SSO_BY_ID, 252 + KeyTag::OAUTH_TOKEN_BY_FAMILY, 120 253 KeyTag::FORMAT_VERSION, 121 254 ]; 122 255 let mut raw: Vec<u8> = tags.iter().map(|t| t.raw()).collect();
+69
crates/tranquil-store/src/metastore/mod.rs
··· 3 3 pub mod blob_ops; 4 4 pub mod blobs; 5 5 pub mod commit_ops; 6 + pub mod delegation_ops; 7 + pub mod delegations; 6 8 pub mod encoding; 7 9 pub mod event_keys; 8 10 pub mod event_ops; 11 + pub mod infra_ops; 12 + pub mod infra_schema; 9 13 pub mod keys; 14 + pub mod oauth_ops; 15 + pub mod oauth_schema; 10 16 pub mod partitions; 11 17 pub mod record_ops; 12 18 pub mod records; ··· 14 20 pub mod repo_meta; 15 21 pub mod repo_ops; 16 22 pub mod scan; 23 + pub mod session_ops; 24 + pub mod sessions; 25 + pub mod sso_ops; 26 + pub mod sso_schema; 17 27 pub mod user_block_ops; 18 28 pub mod user_blocks; 19 29 pub mod user_hash; 30 + pub mod user_ops; 31 + pub mod users; 20 32 21 33 use std::path::Path; 22 34 use std::sync::Arc; ··· 145 157 db: Database, 146 158 partitions: [Keyspace; Partition::ALL.len()], 147 159 user_hashes: Arc<UserHashMap>, 160 + counter_lock: Arc<parking_lot::Mutex<()>>, 148 161 } 149 162 150 163 impl Metastore { ··· 185 198 db, 186 199 partitions, 187 200 user_hashes, 201 + counter_lock: Arc::new(parking_lot::Mutex::new(())), 188 202 }) 189 203 } 190 204 ··· 270 284 pub fn backlink_ops(&self) -> backlink_ops::BacklinkOps { 271 285 backlink_ops::BacklinkOps::new( 272 286 self.partitions[Partition::Indexes.index()].clone(), 287 + Arc::clone(&self.user_hashes), 288 + ) 289 + } 290 + 291 + pub fn delegation_ops(&self) -> delegation_ops::DelegationOps { 292 + delegation_ops::DelegationOps::new( 293 + self.db.clone(), 294 + self.partitions[Partition::Indexes.index()].clone(), 295 + self.partitions[Partition::Users.index()].clone(), 296 + Arc::clone(&self.user_hashes), 297 + ) 298 + } 299 + 300 + pub fn sso_ops(&self) -> sso_ops::SsoOps { 301 + sso_ops::SsoOps::new( 302 + self.db.clone(), 303 + self.partitions[Partition::Indexes.index()].clone(), 304 + ) 305 + } 306 + 307 + pub fn session_ops(&self) -> session_ops::SessionOps { 308 + session_ops::SessionOps::new( 309 + self.db.clone(), 310 + self.partitions[Partition::Auth.index()].clone(), 311 + self.partitions[Partition::Users.index()].clone(), 312 + Arc::clone(&self.user_hashes), 313 + Arc::clone(&self.counter_lock), 314 + ) 315 + } 316 + 317 + pub fn infra_ops(&self) -> infra_ops::InfraOps { 318 + infra_ops::InfraOps::new( 319 + self.db.clone(), 320 + self.partitions[Partition::Infra.index()].clone(), 321 + self.partitions[Partition::RepoData.index()].clone(), 322 + self.partitions[Partition::Users.index()].clone(), 323 + Arc::clone(&self.user_hashes), 324 + ) 325 + } 326 + 327 + pub fn oauth_ops(&self) -> oauth_ops::OAuthOps { 328 + oauth_ops::OAuthOps::new( 329 + self.db.clone(), 330 + self.partitions[Partition::Auth.index()].clone(), 331 + self.partitions[Partition::Users.index()].clone(), 332 + Arc::clone(&self.counter_lock), 333 + ) 334 + } 335 + 336 + pub fn user_ops(&self) -> user_ops::UserOps { 337 + user_ops::UserOps::new( 338 + self.db.clone(), 339 + self.partitions[Partition::Users.index()].clone(), 340 + self.partitions[Partition::RepoData.index()].clone(), 341 + self.partitions[Partition::Auth.index()].clone(), 273 342 Arc::clone(&self.user_hashes), 274 343 ) 275 344 }
+1602
crates/tranquil-store/src/metastore/oauth_ops.rs
··· 1 + use std::sync::Arc; 2 + 3 + use chrono::{DateTime, Duration, Utc}; 4 + use fjall::{Database, Keyspace}; 5 + use uuid::Uuid; 6 + 7 + use super::MetastoreError; 8 + use super::keys::UserHash; 9 + use super::oauth_schema::{ 10 + AccountDeviceValue, AuthorizedClientValue, DeviceTrustValue, DpopJtiValue, OAuthDeviceValue, 11 + OAuthRequestValue, OAuthTokenValue, ScopePrefsValue, TokenIndexValue, TwoFactorChallengeValue, 12 + UsedRefreshValue, deserialize_family_counter, oauth_2fa_by_request_key, 13 + oauth_2fa_challenge_key, oauth_2fa_challenge_prefix, oauth_account_device_key, 14 + oauth_auth_by_code_key, oauth_auth_client_key, oauth_auth_request_key, 15 + oauth_auth_request_prefix, oauth_device_key, oauth_device_trust_key, oauth_device_trust_prefix, 16 + oauth_dpop_jti_key, oauth_dpop_jti_prefix, oauth_scope_prefs_key, oauth_token_by_family_key, 17 + oauth_token_by_id_key, oauth_token_by_prev_refresh_key, oauth_token_by_refresh_key, 18 + oauth_token_family_counter_key, oauth_token_key, oauth_token_user_prefix, 19 + oauth_used_refresh_key, serialize_family_counter, 20 + }; 21 + use super::scan::point_lookup; 22 + use super::users::UserValue; 23 + 24 + use tranquil_db_traits::{ 25 + DeviceAccountRow, DeviceTrustInfo, OAuthSessionListItem, ScopePreference, TokenFamilyId, 26 + TrustedDeviceRow, TwoFactorChallenge, 27 + }; 28 + use tranquil_oauth::{AuthorizedClientData, DeviceData, RequestData, TokenData}; 29 + use tranquil_types::{ 30 + AuthorizationCode, ClientId, DPoPProofId, DeviceId, Did, Handle, RefreshToken, RequestId, 31 + TokenId, 32 + }; 33 + 34 + pub struct OAuthOps { 35 + db: Database, 36 + auth: Keyspace, 37 + users: Keyspace, 38 + counter_lock: Arc<parking_lot::Mutex<()>>, 39 + } 40 + 41 + impl OAuthOps { 42 + pub fn new( 43 + db: Database, 44 + auth: Keyspace, 45 + users: Keyspace, 46 + counter_lock: Arc<parking_lot::Mutex<()>>, 47 + ) -> Self { 48 + Self { 49 + db, 50 + auth, 51 + users, 52 + counter_lock, 53 + } 54 + } 55 + 56 + fn resolve_user_hash_from_did(&self, did: &str) -> UserHash { 57 + UserHash::from_did(did) 58 + } 59 + 60 + fn load_user_value(&self, user_hash: UserHash) -> Result<Option<UserValue>, MetastoreError> { 61 + let key = super::encoding::KeyBuilder::new() 62 + .tag(super::keys::KeyTag::USER_PRIMARY) 63 + .u64(user_hash.raw()) 64 + .build(); 65 + point_lookup( 66 + &self.users, 67 + key.as_slice(), 68 + UserValue::deserialize, 69 + "corrupt user value", 70 + ) 71 + } 72 + 73 + fn next_family_id(&self) -> Result<i32, MetastoreError> { 74 + let _guard = self.counter_lock.lock(); 75 + let counter_key = oauth_token_family_counter_key(); 76 + let current = self 77 + .auth 78 + .get(counter_key.as_slice()) 79 + .map_err(MetastoreError::Fjall)? 80 + .and_then(|raw| deserialize_family_counter(&raw)) 81 + .unwrap_or(0); 82 + let next = current.saturating_add(1); 83 + self.auth 84 + .insert(counter_key.as_slice(), serialize_family_counter(next)) 85 + .map_err(MetastoreError::Fjall)?; 86 + Ok(next) 87 + } 88 + 89 + fn load_token_by_family_id( 90 + &self, 91 + user_hash: UserHash, 92 + family_id: i32, 93 + ) -> Result<Option<OAuthTokenValue>, MetastoreError> { 94 + let key = oauth_token_key(user_hash, family_id); 95 + point_lookup( 96 + &self.auth, 97 + key.as_slice(), 98 + OAuthTokenValue::deserialize, 99 + "corrupt oauth token", 100 + ) 101 + } 102 + 103 + fn token_value_to_data(&self, v: &OAuthTokenValue) -> Result<TokenData, MetastoreError> { 104 + let did = Did::new(v.did.clone()) 105 + .map_err(|_| MetastoreError::CorruptData("invalid did in oauth token"))?; 106 + let token_id = tranquil_oauth::TokenId(v.token_id.clone()); 107 + let refresh_token = if v.refresh_token.is_empty() { 108 + None 109 + } else { 110 + Some(tranquil_oauth::RefreshToken(v.refresh_token.clone())) 111 + }; 112 + 113 + Ok(TokenData { 114 + did, 115 + token_id, 116 + created_at: DateTime::from_timestamp_millis(v.created_at_ms).unwrap_or_default(), 117 + updated_at: DateTime::from_timestamp_millis(v.updated_at_ms).unwrap_or_default(), 118 + expires_at: DateTime::from_timestamp_millis(v.expires_at_ms).unwrap_or_default(), 119 + client_id: v.client_id.clone(), 120 + client_auth: tranquil_oauth::ClientAuth::None, 121 + device_id: None, 122 + parameters: serde_json::from_str(&v.parameters_json) 123 + .unwrap_or_else(|_| default_parameters(&v.client_id)), 124 + details: None, 125 + code: None, 126 + current_refresh_token: refresh_token, 127 + scope: Some(v.scope.clone()).filter(|s| !s.is_empty()), 128 + controller_did: v 129 + .controller_did 130 + .as_ref() 131 + .and_then(|d| Did::new(d.clone()).ok()), 132 + }) 133 + } 134 + 135 + fn delete_token_indexes( 136 + &self, 137 + batch: &mut fjall::OwnedWriteBatch, 138 + token: &OAuthTokenValue, 139 + user_hash: UserHash, 140 + ) { 141 + batch.remove( 142 + &self.auth, 143 + oauth_token_key(user_hash, token.family_id).as_slice(), 144 + ); 145 + batch.remove( 146 + &self.auth, 147 + oauth_token_by_id_key(&token.token_id).as_slice(), 148 + ); 149 + batch.remove( 150 + &self.auth, 151 + oauth_token_by_refresh_key(&token.refresh_token).as_slice(), 152 + ); 153 + if let Some(prev) = &token.previous_refresh_token { 154 + batch.remove(&self.auth, oauth_token_by_prev_refresh_key(prev).as_slice()); 155 + } 156 + batch.remove( 157 + &self.auth, 158 + oauth_token_by_family_key(token.family_id).as_slice(), 159 + ); 160 + } 161 + 162 + fn collect_tokens_for_did( 163 + &self, 164 + user_hash: UserHash, 165 + ) -> Result<Vec<OAuthTokenValue>, MetastoreError> { 166 + let prefix = oauth_token_user_prefix(user_hash); 167 + self.auth 168 + .prefix(prefix.as_slice()) 169 + .try_fold(Vec::new(), |mut acc, guard| { 170 + let (_, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 171 + match OAuthTokenValue::deserialize(&val_bytes) { 172 + Some(v) => { 173 + acc.push(v); 174 + Ok::<_, MetastoreError>(acc) 175 + } 176 + None => Ok(acc), 177 + } 178 + }) 179 + } 180 + 181 + fn request_value_to_data(&self, v: &OAuthRequestValue) -> Result<RequestData, MetastoreError> { 182 + let parameters = serde_json::from_str(&v.parameters_json) 183 + .map_err(|_| MetastoreError::CorruptData("corrupt oauth request parameters"))?; 184 + let client_auth = v 185 + .client_auth_json 186 + .as_ref() 187 + .map(|j| serde_json::from_str(j)) 188 + .transpose() 189 + .map_err(|_| MetastoreError::CorruptData("corrupt oauth client_auth"))?; 190 + 191 + Ok(RequestData { 192 + client_id: v.client_id.clone(), 193 + client_auth, 194 + parameters, 195 + expires_at: DateTime::from_timestamp_millis(v.expires_at_ms).unwrap_or_default(), 196 + did: v 197 + .did 198 + .as_ref() 199 + .map(|d| Did::new(d.clone())) 200 + .transpose() 201 + .map_err(|_| MetastoreError::CorruptData("invalid did in oauth request"))?, 202 + device_id: v 203 + .device_id 204 + .as_ref() 205 + .map(|d| tranquil_oauth::DeviceId(d.clone())), 206 + code: v.code.as_ref().map(|c| tranquil_oauth::Code(c.clone())), 207 + controller_did: v 208 + .controller_did 209 + .as_ref() 210 + .map(|d| Did::new(d.clone())) 211 + .transpose() 212 + .map_err(|_| { 213 + MetastoreError::CorruptData("invalid controller_did in oauth request") 214 + })?, 215 + }) 216 + } 217 + 218 + fn data_to_request_value(&self, data: &RequestData) -> OAuthRequestValue { 219 + OAuthRequestValue { 220 + client_id: data.client_id.clone(), 221 + client_auth_json: data 222 + .client_auth 223 + .as_ref() 224 + .map(|ca| serde_json::to_string(ca).unwrap_or_default()), 225 + parameters_json: serde_json::to_string(&data.parameters).unwrap_or_default(), 226 + expires_at_ms: data.expires_at.timestamp_millis(), 227 + did: data.did.as_ref().map(|d| d.to_string()), 228 + device_id: data.device_id.as_ref().map(|d| d.0.clone()), 229 + code: data.code.as_ref().map(|c| c.0.clone()), 230 + controller_did: data.controller_did.as_ref().map(|d| d.to_string()), 231 + } 232 + } 233 + 234 + pub fn create_token(&self, data: &TokenData) -> Result<TokenFamilyId, MetastoreError> { 235 + let user_hash = self.resolve_user_hash_from_did(data.did.as_str()); 236 + let family_id = self.next_family_id()?; 237 + let now_ms = Utc::now().timestamp_millis(); 238 + 239 + let value = OAuthTokenValue { 240 + family_id, 241 + did: data.did.to_string(), 242 + client_id: data.client_id.clone(), 243 + token_id: data.token_id.0.clone(), 244 + refresh_token: data 245 + .current_refresh_token 246 + .as_ref() 247 + .map(|r| r.0.clone()) 248 + .unwrap_or_default(), 249 + previous_refresh_token: None, 250 + scope: data.scope.clone().unwrap_or_default(), 251 + expires_at_ms: data.expires_at.timestamp_millis(), 252 + created_at_ms: data.created_at.timestamp_millis(), 253 + updated_at_ms: now_ms, 254 + parameters_json: serde_json::to_string(&data.parameters).unwrap_or_default(), 255 + controller_did: data.controller_did.as_ref().map(|d| d.to_string()), 256 + }; 257 + 258 + let index = TokenIndexValue { 259 + user_hash: user_hash.raw(), 260 + family_id, 261 + }; 262 + 263 + let mut batch = self.db.batch(); 264 + batch.insert( 265 + &self.auth, 266 + oauth_token_key(user_hash, family_id).as_slice(), 267 + value.serialize_with_ttl(), 268 + ); 269 + batch.insert( 270 + &self.auth, 271 + oauth_token_by_id_key(&value.token_id).as_slice(), 272 + index.serialize_with_ttl(value.expires_at_ms), 273 + ); 274 + if !value.refresh_token.is_empty() { 275 + batch.insert( 276 + &self.auth, 277 + oauth_token_by_refresh_key(&value.refresh_token).as_slice(), 278 + index.serialize_with_ttl(value.expires_at_ms), 279 + ); 280 + } 281 + batch.insert( 282 + &self.auth, 283 + oauth_token_by_family_key(family_id).as_slice(), 284 + index.serialize_with_ttl(value.expires_at_ms), 285 + ); 286 + batch.commit().map_err(MetastoreError::Fjall)?; 287 + 288 + Ok(TokenFamilyId::new(family_id)) 289 + } 290 + 291 + pub fn get_token_by_id(&self, token_id: &TokenId) -> Result<Option<TokenData>, MetastoreError> { 292 + let index_key = oauth_token_by_id_key(token_id.as_str()); 293 + let index: Option<TokenIndexValue> = point_lookup( 294 + &self.auth, 295 + index_key.as_slice(), 296 + TokenIndexValue::deserialize, 297 + "corrupt oauth token index", 298 + )?; 299 + 300 + match index { 301 + Some(idx) => { 302 + let uh = UserHash::from_raw(idx.user_hash); 303 + self.load_token_by_family_id(uh, idx.family_id)? 304 + .map(|v| self.token_value_to_data(&v)) 305 + .transpose() 306 + } 307 + None => Ok(None), 308 + } 309 + } 310 + 311 + pub fn get_token_by_refresh_token( 312 + &self, 313 + refresh_token: &RefreshToken, 314 + ) -> Result<Option<(TokenFamilyId, TokenData)>, MetastoreError> { 315 + let index_key = oauth_token_by_refresh_key(refresh_token.as_str()); 316 + let index: Option<TokenIndexValue> = point_lookup( 317 + &self.auth, 318 + index_key.as_slice(), 319 + TokenIndexValue::deserialize, 320 + "corrupt oauth token refresh index", 321 + )?; 322 + 323 + match index { 324 + Some(idx) => { 325 + let uh = UserHash::from_raw(idx.user_hash); 326 + self.load_token_by_family_id(uh, idx.family_id)? 327 + .map(|v| { 328 + self.token_value_to_data(&v) 329 + .map(|td| (TokenFamilyId::new(idx.family_id), td)) 330 + }) 331 + .transpose() 332 + } 333 + None => Ok(None), 334 + } 335 + } 336 + 337 + pub fn get_token_by_previous_refresh_token( 338 + &self, 339 + refresh_token: &RefreshToken, 340 + ) -> Result<Option<(TokenFamilyId, TokenData)>, MetastoreError> { 341 + let index_key = oauth_token_by_prev_refresh_key(refresh_token.as_str()); 342 + let index: Option<TokenIndexValue> = point_lookup( 343 + &self.auth, 344 + index_key.as_slice(), 345 + TokenIndexValue::deserialize, 346 + "corrupt oauth token prev refresh index", 347 + )?; 348 + 349 + match index { 350 + Some(idx) => { 351 + let uh = UserHash::from_raw(idx.user_hash); 352 + self.load_token_by_family_id(uh, idx.family_id)? 353 + .map(|v| { 354 + self.token_value_to_data(&v) 355 + .map(|td| (TokenFamilyId::new(idx.family_id), td)) 356 + }) 357 + .transpose() 358 + } 359 + None => Ok(None), 360 + } 361 + } 362 + 363 + fn lookup_by_family_id( 364 + &self, 365 + family_id: i32, 366 + ) -> Result<Option<(UserHash, OAuthTokenValue)>, MetastoreError> { 367 + let index_key = oauth_token_by_family_key(family_id); 368 + let index: Option<TokenIndexValue> = point_lookup( 369 + &self.auth, 370 + index_key.as_slice(), 371 + TokenIndexValue::deserialize, 372 + "corrupt oauth token family index", 373 + )?; 374 + match index { 375 + Some(idx) => { 376 + let uh = UserHash::from_raw(idx.user_hash); 377 + Ok(self 378 + .load_token_by_family_id(uh, idx.family_id)? 379 + .map(|v| (uh, v))) 380 + } 381 + None => Ok(None), 382 + } 383 + } 384 + 385 + pub fn rotate_token( 386 + &self, 387 + old_db_id: TokenFamilyId, 388 + new_refresh_token: &RefreshToken, 389 + new_expires_at: DateTime<Utc>, 390 + ) -> Result<(), MetastoreError> { 391 + let (user_hash, mut token) = self 392 + .lookup_by_family_id(old_db_id.as_i32())? 393 + .ok_or(MetastoreError::InvalidInput("token family not found"))?; 394 + 395 + let old_refresh = token.refresh_token.clone(); 396 + let old_prev = token.previous_refresh_token.clone(); 397 + 398 + token.previous_refresh_token = Some(old_refresh.clone()); 399 + token.refresh_token = new_refresh_token.as_str().to_owned(); 400 + token.expires_at_ms = new_expires_at.timestamp_millis(); 401 + token.updated_at_ms = Utc::now().timestamp_millis(); 402 + 403 + let index = TokenIndexValue { 404 + user_hash: user_hash.raw(), 405 + family_id: token.family_id, 406 + }; 407 + 408 + let mut batch = self.db.batch(); 409 + batch.remove( 410 + &self.auth, 411 + oauth_token_by_refresh_key(&old_refresh).as_slice(), 412 + ); 413 + if let Some(prev) = &old_prev { 414 + batch.remove(&self.auth, oauth_token_by_prev_refresh_key(prev).as_slice()); 415 + } 416 + 417 + batch.insert( 418 + &self.auth, 419 + oauth_token_key(user_hash, token.family_id).as_slice(), 420 + token.serialize_with_ttl(), 421 + ); 422 + batch.insert( 423 + &self.auth, 424 + oauth_token_by_refresh_key(new_refresh_token.as_str()).as_slice(), 425 + index.serialize_with_ttl(token.expires_at_ms), 426 + ); 427 + batch.insert( 428 + &self.auth, 429 + oauth_token_by_prev_refresh_key(&old_refresh).as_slice(), 430 + index.serialize_with_ttl(token.expires_at_ms), 431 + ); 432 + batch.insert( 433 + &self.auth, 434 + oauth_token_by_id_key(&token.token_id).as_slice(), 435 + index.serialize_with_ttl(token.expires_at_ms), 436 + ); 437 + batch.insert( 438 + &self.auth, 439 + oauth_token_by_family_key(token.family_id).as_slice(), 440 + index.serialize_with_ttl(token.expires_at_ms), 441 + ); 442 + batch.commit().map_err(MetastoreError::Fjall)?; 443 + 444 + Ok(()) 445 + } 446 + 447 + pub fn check_refresh_token_used( 448 + &self, 449 + refresh_token: &RefreshToken, 450 + ) -> Result<Option<TokenFamilyId>, MetastoreError> { 451 + let key = oauth_used_refresh_key(refresh_token.as_str()); 452 + match self 453 + .auth 454 + .get(key.as_slice()) 455 + .map_err(MetastoreError::Fjall)? 456 + { 457 + Some(raw) => { 458 + Ok(UsedRefreshValue::deserialize(&raw).map(|v| TokenFamilyId::new(v.family_id))) 459 + } 460 + None => Ok(None), 461 + } 462 + } 463 + 464 + pub fn delete_token(&self, token_id: &TokenId) -> Result<(), MetastoreError> { 465 + let index_key = oauth_token_by_id_key(token_id.as_str()); 466 + let index: Option<TokenIndexValue> = point_lookup( 467 + &self.auth, 468 + index_key.as_slice(), 469 + TokenIndexValue::deserialize, 470 + "corrupt oauth token index", 471 + )?; 472 + 473 + let idx = match index { 474 + Some(idx) => idx, 475 + None => return Ok(()), 476 + }; 477 + 478 + let uh = UserHash::from_raw(idx.user_hash); 479 + let token = match self.load_token_by_family_id(uh, idx.family_id)? { 480 + Some(t) => t, 481 + None => return Ok(()), 482 + }; 483 + 484 + let mut batch = self.db.batch(); 485 + self.delete_token_indexes(&mut batch, &token, uh); 486 + batch.commit().map_err(MetastoreError::Fjall)?; 487 + Ok(()) 488 + } 489 + 490 + pub fn delete_token_family(&self, db_id: TokenFamilyId) -> Result<(), MetastoreError> { 491 + let (user_hash, token) = match self.lookup_by_family_id(db_id.as_i32())? { 492 + Some(f) => f, 493 + None => return Ok(()), 494 + }; 495 + 496 + let used_val = UsedRefreshValue { 497 + family_id: token.family_id, 498 + }; 499 + 500 + let mut batch = self.db.batch(); 501 + self.delete_token_indexes(&mut batch, &token, user_hash); 502 + if !token.refresh_token.is_empty() { 503 + batch.insert( 504 + &self.auth, 505 + oauth_used_refresh_key(&token.refresh_token).as_slice(), 506 + used_val.serialize_with_ttl(token.expires_at_ms), 507 + ); 508 + } 509 + batch.commit().map_err(MetastoreError::Fjall)?; 510 + Ok(()) 511 + } 512 + 513 + pub fn list_tokens_for_user(&self, did: &Did) -> Result<Vec<TokenData>, MetastoreError> { 514 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 515 + let tokens = self.collect_tokens_for_did(user_hash)?; 516 + tokens.iter().map(|v| self.token_value_to_data(v)).collect() 517 + } 518 + 519 + pub fn count_tokens_for_user(&self, did: &Did) -> Result<i64, MetastoreError> { 520 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 521 + let prefix = oauth_token_user_prefix(user_hash); 522 + super::scan::count_prefix(&self.auth, prefix.as_slice()) 523 + } 524 + 525 + pub fn delete_oldest_tokens_for_user( 526 + &self, 527 + did: &Did, 528 + keep_count: i64, 529 + ) -> Result<u64, MetastoreError> { 530 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 531 + let mut tokens = self.collect_tokens_for_did(user_hash)?; 532 + tokens.sort_by_key(|t| std::cmp::Reverse(t.created_at_ms)); 533 + 534 + let keep = usize::try_from(keep_count).unwrap_or(usize::MAX); 535 + let to_delete: Vec<_> = tokens.into_iter().skip(keep).collect(); 536 + let count = u64::try_from(to_delete.len()).unwrap_or(u64::MAX); 537 + 538 + match count { 539 + 0 => Ok(0), 540 + _ => { 541 + let mut batch = self.db.batch(); 542 + to_delete.iter().for_each(|token| { 543 + self.delete_token_indexes(&mut batch, token, user_hash); 544 + }); 545 + batch.commit().map_err(MetastoreError::Fjall)?; 546 + Ok(count) 547 + } 548 + } 549 + } 550 + 551 + pub fn revoke_tokens_for_client( 552 + &self, 553 + did: &Did, 554 + client_id: &ClientId, 555 + ) -> Result<u64, MetastoreError> { 556 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 557 + let tokens = self.collect_tokens_for_did(user_hash)?; 558 + let to_revoke: Vec<_> = tokens 559 + .into_iter() 560 + .filter(|t| t.client_id == client_id.as_str()) 561 + .collect(); 562 + 563 + let count = u64::try_from(to_revoke.len()).unwrap_or(u64::MAX); 564 + match count { 565 + 0 => Ok(0), 566 + _ => { 567 + let mut batch = self.db.batch(); 568 + to_revoke.iter().for_each(|token| { 569 + self.delete_token_indexes(&mut batch, token, user_hash); 570 + }); 571 + batch.commit().map_err(MetastoreError::Fjall)?; 572 + Ok(count) 573 + } 574 + } 575 + } 576 + 577 + pub fn revoke_tokens_for_controller( 578 + &self, 579 + delegated_did: &Did, 580 + controller_did: &Did, 581 + ) -> Result<u64, MetastoreError> { 582 + let user_hash = self.resolve_user_hash_from_did(delegated_did.as_str()); 583 + let tokens = self.collect_tokens_for_did(user_hash)?; 584 + let controller_str = controller_did.to_string(); 585 + let to_revoke: Vec<_> = tokens 586 + .into_iter() 587 + .filter(|t| t.controller_did.as_deref() == Some(controller_str.as_str())) 588 + .collect(); 589 + 590 + let count = u64::try_from(to_revoke.len()).unwrap_or(u64::MAX); 591 + match count { 592 + 0 => Ok(0), 593 + _ => { 594 + let mut batch = self.db.batch(); 595 + to_revoke.iter().for_each(|token| { 596 + self.delete_token_indexes(&mut batch, token, user_hash); 597 + }); 598 + batch.commit().map_err(MetastoreError::Fjall)?; 599 + Ok(count) 600 + } 601 + } 602 + } 603 + 604 + pub fn create_authorization_request( 605 + &self, 606 + request_id: &RequestId, 607 + data: &RequestData, 608 + ) -> Result<(), MetastoreError> { 609 + let value = self.data_to_request_value(data); 610 + let key = oauth_auth_request_key(request_id.as_str()); 611 + self.auth 612 + .insert(key.as_slice(), value.serialize_with_ttl()) 613 + .map_err(MetastoreError::Fjall) 614 + } 615 + 616 + pub fn get_authorization_request( 617 + &self, 618 + request_id: &RequestId, 619 + ) -> Result<Option<RequestData>, MetastoreError> { 620 + let key = oauth_auth_request_key(request_id.as_str()); 621 + let val: Option<OAuthRequestValue> = point_lookup( 622 + &self.auth, 623 + key.as_slice(), 624 + OAuthRequestValue::deserialize, 625 + "corrupt oauth auth request", 626 + )?; 627 + 628 + val.map(|v| self.request_value_to_data(&v)).transpose() 629 + } 630 + 631 + pub fn set_authorization_did( 632 + &self, 633 + request_id: &RequestId, 634 + did: &Did, 635 + device_id: Option<&DeviceId>, 636 + ) -> Result<(), MetastoreError> { 637 + let key = oauth_auth_request_key(request_id.as_str()); 638 + let mut value: OAuthRequestValue = point_lookup( 639 + &self.auth, 640 + key.as_slice(), 641 + OAuthRequestValue::deserialize, 642 + "corrupt oauth auth request", 643 + )? 644 + .ok_or(MetastoreError::InvalidInput("auth request not found"))?; 645 + 646 + value.did = Some(did.to_string()); 647 + value.device_id = device_id.map(|d| d.as_str().to_owned()); 648 + 649 + self.auth 650 + .insert(key.as_slice(), value.serialize_with_ttl()) 651 + .map_err(MetastoreError::Fjall) 652 + } 653 + 654 + pub fn update_authorization_request( 655 + &self, 656 + request_id: &RequestId, 657 + did: &Did, 658 + device_id: Option<&DeviceId>, 659 + code: &AuthorizationCode, 660 + ) -> Result<(), MetastoreError> { 661 + let key = oauth_auth_request_key(request_id.as_str()); 662 + let mut value: OAuthRequestValue = point_lookup( 663 + &self.auth, 664 + key.as_slice(), 665 + OAuthRequestValue::deserialize, 666 + "corrupt oauth auth request", 667 + )? 668 + .ok_or(MetastoreError::InvalidInput("auth request not found"))?; 669 + 670 + value.did = Some(did.to_string()); 671 + value.device_id = device_id.map(|d| d.as_str().to_owned()); 672 + value.code = Some(code.as_str().to_owned()); 673 + 674 + let code_index_key = oauth_auth_by_code_key(code.as_str()); 675 + 676 + let mut batch = self.db.batch(); 677 + batch.insert(&self.auth, key.as_slice(), value.serialize_with_ttl()); 678 + batch.insert( 679 + &self.auth, 680 + code_index_key.as_slice(), 681 + request_id.as_str().as_bytes(), 682 + ); 683 + batch.commit().map_err(MetastoreError::Fjall) 684 + } 685 + 686 + pub fn consume_authorization_request_by_code( 687 + &self, 688 + code: &AuthorizationCode, 689 + ) -> Result<Option<RequestData>, MetastoreError> { 690 + let code_key = oauth_auth_by_code_key(code.as_str()); 691 + let request_id_bytes = match self 692 + .auth 693 + .get(code_key.as_slice()) 694 + .map_err(MetastoreError::Fjall)? 695 + { 696 + Some(bytes) => bytes, 697 + None => return Ok(None), 698 + }; 699 + 700 + let request_id_str = std::str::from_utf8(&request_id_bytes) 701 + .map_err(|_| MetastoreError::CorruptData("corrupt code index value"))?; 702 + let req_key = oauth_auth_request_key(request_id_str); 703 + 704 + let value: Option<OAuthRequestValue> = point_lookup( 705 + &self.auth, 706 + req_key.as_slice(), 707 + OAuthRequestValue::deserialize, 708 + "corrupt oauth auth request", 709 + )?; 710 + 711 + let data = match value { 712 + Some(v) => self.request_value_to_data(&v)?, 713 + None => return Ok(None), 714 + }; 715 + 716 + let mut batch = self.db.batch(); 717 + batch.remove(&self.auth, req_key.as_slice()); 718 + batch.remove(&self.auth, code_key.as_slice()); 719 + batch.commit().map_err(MetastoreError::Fjall)?; 720 + 721 + Ok(Some(data)) 722 + } 723 + 724 + pub fn delete_authorization_request( 725 + &self, 726 + request_id: &RequestId, 727 + ) -> Result<(), MetastoreError> { 728 + let key = oauth_auth_request_key(request_id.as_str()); 729 + let value: Option<OAuthRequestValue> = point_lookup( 730 + &self.auth, 731 + key.as_slice(), 732 + OAuthRequestValue::deserialize, 733 + "corrupt oauth auth request", 734 + )?; 735 + 736 + let mut batch = self.db.batch(); 737 + batch.remove(&self.auth, key.as_slice()); 738 + if let Some(code) = value.and_then(|v| v.code) { 739 + batch.remove(&self.auth, oauth_auth_by_code_key(&code).as_slice()); 740 + } 741 + batch.commit().map_err(MetastoreError::Fjall) 742 + } 743 + 744 + pub fn delete_expired_authorization_requests(&self) -> Result<u64, MetastoreError> { 745 + let now_ms = Utc::now().timestamp_millis(); 746 + let prefix = oauth_auth_request_prefix(); 747 + let mut keys_to_remove: Vec<(Vec<u8>, Option<String>)> = Vec::new(); 748 + 749 + self.auth.prefix(prefix.as_slice()).try_for_each(|guard| { 750 + let (key_bytes, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 751 + match OAuthRequestValue::deserialize(&val_bytes) { 752 + Some(v) if v.expires_at_ms <= now_ms => { 753 + keys_to_remove.push((key_bytes.to_vec(), v.code)); 754 + Ok::<(), MetastoreError>(()) 755 + } 756 + _ => Ok(()), 757 + } 758 + })?; 759 + 760 + let count = u64::try_from(keys_to_remove.len()).unwrap_or(u64::MAX); 761 + match count { 762 + 0 => Ok(0), 763 + _ => { 764 + let mut batch = self.db.batch(); 765 + keys_to_remove.iter().for_each(|(key, code)| { 766 + batch.remove(&self.auth, key); 767 + if let Some(c) = code { 768 + batch.remove(&self.auth, oauth_auth_by_code_key(c).as_slice()); 769 + } 770 + }); 771 + batch.commit().map_err(MetastoreError::Fjall)?; 772 + Ok(count) 773 + } 774 + } 775 + } 776 + 777 + pub fn extend_authorization_request_expiry( 778 + &self, 779 + request_id: &RequestId, 780 + new_expires_at: DateTime<Utc>, 781 + ) -> Result<bool, MetastoreError> { 782 + let key = oauth_auth_request_key(request_id.as_str()); 783 + let value: Option<OAuthRequestValue> = point_lookup( 784 + &self.auth, 785 + key.as_slice(), 786 + OAuthRequestValue::deserialize, 787 + "corrupt oauth auth request", 788 + )?; 789 + 790 + match value { 791 + Some(mut v) => { 792 + v.expires_at_ms = new_expires_at.timestamp_millis(); 793 + self.auth 794 + .insert(key.as_slice(), v.serialize_with_ttl()) 795 + .map_err(MetastoreError::Fjall)?; 796 + Ok(true) 797 + } 798 + None => Ok(false), 799 + } 800 + } 801 + 802 + pub fn mark_request_authenticated( 803 + &self, 804 + request_id: &RequestId, 805 + did: &Did, 806 + device_id: Option<&DeviceId>, 807 + ) -> Result<(), MetastoreError> { 808 + self.set_authorization_did(request_id, did, device_id) 809 + } 810 + 811 + pub fn update_request_scope( 812 + &self, 813 + request_id: &RequestId, 814 + scope: &str, 815 + ) -> Result<(), MetastoreError> { 816 + let key = oauth_auth_request_key(request_id.as_str()); 817 + let mut value: OAuthRequestValue = point_lookup( 818 + &self.auth, 819 + key.as_slice(), 820 + OAuthRequestValue::deserialize, 821 + "corrupt oauth auth request", 822 + )? 823 + .ok_or(MetastoreError::InvalidInput("auth request not found"))?; 824 + 825 + let mut params: serde_json::Value = serde_json::from_str(&value.parameters_json) 826 + .map_err(|_| MetastoreError::CorruptData("corrupt parameters json"))?; 827 + params["scope"] = serde_json::Value::String(scope.to_owned()); 828 + value.parameters_json = serde_json::to_string(&params) 829 + .map_err(|_| MetastoreError::CorruptData("json serialize fail"))?; 830 + 831 + self.auth 832 + .insert(key.as_slice(), value.serialize_with_ttl()) 833 + .map_err(MetastoreError::Fjall) 834 + } 835 + 836 + pub fn set_controller_did( 837 + &self, 838 + request_id: &RequestId, 839 + controller_did: &Did, 840 + ) -> Result<(), MetastoreError> { 841 + let key = oauth_auth_request_key(request_id.as_str()); 842 + let mut value: OAuthRequestValue = point_lookup( 843 + &self.auth, 844 + key.as_slice(), 845 + OAuthRequestValue::deserialize, 846 + "corrupt oauth auth request", 847 + )? 848 + .ok_or(MetastoreError::InvalidInput("auth request not found"))?; 849 + 850 + value.controller_did = Some(controller_did.to_string()); 851 + 852 + self.auth 853 + .insert(key.as_slice(), value.serialize_with_ttl()) 854 + .map_err(MetastoreError::Fjall) 855 + } 856 + 857 + pub fn set_request_did(&self, request_id: &RequestId, did: &Did) -> Result<(), MetastoreError> { 858 + self.set_authorization_did(request_id, did, None) 859 + } 860 + 861 + pub fn create_device( 862 + &self, 863 + device_id: &DeviceId, 864 + data: &DeviceData, 865 + ) -> Result<(), MetastoreError> { 866 + let now_ms = Utc::now().timestamp_millis(); 867 + let value = OAuthDeviceValue { 868 + session_id: data.session_id.0.clone(), 869 + user_agent: data.user_agent.clone(), 870 + ip_address: data.ip_address.clone(), 871 + last_seen_at_ms: data.last_seen_at.timestamp_millis(), 872 + created_at_ms: now_ms, 873 + }; 874 + 875 + let key = oauth_device_key(device_id.as_str()); 876 + self.auth 877 + .insert(key.as_slice(), value.serialize()) 878 + .map_err(MetastoreError::Fjall) 879 + } 880 + 881 + pub fn get_device(&self, device_id: &DeviceId) -> Result<Option<DeviceData>, MetastoreError> { 882 + let key = oauth_device_key(device_id.as_str()); 883 + let val: Option<OAuthDeviceValue> = point_lookup( 884 + &self.auth, 885 + key.as_slice(), 886 + OAuthDeviceValue::deserialize, 887 + "corrupt oauth device", 888 + )?; 889 + 890 + Ok(val.map(|v| DeviceData { 891 + session_id: tranquil_oauth::SessionId(v.session_id), 892 + user_agent: v.user_agent, 893 + ip_address: v.ip_address, 894 + last_seen_at: DateTime::from_timestamp_millis(v.last_seen_at_ms).unwrap_or_default(), 895 + })) 896 + } 897 + 898 + pub fn update_device_last_seen(&self, device_id: &DeviceId) -> Result<(), MetastoreError> { 899 + let key = oauth_device_key(device_id.as_str()); 900 + let mut value: OAuthDeviceValue = point_lookup( 901 + &self.auth, 902 + key.as_slice(), 903 + OAuthDeviceValue::deserialize, 904 + "corrupt oauth device", 905 + )? 906 + .ok_or(MetastoreError::InvalidInput("device not found"))?; 907 + 908 + value.last_seen_at_ms = Utc::now().timestamp_millis(); 909 + 910 + self.auth 911 + .insert(key.as_slice(), value.serialize()) 912 + .map_err(MetastoreError::Fjall) 913 + } 914 + 915 + pub fn delete_device(&self, device_id: &DeviceId) -> Result<(), MetastoreError> { 916 + let key = oauth_device_key(device_id.as_str()); 917 + self.auth 918 + .remove(key.as_slice()) 919 + .map_err(MetastoreError::Fjall) 920 + } 921 + 922 + pub fn upsert_account_device( 923 + &self, 924 + did: &Did, 925 + device_id: &DeviceId, 926 + ) -> Result<(), MetastoreError> { 927 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 928 + let key = oauth_account_device_key(user_hash, device_id.as_str()); 929 + let now_ms = Utc::now().timestamp_millis(); 930 + 931 + let value = AccountDeviceValue { 932 + last_used_at_ms: now_ms, 933 + }; 934 + 935 + self.auth 936 + .insert(key.as_slice(), value.serialize_with_ttl()) 937 + .map_err(MetastoreError::Fjall) 938 + } 939 + 940 + pub fn get_device_accounts( 941 + &self, 942 + device_id: &DeviceId, 943 + ) -> Result<Vec<DeviceAccountRow>, MetastoreError> { 944 + let tag_prefix = [super::keys::KeyTag::OAUTH_ACCOUNT_DEVICE.raw()]; 945 + let device_id_str = device_id.as_str(); 946 + 947 + self.auth 948 + .prefix(tag_prefix) 949 + .try_fold(Vec::new(), |mut acc, guard| { 950 + let (key_bytes, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 951 + let val = match AccountDeviceValue::deserialize(&val_bytes) { 952 + Some(v) => v, 953 + None => return Ok(acc), 954 + }; 955 + 956 + let mut reader = super::encoding::KeyReader::new(&key_bytes[1..]); 957 + let user_hash_raw = reader 958 + .u64() 959 + .ok_or(MetastoreError::CorruptData("corrupt account device key"))?; 960 + let stored_device_id = reader 961 + .string() 962 + .ok_or(MetastoreError::CorruptData("corrupt account device key"))?; 963 + 964 + if stored_device_id != device_id_str { 965 + return Ok(acc); 966 + } 967 + 968 + let uh = UserHash::from_raw(user_hash_raw); 969 + if let Some(user) = self.load_user_value(uh)? { 970 + let did = Did::new(user.did.clone()) 971 + .map_err(|_| MetastoreError::CorruptData("invalid did in user"))?; 972 + let handle = Handle::new(user.handle.clone()) 973 + .map_err(|_| MetastoreError::CorruptData("invalid handle in user"))?; 974 + acc.push(DeviceAccountRow { 975 + did, 976 + handle, 977 + email: user.email, 978 + last_used_at: DateTime::from_timestamp_millis(val.last_used_at_ms) 979 + .unwrap_or_default(), 980 + }); 981 + } 982 + Ok(acc) 983 + }) 984 + } 985 + 986 + pub fn verify_account_on_device( 987 + &self, 988 + device_id: &DeviceId, 989 + did: &Did, 990 + ) -> Result<bool, MetastoreError> { 991 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 992 + let key = oauth_account_device_key(user_hash, device_id.as_str()); 993 + Ok(self 994 + .auth 995 + .get(key.as_slice()) 996 + .map_err(MetastoreError::Fjall)? 997 + .is_some()) 998 + } 999 + 1000 + pub fn check_and_record_dpop_jti(&self, jti: &DPoPProofId) -> Result<bool, MetastoreError> { 1001 + let key = oauth_dpop_jti_key(jti.as_str()); 1002 + let existing = self 1003 + .auth 1004 + .get(key.as_slice()) 1005 + .map_err(MetastoreError::Fjall)?; 1006 + 1007 + match existing { 1008 + Some(_) => Ok(false), 1009 + None => { 1010 + let value = DpopJtiValue { 1011 + recorded_at_ms: Utc::now().timestamp_millis(), 1012 + }; 1013 + self.auth 1014 + .insert(key.as_slice(), value.serialize_with_ttl()) 1015 + .map_err(MetastoreError::Fjall)?; 1016 + Ok(true) 1017 + } 1018 + } 1019 + } 1020 + 1021 + pub fn cleanup_expired_dpop_jtis(&self, max_age_secs: i64) -> Result<u64, MetastoreError> { 1022 + let cutoff_ms = Utc::now() 1023 + .timestamp_millis() 1024 + .saturating_sub(max_age_secs.saturating_mul(1000)); 1025 + let prefix = oauth_dpop_jti_prefix(); 1026 + let mut keys_to_remove: Vec<Vec<u8>> = Vec::new(); 1027 + 1028 + self.auth.prefix(prefix.as_slice()).try_for_each(|guard| { 1029 + let (key_bytes, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 1030 + match DpopJtiValue::ttl_ms(&val_bytes) { 1031 + Some(recorded_ms) if (recorded_ms as i64) < cutoff_ms => { 1032 + keys_to_remove.push(key_bytes.to_vec()); 1033 + } 1034 + _ => {} 1035 + } 1036 + Ok::<(), MetastoreError>(()) 1037 + })?; 1038 + 1039 + let count = u64::try_from(keys_to_remove.len()).unwrap_or(u64::MAX); 1040 + match count { 1041 + 0 => Ok(0), 1042 + _ => { 1043 + let mut batch = self.db.batch(); 1044 + keys_to_remove.iter().for_each(|key| { 1045 + batch.remove(&self.auth, key); 1046 + }); 1047 + batch.commit().map_err(MetastoreError::Fjall)?; 1048 + Ok(count) 1049 + } 1050 + } 1051 + } 1052 + 1053 + pub fn create_2fa_challenge( 1054 + &self, 1055 + did: &Did, 1056 + request_uri: &RequestId, 1057 + ) -> Result<TwoFactorChallenge, MetastoreError> { 1058 + let id = Uuid::new_v4(); 1059 + let now = Utc::now(); 1060 + let expires_at = now + Duration::minutes(10); 1061 + let code: String = { 1062 + let entropy = Uuid::new_v4(); 1063 + let bytes = entropy.as_bytes(); 1064 + (0..6) 1065 + .map(|i| std::char::from_digit(u32::from(bytes[i] % 10), 10).unwrap_or('0')) 1066 + .collect() 1067 + }; 1068 + 1069 + let value = TwoFactorChallengeValue { 1070 + id: *id.as_bytes(), 1071 + did: did.to_string(), 1072 + request_uri: request_uri.as_str().to_owned(), 1073 + code: code.clone(), 1074 + attempts: 0, 1075 + created_at_ms: now.timestamp_millis(), 1076 + expires_at_ms: expires_at.timestamp_millis(), 1077 + }; 1078 + 1079 + let primary_key = oauth_2fa_challenge_key(id.as_bytes()); 1080 + let request_index_key = oauth_2fa_by_request_key(request_uri.as_str()); 1081 + 1082 + let mut batch = self.db.batch(); 1083 + batch.insert( 1084 + &self.auth, 1085 + primary_key.as_slice(), 1086 + value.serialize_with_ttl(), 1087 + ); 1088 + batch.insert(&self.auth, request_index_key.as_slice(), id.as_bytes()); 1089 + batch.commit().map_err(MetastoreError::Fjall)?; 1090 + 1091 + Ok(TwoFactorChallenge { 1092 + id, 1093 + did: did.clone(), 1094 + request_uri: request_uri.as_str().to_owned(), 1095 + code, 1096 + attempts: 0, 1097 + created_at: now, 1098 + expires_at, 1099 + }) 1100 + } 1101 + 1102 + pub fn get_2fa_challenge( 1103 + &self, 1104 + request_uri: &RequestId, 1105 + ) -> Result<Option<TwoFactorChallenge>, MetastoreError> { 1106 + let index_key = oauth_2fa_by_request_key(request_uri.as_str()); 1107 + let id_bytes = match self 1108 + .auth 1109 + .get(index_key.as_slice()) 1110 + .map_err(MetastoreError::Fjall)? 1111 + { 1112 + Some(bytes) => bytes, 1113 + None => return Ok(None), 1114 + }; 1115 + 1116 + let id_array: [u8; 16] = id_bytes 1117 + .as_ref() 1118 + .try_into() 1119 + .map_err(|_| MetastoreError::CorruptData("corrupt 2fa index"))?; 1120 + 1121 + let primary_key = oauth_2fa_challenge_key(&id_array); 1122 + let val: Option<TwoFactorChallengeValue> = point_lookup( 1123 + &self.auth, 1124 + primary_key.as_slice(), 1125 + TwoFactorChallengeValue::deserialize, 1126 + "corrupt 2fa challenge", 1127 + )?; 1128 + 1129 + val.map(|v| { 1130 + Ok(TwoFactorChallenge { 1131 + id: Uuid::from_bytes(v.id), 1132 + did: Did::new(v.did) 1133 + .map_err(|_| MetastoreError::CorruptData("invalid did in 2fa challenge"))?, 1134 + request_uri: v.request_uri, 1135 + code: v.code, 1136 + attempts: v.attempts, 1137 + created_at: DateTime::from_timestamp_millis(v.created_at_ms).unwrap_or_default(), 1138 + expires_at: DateTime::from_timestamp_millis(v.expires_at_ms).unwrap_or_default(), 1139 + }) 1140 + }) 1141 + .transpose() 1142 + } 1143 + 1144 + pub fn increment_2fa_attempts(&self, id: Uuid) -> Result<i32, MetastoreError> { 1145 + let primary_key = oauth_2fa_challenge_key(id.as_bytes()); 1146 + let mut value: TwoFactorChallengeValue = point_lookup( 1147 + &self.auth, 1148 + primary_key.as_slice(), 1149 + TwoFactorChallengeValue::deserialize, 1150 + "corrupt 2fa challenge", 1151 + )? 1152 + .ok_or(MetastoreError::InvalidInput("2fa challenge not found"))?; 1153 + 1154 + value.attempts = value.attempts.saturating_add(1); 1155 + 1156 + self.auth 1157 + .insert(primary_key.as_slice(), value.serialize_with_ttl()) 1158 + .map_err(MetastoreError::Fjall)?; 1159 + 1160 + Ok(value.attempts) 1161 + } 1162 + 1163 + pub fn delete_2fa_challenge(&self, id: Uuid) -> Result<(), MetastoreError> { 1164 + let primary_key = oauth_2fa_challenge_key(id.as_bytes()); 1165 + let value: Option<TwoFactorChallengeValue> = point_lookup( 1166 + &self.auth, 1167 + primary_key.as_slice(), 1168 + TwoFactorChallengeValue::deserialize, 1169 + "corrupt 2fa challenge", 1170 + )?; 1171 + 1172 + let mut batch = self.db.batch(); 1173 + batch.remove(&self.auth, primary_key.as_slice()); 1174 + if let Some(v) = value { 1175 + batch.remove( 1176 + &self.auth, 1177 + oauth_2fa_by_request_key(&v.request_uri).as_slice(), 1178 + ); 1179 + } 1180 + batch.commit().map_err(MetastoreError::Fjall) 1181 + } 1182 + 1183 + pub fn delete_2fa_challenge_by_request_uri( 1184 + &self, 1185 + request_uri: &RequestId, 1186 + ) -> Result<(), MetastoreError> { 1187 + let index_key = oauth_2fa_by_request_key(request_uri.as_str()); 1188 + let id_bytes = match self 1189 + .auth 1190 + .get(index_key.as_slice()) 1191 + .map_err(MetastoreError::Fjall)? 1192 + { 1193 + Some(bytes) => bytes, 1194 + None => return Ok(()), 1195 + }; 1196 + 1197 + let id_array: [u8; 16] = match id_bytes.as_ref().try_into() { 1198 + Ok(arr) => arr, 1199 + Err(_) => return Ok(()), 1200 + }; 1201 + 1202 + let primary_key = oauth_2fa_challenge_key(&id_array); 1203 + let mut batch = self.db.batch(); 1204 + batch.remove(&self.auth, primary_key.as_slice()); 1205 + batch.remove(&self.auth, index_key.as_slice()); 1206 + batch.commit().map_err(MetastoreError::Fjall) 1207 + } 1208 + 1209 + pub fn cleanup_expired_2fa_challenges(&self) -> Result<u64, MetastoreError> { 1210 + let now_ms = Utc::now().timestamp_millis(); 1211 + let prefix = oauth_2fa_challenge_prefix(); 1212 + let mut to_remove: Vec<(Vec<u8>, String)> = Vec::new(); 1213 + 1214 + self.auth.prefix(prefix.as_slice()).try_for_each(|guard| { 1215 + let (key_bytes, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 1216 + match TwoFactorChallengeValue::ttl_ms(&val_bytes) { 1217 + Some(expires_ms) if (expires_ms as i64) <= now_ms => { 1218 + let v = TwoFactorChallengeValue::deserialize(&val_bytes); 1219 + let request_uri = v.map(|v| v.request_uri).unwrap_or_default(); 1220 + to_remove.push((key_bytes.to_vec(), request_uri)); 1221 + } 1222 + _ => {} 1223 + } 1224 + Ok::<(), MetastoreError>(()) 1225 + })?; 1226 + 1227 + let count = u64::try_from(to_remove.len()).unwrap_or(u64::MAX); 1228 + match count { 1229 + 0 => Ok(0), 1230 + _ => { 1231 + let mut batch = self.db.batch(); 1232 + to_remove.iter().for_each(|(key, request_uri)| { 1233 + batch.remove(&self.auth, key); 1234 + if !request_uri.is_empty() { 1235 + batch.remove(&self.auth, oauth_2fa_by_request_key(request_uri).as_slice()); 1236 + } 1237 + }); 1238 + batch.commit().map_err(MetastoreError::Fjall)?; 1239 + Ok(count) 1240 + } 1241 + } 1242 + } 1243 + 1244 + pub fn check_user_2fa_enabled(&self, did: &Did) -> Result<bool, MetastoreError> { 1245 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 1246 + let user = self.load_user_value(user_hash)?; 1247 + Ok(user 1248 + .map(|u| u.two_factor_enabled || u.totp_enabled || u.email_2fa_enabled) 1249 + .unwrap_or(false)) 1250 + } 1251 + 1252 + pub fn get_scope_preferences( 1253 + &self, 1254 + did: &Did, 1255 + client_id: &ClientId, 1256 + ) -> Result<Vec<ScopePreference>, MetastoreError> { 1257 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 1258 + let key = oauth_scope_prefs_key(user_hash, client_id.as_str()); 1259 + let val: Option<ScopePrefsValue> = point_lookup( 1260 + &self.auth, 1261 + key.as_slice(), 1262 + ScopePrefsValue::deserialize, 1263 + "corrupt scope preferences", 1264 + )?; 1265 + 1266 + match val { 1267 + Some(v) => serde_json::from_str(&v.prefs_json) 1268 + .map_err(|_| MetastoreError::CorruptData("corrupt scope prefs json")), 1269 + None => Ok(Vec::new()), 1270 + } 1271 + } 1272 + 1273 + pub fn upsert_scope_preferences( 1274 + &self, 1275 + did: &Did, 1276 + client_id: &ClientId, 1277 + prefs: &[ScopePreference], 1278 + ) -> Result<(), MetastoreError> { 1279 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 1280 + let key = oauth_scope_prefs_key(user_hash, client_id.as_str()); 1281 + let prefs_json = serde_json::to_string(prefs) 1282 + .map_err(|_| MetastoreError::CorruptData("scope prefs serialize fail"))?; 1283 + 1284 + let value = ScopePrefsValue { prefs_json }; 1285 + self.auth 1286 + .insert(key.as_slice(), value.serialize()) 1287 + .map_err(MetastoreError::Fjall) 1288 + } 1289 + 1290 + pub fn delete_scope_preferences( 1291 + &self, 1292 + did: &Did, 1293 + client_id: &ClientId, 1294 + ) -> Result<(), MetastoreError> { 1295 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 1296 + let key = oauth_scope_prefs_key(user_hash, client_id.as_str()); 1297 + self.auth 1298 + .remove(key.as_slice()) 1299 + .map_err(MetastoreError::Fjall) 1300 + } 1301 + 1302 + pub fn upsert_authorized_client( 1303 + &self, 1304 + did: &Did, 1305 + client_id: &ClientId, 1306 + data: &AuthorizedClientData, 1307 + ) -> Result<(), MetastoreError> { 1308 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 1309 + let key = oauth_auth_client_key(user_hash, client_id.as_str()); 1310 + let data_json = serde_json::to_string(data) 1311 + .map_err(|_| MetastoreError::CorruptData("authorized client serialize fail"))?; 1312 + 1313 + let value = AuthorizedClientValue { data_json }; 1314 + self.auth 1315 + .insert(key.as_slice(), value.serialize()) 1316 + .map_err(MetastoreError::Fjall) 1317 + } 1318 + 1319 + pub fn get_authorized_client( 1320 + &self, 1321 + did: &Did, 1322 + client_id: &ClientId, 1323 + ) -> Result<Option<AuthorizedClientData>, MetastoreError> { 1324 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 1325 + let key = oauth_auth_client_key(user_hash, client_id.as_str()); 1326 + let val: Option<AuthorizedClientValue> = point_lookup( 1327 + &self.auth, 1328 + key.as_slice(), 1329 + AuthorizedClientValue::deserialize, 1330 + "corrupt authorized client", 1331 + )?; 1332 + 1333 + match val { 1334 + Some(v) => serde_json::from_str(&v.data_json) 1335 + .map_err(|_| MetastoreError::CorruptData("corrupt authorized client json")) 1336 + .map(Some), 1337 + None => Ok(None), 1338 + } 1339 + } 1340 + 1341 + pub fn list_trusted_devices(&self, did: &Did) -> Result<Vec<TrustedDeviceRow>, MetastoreError> { 1342 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 1343 + let prefix = oauth_device_trust_prefix(user_hash); 1344 + 1345 + self.auth 1346 + .prefix(prefix.as_slice()) 1347 + .try_fold(Vec::new(), |mut acc, guard| { 1348 + let (_, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 1349 + match DeviceTrustValue::deserialize(&val_bytes) { 1350 + Some(v) => { 1351 + acc.push(TrustedDeviceRow { 1352 + id: v.device_id, 1353 + user_agent: v.user_agent, 1354 + friendly_name: v.friendly_name, 1355 + trusted_at: v.trusted_at_ms.and_then(DateTime::from_timestamp_millis), 1356 + trusted_until: v 1357 + .trusted_until_ms 1358 + .and_then(DateTime::from_timestamp_millis), 1359 + last_seen_at: DateTime::from_timestamp_millis(v.last_seen_at_ms) 1360 + .unwrap_or_default(), 1361 + }); 1362 + Ok(acc) 1363 + } 1364 + None => Ok(acc), 1365 + } 1366 + }) 1367 + } 1368 + 1369 + pub fn get_device_trust_info( 1370 + &self, 1371 + device_id: &DeviceId, 1372 + did: &Did, 1373 + ) -> Result<Option<DeviceTrustInfo>, MetastoreError> { 1374 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 1375 + let key = oauth_device_trust_key(user_hash, device_id.as_str()); 1376 + let val: Option<DeviceTrustValue> = point_lookup( 1377 + &self.auth, 1378 + key.as_slice(), 1379 + DeviceTrustValue::deserialize, 1380 + "corrupt device trust", 1381 + )?; 1382 + 1383 + Ok(val.map(|v| DeviceTrustInfo { 1384 + trusted_at: v.trusted_at_ms.and_then(DateTime::from_timestamp_millis), 1385 + trusted_until: v.trusted_until_ms.and_then(DateTime::from_timestamp_millis), 1386 + })) 1387 + } 1388 + 1389 + pub fn device_belongs_to_user( 1390 + &self, 1391 + device_id: &DeviceId, 1392 + did: &Did, 1393 + ) -> Result<bool, MetastoreError> { 1394 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 1395 + let key = oauth_account_device_key(user_hash, device_id.as_str()); 1396 + Ok(self 1397 + .auth 1398 + .get(key.as_slice()) 1399 + .map_err(MetastoreError::Fjall)? 1400 + .is_some()) 1401 + } 1402 + 1403 + pub fn revoke_device_trust( 1404 + &self, 1405 + device_id: &DeviceId, 1406 + did: &Did, 1407 + ) -> Result<(), MetastoreError> { 1408 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 1409 + let key = oauth_device_trust_key(user_hash, device_id.as_str()); 1410 + let val: Option<DeviceTrustValue> = point_lookup( 1411 + &self.auth, 1412 + key.as_slice(), 1413 + DeviceTrustValue::deserialize, 1414 + "corrupt device trust", 1415 + )?; 1416 + match val { 1417 + Some(mut v) => { 1418 + v.trusted_at_ms = None; 1419 + v.trusted_until_ms = None; 1420 + self.auth 1421 + .insert(key.as_slice(), v.serialize()) 1422 + .map_err(MetastoreError::Fjall) 1423 + } 1424 + None => Ok(()), 1425 + } 1426 + } 1427 + 1428 + pub fn update_device_friendly_name( 1429 + &self, 1430 + device_id: &DeviceId, 1431 + did: &Did, 1432 + friendly_name: Option<&str>, 1433 + ) -> Result<(), MetastoreError> { 1434 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 1435 + let key = oauth_device_trust_key(user_hash, device_id.as_str()); 1436 + let val: Option<DeviceTrustValue> = point_lookup( 1437 + &self.auth, 1438 + key.as_slice(), 1439 + DeviceTrustValue::deserialize, 1440 + "corrupt device trust", 1441 + )?; 1442 + match val { 1443 + Some(mut v) => { 1444 + v.friendly_name = friendly_name.map(str::to_owned); 1445 + self.auth 1446 + .insert(key.as_slice(), v.serialize()) 1447 + .map_err(MetastoreError::Fjall) 1448 + } 1449 + None => Ok(()), 1450 + } 1451 + } 1452 + 1453 + pub fn trust_device( 1454 + &self, 1455 + device_id: &DeviceId, 1456 + did: &Did, 1457 + trusted_at: DateTime<Utc>, 1458 + trusted_until: DateTime<Utc>, 1459 + ) -> Result<(), MetastoreError> { 1460 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 1461 + let key = oauth_device_trust_key(user_hash, device_id.as_str()); 1462 + let val: Option<DeviceTrustValue> = point_lookup( 1463 + &self.auth, 1464 + key.as_slice(), 1465 + DeviceTrustValue::deserialize, 1466 + "corrupt device trust", 1467 + )?; 1468 + match val { 1469 + Some(mut v) => { 1470 + v.trusted_at_ms = Some(trusted_at.timestamp_millis()); 1471 + v.trusted_until_ms = Some(trusted_until.timestamp_millis()); 1472 + self.auth 1473 + .insert(key.as_slice(), v.serialize()) 1474 + .map_err(MetastoreError::Fjall) 1475 + } 1476 + None => Ok(()), 1477 + } 1478 + } 1479 + 1480 + pub fn extend_device_trust( 1481 + &self, 1482 + device_id: &DeviceId, 1483 + did: &Did, 1484 + trusted_until: DateTime<Utc>, 1485 + ) -> Result<(), MetastoreError> { 1486 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 1487 + let key = oauth_device_trust_key(user_hash, device_id.as_str()); 1488 + let val: Option<DeviceTrustValue> = point_lookup( 1489 + &self.auth, 1490 + key.as_slice(), 1491 + DeviceTrustValue::deserialize, 1492 + "corrupt device trust", 1493 + )?; 1494 + match val { 1495 + Some(mut v) => { 1496 + v.trusted_until_ms = Some(trusted_until.timestamp_millis()); 1497 + self.auth 1498 + .insert(key.as_slice(), v.serialize()) 1499 + .map_err(MetastoreError::Fjall) 1500 + } 1501 + None => Ok(()), 1502 + } 1503 + } 1504 + 1505 + pub fn list_sessions_by_did( 1506 + &self, 1507 + did: &Did, 1508 + ) -> Result<Vec<OAuthSessionListItem>, MetastoreError> { 1509 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 1510 + let now_ms = Utc::now().timestamp_millis(); 1511 + let tokens = self.collect_tokens_for_did(user_hash)?; 1512 + 1513 + Ok(tokens 1514 + .iter() 1515 + .filter(|t| t.expires_at_ms > now_ms) 1516 + .map(|t| OAuthSessionListItem { 1517 + id: TokenFamilyId::new(t.family_id), 1518 + token_id: TokenId::new(t.token_id.clone()), 1519 + created_at: DateTime::from_timestamp_millis(t.created_at_ms).unwrap_or_default(), 1520 + expires_at: DateTime::from_timestamp_millis(t.expires_at_ms).unwrap_or_default(), 1521 + client_id: ClientId::new(t.client_id.clone()), 1522 + }) 1523 + .collect()) 1524 + } 1525 + 1526 + pub fn delete_session_by_id( 1527 + &self, 1528 + session_id: TokenFamilyId, 1529 + did: &Did, 1530 + ) -> Result<u64, MetastoreError> { 1531 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 1532 + let token = match self.load_token_by_family_id(user_hash, session_id.as_i32())? { 1533 + Some(t) => t, 1534 + None => return Ok(0), 1535 + }; 1536 + 1537 + let mut batch = self.db.batch(); 1538 + self.delete_token_indexes(&mut batch, &token, user_hash); 1539 + batch.commit().map_err(MetastoreError::Fjall)?; 1540 + Ok(1) 1541 + } 1542 + 1543 + pub fn delete_sessions_by_did(&self, did: &Did) -> Result<u64, MetastoreError> { 1544 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 1545 + let tokens = self.collect_tokens_for_did(user_hash)?; 1546 + 1547 + let count = u64::try_from(tokens.len()).unwrap_or(u64::MAX); 1548 + match count { 1549 + 0 => Ok(0), 1550 + _ => { 1551 + let mut batch = self.db.batch(); 1552 + tokens.iter().for_each(|token| { 1553 + self.delete_token_indexes(&mut batch, token, user_hash); 1554 + }); 1555 + batch.commit().map_err(MetastoreError::Fjall)?; 1556 + Ok(count) 1557 + } 1558 + } 1559 + } 1560 + 1561 + pub fn delete_sessions_by_did_except( 1562 + &self, 1563 + did: &Did, 1564 + except_token_id: &TokenId, 1565 + ) -> Result<u64, MetastoreError> { 1566 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 1567 + let tokens = self.collect_tokens_for_did(user_hash)?; 1568 + let except_str = except_token_id.as_str(); 1569 + 1570 + let to_delete: Vec<_> = tokens.iter().filter(|t| t.token_id != except_str).collect(); 1571 + 1572 + let count = u64::try_from(to_delete.len()).unwrap_or(u64::MAX); 1573 + match count { 1574 + 0 => Ok(0), 1575 + _ => { 1576 + let mut batch = self.db.batch(); 1577 + to_delete.iter().for_each(|token| { 1578 + self.delete_token_indexes(&mut batch, token, user_hash); 1579 + }); 1580 + batch.commit().map_err(MetastoreError::Fjall)?; 1581 + Ok(count) 1582 + } 1583 + } 1584 + } 1585 + } 1586 + 1587 + fn default_parameters(client_id: &str) -> tranquil_oauth::AuthorizationRequestParameters { 1588 + tranquil_oauth::AuthorizationRequestParameters { 1589 + response_type: tranquil_oauth::ResponseType::Code, 1590 + client_id: client_id.to_owned(), 1591 + redirect_uri: String::new(), 1592 + scope: None, 1593 + state: None, 1594 + code_challenge: String::new(), 1595 + code_challenge_method: tranquil_oauth::CodeChallengeMethod::S256, 1596 + response_mode: None, 1597 + login_hint: None, 1598 + dpop_jkt: None, 1599 + prompt: None, 1600 + extra: None, 1601 + } 1602 + }
+556
crates/tranquil-store/src/metastore/oauth_schema.rs
··· 1 + use serde::{Deserialize, Serialize}; 2 + use smallvec::SmallVec; 3 + 4 + use super::encoding::KeyBuilder; 5 + use super::keys::{KeyTag, UserHash}; 6 + 7 + const TOKEN_SCHEMA_VERSION: u8 = 1; 8 + const REQUEST_SCHEMA_VERSION: u8 = 1; 9 + const DEVICE_SCHEMA_VERSION: u8 = 1; 10 + const ACCOUNT_DEVICE_SCHEMA_VERSION: u8 = 1; 11 + const DPOP_JTI_SCHEMA_VERSION: u8 = 1; 12 + const CHALLENGE_SCHEMA_VERSION: u8 = 1; 13 + const SCOPE_PREFS_SCHEMA_VERSION: u8 = 1; 14 + const AUTH_CLIENT_SCHEMA_VERSION: u8 = 1; 15 + const DEVICE_TRUST_SCHEMA_VERSION: u8 = 1; 16 + 17 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 18 + pub struct OAuthTokenValue { 19 + pub family_id: i32, 20 + pub did: String, 21 + pub client_id: String, 22 + pub token_id: String, 23 + pub refresh_token: String, 24 + pub previous_refresh_token: Option<String>, 25 + pub scope: String, 26 + pub expires_at_ms: i64, 27 + pub created_at_ms: i64, 28 + pub updated_at_ms: i64, 29 + pub parameters_json: String, 30 + pub controller_did: Option<String>, 31 + } 32 + 33 + impl OAuthTokenValue { 34 + pub fn serialize_with_ttl(&self) -> Vec<u8> { 35 + let ttl_bytes = u64::try_from(self.expires_at_ms).unwrap_or(0).to_be_bytes(); 36 + let payload = 37 + postcard::to_allocvec(self).expect("OAuthTokenValue serialization cannot fail"); 38 + let mut buf = Vec::with_capacity(8 + 1 + payload.len()); 39 + buf.extend_from_slice(&ttl_bytes); 40 + buf.push(TOKEN_SCHEMA_VERSION); 41 + buf.extend_from_slice(&payload); 42 + buf 43 + } 44 + 45 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 46 + if bytes.len() < 9 { 47 + return None; 48 + } 49 + let (_ttl, rest) = bytes.split_at(8); 50 + let (&version, payload) = rest.split_first()?; 51 + match version { 52 + TOKEN_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 53 + _ => None, 54 + } 55 + } 56 + } 57 + 58 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 59 + pub struct OAuthRequestValue { 60 + pub client_id: String, 61 + pub client_auth_json: Option<String>, 62 + pub parameters_json: String, 63 + pub expires_at_ms: i64, 64 + pub did: Option<String>, 65 + pub device_id: Option<String>, 66 + pub code: Option<String>, 67 + pub controller_did: Option<String>, 68 + } 69 + 70 + impl OAuthRequestValue { 71 + pub fn serialize_with_ttl(&self) -> Vec<u8> { 72 + let ttl_bytes = u64::try_from(self.expires_at_ms).unwrap_or(0).to_be_bytes(); 73 + let payload = 74 + postcard::to_allocvec(self).expect("OAuthRequestValue serialization cannot fail"); 75 + let mut buf = Vec::with_capacity(8 + 1 + payload.len()); 76 + buf.extend_from_slice(&ttl_bytes); 77 + buf.push(REQUEST_SCHEMA_VERSION); 78 + buf.extend_from_slice(&payload); 79 + buf 80 + } 81 + 82 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 83 + if bytes.len() < 9 { 84 + return None; 85 + } 86 + let (_ttl, rest) = bytes.split_at(8); 87 + let (&version, payload) = rest.split_first()?; 88 + match version { 89 + REQUEST_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 90 + _ => None, 91 + } 92 + } 93 + } 94 + 95 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 96 + pub struct OAuthDeviceValue { 97 + pub session_id: String, 98 + pub user_agent: Option<String>, 99 + pub ip_address: String, 100 + pub last_seen_at_ms: i64, 101 + pub created_at_ms: i64, 102 + } 103 + 104 + impl OAuthDeviceValue { 105 + pub fn serialize(&self) -> Vec<u8> { 106 + let payload = 107 + postcard::to_allocvec(self).expect("OAuthDeviceValue serialization cannot fail"); 108 + let mut buf = Vec::with_capacity(8 + 1 + payload.len()); 109 + buf.extend_from_slice(&0u64.to_be_bytes()); 110 + buf.push(DEVICE_SCHEMA_VERSION); 111 + buf.extend_from_slice(&payload); 112 + buf 113 + } 114 + 115 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 116 + let rest = bytes.get(8..)?; 117 + let (&version, payload) = rest.split_first()?; 118 + match version { 119 + DEVICE_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 120 + _ => None, 121 + } 122 + } 123 + } 124 + 125 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 126 + pub struct AccountDeviceValue { 127 + pub last_used_at_ms: i64, 128 + } 129 + 130 + impl AccountDeviceValue { 131 + pub fn serialize_with_ttl(&self) -> Vec<u8> { 132 + let ttl_bytes = 0u64.to_be_bytes(); 133 + let payload = 134 + postcard::to_allocvec(self).expect("AccountDeviceValue serialization cannot fail"); 135 + let mut buf = Vec::with_capacity(8 + 1 + payload.len()); 136 + buf.extend_from_slice(&ttl_bytes); 137 + buf.push(ACCOUNT_DEVICE_SCHEMA_VERSION); 138 + buf.extend_from_slice(&payload); 139 + buf 140 + } 141 + 142 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 143 + if bytes.len() < 9 { 144 + return None; 145 + } 146 + let (_ttl, rest) = bytes.split_at(8); 147 + let (&version, payload) = rest.split_first()?; 148 + match version { 149 + ACCOUNT_DEVICE_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 150 + _ => None, 151 + } 152 + } 153 + } 154 + 155 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 156 + pub struct DpopJtiValue { 157 + pub recorded_at_ms: i64, 158 + } 159 + 160 + const DPOP_JTI_WINDOW_MS: i64 = 300_000; 161 + 162 + impl DpopJtiValue { 163 + pub fn serialize_with_ttl(&self) -> Vec<u8> { 164 + let expires_at_ms = self.recorded_at_ms.saturating_add(DPOP_JTI_WINDOW_MS); 165 + let ttl_bytes = u64::try_from(expires_at_ms).unwrap_or(0).to_be_bytes(); 166 + let payload = postcard::to_allocvec(self).expect("DpopJtiValue serialization cannot fail"); 167 + let mut buf = Vec::with_capacity(8 + 1 + payload.len()); 168 + buf.extend_from_slice(&ttl_bytes); 169 + buf.push(DPOP_JTI_SCHEMA_VERSION); 170 + buf.extend_from_slice(&payload); 171 + buf 172 + } 173 + 174 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 175 + if bytes.len() < 9 { 176 + return None; 177 + } 178 + let (_ttl, rest) = bytes.split_at(8); 179 + let (&version, payload) = rest.split_first()?; 180 + match version { 181 + DPOP_JTI_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 182 + _ => None, 183 + } 184 + } 185 + 186 + pub fn ttl_ms(bytes: &[u8]) -> Option<u64> { 187 + if bytes.len() < 8 { 188 + return None; 189 + } 190 + let ttl_bytes: [u8; 8] = bytes[..8].try_into().ok()?; 191 + Some(u64::from_be_bytes(ttl_bytes)) 192 + } 193 + } 194 + 195 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 196 + pub struct TwoFactorChallengeValue { 197 + pub id: [u8; 16], 198 + pub did: String, 199 + pub request_uri: String, 200 + pub code: String, 201 + pub attempts: i32, 202 + pub created_at_ms: i64, 203 + pub expires_at_ms: i64, 204 + } 205 + 206 + impl TwoFactorChallengeValue { 207 + pub fn serialize_with_ttl(&self) -> Vec<u8> { 208 + let ttl_bytes = u64::try_from(self.expires_at_ms).unwrap_or(0).to_be_bytes(); 209 + let payload = 210 + postcard::to_allocvec(self).expect("TwoFactorChallengeValue serialization cannot fail"); 211 + let mut buf = Vec::with_capacity(8 + 1 + payload.len()); 212 + buf.extend_from_slice(&ttl_bytes); 213 + buf.push(CHALLENGE_SCHEMA_VERSION); 214 + buf.extend_from_slice(&payload); 215 + buf 216 + } 217 + 218 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 219 + if bytes.len() < 9 { 220 + return None; 221 + } 222 + let (_ttl, rest) = bytes.split_at(8); 223 + let (&version, payload) = rest.split_first()?; 224 + match version { 225 + CHALLENGE_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 226 + _ => None, 227 + } 228 + } 229 + 230 + pub fn ttl_ms(bytes: &[u8]) -> Option<u64> { 231 + if bytes.len() < 8 { 232 + return None; 233 + } 234 + let ttl_bytes: [u8; 8] = bytes[..8].try_into().ok()?; 235 + Some(u64::from_be_bytes(ttl_bytes)) 236 + } 237 + } 238 + 239 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 240 + pub struct ScopePrefsValue { 241 + pub prefs_json: String, 242 + } 243 + 244 + impl ScopePrefsValue { 245 + pub fn serialize(&self) -> Vec<u8> { 246 + let payload = 247 + postcard::to_allocvec(self).expect("ScopePrefsValue serialization cannot fail"); 248 + let mut buf = Vec::with_capacity(8 + 1 + payload.len()); 249 + buf.extend_from_slice(&0u64.to_be_bytes()); 250 + buf.push(SCOPE_PREFS_SCHEMA_VERSION); 251 + buf.extend_from_slice(&payload); 252 + buf 253 + } 254 + 255 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 256 + let rest = bytes.get(8..)?; 257 + let (&version, payload) = rest.split_first()?; 258 + match version { 259 + SCOPE_PREFS_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 260 + _ => None, 261 + } 262 + } 263 + } 264 + 265 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 266 + pub struct AuthorizedClientValue { 267 + pub data_json: String, 268 + } 269 + 270 + impl AuthorizedClientValue { 271 + pub fn serialize(&self) -> Vec<u8> { 272 + let payload = 273 + postcard::to_allocvec(self).expect("AuthorizedClientValue serialization cannot fail"); 274 + let mut buf = Vec::with_capacity(8 + 1 + payload.len()); 275 + buf.extend_from_slice(&0u64.to_be_bytes()); 276 + buf.push(AUTH_CLIENT_SCHEMA_VERSION); 277 + buf.extend_from_slice(&payload); 278 + buf 279 + } 280 + 281 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 282 + let rest = bytes.get(8..)?; 283 + let (&version, payload) = rest.split_first()?; 284 + match version { 285 + AUTH_CLIENT_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 286 + _ => None, 287 + } 288 + } 289 + } 290 + 291 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 292 + pub struct DeviceTrustValue { 293 + pub device_id: String, 294 + pub did: String, 295 + pub user_agent: Option<String>, 296 + pub friendly_name: Option<String>, 297 + pub trusted_at_ms: Option<i64>, 298 + pub trusted_until_ms: Option<i64>, 299 + pub last_seen_at_ms: i64, 300 + } 301 + 302 + impl DeviceTrustValue { 303 + pub fn serialize(&self) -> Vec<u8> { 304 + let payload = 305 + postcard::to_allocvec(self).expect("DeviceTrustValue serialization cannot fail"); 306 + let mut buf = Vec::with_capacity(8 + 1 + payload.len()); 307 + buf.extend_from_slice(&0u64.to_be_bytes()); 308 + buf.push(DEVICE_TRUST_SCHEMA_VERSION); 309 + buf.extend_from_slice(&payload); 310 + buf 311 + } 312 + 313 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 314 + let rest = bytes.get(8..)?; 315 + let (&version, payload) = rest.split_first()?; 316 + match version { 317 + DEVICE_TRUST_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 318 + _ => None, 319 + } 320 + } 321 + } 322 + 323 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 324 + pub struct UsedRefreshValue { 325 + pub family_id: i32, 326 + } 327 + 328 + impl UsedRefreshValue { 329 + pub fn serialize_with_ttl(&self, expires_at_ms: i64) -> Vec<u8> { 330 + let ttl_bytes = u64::try_from(expires_at_ms).unwrap_or(0).to_be_bytes(); 331 + let payload = 332 + postcard::to_allocvec(self).expect("UsedRefreshValue serialization cannot fail"); 333 + let mut buf = Vec::with_capacity(8 + 1 + payload.len()); 334 + buf.extend_from_slice(&ttl_bytes); 335 + buf.push(TOKEN_SCHEMA_VERSION); 336 + buf.extend_from_slice(&payload); 337 + buf 338 + } 339 + 340 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 341 + if bytes.len() < 9 { 342 + return None; 343 + } 344 + let (_ttl, rest) = bytes.split_at(8); 345 + let (&version, payload) = rest.split_first()?; 346 + match version { 347 + TOKEN_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 348 + _ => None, 349 + } 350 + } 351 + } 352 + 353 + pub fn oauth_token_key(user_hash: UserHash, family_id: i32) -> SmallVec<[u8; 128]> { 354 + KeyBuilder::new() 355 + .tag(KeyTag::OAUTH_TOKEN) 356 + .u64(user_hash.raw()) 357 + .raw(&family_id.to_be_bytes()) 358 + .build() 359 + } 360 + 361 + pub fn oauth_token_user_prefix(user_hash: UserHash) -> SmallVec<[u8; 128]> { 362 + KeyBuilder::new() 363 + .tag(KeyTag::OAUTH_TOKEN) 364 + .u64(user_hash.raw()) 365 + .build() 366 + } 367 + 368 + pub fn oauth_token_by_id_key(token_id: &str) -> SmallVec<[u8; 128]> { 369 + KeyBuilder::new() 370 + .tag(KeyTag::OAUTH_TOKEN_BY_ID) 371 + .string(token_id) 372 + .build() 373 + } 374 + 375 + pub fn oauth_token_by_refresh_key(refresh_token: &str) -> SmallVec<[u8; 128]> { 376 + KeyBuilder::new() 377 + .tag(KeyTag::OAUTH_TOKEN_BY_REFRESH) 378 + .string(refresh_token) 379 + .build() 380 + } 381 + 382 + pub fn oauth_token_by_prev_refresh_key(refresh_token: &str) -> SmallVec<[u8; 128]> { 383 + KeyBuilder::new() 384 + .tag(KeyTag::OAUTH_TOKEN_BY_PREV_REFRESH) 385 + .string(refresh_token) 386 + .build() 387 + } 388 + 389 + pub fn oauth_used_refresh_key(refresh_token: &str) -> SmallVec<[u8; 128]> { 390 + KeyBuilder::new() 391 + .tag(KeyTag::OAUTH_USED_REFRESH) 392 + .string(refresh_token) 393 + .build() 394 + } 395 + 396 + pub fn oauth_auth_request_key(request_id: &str) -> SmallVec<[u8; 128]> { 397 + KeyBuilder::new() 398 + .tag(KeyTag::OAUTH_AUTH_REQUEST) 399 + .string(request_id) 400 + .build() 401 + } 402 + 403 + pub fn oauth_auth_request_prefix() -> SmallVec<[u8; 128]> { 404 + KeyBuilder::new().tag(KeyTag::OAUTH_AUTH_REQUEST).build() 405 + } 406 + 407 + pub fn oauth_auth_by_code_key(code: &str) -> SmallVec<[u8; 128]> { 408 + KeyBuilder::new() 409 + .tag(KeyTag::OAUTH_AUTH_BY_CODE) 410 + .string(code) 411 + .build() 412 + } 413 + 414 + pub fn oauth_device_key(device_id: &str) -> SmallVec<[u8; 128]> { 415 + KeyBuilder::new() 416 + .tag(KeyTag::OAUTH_DEVICE) 417 + .string(device_id) 418 + .build() 419 + } 420 + 421 + pub fn oauth_account_device_key(user_hash: UserHash, device_id: &str) -> SmallVec<[u8; 128]> { 422 + KeyBuilder::new() 423 + .tag(KeyTag::OAUTH_ACCOUNT_DEVICE) 424 + .u64(user_hash.raw()) 425 + .string(device_id) 426 + .build() 427 + } 428 + 429 + pub fn oauth_account_device_prefix(user_hash: UserHash) -> SmallVec<[u8; 128]> { 430 + KeyBuilder::new() 431 + .tag(KeyTag::OAUTH_ACCOUNT_DEVICE) 432 + .u64(user_hash.raw()) 433 + .build() 434 + } 435 + 436 + pub fn oauth_dpop_jti_key(jti: &str) -> SmallVec<[u8; 128]> { 437 + KeyBuilder::new() 438 + .tag(KeyTag::OAUTH_DPOP_JTI) 439 + .string(jti) 440 + .build() 441 + } 442 + 443 + pub fn oauth_dpop_jti_prefix() -> SmallVec<[u8; 128]> { 444 + KeyBuilder::new().tag(KeyTag::OAUTH_DPOP_JTI).build() 445 + } 446 + 447 + pub fn oauth_2fa_challenge_key(id: &[u8; 16]) -> SmallVec<[u8; 128]> { 448 + KeyBuilder::new() 449 + .tag(KeyTag::OAUTH_2FA_CHALLENGE) 450 + .raw(id) 451 + .build() 452 + } 453 + 454 + pub fn oauth_2fa_challenge_prefix() -> SmallVec<[u8; 128]> { 455 + KeyBuilder::new().tag(KeyTag::OAUTH_2FA_CHALLENGE).build() 456 + } 457 + 458 + pub fn oauth_2fa_by_request_key(request_uri: &str) -> SmallVec<[u8; 128]> { 459 + KeyBuilder::new() 460 + .tag(KeyTag::OAUTH_2FA_BY_REQUEST) 461 + .string(request_uri) 462 + .build() 463 + } 464 + 465 + pub fn oauth_scope_prefs_key(user_hash: UserHash, client_id: &str) -> SmallVec<[u8; 128]> { 466 + KeyBuilder::new() 467 + .tag(KeyTag::OAUTH_SCOPE_PREFS) 468 + .u64(user_hash.raw()) 469 + .string(client_id) 470 + .build() 471 + } 472 + 473 + pub fn oauth_auth_client_key(user_hash: UserHash, client_id: &str) -> SmallVec<[u8; 128]> { 474 + KeyBuilder::new() 475 + .tag(KeyTag::OAUTH_AUTH_CLIENT) 476 + .u64(user_hash.raw()) 477 + .string(client_id) 478 + .build() 479 + } 480 + 481 + pub fn oauth_device_trust_key(user_hash: UserHash, device_id: &str) -> SmallVec<[u8; 128]> { 482 + KeyBuilder::new() 483 + .tag(KeyTag::OAUTH_DEVICE_TRUST) 484 + .u64(user_hash.raw()) 485 + .string(device_id) 486 + .build() 487 + } 488 + 489 + pub fn oauth_device_trust_prefix(user_hash: UserHash) -> SmallVec<[u8; 128]> { 490 + KeyBuilder::new() 491 + .tag(KeyTag::OAUTH_DEVICE_TRUST) 492 + .u64(user_hash.raw()) 493 + .build() 494 + } 495 + 496 + pub fn oauth_token_family_counter_key() -> SmallVec<[u8; 128]> { 497 + KeyBuilder::new() 498 + .tag(KeyTag::OAUTH_TOKEN_FAMILY_COUNTER) 499 + .build() 500 + } 501 + 502 + pub fn serialize_family_counter(counter: i32) -> Vec<u8> { 503 + let mut buf = Vec::with_capacity(12); 504 + buf.extend_from_slice(&0u64.to_be_bytes()); 505 + buf.extend_from_slice(&counter.to_be_bytes()); 506 + buf 507 + } 508 + 509 + pub fn deserialize_family_counter(bytes: &[u8]) -> Option<i32> { 510 + match bytes.len() { 511 + 4 => Some(i32::from_be_bytes(bytes.try_into().ok()?)), 512 + n if n >= 12 => { 513 + let arr: [u8; 4] = bytes[8..12].try_into().ok()?; 514 + Some(i32::from_be_bytes(arr)) 515 + } 516 + _ => None, 517 + } 518 + } 519 + 520 + pub fn oauth_token_by_family_key(family_id: i32) -> SmallVec<[u8; 128]> { 521 + KeyBuilder::new() 522 + .tag(KeyTag::OAUTH_TOKEN_BY_FAMILY) 523 + .raw(&family_id.to_be_bytes()) 524 + .build() 525 + } 526 + 527 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 528 + pub struct TokenIndexValue { 529 + pub user_hash: u64, 530 + pub family_id: i32, 531 + } 532 + 533 + impl TokenIndexValue { 534 + pub fn serialize_with_ttl(&self, expires_at_ms: i64) -> Vec<u8> { 535 + let ttl_bytes = u64::try_from(expires_at_ms).unwrap_or(0).to_be_bytes(); 536 + let payload = 537 + postcard::to_allocvec(self).expect("TokenIndexValue serialization cannot fail"); 538 + let mut buf = Vec::with_capacity(8 + 1 + payload.len()); 539 + buf.extend_from_slice(&ttl_bytes); 540 + buf.push(TOKEN_SCHEMA_VERSION); 541 + buf.extend_from_slice(&payload); 542 + buf 543 + } 544 + 545 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 546 + if bytes.len() < 9 { 547 + return None; 548 + } 549 + let (_ttl, rest) = bytes.split_at(8); 550 + let (&version, payload) = rest.split_first()?; 551 + match version { 552 + TOKEN_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 553 + _ => None, 554 + } 555 + } 556 + }
+919
crates/tranquil-store/src/metastore/session_ops.rs
··· 1 + use std::sync::Arc; 2 + 3 + use chrono::{DateTime, Utc}; 4 + use fjall::{Database, Keyspace}; 5 + use uuid::Uuid; 6 + 7 + use super::MetastoreError; 8 + use super::keys::UserHash; 9 + use super::scan::point_lookup; 10 + use super::sessions::{ 11 + AppPasswordValue, SessionIndexValue, SessionTokenValue, deserialize_id_counter_value, 12 + deserialize_last_reauth_value, deserialize_used_refresh_value, login_type_to_u8, 13 + privilege_to_u8, serialize_by_did_value, serialize_id_counter_value, 14 + serialize_last_reauth_value, serialize_used_refresh_value, session_app_password_key, 15 + session_app_password_prefix, session_by_access_key, session_by_did_key, session_by_did_prefix, 16 + session_by_refresh_key, session_id_counter_key, session_last_reauth_key, session_primary_key, 17 + session_used_refresh_key, u8_to_login_type, u8_to_privilege, 18 + }; 19 + use super::user_hash::UserHashMap; 20 + use super::users::UserValue; 21 + 22 + use tranquil_db_traits::{ 23 + AppPasswordCreate, AppPasswordRecord, LoginType, RefreshSessionResult, SessionForRefresh, 24 + SessionId, SessionListItem, SessionMfaStatus, SessionRefreshData, SessionToken, 25 + SessionTokenCreate, 26 + }; 27 + use tranquil_types::Did; 28 + 29 + pub struct SessionOps { 30 + db: Database, 31 + auth: Keyspace, 32 + users: Keyspace, 33 + user_hashes: Arc<UserHashMap>, 34 + counter_lock: Arc<parking_lot::Mutex<()>>, 35 + } 36 + 37 + impl SessionOps { 38 + pub fn new( 39 + db: Database, 40 + auth: Keyspace, 41 + users: Keyspace, 42 + user_hashes: Arc<UserHashMap>, 43 + counter_lock: Arc<parking_lot::Mutex<()>>, 44 + ) -> Self { 45 + Self { 46 + db, 47 + auth, 48 + users, 49 + user_hashes, 50 + counter_lock, 51 + } 52 + } 53 + 54 + fn resolve_user_hash_from_did(&self, did: &str) -> UserHash { 55 + UserHash::from_did(did) 56 + } 57 + 58 + fn resolve_user_hash_from_uuid(&self, user_id: Uuid) -> Result<UserHash, MetastoreError> { 59 + self.user_hashes 60 + .get(&user_id) 61 + .ok_or(MetastoreError::InvalidInput("unknown user_id")) 62 + } 63 + 64 + fn next_session_id(&self) -> Result<i32, MetastoreError> { 65 + let _guard = self.counter_lock.lock(); 66 + let counter_key = session_id_counter_key(); 67 + let current = self 68 + .auth 69 + .get(counter_key.as_slice()) 70 + .map_err(MetastoreError::Fjall)? 71 + .and_then(|raw| deserialize_id_counter_value(&raw)) 72 + .unwrap_or(0); 73 + let next = current.saturating_add(1); 74 + self.auth 75 + .insert(counter_key.as_slice(), serialize_id_counter_value(next)) 76 + .map_err(MetastoreError::Fjall)?; 77 + Ok(next) 78 + } 79 + 80 + fn value_to_session_token( 81 + &self, 82 + v: &SessionTokenValue, 83 + ) -> Result<SessionToken, MetastoreError> { 84 + Ok(SessionToken { 85 + id: SessionId::new(v.id), 86 + did: Did::new(v.did.clone()) 87 + .map_err(|_| MetastoreError::CorruptData("invalid session did"))?, 88 + access_jti: v.access_jti.clone(), 89 + refresh_jti: v.refresh_jti.clone(), 90 + access_expires_at: DateTime::from_timestamp_millis(v.access_expires_at_ms) 91 + .unwrap_or_default(), 92 + refresh_expires_at: DateTime::from_timestamp_millis(v.refresh_expires_at_ms) 93 + .unwrap_or_default(), 94 + login_type: u8_to_login_type(v.login_type).unwrap_or(LoginType::Modern), 95 + mfa_verified: v.mfa_verified, 96 + scope: v.scope.clone(), 97 + controller_did: v 98 + .controller_did 99 + .as_ref() 100 + .and_then(|d| Did::new(d.clone()).ok()), 101 + app_password_name: v.app_password_name.clone(), 102 + created_at: DateTime::from_timestamp_millis(v.created_at_ms).unwrap_or_default(), 103 + updated_at: DateTime::from_timestamp_millis(v.updated_at_ms).unwrap_or_default(), 104 + }) 105 + } 106 + 107 + fn value_to_app_password( 108 + &self, 109 + v: &AppPasswordValue, 110 + ) -> Result<AppPasswordRecord, MetastoreError> { 111 + Ok(AppPasswordRecord { 112 + id: v.id, 113 + user_id: v.user_id, 114 + name: v.name.clone(), 115 + password_hash: v.password_hash.clone(), 116 + created_at: DateTime::from_timestamp_millis(v.created_at_ms).unwrap_or_default(), 117 + privilege: u8_to_privilege(v.privilege) 118 + .unwrap_or(tranquil_db_traits::AppPasswordPrivilege::Standard), 119 + scopes: v.scopes.clone(), 120 + created_by_controller_did: v 121 + .created_by_controller_did 122 + .as_ref() 123 + .and_then(|d| Did::new(d.clone()).ok()), 124 + }) 125 + } 126 + 127 + fn load_session_by_id( 128 + &self, 129 + session_id: i32, 130 + ) -> Result<Option<SessionTokenValue>, MetastoreError> { 131 + let key = session_primary_key(session_id); 132 + point_lookup( 133 + &self.auth, 134 + key.as_slice(), 135 + SessionTokenValue::deserialize, 136 + "corrupt session token", 137 + ) 138 + } 139 + 140 + fn load_user_value(&self, user_hash: UserHash) -> Result<Option<UserValue>, MetastoreError> { 141 + let key = super::encoding::KeyBuilder::new() 142 + .tag(super::keys::KeyTag::USER_PRIMARY) 143 + .u64(user_hash.raw()) 144 + .build(); 145 + point_lookup( 146 + &self.users, 147 + key.as_slice(), 148 + UserValue::deserialize, 149 + "corrupt user value", 150 + ) 151 + } 152 + 153 + fn delete_session_indexes( 154 + &self, 155 + batch: &mut fjall::OwnedWriteBatch, 156 + session: &SessionTokenValue, 157 + ) { 158 + let user_hash = self.resolve_user_hash_from_did(&session.did); 159 + batch.remove(&self.auth, session_primary_key(session.id).as_slice()); 160 + batch.remove( 161 + &self.auth, 162 + session_by_access_key(&session.access_jti).as_slice(), 163 + ); 164 + batch.remove( 165 + &self.auth, 166 + session_by_refresh_key(&session.refresh_jti).as_slice(), 167 + ); 168 + batch.remove( 169 + &self.auth, 170 + session_by_did_key(user_hash, session.id).as_slice(), 171 + ); 172 + } 173 + 174 + fn collect_sessions_for_did( 175 + &self, 176 + user_hash: UserHash, 177 + ) -> Result<Vec<SessionTokenValue>, MetastoreError> { 178 + let prefix = session_by_did_prefix(user_hash); 179 + self.auth 180 + .prefix(prefix.as_slice()) 181 + .try_fold(Vec::new(), |mut acc, guard| { 182 + let (key_bytes, _) = guard.into_inner().map_err(MetastoreError::Fjall)?; 183 + let mut reader = super::encoding::KeyReader::new(&key_bytes); 184 + let _tag = reader.tag(); 185 + let _hash = reader.u64(); 186 + let sid_bytes: [u8; 4] = reader 187 + .remaining() 188 + .try_into() 189 + .map_err(|_| MetastoreError::CorruptData("session_by_did key truncated"))?; 190 + let sid = i32::from_be_bytes(sid_bytes); 191 + match self.load_session_by_id(sid)? { 192 + Some(session) => { 193 + acc.push(session); 194 + Ok::<_, MetastoreError>(acc) 195 + } 196 + None => Ok(acc), 197 + } 198 + }) 199 + } 200 + 201 + pub fn create_session(&self, data: &SessionTokenCreate) -> Result<SessionId, MetastoreError> { 202 + let user_hash = self.resolve_user_hash_from_did(data.did.as_str()); 203 + let session_id = self.next_session_id()?; 204 + let now_ms = Utc::now().timestamp_millis(); 205 + 206 + let value = SessionTokenValue { 207 + id: session_id, 208 + did: data.did.to_string(), 209 + access_jti: data.access_jti.clone(), 210 + refresh_jti: data.refresh_jti.clone(), 211 + access_expires_at_ms: data.access_expires_at.timestamp_millis(), 212 + refresh_expires_at_ms: data.refresh_expires_at.timestamp_millis(), 213 + login_type: login_type_to_u8(data.login_type), 214 + mfa_verified: data.mfa_verified, 215 + scope: data.scope.clone(), 216 + controller_did: data.controller_did.as_ref().map(|d| d.to_string()), 217 + app_password_name: data.app_password_name.clone(), 218 + created_at_ms: now_ms, 219 + updated_at_ms: now_ms, 220 + }; 221 + 222 + let access_index = SessionIndexValue { 223 + user_hash: user_hash.raw(), 224 + session_id, 225 + }; 226 + let refresh_index = SessionIndexValue { 227 + user_hash: user_hash.raw(), 228 + session_id, 229 + }; 230 + 231 + let mut batch = self.db.batch(); 232 + batch.insert( 233 + &self.auth, 234 + session_primary_key(session_id).as_slice(), 235 + value.serialize(), 236 + ); 237 + batch.insert( 238 + &self.auth, 239 + session_by_access_key(&data.access_jti).as_slice(), 240 + access_index.serialize(value.refresh_expires_at_ms), 241 + ); 242 + batch.insert( 243 + &self.auth, 244 + session_by_refresh_key(&data.refresh_jti).as_slice(), 245 + refresh_index.serialize(value.refresh_expires_at_ms), 246 + ); 247 + batch.insert( 248 + &self.auth, 249 + session_by_did_key(user_hash, session_id).as_slice(), 250 + serialize_by_did_value(value.refresh_expires_at_ms), 251 + ); 252 + batch.commit().map_err(MetastoreError::Fjall)?; 253 + 254 + Ok(SessionId::new(session_id)) 255 + } 256 + 257 + pub fn get_session_by_access_jti( 258 + &self, 259 + access_jti: &str, 260 + ) -> Result<Option<SessionToken>, MetastoreError> { 261 + let index_key = session_by_access_key(access_jti); 262 + let index_val: Option<SessionIndexValue> = point_lookup( 263 + &self.auth, 264 + index_key.as_slice(), 265 + SessionIndexValue::deserialize, 266 + "corrupt session access index", 267 + )?; 268 + 269 + match index_val { 270 + Some(idx) => { 271 + let session = self.load_session_by_id(idx.session_id)?; 272 + session.map(|v| self.value_to_session_token(&v)).transpose() 273 + } 274 + None => Ok(None), 275 + } 276 + } 277 + 278 + pub fn get_session_for_refresh( 279 + &self, 280 + refresh_jti: &str, 281 + ) -> Result<Option<SessionForRefresh>, MetastoreError> { 282 + let index_key = session_by_refresh_key(refresh_jti); 283 + let index_val: Option<SessionIndexValue> = point_lookup( 284 + &self.auth, 285 + index_key.as_slice(), 286 + SessionIndexValue::deserialize, 287 + "corrupt session refresh index", 288 + )?; 289 + 290 + let idx = match index_val { 291 + Some(idx) => idx, 292 + None => return Ok(None), 293 + }; 294 + 295 + let session = match self.load_session_by_id(idx.session_id)? { 296 + Some(s) => s, 297 + None => return Ok(None), 298 + }; 299 + 300 + let now_ms = Utc::now().timestamp_millis(); 301 + if session.refresh_expires_at_ms <= now_ms { 302 + return Ok(None); 303 + } 304 + 305 + let user_hash = self.resolve_user_hash_from_did(&session.did); 306 + let user_value = self.load_user_value(user_hash)?; 307 + 308 + match user_value { 309 + Some(user) => Ok(Some(SessionForRefresh { 310 + id: SessionId::new(session.id), 311 + did: Did::new(session.did) 312 + .map_err(|_| MetastoreError::CorruptData("invalid session did"))?, 313 + scope: session.scope, 314 + controller_did: session.controller_did.and_then(|d| Did::new(d).ok()), 315 + key_bytes: user.key_bytes, 316 + encryption_version: user.encryption_version, 317 + })), 318 + None => Ok(None), 319 + } 320 + } 321 + 322 + pub fn update_session_tokens( 323 + &self, 324 + session_id: SessionId, 325 + new_access_jti: &str, 326 + new_refresh_jti: &str, 327 + new_access_expires_at: DateTime<Utc>, 328 + new_refresh_expires_at: DateTime<Utc>, 329 + ) -> Result<(), MetastoreError> { 330 + let mut session = self 331 + .load_session_by_id(session_id.as_i32())? 332 + .ok_or(MetastoreError::InvalidInput("session not found"))?; 333 + 334 + let user_hash = self.resolve_user_hash_from_did(&session.did); 335 + let old_access_jti = session.access_jti.clone(); 336 + let old_refresh_jti = session.refresh_jti.clone(); 337 + 338 + session.access_jti = new_access_jti.to_owned(); 339 + session.refresh_jti = new_refresh_jti.to_owned(); 340 + session.access_expires_at_ms = new_access_expires_at.timestamp_millis(); 341 + session.refresh_expires_at_ms = new_refresh_expires_at.timestamp_millis(); 342 + session.updated_at_ms = Utc::now().timestamp_millis(); 343 + 344 + let new_access_index = SessionIndexValue { 345 + user_hash: user_hash.raw(), 346 + session_id: session.id, 347 + }; 348 + let new_refresh_index = SessionIndexValue { 349 + user_hash: user_hash.raw(), 350 + session_id: session.id, 351 + }; 352 + 353 + let mut batch = self.db.batch(); 354 + batch.remove( 355 + &self.auth, 356 + session_by_access_key(&old_access_jti).as_slice(), 357 + ); 358 + batch.remove( 359 + &self.auth, 360 + session_by_refresh_key(&old_refresh_jti).as_slice(), 361 + ); 362 + batch.insert( 363 + &self.auth, 364 + session_primary_key(session.id).as_slice(), 365 + session.serialize(), 366 + ); 367 + batch.insert( 368 + &self.auth, 369 + session_by_access_key(new_access_jti).as_slice(), 370 + new_access_index.serialize(session.refresh_expires_at_ms), 371 + ); 372 + batch.insert( 373 + &self.auth, 374 + session_by_refresh_key(new_refresh_jti).as_slice(), 375 + new_refresh_index.serialize(session.refresh_expires_at_ms), 376 + ); 377 + batch.insert( 378 + &self.auth, 379 + session_by_did_key(user_hash, session.id).as_slice(), 380 + serialize_by_did_value(session.refresh_expires_at_ms), 381 + ); 382 + batch.commit().map_err(MetastoreError::Fjall)?; 383 + 384 + Ok(()) 385 + } 386 + 387 + pub fn delete_session_by_access_jti(&self, access_jti: &str) -> Result<u64, MetastoreError> { 388 + let index_key = session_by_access_key(access_jti); 389 + let index_val: Option<SessionIndexValue> = point_lookup( 390 + &self.auth, 391 + index_key.as_slice(), 392 + SessionIndexValue::deserialize, 393 + "corrupt session access index", 394 + )?; 395 + 396 + let idx = match index_val { 397 + Some(idx) => idx, 398 + None => return Ok(0), 399 + }; 400 + 401 + let session = match self.load_session_by_id(idx.session_id)? { 402 + Some(s) => s, 403 + None => return Ok(0), 404 + }; 405 + 406 + let mut batch = self.db.batch(); 407 + self.delete_session_indexes(&mut batch, &session); 408 + batch.commit().map_err(MetastoreError::Fjall)?; 409 + 410 + Ok(1) 411 + } 412 + 413 + pub fn delete_session_by_id(&self, session_id: SessionId) -> Result<u64, MetastoreError> { 414 + let session = match self.load_session_by_id(session_id.as_i32())? { 415 + Some(s) => s, 416 + None => return Ok(0), 417 + }; 418 + 419 + let mut batch = self.db.batch(); 420 + self.delete_session_indexes(&mut batch, &session); 421 + batch.commit().map_err(MetastoreError::Fjall)?; 422 + 423 + Ok(1) 424 + } 425 + 426 + pub fn delete_sessions_by_did(&self, did: &Did) -> Result<u64, MetastoreError> { 427 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 428 + let sessions = self.collect_sessions_for_did(user_hash)?; 429 + 430 + let count = u64::try_from(sessions.len()).unwrap_or(u64::MAX); 431 + match count { 432 + 0 => Ok(0), 433 + _ => { 434 + let mut batch = self.db.batch(); 435 + sessions.iter().for_each(|session| { 436 + self.delete_session_indexes(&mut batch, session); 437 + }); 438 + batch.commit().map_err(MetastoreError::Fjall)?; 439 + Ok(count) 440 + } 441 + } 442 + } 443 + 444 + pub fn delete_sessions_by_did_except_jti( 445 + &self, 446 + did: &Did, 447 + except_jti: &str, 448 + ) -> Result<u64, MetastoreError> { 449 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 450 + let sessions = self.collect_sessions_for_did(user_hash)?; 451 + 452 + let to_delete: Vec<_> = sessions 453 + .iter() 454 + .filter(|s| s.access_jti != except_jti) 455 + .collect(); 456 + 457 + let count = u64::try_from(to_delete.len()).unwrap_or(u64::MAX); 458 + match count { 459 + 0 => Ok(0), 460 + _ => { 461 + let mut batch = self.db.batch(); 462 + to_delete.iter().for_each(|session| { 463 + self.delete_session_indexes(&mut batch, session); 464 + }); 465 + batch.commit().map_err(MetastoreError::Fjall)?; 466 + Ok(count) 467 + } 468 + } 469 + } 470 + 471 + pub fn list_sessions_by_did(&self, did: &Did) -> Result<Vec<SessionListItem>, MetastoreError> { 472 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 473 + let now_ms = Utc::now().timestamp_millis(); 474 + let mut sessions = self.collect_sessions_for_did(user_hash)?; 475 + sessions.retain(|s| s.refresh_expires_at_ms > now_ms); 476 + sessions.sort_by_key(|s| std::cmp::Reverse(s.created_at_ms)); 477 + 478 + Ok(sessions 479 + .iter() 480 + .map(|s| SessionListItem { 481 + id: SessionId::new(s.id), 482 + access_jti: s.access_jti.clone(), 483 + created_at: DateTime::from_timestamp_millis(s.created_at_ms).unwrap_or_default(), 484 + refresh_expires_at: DateTime::from_timestamp_millis(s.refresh_expires_at_ms) 485 + .unwrap_or_default(), 486 + }) 487 + .collect()) 488 + } 489 + 490 + pub fn get_session_access_jti_by_id( 491 + &self, 492 + session_id: SessionId, 493 + did: &Did, 494 + ) -> Result<Option<String>, MetastoreError> { 495 + self.load_session_by_id(session_id.as_i32())? 496 + .filter(|s| s.did == did.as_str()) 497 + .map(|s| Ok(s.access_jti)) 498 + .transpose() 499 + } 500 + 501 + pub fn delete_sessions_by_app_password( 502 + &self, 503 + did: &Did, 504 + app_password_name: &str, 505 + ) -> Result<u64, MetastoreError> { 506 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 507 + let sessions = self.collect_sessions_for_did(user_hash)?; 508 + 509 + let to_delete: Vec<_> = sessions 510 + .iter() 511 + .filter(|s| s.app_password_name.as_deref() == Some(app_password_name)) 512 + .collect(); 513 + 514 + let count = u64::try_from(to_delete.len()).unwrap_or(u64::MAX); 515 + match count { 516 + 0 => Ok(0), 517 + _ => { 518 + let mut batch = self.db.batch(); 519 + to_delete.iter().for_each(|session| { 520 + self.delete_session_indexes(&mut batch, session); 521 + }); 522 + batch.commit().map_err(MetastoreError::Fjall)?; 523 + Ok(count) 524 + } 525 + } 526 + } 527 + 528 + pub fn get_session_jtis_by_app_password( 529 + &self, 530 + did: &Did, 531 + app_password_name: &str, 532 + ) -> Result<Vec<String>, MetastoreError> { 533 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 534 + let sessions = self.collect_sessions_for_did(user_hash)?; 535 + 536 + Ok(sessions 537 + .iter() 538 + .filter(|s| s.app_password_name.as_deref() == Some(app_password_name)) 539 + .map(|s| s.access_jti.clone()) 540 + .collect()) 541 + } 542 + 543 + pub fn check_refresh_token_used( 544 + &self, 545 + refresh_jti: &str, 546 + ) -> Result<Option<SessionId>, MetastoreError> { 547 + let key = session_used_refresh_key(refresh_jti); 548 + match self 549 + .auth 550 + .get(key.as_slice()) 551 + .map_err(MetastoreError::Fjall)? 552 + { 553 + Some(raw) => Ok(deserialize_used_refresh_value(&raw).map(SessionId::new)), 554 + None => Ok(None), 555 + } 556 + } 557 + 558 + pub fn mark_refresh_token_used( 559 + &self, 560 + refresh_jti: &str, 561 + session_id: SessionId, 562 + ) -> Result<bool, MetastoreError> { 563 + let key = session_used_refresh_key(refresh_jti); 564 + let existing = self 565 + .auth 566 + .get(key.as_slice()) 567 + .map_err(MetastoreError::Fjall)?; 568 + 569 + match existing { 570 + Some(_) => Ok(false), 571 + None => { 572 + let session = self.load_session_by_id(session_id.as_i32())?; 573 + let expires_at_ms = session 574 + .map(|s| s.refresh_expires_at_ms) 575 + .unwrap_or(Utc::now().timestamp_millis().saturating_add(86_400_000)); 576 + 577 + self.auth 578 + .insert( 579 + key.as_slice(), 580 + serialize_used_refresh_value(expires_at_ms, session_id.as_i32()), 581 + ) 582 + .map_err(MetastoreError::Fjall)?; 583 + Ok(true) 584 + } 585 + } 586 + } 587 + 588 + pub fn list_app_passwords( 589 + &self, 590 + user_id: Uuid, 591 + ) -> Result<Vec<AppPasswordRecord>, MetastoreError> { 592 + let user_hash = self.resolve_user_hash_from_uuid(user_id)?; 593 + let prefix = session_app_password_prefix(user_hash); 594 + 595 + let mut records: Vec<AppPasswordRecord> = 596 + self.auth 597 + .prefix(prefix.as_slice()) 598 + .try_fold(Vec::new(), |mut acc, guard| { 599 + let (_, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 600 + let val = AppPasswordValue::deserialize(&val_bytes) 601 + .ok_or(MetastoreError::CorruptData("corrupt app password"))?; 602 + acc.push(self.value_to_app_password(&val)?); 603 + Ok::<_, MetastoreError>(acc) 604 + })?; 605 + 606 + records.sort_by_key(|r| std::cmp::Reverse(r.created_at)); 607 + Ok(records) 608 + } 609 + 610 + pub fn get_app_passwords_for_login( 611 + &self, 612 + user_id: Uuid, 613 + ) -> Result<Vec<AppPasswordRecord>, MetastoreError> { 614 + let mut passwords = self.list_app_passwords(user_id)?; 615 + passwords.truncate(20); 616 + Ok(passwords) 617 + } 618 + 619 + pub fn get_app_password_by_name( 620 + &self, 621 + user_id: Uuid, 622 + name: &str, 623 + ) -> Result<Option<AppPasswordRecord>, MetastoreError> { 624 + let user_hash = self.resolve_user_hash_from_uuid(user_id)?; 625 + let key = session_app_password_key(user_hash, name); 626 + 627 + let val: Option<AppPasswordValue> = point_lookup( 628 + &self.auth, 629 + key.as_slice(), 630 + AppPasswordValue::deserialize, 631 + "corrupt app password", 632 + )?; 633 + 634 + val.map(|v| self.value_to_app_password(&v)).transpose() 635 + } 636 + 637 + pub fn create_app_password(&self, data: &AppPasswordCreate) -> Result<Uuid, MetastoreError> { 638 + let user_hash = self.resolve_user_hash_from_uuid(data.user_id)?; 639 + let id = Uuid::new_v4(); 640 + let now_ms = Utc::now().timestamp_millis(); 641 + 642 + let value = AppPasswordValue { 643 + id, 644 + user_id: data.user_id, 645 + name: data.name.clone(), 646 + password_hash: data.password_hash.clone(), 647 + created_at_ms: now_ms, 648 + privilege: privilege_to_u8(data.privilege), 649 + scopes: data.scopes.clone(), 650 + created_by_controller_did: data 651 + .created_by_controller_did 652 + .as_ref() 653 + .map(|d| d.to_string()), 654 + }; 655 + 656 + let key = session_app_password_key(user_hash, &data.name); 657 + self.auth 658 + .insert(key.as_slice(), value.serialize()) 659 + .map_err(MetastoreError::Fjall)?; 660 + 661 + Ok(id) 662 + } 663 + 664 + pub fn delete_app_password(&self, user_id: Uuid, name: &str) -> Result<u64, MetastoreError> { 665 + let user_hash = self.resolve_user_hash_from_uuid(user_id)?; 666 + let key = session_app_password_key(user_hash, name); 667 + 668 + let exists = self 669 + .auth 670 + .get(key.as_slice()) 671 + .map_err(MetastoreError::Fjall)? 672 + .is_some(); 673 + 674 + match exists { 675 + true => { 676 + self.auth 677 + .remove(key.as_slice()) 678 + .map_err(MetastoreError::Fjall)?; 679 + Ok(1) 680 + } 681 + false => Ok(0), 682 + } 683 + } 684 + 685 + pub fn delete_app_passwords_by_controller( 686 + &self, 687 + did: &Did, 688 + controller_did: &Did, 689 + ) -> Result<u64, MetastoreError> { 690 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 691 + let user_uuid = self 692 + .user_hashes 693 + .get_uuid(&user_hash) 694 + .ok_or(MetastoreError::InvalidInput("unknown did"))?; 695 + let _ = user_uuid; 696 + 697 + let prefix = session_app_password_prefix(user_hash); 698 + let controller_str = controller_did.to_string(); 699 + 700 + let keys_to_remove: Vec<_> = self 701 + .auth 702 + .prefix(prefix.as_slice()) 703 + .filter_map(|guard| { 704 + let (key_bytes, val_bytes) = guard.into_inner().ok()?; 705 + let val = AppPasswordValue::deserialize(&val_bytes)?; 706 + match val.created_by_controller_did.as_deref() == Some(controller_str.as_str()) { 707 + true => Some(key_bytes.to_vec()), 708 + false => None, 709 + } 710 + }) 711 + .collect(); 712 + 713 + let count = u64::try_from(keys_to_remove.len()).unwrap_or(u64::MAX); 714 + match count { 715 + 0 => Ok(0), 716 + _ => { 717 + let mut batch = self.db.batch(); 718 + keys_to_remove.iter().for_each(|key| { 719 + batch.remove(&self.auth, key); 720 + }); 721 + batch.commit().map_err(MetastoreError::Fjall)?; 722 + Ok(count) 723 + } 724 + } 725 + } 726 + 727 + pub fn get_last_reauth_at(&self, did: &Did) -> Result<Option<DateTime<Utc>>, MetastoreError> { 728 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 729 + let key = session_last_reauth_key(user_hash); 730 + 731 + match self 732 + .auth 733 + .get(key.as_slice()) 734 + .map_err(MetastoreError::Fjall)? 735 + { 736 + Some(raw) => { 737 + Ok(deserialize_last_reauth_value(&raw).and_then(DateTime::from_timestamp_millis)) 738 + } 739 + None => Ok(None), 740 + } 741 + } 742 + 743 + pub fn update_last_reauth(&self, did: &Did) -> Result<DateTime<Utc>, MetastoreError> { 744 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 745 + let now = Utc::now(); 746 + let key = session_last_reauth_key(user_hash); 747 + 748 + self.auth 749 + .insert( 750 + key.as_slice(), 751 + serialize_last_reauth_value(now.timestamp_millis()), 752 + ) 753 + .map_err(MetastoreError::Fjall)?; 754 + 755 + Ok(now) 756 + } 757 + 758 + pub fn get_session_mfa_status( 759 + &self, 760 + did: &Did, 761 + ) -> Result<Option<SessionMfaStatus>, MetastoreError> { 762 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 763 + let mut sessions = self.collect_sessions_for_did(user_hash)?; 764 + sessions.sort_by_key(|s| std::cmp::Reverse(s.created_at_ms)); 765 + 766 + let latest = match sessions.first() { 767 + Some(s) => s, 768 + None => return Ok(None), 769 + }; 770 + 771 + let last_reauth_at = self.get_last_reauth_at(did)?; 772 + 773 + Ok(Some(SessionMfaStatus { 774 + login_type: u8_to_login_type(latest.login_type).unwrap_or(LoginType::Modern), 775 + mfa_verified: latest.mfa_verified, 776 + last_reauth_at, 777 + })) 778 + } 779 + 780 + pub fn update_mfa_verified(&self, did: &Did) -> Result<(), MetastoreError> { 781 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 782 + let sessions = self.collect_sessions_for_did(user_hash)?; 783 + let now = Utc::now(); 784 + let now_ms = now.timestamp_millis(); 785 + 786 + let mut batch = self.db.batch(); 787 + sessions.iter().for_each(|session| { 788 + let mut updated = session.clone(); 789 + updated.mfa_verified = true; 790 + updated.updated_at_ms = now_ms; 791 + batch.insert( 792 + &self.auth, 793 + session_primary_key(updated.id).as_slice(), 794 + updated.serialize(), 795 + ); 796 + }); 797 + 798 + let reauth_key = session_last_reauth_key(user_hash); 799 + batch.insert( 800 + &self.auth, 801 + reauth_key.as_slice(), 802 + serialize_last_reauth_value(now_ms), 803 + ); 804 + 805 + batch.commit().map_err(MetastoreError::Fjall)?; 806 + Ok(()) 807 + } 808 + 809 + pub fn get_app_password_hashes_by_did(&self, did: &Did) -> Result<Vec<String>, MetastoreError> { 810 + let user_hash = self.resolve_user_hash_from_did(did.as_str()); 811 + match self.user_hashes.get_uuid(&user_hash) { 812 + Some(_) => {} 813 + None => return Ok(Vec::new()), 814 + }; 815 + 816 + let prefix = session_app_password_prefix(user_hash); 817 + 818 + self.auth 819 + .prefix(prefix.as_slice()) 820 + .try_fold(Vec::new(), |mut acc, guard| { 821 + let (_, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 822 + let val = AppPasswordValue::deserialize(&val_bytes) 823 + .ok_or(MetastoreError::CorruptData("corrupt app password"))?; 824 + acc.push(val.password_hash); 825 + Ok::<_, MetastoreError>(acc) 826 + }) 827 + } 828 + 829 + pub fn refresh_session_atomic( 830 + &self, 831 + data: &SessionRefreshData, 832 + ) -> Result<RefreshSessionResult, MetastoreError> { 833 + let used_key = session_used_refresh_key(&data.old_refresh_jti); 834 + let already_used = self 835 + .auth 836 + .get(used_key.as_slice()) 837 + .map_err(MetastoreError::Fjall)?; 838 + 839 + if already_used.is_some() { 840 + let mut batch = self.db.batch(); 841 + let session = self.load_session_by_id(data.session_id.as_i32())?; 842 + if let Some(s) = session { 843 + self.delete_session_indexes(&mut batch, &s); 844 + } 845 + batch.commit().map_err(MetastoreError::Fjall)?; 846 + return Ok(RefreshSessionResult::TokenAlreadyUsed); 847 + } 848 + 849 + let mut session = match self.load_session_by_id(data.session_id.as_i32())? { 850 + Some(s) => s, 851 + None => return Ok(RefreshSessionResult::ConcurrentRefresh), 852 + }; 853 + 854 + if session.refresh_jti != data.old_refresh_jti { 855 + return Ok(RefreshSessionResult::ConcurrentRefresh); 856 + } 857 + 858 + let user_hash = self.resolve_user_hash_from_did(&session.did); 859 + let old_access_jti = session.access_jti.clone(); 860 + let old_refresh_jti = session.refresh_jti.clone(); 861 + 862 + session.access_jti = data.new_access_jti.clone(); 863 + session.refresh_jti = data.new_refresh_jti.clone(); 864 + session.access_expires_at_ms = data.new_access_expires_at.timestamp_millis(); 865 + session.refresh_expires_at_ms = data.new_refresh_expires_at.timestamp_millis(); 866 + session.updated_at_ms = Utc::now().timestamp_millis(); 867 + 868 + let new_access_index = SessionIndexValue { 869 + user_hash: user_hash.raw(), 870 + session_id: session.id, 871 + }; 872 + let new_refresh_index = SessionIndexValue { 873 + user_hash: user_hash.raw(), 874 + session_id: session.id, 875 + }; 876 + 877 + let mut batch = self.db.batch(); 878 + 879 + batch.insert( 880 + &self.auth, 881 + used_key.as_slice(), 882 + serialize_used_refresh_value(session.refresh_expires_at_ms, session.id), 883 + ); 884 + 885 + batch.remove( 886 + &self.auth, 887 + session_by_access_key(&old_access_jti).as_slice(), 888 + ); 889 + batch.remove( 890 + &self.auth, 891 + session_by_refresh_key(&old_refresh_jti).as_slice(), 892 + ); 893 + 894 + batch.insert( 895 + &self.auth, 896 + session_primary_key(session.id).as_slice(), 897 + session.serialize(), 898 + ); 899 + batch.insert( 900 + &self.auth, 901 + session_by_access_key(&data.new_access_jti).as_slice(), 902 + new_access_index.serialize(session.refresh_expires_at_ms), 903 + ); 904 + batch.insert( 905 + &self.auth, 906 + session_by_refresh_key(&data.new_refresh_jti).as_slice(), 907 + new_refresh_index.serialize(session.refresh_expires_at_ms), 908 + ); 909 + batch.insert( 910 + &self.auth, 911 + session_by_did_key(user_hash, session.id).as_slice(), 912 + serialize_by_did_value(session.refresh_expires_at_ms), 913 + ); 914 + 915 + batch.commit().map_err(MetastoreError::Fjall)?; 916 + 917 + Ok(RefreshSessionResult::Success) 918 + } 919 + }
+459
crates/tranquil-store/src/metastore/sessions.rs
··· 1 + use serde::{Deserialize, Serialize}; 2 + use smallvec::SmallVec; 3 + 4 + use super::encoding::KeyBuilder; 5 + use super::keys::{KeyTag, UserHash}; 6 + 7 + const SESSION_SCHEMA_VERSION: u8 = 1; 8 + const APP_PASSWORD_SCHEMA_VERSION: u8 = 1; 9 + const SESSION_INDEX_SCHEMA_VERSION: u8 = 1; 10 + 11 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 12 + pub struct SessionTokenValue { 13 + pub id: i32, 14 + pub did: String, 15 + pub access_jti: String, 16 + pub refresh_jti: String, 17 + pub access_expires_at_ms: i64, 18 + pub refresh_expires_at_ms: i64, 19 + pub login_type: u8, 20 + pub mfa_verified: bool, 21 + pub scope: Option<String>, 22 + pub controller_did: Option<String>, 23 + pub app_password_name: Option<String>, 24 + pub created_at_ms: i64, 25 + pub updated_at_ms: i64, 26 + } 27 + 28 + impl SessionTokenValue { 29 + pub fn serialize(&self) -> Vec<u8> { 30 + let ttl_bytes = u64::try_from(self.refresh_expires_at_ms) 31 + .unwrap_or(0) 32 + .to_be_bytes(); 33 + let payload = 34 + postcard::to_allocvec(self).expect("SessionTokenValue serialization cannot fail"); 35 + let mut buf = Vec::with_capacity(8 + 1 + payload.len()); 36 + buf.extend_from_slice(&ttl_bytes); 37 + buf.push(SESSION_SCHEMA_VERSION); 38 + buf.extend_from_slice(&payload); 39 + buf 40 + } 41 + 42 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 43 + let rest = bytes.get(8..)?; 44 + let (&version, payload) = rest.split_first()?; 45 + match version { 46 + SESSION_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 47 + _ => None, 48 + } 49 + } 50 + } 51 + 52 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 53 + pub struct AppPasswordValue { 54 + pub id: uuid::Uuid, 55 + pub user_id: uuid::Uuid, 56 + pub name: String, 57 + pub password_hash: String, 58 + pub created_at_ms: i64, 59 + pub privilege: u8, 60 + pub scopes: Option<String>, 61 + pub created_by_controller_did: Option<String>, 62 + } 63 + 64 + impl AppPasswordValue { 65 + pub fn serialize(&self) -> Vec<u8> { 66 + let payload = 67 + postcard::to_allocvec(self).expect("AppPasswordValue serialization cannot fail"); 68 + let mut buf = Vec::with_capacity(8 + 1 + payload.len()); 69 + buf.extend_from_slice(&0u64.to_be_bytes()); 70 + buf.push(APP_PASSWORD_SCHEMA_VERSION); 71 + buf.extend_from_slice(&payload); 72 + buf 73 + } 74 + 75 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 76 + let rest = bytes.get(8..)?; 77 + let (&version, payload) = rest.split_first()?; 78 + match version { 79 + APP_PASSWORD_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 80 + _ => None, 81 + } 82 + } 83 + } 84 + 85 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 86 + pub struct SessionIndexValue { 87 + pub user_hash: u64, 88 + pub session_id: i32, 89 + } 90 + 91 + impl SessionIndexValue { 92 + pub fn serialize(&self, expires_at_ms: i64) -> Vec<u8> { 93 + let ttl_bytes = u64::try_from(expires_at_ms).unwrap_or(0).to_be_bytes(); 94 + let payload = 95 + postcard::to_allocvec(self).expect("SessionIndexValue serialization cannot fail"); 96 + let mut buf = Vec::with_capacity(8 + 1 + payload.len()); 97 + buf.extend_from_slice(&ttl_bytes); 98 + buf.push(SESSION_INDEX_SCHEMA_VERSION); 99 + buf.extend_from_slice(&payload); 100 + buf 101 + } 102 + 103 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 104 + let rest = bytes.get(8..)?; 105 + let (&version, payload) = rest.split_first()?; 106 + match version { 107 + SESSION_INDEX_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 108 + _ => None, 109 + } 110 + } 111 + } 112 + 113 + pub fn login_type_to_u8(t: tranquil_db_traits::LoginType) -> u8 { 114 + match t { 115 + tranquil_db_traits::LoginType::Modern => 0, 116 + tranquil_db_traits::LoginType::Legacy => 1, 117 + } 118 + } 119 + 120 + pub fn u8_to_login_type(v: u8) -> Option<tranquil_db_traits::LoginType> { 121 + match v { 122 + 0 => Some(tranquil_db_traits::LoginType::Modern), 123 + 1 => Some(tranquil_db_traits::LoginType::Legacy), 124 + _ => None, 125 + } 126 + } 127 + 128 + pub fn privilege_to_u8(p: tranquil_db_traits::AppPasswordPrivilege) -> u8 { 129 + match p { 130 + tranquil_db_traits::AppPasswordPrivilege::Standard => 0, 131 + tranquil_db_traits::AppPasswordPrivilege::Privileged => 1, 132 + } 133 + } 134 + 135 + pub fn u8_to_privilege(v: u8) -> Option<tranquil_db_traits::AppPasswordPrivilege> { 136 + match v { 137 + 0 => Some(tranquil_db_traits::AppPasswordPrivilege::Standard), 138 + 1 => Some(tranquil_db_traits::AppPasswordPrivilege::Privileged), 139 + _ => None, 140 + } 141 + } 142 + 143 + pub fn session_primary_key(session_id: i32) -> SmallVec<[u8; 128]> { 144 + KeyBuilder::new() 145 + .tag(KeyTag::SESSION_PRIMARY) 146 + .raw(&session_id.to_be_bytes()) 147 + .build() 148 + } 149 + 150 + pub fn session_by_access_key(access_jti: &str) -> SmallVec<[u8; 128]> { 151 + KeyBuilder::new() 152 + .tag(KeyTag::SESSION_BY_ACCESS) 153 + .string(access_jti) 154 + .build() 155 + } 156 + 157 + pub fn session_by_refresh_key(refresh_jti: &str) -> SmallVec<[u8; 128]> { 158 + KeyBuilder::new() 159 + .tag(KeyTag::SESSION_BY_REFRESH) 160 + .string(refresh_jti) 161 + .build() 162 + } 163 + 164 + pub fn session_used_refresh_key(refresh_jti: &str) -> SmallVec<[u8; 128]> { 165 + KeyBuilder::new() 166 + .tag(KeyTag::SESSION_USED_REFRESH) 167 + .string(refresh_jti) 168 + .build() 169 + } 170 + 171 + pub fn session_app_password_key(user_hash: UserHash, name: &str) -> SmallVec<[u8; 128]> { 172 + KeyBuilder::new() 173 + .tag(KeyTag::SESSION_APP_PASSWORD) 174 + .u64(user_hash.raw()) 175 + .string(name) 176 + .build() 177 + } 178 + 179 + pub fn session_app_password_prefix(user_hash: UserHash) -> SmallVec<[u8; 128]> { 180 + KeyBuilder::new() 181 + .tag(KeyTag::SESSION_APP_PASSWORD) 182 + .u64(user_hash.raw()) 183 + .build() 184 + } 185 + 186 + pub fn session_by_did_key(user_hash: UserHash, session_id: i32) -> SmallVec<[u8; 128]> { 187 + KeyBuilder::new() 188 + .tag(KeyTag::SESSION_BY_DID) 189 + .u64(user_hash.raw()) 190 + .raw(&session_id.to_be_bytes()) 191 + .build() 192 + } 193 + 194 + pub fn session_by_did_prefix(user_hash: UserHash) -> SmallVec<[u8; 128]> { 195 + KeyBuilder::new() 196 + .tag(KeyTag::SESSION_BY_DID) 197 + .u64(user_hash.raw()) 198 + .build() 199 + } 200 + 201 + pub fn session_last_reauth_key(user_hash: UserHash) -> SmallVec<[u8; 128]> { 202 + KeyBuilder::new() 203 + .tag(KeyTag::SESSION_LAST_REAUTH) 204 + .u64(user_hash.raw()) 205 + .build() 206 + } 207 + 208 + pub fn session_id_counter_key() -> SmallVec<[u8; 128]> { 209 + KeyBuilder::new().tag(KeyTag::SESSION_ID_COUNTER).build() 210 + } 211 + 212 + fn serialize_ttl_i32(expires_at_ms: i64, session_id: i32) -> Vec<u8> { 213 + let mut buf = Vec::with_capacity(12); 214 + buf.extend_from_slice(&u64::try_from(expires_at_ms).unwrap_or(0).to_be_bytes()); 215 + buf.extend_from_slice(&session_id.to_be_bytes()); 216 + buf 217 + } 218 + 219 + fn deserialize_ttl_i32(bytes: &[u8]) -> Option<i32> { 220 + let rest = bytes.get(8..)?; 221 + let arr: [u8; 4] = rest.try_into().ok()?; 222 + Some(i32::from_be_bytes(arr)) 223 + } 224 + 225 + pub fn serialize_used_refresh_value(expires_at_ms: i64, session_id: i32) -> Vec<u8> { 226 + serialize_ttl_i32(expires_at_ms, session_id) 227 + } 228 + 229 + pub fn deserialize_used_refresh_value(bytes: &[u8]) -> Option<i32> { 230 + deserialize_ttl_i32(bytes) 231 + } 232 + 233 + fn serialize_ttl_i64(timestamp_ms: i64) -> Vec<u8> { 234 + let mut buf = Vec::with_capacity(16); 235 + buf.extend_from_slice(&0u64.to_be_bytes()); 236 + buf.extend_from_slice(&timestamp_ms.to_be_bytes()); 237 + buf 238 + } 239 + 240 + fn deserialize_ttl_i64(bytes: &[u8]) -> Option<i64> { 241 + let rest = bytes.get(8..)?; 242 + let arr: [u8; 8] = rest.try_into().ok()?; 243 + Some(i64::from_be_bytes(arr)) 244 + } 245 + 246 + pub fn serialize_last_reauth_value(timestamp_ms: i64) -> Vec<u8> { 247 + serialize_ttl_i64(timestamp_ms) 248 + } 249 + 250 + pub fn deserialize_last_reauth_value(bytes: &[u8]) -> Option<i64> { 251 + deserialize_ttl_i64(bytes) 252 + } 253 + 254 + pub fn serialize_by_did_value(expires_at_ms: i64) -> Vec<u8> { 255 + u64::try_from(expires_at_ms) 256 + .unwrap_or(0) 257 + .to_be_bytes() 258 + .to_vec() 259 + } 260 + 261 + pub fn serialize_id_counter_value(counter: i32) -> Vec<u8> { 262 + let mut buf = Vec::with_capacity(12); 263 + buf.extend_from_slice(&0u64.to_be_bytes()); 264 + buf.extend_from_slice(&counter.to_be_bytes()); 265 + buf 266 + } 267 + 268 + pub fn deserialize_id_counter_value(bytes: &[u8]) -> Option<i32> { 269 + deserialize_ttl_i32(bytes) 270 + } 271 + 272 + #[cfg(test)] 273 + mod tests { 274 + use super::*; 275 + 276 + #[test] 277 + fn session_token_value_roundtrip() { 278 + let val = SessionTokenValue { 279 + id: 42, 280 + did: "did:plc:test".to_owned(), 281 + access_jti: "access_abc".to_owned(), 282 + refresh_jti: "refresh_xyz".to_owned(), 283 + access_expires_at_ms: 1700000060000, 284 + refresh_expires_at_ms: 1700000600000, 285 + login_type: 0, 286 + mfa_verified: true, 287 + scope: Some("atproto".to_owned()), 288 + controller_did: None, 289 + app_password_name: None, 290 + created_at_ms: 1700000000000, 291 + updated_at_ms: 1700000000000, 292 + }; 293 + let bytes = val.serialize(); 294 + let ttl = u64::from_be_bytes(bytes[..8].try_into().unwrap()); 295 + assert_eq!(ttl, u64::try_from(val.refresh_expires_at_ms).unwrap_or(0)); 296 + assert_eq!(bytes[8], SESSION_SCHEMA_VERSION); 297 + let decoded = SessionTokenValue::deserialize(&bytes).unwrap(); 298 + assert_eq!(val, decoded); 299 + } 300 + 301 + #[test] 302 + fn app_password_value_roundtrip() { 303 + let val = AppPasswordValue { 304 + id: uuid::Uuid::new_v4(), 305 + user_id: uuid::Uuid::new_v4(), 306 + name: "test-app".to_owned(), 307 + password_hash: "hashed".to_owned(), 308 + created_at_ms: 1700000000000, 309 + privilege: 0, 310 + scopes: None, 311 + created_by_controller_did: None, 312 + }; 313 + let bytes = val.serialize(); 314 + let ttl = u64::from_be_bytes(bytes[..8].try_into().unwrap()); 315 + assert_eq!(ttl, 0); 316 + assert_eq!(bytes[8], APP_PASSWORD_SCHEMA_VERSION); 317 + let decoded = AppPasswordValue::deserialize(&bytes).unwrap(); 318 + assert_eq!(val, decoded); 319 + } 320 + 321 + #[test] 322 + fn session_index_value_roundtrip() { 323 + let val = SessionIndexValue { 324 + user_hash: 0xDEAD_BEEF, 325 + session_id: 99, 326 + }; 327 + let expires_at_ms = 1700000600000i64; 328 + let bytes = val.serialize(expires_at_ms); 329 + let ttl = u64::from_be_bytes(bytes[..8].try_into().unwrap()); 330 + assert_eq!(ttl, u64::try_from(expires_at_ms).unwrap_or(0)); 331 + let decoded = SessionIndexValue::deserialize(&bytes).unwrap(); 332 + assert_eq!(val, decoded); 333 + } 334 + 335 + #[test] 336 + fn used_refresh_value_roundtrip() { 337 + let session_id = 7; 338 + let expires_at_ms = 1700000600000i64; 339 + let bytes = serialize_used_refresh_value(expires_at_ms, session_id); 340 + let ttl = u64::from_be_bytes(bytes[..8].try_into().unwrap()); 341 + assert_eq!(ttl, u64::try_from(expires_at_ms).unwrap_or(0)); 342 + assert_eq!(deserialize_used_refresh_value(&bytes), Some(session_id)); 343 + } 344 + 345 + #[test] 346 + fn last_reauth_value_roundtrip() { 347 + let ts = 1700000000000i64; 348 + let bytes = serialize_last_reauth_value(ts); 349 + let ttl = u64::from_be_bytes(bytes[..8].try_into().unwrap()); 350 + assert_eq!(ttl, 0); 351 + assert_eq!(deserialize_last_reauth_value(&bytes), Some(ts)); 352 + } 353 + 354 + #[test] 355 + fn id_counter_value_roundtrip() { 356 + let counter = 123; 357 + let bytes = serialize_id_counter_value(counter); 358 + let ttl = u64::from_be_bytes(bytes[..8].try_into().unwrap()); 359 + assert_eq!(ttl, 0); 360 + assert_eq!(deserialize_id_counter_value(&bytes), Some(counter)); 361 + } 362 + 363 + #[test] 364 + fn session_primary_key_roundtrip() { 365 + use super::super::encoding::KeyReader; 366 + let key = session_primary_key(42); 367 + let mut reader = KeyReader::new(&key); 368 + assert_eq!(reader.tag(), Some(KeyTag::SESSION_PRIMARY.raw())); 369 + let remaining = reader.remaining(); 370 + let arr: [u8; 4] = remaining.try_into().unwrap(); 371 + assert_eq!(i32::from_be_bytes(arr), 42); 372 + } 373 + 374 + #[test] 375 + fn session_by_access_key_roundtrip() { 376 + use super::super::encoding::KeyReader; 377 + let key = session_by_access_key("jti_abc"); 378 + let mut reader = KeyReader::new(&key); 379 + assert_eq!(reader.tag(), Some(KeyTag::SESSION_BY_ACCESS.raw())); 380 + assert_eq!(reader.string(), Some("jti_abc".to_owned())); 381 + assert!(reader.is_empty()); 382 + } 383 + 384 + #[test] 385 + fn session_by_did_prefix_is_prefix_of_key() { 386 + let uh = UserHash::from_did("did:plc:test"); 387 + let prefix = session_by_did_prefix(uh); 388 + let key = session_by_did_key(uh, 5); 389 + assert!(key.starts_with(prefix.as_slice())); 390 + } 391 + 392 + #[test] 393 + fn app_password_prefix_is_prefix_of_key() { 394 + let uh = UserHash::from_did("did:plc:test"); 395 + let prefix = session_app_password_prefix(uh); 396 + let key = session_app_password_key(uh, "my-app"); 397 + assert!(key.starts_with(prefix.as_slice())); 398 + } 399 + 400 + #[test] 401 + fn deserialize_unknown_version_returns_none() { 402 + let val = SessionTokenValue { 403 + id: 1, 404 + did: String::new(), 405 + access_jti: String::new(), 406 + refresh_jti: String::new(), 407 + access_expires_at_ms: 0, 408 + refresh_expires_at_ms: 0, 409 + login_type: 0, 410 + mfa_verified: false, 411 + scope: None, 412 + controller_did: None, 413 + app_password_name: None, 414 + created_at_ms: 0, 415 + updated_at_ms: 0, 416 + }; 417 + let mut bytes = val.serialize(); 418 + bytes[8] = 99; 419 + assert!(SessionTokenValue::deserialize(&bytes).is_none()); 420 + } 421 + 422 + #[test] 423 + fn deserialize_too_short_returns_none() { 424 + assert!(SessionTokenValue::deserialize(&[0; 8]).is_none()); 425 + assert!(SessionTokenValue::deserialize(&[0; 7]).is_none()); 426 + assert!(AppPasswordValue::deserialize(&[0; 8]).is_none()); 427 + assert!(SessionIndexValue::deserialize(&[0; 8]).is_none()); 428 + } 429 + 430 + #[test] 431 + fn login_type_conversion_roundtrip() { 432 + assert_eq!( 433 + u8_to_login_type(login_type_to_u8(tranquil_db_traits::LoginType::Modern)), 434 + Some(tranquil_db_traits::LoginType::Modern) 435 + ); 436 + assert_eq!( 437 + u8_to_login_type(login_type_to_u8(tranquil_db_traits::LoginType::Legacy)), 438 + Some(tranquil_db_traits::LoginType::Legacy) 439 + ); 440 + assert_eq!(u8_to_login_type(99), None); 441 + } 442 + 443 + #[test] 444 + fn privilege_conversion_roundtrip() { 445 + assert_eq!( 446 + u8_to_privilege(privilege_to_u8( 447 + tranquil_db_traits::AppPasswordPrivilege::Standard 448 + )), 449 + Some(tranquil_db_traits::AppPasswordPrivilege::Standard) 450 + ); 451 + assert_eq!( 452 + u8_to_privilege(privilege_to_u8( 453 + tranquil_db_traits::AppPasswordPrivilege::Privileged 454 + )), 455 + Some(tranquil_db_traits::AppPasswordPrivilege::Privileged) 456 + ); 457 + assert_eq!(u8_to_privilege(99), None); 458 + } 459 + }
+482
crates/tranquil-store/src/metastore/sso_ops.rs
··· 1 + use chrono::{DateTime, Utc}; 2 + use fjall::{Database, Keyspace}; 3 + use uuid::Uuid; 4 + 5 + use super::MetastoreError; 6 + use super::keys::UserHash; 7 + use super::scan::point_lookup; 8 + use super::sso_schema::{ 9 + ExternalIdentityValue, PendingRegistrationValue, SsoAuthStateValue, auth_state_key, 10 + auth_state_prefix, by_id_key, by_provider_key, identity_key, identity_user_prefix, 11 + pending_reg_key, pending_reg_prefix, provider_to_u8, u8_to_provider, 12 + }; 13 + 14 + use tranquil_db_traits::{ 15 + ExternalEmail, ExternalIdentity, ExternalUserId, ExternalUsername, SsoAction, SsoAuthState, 16 + SsoPendingRegistration, SsoProviderType, 17 + }; 18 + use tranquil_types::Did; 19 + 20 + pub struct SsoOps { 21 + db: Database, 22 + indexes: Keyspace, 23 + } 24 + 25 + impl SsoOps { 26 + pub fn new(db: Database, indexes: Keyspace) -> Self { 27 + Self { db, indexes } 28 + } 29 + 30 + fn value_to_identity(v: &ExternalIdentityValue) -> Result<ExternalIdentity, MetastoreError> { 31 + Ok(ExternalIdentity { 32 + id: v.id, 33 + did: Did::new(v.did.clone()) 34 + .map_err(|_| MetastoreError::CorruptData("invalid did in sso identity"))?, 35 + provider: u8_to_provider(v.provider) 36 + .ok_or(MetastoreError::CorruptData("unknown sso provider"))?, 37 + provider_user_id: ExternalUserId::new(v.provider_user_id.clone()), 38 + provider_username: v 39 + .provider_username 40 + .as_ref() 41 + .map(|s| ExternalUsername::new(s.clone())), 42 + provider_email: v 43 + .provider_email 44 + .as_ref() 45 + .map(|s| ExternalEmail::new(s.clone())), 46 + created_at: DateTime::from_timestamp_millis(v.created_at_ms).unwrap_or_default(), 47 + updated_at: DateTime::from_timestamp_millis(v.updated_at_ms).unwrap_or_default(), 48 + last_login_at: v.last_login_at_ms.and_then(DateTime::from_timestamp_millis), 49 + }) 50 + } 51 + 52 + fn value_to_auth_state(v: &SsoAuthStateValue) -> Result<SsoAuthState, MetastoreError> { 53 + Ok(SsoAuthState { 54 + state: v.state.clone(), 55 + request_uri: v.request_uri.clone(), 56 + provider: u8_to_provider(v.provider) 57 + .ok_or(MetastoreError::CorruptData("unknown sso provider"))?, 58 + action: SsoAction::parse(&v.action) 59 + .ok_or(MetastoreError::CorruptData("unknown sso action"))?, 60 + nonce: v.nonce.clone(), 61 + code_verifier: v.code_verifier.clone(), 62 + did: v 63 + .did 64 + .as_ref() 65 + .map(|d| Did::new(d.clone())) 66 + .transpose() 67 + .map_err(|_| MetastoreError::CorruptData("invalid did in sso auth state"))?, 68 + created_at: DateTime::from_timestamp_millis(v.created_at_ms).unwrap_or_default(), 69 + expires_at: DateTime::from_timestamp_millis(v.expires_at_ms).unwrap_or_default(), 70 + }) 71 + } 72 + 73 + fn value_to_pending_reg( 74 + v: &PendingRegistrationValue, 75 + ) -> Result<SsoPendingRegistration, MetastoreError> { 76 + Ok(SsoPendingRegistration { 77 + token: v.token.clone(), 78 + request_uri: v.request_uri.clone(), 79 + provider: u8_to_provider(v.provider) 80 + .ok_or(MetastoreError::CorruptData("unknown sso provider"))?, 81 + provider_user_id: ExternalUserId::new(v.provider_user_id.clone()), 82 + provider_username: v 83 + .provider_username 84 + .as_ref() 85 + .map(|s| ExternalUsername::new(s.clone())), 86 + provider_email: v 87 + .provider_email 88 + .as_ref() 89 + .map(|s| ExternalEmail::new(s.clone())), 90 + provider_email_verified: v.provider_email_verified, 91 + created_at: DateTime::from_timestamp_millis(v.created_at_ms).unwrap_or_default(), 92 + expires_at: DateTime::from_timestamp_millis(v.expires_at_ms).unwrap_or_default(), 93 + }) 94 + } 95 + 96 + pub fn create_external_identity( 97 + &self, 98 + did: &Did, 99 + provider: SsoProviderType, 100 + provider_user_id: &str, 101 + provider_username: Option<&str>, 102 + provider_email: Option<&str>, 103 + ) -> Result<Uuid, MetastoreError> { 104 + let user_hash = UserHash::from_did(did.as_str()); 105 + let prov_u8 = provider_to_u8(provider); 106 + let id = Uuid::new_v4(); 107 + let now_ms = Utc::now().timestamp_millis(); 108 + 109 + let value = ExternalIdentityValue { 110 + id, 111 + did: did.to_string(), 112 + provider: prov_u8, 113 + provider_user_id: provider_user_id.to_owned(), 114 + provider_username: provider_username.map(str::to_owned), 115 + provider_email: provider_email.map(str::to_owned), 116 + created_at_ms: now_ms, 117 + updated_at_ms: now_ms, 118 + last_login_at_ms: None, 119 + }; 120 + 121 + let primary = identity_key(user_hash, prov_u8, provider_user_id); 122 + let provider_index = by_provider_key(prov_u8, provider_user_id); 123 + let id_index = by_id_key(id); 124 + 125 + let provider_index_val = { 126 + let mut buf = Vec::with_capacity(8 + 16); 127 + buf.extend_from_slice(&user_hash.raw().to_be_bytes()); 128 + buf.extend_from_slice(id.as_bytes()); 129 + buf 130 + }; 131 + 132 + let id_index_val = { 133 + let puid_bytes = provider_user_id.as_bytes(); 134 + let mut buf = Vec::with_capacity(8 + 1 + puid_bytes.len()); 135 + buf.extend_from_slice(&user_hash.raw().to_be_bytes()); 136 + buf.push(prov_u8); 137 + buf.extend_from_slice(puid_bytes); 138 + buf 139 + }; 140 + 141 + let mut batch = self.db.batch(); 142 + batch.insert(&self.indexes, primary.as_slice(), value.serialize()); 143 + batch.insert(&self.indexes, provider_index.as_slice(), provider_index_val); 144 + batch.insert(&self.indexes, id_index.as_slice(), id_index_val); 145 + batch.commit().map_err(MetastoreError::Fjall)?; 146 + 147 + Ok(id) 148 + } 149 + 150 + pub fn get_external_identity_by_provider( 151 + &self, 152 + provider: SsoProviderType, 153 + provider_user_id: &str, 154 + ) -> Result<Option<ExternalIdentity>, MetastoreError> { 155 + let prov_u8 = provider_to_u8(provider); 156 + let index = by_provider_key(prov_u8, provider_user_id); 157 + 158 + let index_val = match self 159 + .indexes 160 + .get(index.as_slice()) 161 + .map_err(MetastoreError::Fjall)? 162 + { 163 + Some(v) => v, 164 + None => return Ok(None), 165 + }; 166 + 167 + let (user_hash_raw, _id_bytes) = index_val.as_ref().split_at(8); 168 + let user_hash = 169 + UserHash::from_raw(u64::from_be_bytes(user_hash_raw.try_into().map_err( 170 + |_| MetastoreError::CorruptData("corrupt sso by_provider index"), 171 + )?)); 172 + 173 + let primary = identity_key(user_hash, prov_u8, provider_user_id); 174 + let val: Option<ExternalIdentityValue> = point_lookup( 175 + &self.indexes, 176 + primary.as_slice(), 177 + ExternalIdentityValue::deserialize, 178 + "corrupt sso identity", 179 + )?; 180 + 181 + val.map(|v| Self::value_to_identity(&v)).transpose() 182 + } 183 + 184 + pub fn get_external_identities_by_did( 185 + &self, 186 + did: &Did, 187 + ) -> Result<Vec<ExternalIdentity>, MetastoreError> { 188 + let user_hash = UserHash::from_did(did.as_str()); 189 + let prefix = identity_user_prefix(user_hash); 190 + 191 + self.indexes 192 + .prefix(prefix.as_slice()) 193 + .try_fold(Vec::new(), |mut acc, guard| { 194 + let (_, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 195 + let val = ExternalIdentityValue::deserialize(&val_bytes) 196 + .ok_or(MetastoreError::CorruptData("corrupt sso identity"))?; 197 + acc.push(Self::value_to_identity(&val)?); 198 + Ok::<_, MetastoreError>(acc) 199 + }) 200 + } 201 + 202 + pub fn update_external_identity_login( 203 + &self, 204 + id: Uuid, 205 + provider_username: Option<&str>, 206 + provider_email: Option<&str>, 207 + ) -> Result<(), MetastoreError> { 208 + let id_idx = by_id_key(id); 209 + let id_val = match self 210 + .indexes 211 + .get(id_idx.as_slice()) 212 + .map_err(MetastoreError::Fjall)? 213 + { 214 + Some(v) => v, 215 + None => return Ok(()), 216 + }; 217 + 218 + let raw = id_val.as_ref(); 219 + if raw.len() < 9 { 220 + return Err(MetastoreError::CorruptData("corrupt sso by_id index")); 221 + } 222 + let user_hash = UserHash::from_raw(u64::from_be_bytes( 223 + raw[..8] 224 + .try_into() 225 + .map_err(|_| MetastoreError::CorruptData("corrupt sso by_id index"))?, 226 + )); 227 + let provider = raw[8]; 228 + let provider_user_id = std::str::from_utf8(&raw[9..]) 229 + .map_err(|_| MetastoreError::CorruptData("corrupt sso by_id index"))?; 230 + 231 + let primary = identity_key(user_hash, provider, provider_user_id); 232 + let existing: Option<ExternalIdentityValue> = point_lookup( 233 + &self.indexes, 234 + primary.as_slice(), 235 + ExternalIdentityValue::deserialize, 236 + "corrupt sso identity", 237 + )?; 238 + 239 + match existing { 240 + Some(mut val) => { 241 + val.provider_username = provider_username.map(str::to_owned); 242 + val.provider_email = provider_email.map(str::to_owned); 243 + let now_ms = Utc::now().timestamp_millis(); 244 + val.last_login_at_ms = Some(now_ms); 245 + val.updated_at_ms = now_ms; 246 + self.indexes 247 + .insert(primary.as_slice(), val.serialize()) 248 + .map_err(MetastoreError::Fjall)?; 249 + Ok(()) 250 + } 251 + None => Ok(()), 252 + } 253 + } 254 + 255 + pub fn delete_external_identity(&self, id: Uuid, _did: &Did) -> Result<bool, MetastoreError> { 256 + let id_idx = by_id_key(id); 257 + let id_val = match self 258 + .indexes 259 + .get(id_idx.as_slice()) 260 + .map_err(MetastoreError::Fjall)? 261 + { 262 + Some(v) => v, 263 + None => return Ok(false), 264 + }; 265 + 266 + let raw = id_val.as_ref(); 267 + if raw.len() < 9 { 268 + return Err(MetastoreError::CorruptData("corrupt sso by_id index")); 269 + } 270 + let user_hash = UserHash::from_raw(u64::from_be_bytes( 271 + raw[..8] 272 + .try_into() 273 + .map_err(|_| MetastoreError::CorruptData("corrupt sso by_id index"))?, 274 + )); 275 + let provider = raw[8]; 276 + let provider_user_id = std::str::from_utf8(&raw[9..]) 277 + .map_err(|_| MetastoreError::CorruptData("corrupt sso by_id index"))?; 278 + 279 + let primary = identity_key(user_hash, provider, provider_user_id); 280 + let provider_idx = by_provider_key(provider, provider_user_id); 281 + 282 + let mut batch = self.db.batch(); 283 + batch.remove(&self.indexes, primary.as_slice()); 284 + batch.remove(&self.indexes, provider_idx.as_slice()); 285 + batch.remove(&self.indexes, id_idx.as_slice()); 286 + batch.commit().map_err(MetastoreError::Fjall)?; 287 + 288 + Ok(true) 289 + } 290 + 291 + #[allow(clippy::too_many_arguments)] 292 + pub fn create_sso_auth_state( 293 + &self, 294 + state: &str, 295 + request_uri: &str, 296 + provider: SsoProviderType, 297 + action: SsoAction, 298 + nonce: Option<&str>, 299 + code_verifier: Option<&str>, 300 + did: Option<&Did>, 301 + ) -> Result<(), MetastoreError> { 302 + let now_ms = Utc::now().timestamp_millis(); 303 + let expires_at_ms = now_ms.saturating_add(600_000); 304 + 305 + let value = SsoAuthStateValue { 306 + state: state.to_owned(), 307 + request_uri: request_uri.to_owned(), 308 + provider: provider_to_u8(provider), 309 + action: action.as_str().to_owned(), 310 + nonce: nonce.map(str::to_owned), 311 + code_verifier: code_verifier.map(str::to_owned), 312 + did: did.map(|d| d.to_string()), 313 + created_at_ms: now_ms, 314 + expires_at_ms, 315 + }; 316 + 317 + let key = auth_state_key(state); 318 + self.indexes 319 + .insert(key.as_slice(), value.serialize()) 320 + .map_err(MetastoreError::Fjall)?; 321 + Ok(()) 322 + } 323 + 324 + pub fn consume_sso_auth_state( 325 + &self, 326 + state: &str, 327 + ) -> Result<Option<SsoAuthState>, MetastoreError> { 328 + let key = auth_state_key(state); 329 + 330 + let val: Option<SsoAuthStateValue> = point_lookup( 331 + &self.indexes, 332 + key.as_slice(), 333 + SsoAuthStateValue::deserialize, 334 + "corrupt sso auth state", 335 + )?; 336 + 337 + match val { 338 + Some(v) => { 339 + self.indexes 340 + .remove(key.as_slice()) 341 + .map_err(MetastoreError::Fjall)?; 342 + let now_ms = Utc::now().timestamp_millis(); 343 + match v.expires_at_ms < now_ms { 344 + true => Ok(None), 345 + false => Self::value_to_auth_state(&v).map(Some), 346 + } 347 + } 348 + None => Ok(None), 349 + } 350 + } 351 + 352 + pub fn cleanup_expired_sso_auth_states(&self) -> Result<u64, MetastoreError> { 353 + let prefix = auth_state_prefix(); 354 + let now_ms = Utc::now().timestamp_millis(); 355 + let mut count = 0u64; 356 + let mut batch = self.db.batch(); 357 + 358 + self.indexes 359 + .prefix(prefix.as_slice()) 360 + .try_for_each(|guard| { 361 + let (key_bytes, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 362 + let val = SsoAuthStateValue::deserialize(&val_bytes) 363 + .ok_or(MetastoreError::CorruptData("corrupt sso auth state"))?; 364 + if val.expires_at_ms < now_ms { 365 + batch.remove(&self.indexes, key_bytes.as_ref()); 366 + count = count.saturating_add(1); 367 + } 368 + Ok::<_, MetastoreError>(()) 369 + })?; 370 + 371 + batch.commit().map_err(MetastoreError::Fjall)?; 372 + Ok(count) 373 + } 374 + 375 + #[allow(clippy::too_many_arguments)] 376 + pub fn create_pending_registration( 377 + &self, 378 + token: &str, 379 + request_uri: &str, 380 + provider: SsoProviderType, 381 + provider_user_id: &str, 382 + provider_username: Option<&str>, 383 + provider_email: Option<&str>, 384 + provider_email_verified: bool, 385 + ) -> Result<(), MetastoreError> { 386 + let now_ms = Utc::now().timestamp_millis(); 387 + let expires_at_ms = now_ms.saturating_add(600_000); 388 + 389 + let value = PendingRegistrationValue { 390 + token: token.to_owned(), 391 + request_uri: request_uri.to_owned(), 392 + provider: provider_to_u8(provider), 393 + provider_user_id: provider_user_id.to_owned(), 394 + provider_username: provider_username.map(str::to_owned), 395 + provider_email: provider_email.map(str::to_owned), 396 + provider_email_verified, 397 + created_at_ms: now_ms, 398 + expires_at_ms, 399 + }; 400 + 401 + let key = pending_reg_key(token); 402 + self.indexes 403 + .insert(key.as_slice(), value.serialize()) 404 + .map_err(MetastoreError::Fjall)?; 405 + Ok(()) 406 + } 407 + 408 + pub fn get_pending_registration( 409 + &self, 410 + token: &str, 411 + ) -> Result<Option<SsoPendingRegistration>, MetastoreError> { 412 + let key = pending_reg_key(token); 413 + let val: Option<PendingRegistrationValue> = point_lookup( 414 + &self.indexes, 415 + key.as_slice(), 416 + PendingRegistrationValue::deserialize, 417 + "corrupt sso pending registration", 418 + )?; 419 + 420 + match val { 421 + Some(v) => { 422 + let now_ms = Utc::now().timestamp_millis(); 423 + match v.expires_at_ms < now_ms { 424 + true => Ok(None), 425 + false => Self::value_to_pending_reg(&v).map(Some), 426 + } 427 + } 428 + None => Ok(None), 429 + } 430 + } 431 + 432 + pub fn consume_pending_registration( 433 + &self, 434 + token: &str, 435 + ) -> Result<Option<SsoPendingRegistration>, MetastoreError> { 436 + let key = pending_reg_key(token); 437 + let val: Option<PendingRegistrationValue> = point_lookup( 438 + &self.indexes, 439 + key.as_slice(), 440 + PendingRegistrationValue::deserialize, 441 + "corrupt sso pending registration", 442 + )?; 443 + 444 + match val { 445 + Some(v) => { 446 + self.indexes 447 + .remove(key.as_slice()) 448 + .map_err(MetastoreError::Fjall)?; 449 + let now_ms = Utc::now().timestamp_millis(); 450 + match v.expires_at_ms < now_ms { 451 + true => Ok(None), 452 + false => Self::value_to_pending_reg(&v).map(Some), 453 + } 454 + } 455 + None => Ok(None), 456 + } 457 + } 458 + 459 + pub fn cleanup_expired_pending_registrations(&self) -> Result<u64, MetastoreError> { 460 + let prefix = pending_reg_prefix(); 461 + let now_ms = Utc::now().timestamp_millis(); 462 + let mut count = 0u64; 463 + let mut batch = self.db.batch(); 464 + 465 + self.indexes 466 + .prefix(prefix.as_slice()) 467 + .try_for_each(|guard| { 468 + let (key_bytes, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 469 + let val = PendingRegistrationValue::deserialize(&val_bytes).ok_or( 470 + MetastoreError::CorruptData("corrupt sso pending registration"), 471 + )?; 472 + if val.expires_at_ms < now_ms { 473 + batch.remove(&self.indexes, key_bytes.as_ref()); 474 + count = count.saturating_add(1); 475 + } 476 + Ok::<_, MetastoreError>(()) 477 + })?; 478 + 479 + batch.commit().map_err(MetastoreError::Fjall)?; 480 + Ok(count) 481 + } 482 + }
+227
crates/tranquil-store/src/metastore/sso_schema.rs
··· 1 + use serde::{Deserialize, Serialize}; 2 + use smallvec::SmallVec; 3 + 4 + use super::encoding::KeyBuilder; 5 + use super::keys::{KeyTag, UserHash}; 6 + 7 + const IDENTITY_SCHEMA_VERSION: u8 = 1; 8 + const AUTH_STATE_SCHEMA_VERSION: u8 = 1; 9 + const PENDING_REG_SCHEMA_VERSION: u8 = 1; 10 + 11 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 12 + pub struct ExternalIdentityValue { 13 + pub id: uuid::Uuid, 14 + pub did: String, 15 + pub provider: u8, 16 + pub provider_user_id: String, 17 + pub provider_username: Option<String>, 18 + pub provider_email: Option<String>, 19 + pub created_at_ms: i64, 20 + pub updated_at_ms: i64, 21 + pub last_login_at_ms: Option<i64>, 22 + } 23 + 24 + impl ExternalIdentityValue { 25 + pub fn serialize(&self) -> Vec<u8> { 26 + let payload = 27 + postcard::to_allocvec(self).expect("ExternalIdentityValue serialization cannot fail"); 28 + let mut buf = Vec::with_capacity(1 + payload.len()); 29 + buf.push(IDENTITY_SCHEMA_VERSION); 30 + buf.extend_from_slice(&payload); 31 + buf 32 + } 33 + 34 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 35 + let (&version, payload) = bytes.split_first()?; 36 + match version { 37 + IDENTITY_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 38 + _ => None, 39 + } 40 + } 41 + } 42 + 43 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 44 + pub struct SsoAuthStateValue { 45 + pub state: String, 46 + pub request_uri: String, 47 + pub provider: u8, 48 + pub action: String, 49 + pub nonce: Option<String>, 50 + pub code_verifier: Option<String>, 51 + pub did: Option<String>, 52 + pub created_at_ms: i64, 53 + pub expires_at_ms: i64, 54 + } 55 + 56 + impl SsoAuthStateValue { 57 + pub fn serialize(&self) -> Vec<u8> { 58 + let payload = 59 + postcard::to_allocvec(self).expect("SsoAuthStateValue serialization cannot fail"); 60 + let mut buf = Vec::with_capacity(1 + payload.len()); 61 + buf.push(AUTH_STATE_SCHEMA_VERSION); 62 + buf.extend_from_slice(&payload); 63 + buf 64 + } 65 + 66 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 67 + let (&version, payload) = bytes.split_first()?; 68 + match version { 69 + AUTH_STATE_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 70 + _ => None, 71 + } 72 + } 73 + } 74 + 75 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 76 + pub struct PendingRegistrationValue { 77 + pub token: String, 78 + pub request_uri: String, 79 + pub provider: u8, 80 + pub provider_user_id: String, 81 + pub provider_username: Option<String>, 82 + pub provider_email: Option<String>, 83 + pub provider_email_verified: bool, 84 + pub created_at_ms: i64, 85 + pub expires_at_ms: i64, 86 + } 87 + 88 + impl PendingRegistrationValue { 89 + pub fn serialize(&self) -> Vec<u8> { 90 + let payload = postcard::to_allocvec(self) 91 + .expect("PendingRegistrationValue serialization cannot fail"); 92 + let mut buf = Vec::with_capacity(1 + payload.len()); 93 + buf.push(PENDING_REG_SCHEMA_VERSION); 94 + buf.extend_from_slice(&payload); 95 + buf 96 + } 97 + 98 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 99 + let (&version, payload) = bytes.split_first()?; 100 + match version { 101 + PENDING_REG_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 102 + _ => None, 103 + } 104 + } 105 + } 106 + 107 + pub fn provider_to_u8(p: tranquil_db_traits::SsoProviderType) -> u8 { 108 + match p { 109 + tranquil_db_traits::SsoProviderType::Github => 0, 110 + tranquil_db_traits::SsoProviderType::Discord => 1, 111 + tranquil_db_traits::SsoProviderType::Google => 2, 112 + tranquil_db_traits::SsoProviderType::Gitlab => 3, 113 + tranquil_db_traits::SsoProviderType::Oidc => 4, 114 + tranquil_db_traits::SsoProviderType::Apple => 5, 115 + } 116 + } 117 + 118 + pub fn u8_to_provider(v: u8) -> Option<tranquil_db_traits::SsoProviderType> { 119 + match v { 120 + 0 => Some(tranquil_db_traits::SsoProviderType::Github), 121 + 1 => Some(tranquil_db_traits::SsoProviderType::Discord), 122 + 2 => Some(tranquil_db_traits::SsoProviderType::Google), 123 + 3 => Some(tranquil_db_traits::SsoProviderType::Gitlab), 124 + 4 => Some(tranquil_db_traits::SsoProviderType::Oidc), 125 + 5 => Some(tranquil_db_traits::SsoProviderType::Apple), 126 + _ => None, 127 + } 128 + } 129 + 130 + pub fn identity_key( 131 + user_hash: UserHash, 132 + provider: u8, 133 + provider_user_id: &str, 134 + ) -> SmallVec<[u8; 128]> { 135 + KeyBuilder::new() 136 + .tag(KeyTag::SSO_IDENTITY) 137 + .u64(user_hash.raw()) 138 + .raw(&[provider]) 139 + .string(provider_user_id) 140 + .build() 141 + } 142 + 143 + pub fn identity_user_prefix(user_hash: UserHash) -> SmallVec<[u8; 128]> { 144 + KeyBuilder::new() 145 + .tag(KeyTag::SSO_IDENTITY) 146 + .u64(user_hash.raw()) 147 + .build() 148 + } 149 + 150 + pub fn by_provider_key(provider: u8, provider_user_id: &str) -> SmallVec<[u8; 128]> { 151 + KeyBuilder::new() 152 + .tag(KeyTag::SSO_BY_PROVIDER) 153 + .raw(&[provider]) 154 + .string(provider_user_id) 155 + .build() 156 + } 157 + 158 + pub fn by_id_key(id: uuid::Uuid) -> SmallVec<[u8; 128]> { 159 + KeyBuilder::new() 160 + .tag(KeyTag::SSO_BY_ID) 161 + .fixed(id.as_bytes()) 162 + .build() 163 + } 164 + 165 + pub fn auth_state_key(state: &str) -> SmallVec<[u8; 128]> { 166 + KeyBuilder::new() 167 + .tag(KeyTag::SSO_AUTH_STATE) 168 + .string(state) 169 + .build() 170 + } 171 + 172 + pub fn auth_state_prefix() -> SmallVec<[u8; 128]> { 173 + KeyBuilder::new().tag(KeyTag::SSO_AUTH_STATE).build() 174 + } 175 + 176 + pub fn pending_reg_key(token: &str) -> SmallVec<[u8; 128]> { 177 + KeyBuilder::new() 178 + .tag(KeyTag::SSO_PENDING_REG) 179 + .string(token) 180 + .build() 181 + } 182 + 183 + pub fn pending_reg_prefix() -> SmallVec<[u8; 128]> { 184 + KeyBuilder::new().tag(KeyTag::SSO_PENDING_REG).build() 185 + } 186 + 187 + #[cfg(test)] 188 + mod tests { 189 + use super::*; 190 + 191 + #[test] 192 + fn identity_value_roundtrip() { 193 + let val = ExternalIdentityValue { 194 + id: uuid::Uuid::new_v4(), 195 + did: "did:plc:test".to_owned(), 196 + provider: 0, 197 + provider_user_id: "12345".to_owned(), 198 + provider_username: Some("user".to_owned()), 199 + provider_email: Some("user@example.com".to_owned()), 200 + created_at_ms: 1700000000000, 201 + updated_at_ms: 1700000000000, 202 + last_login_at_ms: None, 203 + }; 204 + let bytes = val.serialize(); 205 + assert_eq!(bytes[0], IDENTITY_SCHEMA_VERSION); 206 + let decoded = ExternalIdentityValue::deserialize(&bytes).unwrap(); 207 + assert_eq!(val, decoded); 208 + } 209 + 210 + #[test] 211 + fn auth_state_value_roundtrip() { 212 + let val = SsoAuthStateValue { 213 + state: "random-state".to_owned(), 214 + request_uri: "urn:ietf:params:oauth:request_uri:abc".to_owned(), 215 + provider: 0, 216 + action: "login".to_owned(), 217 + nonce: None, 218 + code_verifier: None, 219 + did: None, 220 + created_at_ms: 1700000000000, 221 + expires_at_ms: 1700000600000, 222 + }; 223 + let bytes = val.serialize(); 224 + let decoded = SsoAuthStateValue::deserialize(&bytes).unwrap(); 225 + assert_eq!(val, decoded); 226 + } 227 + }
+3099
crates/tranquil-store/src/metastore/user_ops.rs
··· 1 + use std::sync::Arc; 2 + 3 + use chrono::{DateTime, Utc}; 4 + use fjall::{Database, Keyspace}; 5 + use uuid::Uuid; 6 + 7 + use super::MetastoreError; 8 + use super::infra_schema::{channel_to_u8, u8_to_channel}; 9 + use super::keys::UserHash; 10 + use super::repo_meta::{RepoMetaValue, RepoStatus, handle_key, repo_meta_key}; 11 + use super::repo_ops::cid_link_to_bytes; 12 + use super::scan::{count_prefix, delete_all_by_prefix, point_lookup}; 13 + use super::sessions::{SessionIndexValue, session_by_access_key}; 14 + use super::user_hash::UserHashMap; 15 + use super::users::{ 16 + BackupCodeValue, DidWebOverridesValue, HandleReservationValue, PasskeyIndexValue, PasskeyValue, 17 + RecoveryTokenValue, ResetCodeValue, TotpValue, UserValue, WebauthnChallengeValue, 18 + account_type_to_u8, backup_code_key, backup_code_prefix, challenge_type_to_u8, 19 + did_web_overrides_key, discord_lookup_key, handle_reservation_key, handle_reservation_prefix, 20 + passkey_by_cred_key, passkey_key, passkey_prefix, recovery_token_key, reset_code_key, 21 + telegram_lookup_key, totp_key, u8_to_account_type, user_by_email_key, user_by_handle_key, 22 + user_primary_key, user_primary_prefix, webauthn_challenge_key, 23 + }; 24 + 25 + use tranquil_db_traits::{ 26 + AccountSearchResult, AccountType, ChannelVerificationStatus, CommsChannel, 27 + CompletePasskeySetupInput, CreateAccountError, CreateDelegatedAccountInput, 28 + CreatePasskeyAccountInput, CreatePasswordAccountInput, CreatePasswordAccountResult, 29 + CreateSsoAccountInput, DidWebOverrides, MigrationReactivationError, MigrationReactivationInput, 30 + NotificationPrefs, OAuthTokenWithUser, PasswordResetResult, ReactivatedAccountInfo, 31 + RecoverPasskeyAccountInput, RecoverPasskeyAccountResult, ScheduledDeletionAccount, 32 + StoredBackupCode, StoredPasskey, TotpRecord, TotpRecordState, User2faStatus, UserAuthInfo, 33 + UserCommsPrefs, UserConfirmSignup, UserDidWebInfo, UserEmailInfo, UserForDeletion, 34 + UserForDidDoc, UserForDidDocBuild, UserForPasskeyRecovery, UserForPasskeySetup, 35 + UserForRecovery, UserForVerification, UserIdAndHandle, UserIdAndPasswordHash, 36 + UserIdHandleEmail, UserInfoForAuth, UserKeyInfo, UserKeyWithId, UserLegacyLoginPref, 37 + UserLoginCheck, UserLoginFull, UserLoginInfo, UserPasswordInfo, UserResendVerification, 38 + UserResetCodeInfo, UserRow, UserSessionInfo, UserStatus, UserVerificationInfo, UserWithKey, 39 + WebauthnChallengeType, 40 + }; 41 + use tranquil_types::{CidLink, Did, Handle}; 42 + 43 + pub struct UserOps { 44 + db: Database, 45 + users: Keyspace, 46 + repo_data: Keyspace, 47 + auth: Keyspace, 48 + user_hashes: Arc<UserHashMap>, 49 + } 50 + 51 + impl UserOps { 52 + pub fn new( 53 + db: Database, 54 + users: Keyspace, 55 + repo_data: Keyspace, 56 + auth: Keyspace, 57 + user_hashes: Arc<UserHashMap>, 58 + ) -> Self { 59 + Self { 60 + db, 61 + users, 62 + repo_data, 63 + auth, 64 + user_hashes, 65 + } 66 + } 67 + 68 + fn resolve_hash(&self, did: &str) -> UserHash { 69 + UserHash::from_did(did) 70 + } 71 + 72 + fn resolve_hash_from_uuid(&self, user_id: Uuid) -> Result<UserHash, MetastoreError> { 73 + self.user_hashes 74 + .get(&user_id) 75 + .ok_or(MetastoreError::InvalidInput("unknown user_id")) 76 + } 77 + 78 + fn load_user(&self, user_hash: UserHash) -> Result<Option<UserValue>, MetastoreError> { 79 + point_lookup( 80 + &self.users, 81 + user_primary_key(user_hash).as_slice(), 82 + UserValue::deserialize, 83 + "corrupt user value", 84 + ) 85 + } 86 + 87 + fn load_user_by_did(&self, did: &str) -> Result<Option<UserValue>, MetastoreError> { 88 + self.load_user(self.resolve_hash(did)) 89 + } 90 + 91 + fn save_user(&self, user_hash: UserHash, val: &UserValue) -> Result<(), MetastoreError> { 92 + self.users 93 + .insert(user_primary_key(user_hash).as_slice(), val.serialize()) 94 + .map_err(MetastoreError::Fjall) 95 + } 96 + 97 + fn load_by_handle(&self, handle: &str) -> Result<Option<UserValue>, MetastoreError> { 98 + let idx_key = user_by_handle_key(handle); 99 + match self 100 + .users 101 + .get(idx_key.as_slice()) 102 + .map_err(MetastoreError::Fjall)? 103 + { 104 + Some(raw) if raw.len() >= 8 => { 105 + let hash_raw = u64::from_be_bytes(raw[..8].try_into().unwrap()); 106 + self.load_user(UserHash::from_raw(hash_raw)) 107 + } 108 + _ => Ok(None), 109 + } 110 + } 111 + 112 + fn load_by_email(&self, email: &str) -> Result<Option<UserValue>, MetastoreError> { 113 + let idx_key = user_by_email_key(email); 114 + match self 115 + .users 116 + .get(idx_key.as_slice()) 117 + .map_err(MetastoreError::Fjall)? 118 + { 119 + Some(raw) if raw.len() >= 8 => { 120 + let hash_raw = u64::from_be_bytes(raw[..8].try_into().unwrap()); 121 + self.load_user(UserHash::from_raw(hash_raw)) 122 + } 123 + _ => Ok(None), 124 + } 125 + } 126 + 127 + fn load_by_identifier(&self, identifier: &str) -> Result<Option<UserValue>, MetastoreError> { 128 + match identifier.contains('@') { 129 + true => self.load_by_email(identifier).and_then(|opt| match opt { 130 + Some(v) => Ok(Some(v)), 131 + None => self.load_by_handle(identifier), 132 + }), 133 + false => self.load_by_handle(identifier).and_then(|opt| match opt { 134 + Some(v) => Ok(Some(v)), 135 + None => self.load_by_email(identifier), 136 + }), 137 + } 138 + } 139 + 140 + fn mutate_user<F>(&self, user_hash: UserHash, f: F) -> Result<bool, MetastoreError> 141 + where 142 + F: FnOnce(&mut UserValue), 143 + { 144 + match self.load_user(user_hash)? { 145 + Some(mut val) => { 146 + f(&mut val); 147 + self.save_user(user_hash, &val)?; 148 + Ok(true) 149 + } 150 + None => Ok(false), 151 + } 152 + } 153 + 154 + fn mutate_user_by_uuid<F>(&self, user_id: Uuid, f: F) -> Result<bool, MetastoreError> 155 + where 156 + F: FnOnce(&mut UserValue), 157 + { 158 + let user_hash = self.resolve_hash_from_uuid(user_id)?; 159 + self.mutate_user(user_hash, f) 160 + } 161 + 162 + fn channel_verification(val: &UserValue) -> ChannelVerificationStatus { 163 + ChannelVerificationStatus::from_db_row( 164 + val.email_verified, 165 + val.discord_verified, 166 + val.telegram_verified, 167 + val.signal_verified, 168 + ) 169 + } 170 + 171 + fn comms_channel(val: &UserValue) -> CommsChannel { 172 + val.preferred_comms_channel 173 + .and_then(u8_to_channel) 174 + .unwrap_or(CommsChannel::Email) 175 + } 176 + 177 + fn to_user_row(val: &UserValue) -> Result<UserRow, MetastoreError> { 178 + Ok(UserRow { 179 + id: val.id, 180 + did: Did::new(val.did.clone()) 181 + .map_err(|_| MetastoreError::CorruptData("invalid user did"))?, 182 + handle: Handle::new(val.handle.clone()) 183 + .map_err(|_| MetastoreError::CorruptData("invalid user handle"))?, 184 + email: val.email.clone(), 185 + created_at: DateTime::from_timestamp_millis(val.created_at_ms).unwrap_or_default(), 186 + deactivated_at: val 187 + .deactivated_at_ms 188 + .and_then(DateTime::from_timestamp_millis), 189 + takedown_ref: val.takedown_ref.clone(), 190 + is_admin: val.is_admin, 191 + }) 192 + } 193 + 194 + fn to_stored_passkey(pv: &PasskeyValue) -> Result<StoredPasskey, MetastoreError> { 195 + Ok(StoredPasskey { 196 + id: pv.id, 197 + did: Did::new(pv.did.clone()) 198 + .map_err(|_| MetastoreError::CorruptData("invalid passkey did"))?, 199 + credential_id: pv.credential_id.clone(), 200 + public_key: pv.public_key.clone(), 201 + sign_count: pv.sign_count, 202 + created_at: DateTime::from_timestamp_millis(pv.created_at_ms).unwrap_or_default(), 203 + last_used: pv.last_used_at_ms.and_then(DateTime::from_timestamp_millis), 204 + friendly_name: pv.friendly_name.clone(), 205 + aaguid: pv.aaguid.clone(), 206 + transports: pv.transports.clone(), 207 + }) 208 + } 209 + 210 + fn is_first_account(&self) -> Result<bool, MetastoreError> { 211 + let prefix = user_primary_prefix(); 212 + match self.users.prefix(prefix.as_slice()).next() { 213 + Some(guard) => { 214 + guard.into_inner().map_err(MetastoreError::Fjall)?; 215 + Ok(false) 216 + } 217 + None => Ok(true), 218 + } 219 + } 220 + 221 + pub fn get_by_did(&self, did: &Did) -> Result<Option<UserRow>, MetastoreError> { 222 + self.load_user_by_did(did.as_str())? 223 + .map(|v| Self::to_user_row(&v)) 224 + .transpose() 225 + } 226 + 227 + pub fn get_by_handle(&self, handle: &Handle) -> Result<Option<UserRow>, MetastoreError> { 228 + self.load_by_handle(handle.as_str())? 229 + .map(|v| Self::to_user_row(&v)) 230 + .transpose() 231 + } 232 + 233 + pub fn get_with_key_by_did(&self, did: &Did) -> Result<Option<UserWithKey>, MetastoreError> { 234 + self.load_user_by_did(did.as_str())? 235 + .map(|v| { 236 + Ok(UserWithKey { 237 + id: v.id, 238 + did: Did::new(v.did.clone()) 239 + .map_err(|_| MetastoreError::CorruptData("invalid user did"))?, 240 + handle: Handle::new(v.handle.clone()) 241 + .map_err(|_| MetastoreError::CorruptData("invalid user handle"))?, 242 + email: v.email.clone(), 243 + deactivated_at: v 244 + .deactivated_at_ms 245 + .and_then(DateTime::from_timestamp_millis), 246 + takedown_ref: v.takedown_ref.clone(), 247 + is_admin: v.is_admin, 248 + key_bytes: v.key_bytes.clone(), 249 + encryption_version: Some(v.encryption_version), 250 + }) 251 + }) 252 + .transpose() 253 + } 254 + 255 + pub fn get_status_by_did(&self, did: &Did) -> Result<Option<UserStatus>, MetastoreError> { 256 + Ok(self.load_user_by_did(did.as_str())?.map(|v| UserStatus { 257 + deactivated_at: v 258 + .deactivated_at_ms 259 + .and_then(DateTime::from_timestamp_millis), 260 + takedown_ref: v.takedown_ref.clone(), 261 + is_admin: v.is_admin, 262 + })) 263 + } 264 + 265 + pub fn count_users(&self) -> Result<i64, MetastoreError> { 266 + count_prefix(&self.users, user_primary_prefix().as_slice()) 267 + } 268 + 269 + pub fn get_session_access_expiry( 270 + &self, 271 + did: &Did, 272 + access_jti: &str, 273 + ) -> Result<Option<DateTime<Utc>>, MetastoreError> { 274 + let index_key = session_by_access_key(access_jti); 275 + let index_val: Option<SessionIndexValue> = point_lookup( 276 + &self.auth, 277 + index_key.as_slice(), 278 + SessionIndexValue::deserialize, 279 + "corrupt session access index", 280 + )?; 281 + 282 + match index_val { 283 + Some(idx) => { 284 + let session_key = super::sessions::session_primary_key(idx.session_id); 285 + let session: Option<super::sessions::SessionTokenValue> = point_lookup( 286 + &self.auth, 287 + session_key.as_slice(), 288 + super::sessions::SessionTokenValue::deserialize, 289 + "corrupt session token", 290 + )?; 291 + match session.filter(|s| s.did == did.as_str()) { 292 + Some(s) => Ok(DateTime::from_timestamp_millis(s.access_expires_at_ms)), 293 + None => Ok(None), 294 + } 295 + } 296 + None => Ok(None), 297 + } 298 + } 299 + 300 + pub fn get_oauth_token_with_user( 301 + &self, 302 + token_id: &str, 303 + ) -> Result<Option<OAuthTokenWithUser>, MetastoreError> { 304 + let index_key = super::oauth_schema::oauth_token_by_id_key(token_id); 305 + let index_val: Option<super::oauth_schema::TokenIndexValue> = point_lookup( 306 + &self.auth, 307 + index_key.as_slice(), 308 + super::oauth_schema::TokenIndexValue::deserialize, 309 + "corrupt oauth token index", 310 + )?; 311 + 312 + let idx = match index_val { 313 + Some(idx) => idx, 314 + None => return Ok(None), 315 + }; 316 + 317 + let uh = UserHash::from_raw(idx.user_hash); 318 + let token_key = super::oauth_schema::oauth_token_key(uh, idx.family_id); 319 + let token: Option<super::oauth_schema::OAuthTokenValue> = point_lookup( 320 + &self.auth, 321 + token_key.as_slice(), 322 + super::oauth_schema::OAuthTokenValue::deserialize, 323 + "corrupt oauth token", 324 + )?; 325 + 326 + let token = match token { 327 + Some(t) => t, 328 + None => return Ok(None), 329 + }; 330 + 331 + let user = self.load_user(uh)?; 332 + match user { 333 + Some(u) => Ok(Some(OAuthTokenWithUser { 334 + did: Did::new(token.did) 335 + .map_err(|_| MetastoreError::CorruptData("invalid oauth token did"))?, 336 + expires_at: DateTime::from_timestamp_millis(token.expires_at_ms) 337 + .unwrap_or_default(), 338 + deactivated_at: u 339 + .deactivated_at_ms 340 + .and_then(DateTime::from_timestamp_millis), 341 + takedown_ref: u.takedown_ref.clone(), 342 + is_admin: u.is_admin, 343 + key_bytes: Some(u.key_bytes.clone()), 344 + encryption_version: Some(u.encryption_version), 345 + })), 346 + None => Ok(None), 347 + } 348 + } 349 + 350 + pub fn get_user_info_by_did( 351 + &self, 352 + did: &Did, 353 + ) -> Result<Option<UserInfoForAuth>, MetastoreError> { 354 + Ok(self 355 + .load_user_by_did(did.as_str())? 356 + .map(|v| UserInfoForAuth { 357 + deactivated_at: v 358 + .deactivated_at_ms 359 + .and_then(DateTime::from_timestamp_millis), 360 + takedown_ref: v.takedown_ref.clone(), 361 + is_admin: v.is_admin, 362 + key_bytes: Some(v.key_bytes.clone()), 363 + encryption_version: Some(v.encryption_version), 364 + })) 365 + } 366 + 367 + pub fn get_any_admin_user_id(&self) -> Result<Option<Uuid>, MetastoreError> { 368 + let prefix = user_primary_prefix(); 369 + self.users 370 + .prefix(prefix.as_slice()) 371 + .map(|guard| -> Result<Option<Uuid>, MetastoreError> { 372 + let (_, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 373 + let val = UserValue::deserialize(&val_bytes) 374 + .ok_or(MetastoreError::CorruptData("corrupt user value"))?; 375 + Ok(val.is_admin.then_some(val.id)) 376 + }) 377 + .filter_map(Result::transpose) 378 + .next() 379 + .transpose() 380 + } 381 + 382 + pub fn set_invites_disabled(&self, did: &Did, disabled: bool) -> Result<bool, MetastoreError> { 383 + let user_hash = self.resolve_hash(did.as_str()); 384 + self.mutate_user(user_hash, |u| { 385 + u.invites_disabled = disabled; 386 + }) 387 + } 388 + 389 + pub fn search_accounts( 390 + &self, 391 + cursor_did: Option<&Did>, 392 + email_filter: Option<&str>, 393 + handle_filter: Option<&str>, 394 + limit: i64, 395 + ) -> Result<Vec<AccountSearchResult>, MetastoreError> { 396 + let prefix = user_primary_prefix(); 397 + let limit = usize::try_from(limit).unwrap_or(0); 398 + let cursor_hash = cursor_did.map(|d| self.resolve_hash(d.as_str())); 399 + 400 + self.users 401 + .prefix(prefix.as_slice()) 402 + .map( 403 + |guard| -> Result<Option<AccountSearchResult>, MetastoreError> { 404 + let (_, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 405 + let val = UserValue::deserialize(&val_bytes) 406 + .ok_or(MetastoreError::CorruptData("corrupt user value"))?; 407 + 408 + let val_hash = UserHash::from_did(&val.did); 409 + if cursor_hash.is_some_and(|cursor| val_hash.raw() <= cursor.raw()) { 410 + return Ok(None); 411 + } 412 + 413 + let email_match = email_filter.map_or(true, |f| { 414 + val.email.as_deref().map_or(false, |e| e.contains(f)) 415 + }); 416 + let handle_match = handle_filter.map_or(true, |f| val.handle.contains(f)); 417 + 418 + match email_match && handle_match { 419 + true => Ok(Some(AccountSearchResult { 420 + did: Did::new(val.did.clone()) 421 + .map_err(|_| MetastoreError::CorruptData("invalid user did"))?, 422 + handle: Handle::new(val.handle.clone()) 423 + .map_err(|_| MetastoreError::CorruptData("invalid user handle"))?, 424 + email: val.email.clone(), 425 + created_at: DateTime::from_timestamp_millis(val.created_at_ms) 426 + .unwrap_or_default(), 427 + email_verified: val.email_verified, 428 + deactivated_at: val 429 + .deactivated_at_ms 430 + .and_then(DateTime::from_timestamp_millis), 431 + invites_disabled: Some(val.invites_disabled), 432 + })), 433 + false => Ok(None), 434 + } 435 + }, 436 + ) 437 + .filter_map(Result::transpose) 438 + .take(limit) 439 + .collect() 440 + } 441 + 442 + pub fn get_auth_info_by_did(&self, did: &Did) -> Result<Option<UserAuthInfo>, MetastoreError> { 443 + self.load_user_by_did(did.as_str())? 444 + .map(|v| { 445 + Ok(UserAuthInfo { 446 + id: v.id, 447 + did: Did::new(v.did.clone()) 448 + .map_err(|_| MetastoreError::CorruptData("invalid user did"))?, 449 + password_hash: v.password_hash.clone(), 450 + deactivated_at: v 451 + .deactivated_at_ms 452 + .and_then(DateTime::from_timestamp_millis), 453 + takedown_ref: v.takedown_ref.clone(), 454 + channel_verification: Self::channel_verification(&v), 455 + }) 456 + }) 457 + .transpose() 458 + } 459 + 460 + pub fn get_by_email(&self, email: &str) -> Result<Option<UserForVerification>, MetastoreError> { 461 + self.load_by_email(email)? 462 + .map(|v| { 463 + Ok(UserForVerification { 464 + id: v.id, 465 + did: Did::new(v.did.clone()) 466 + .map_err(|_| MetastoreError::CorruptData("invalid user did"))?, 467 + email: v.email.clone(), 468 + email_verified: v.email_verified, 469 + handle: Handle::new(v.handle.clone()) 470 + .map_err(|_| MetastoreError::CorruptData("invalid user handle"))?, 471 + }) 472 + }) 473 + .transpose() 474 + } 475 + 476 + pub fn get_login_check_by_handle_or_email( 477 + &self, 478 + identifier: &str, 479 + ) -> Result<Option<UserLoginCheck>, MetastoreError> { 480 + self.load_by_identifier(identifier)? 481 + .map(|v| { 482 + Ok(UserLoginCheck { 483 + did: Did::new(v.did.clone()) 484 + .map_err(|_| MetastoreError::CorruptData("invalid user did"))?, 485 + password_hash: v.password_hash.clone(), 486 + }) 487 + }) 488 + .transpose() 489 + } 490 + 491 + pub fn get_login_info_by_handle_or_email( 492 + &self, 493 + identifier: &str, 494 + ) -> Result<Option<UserLoginInfo>, MetastoreError> { 495 + self.load_by_identifier(identifier)? 496 + .map(|v| { 497 + Ok(UserLoginInfo { 498 + id: v.id, 499 + did: Did::new(v.did.clone()) 500 + .map_err(|_| MetastoreError::CorruptData("invalid user did"))?, 501 + email: v.email.clone(), 502 + password_hash: v.password_hash.clone(), 503 + password_required: v.password_required, 504 + two_factor_enabled: v.two_factor_enabled, 505 + preferred_comms_channel: Self::comms_channel(&v), 506 + deactivated_at: v 507 + .deactivated_at_ms 508 + .and_then(DateTime::from_timestamp_millis), 509 + takedown_ref: v.takedown_ref.clone(), 510 + channel_verification: Self::channel_verification(&v), 511 + account_type: u8_to_account_type(v.account_type) 512 + .unwrap_or(AccountType::Personal), 513 + }) 514 + }) 515 + .transpose() 516 + } 517 + 518 + pub fn get_2fa_status_by_did( 519 + &self, 520 + did: &Did, 521 + ) -> Result<Option<User2faStatus>, MetastoreError> { 522 + Ok(self.load_user_by_did(did.as_str())?.map(|v| User2faStatus { 523 + id: v.id, 524 + two_factor_enabled: v.two_factor_enabled, 525 + preferred_comms_channel: Self::comms_channel(&v), 526 + channel_verification: Self::channel_verification(&v), 527 + })) 528 + } 529 + 530 + pub fn get_comms_prefs(&self, user_id: Uuid) -> Result<Option<UserCommsPrefs>, MetastoreError> { 531 + let user_hash = self.resolve_hash_from_uuid(user_id)?; 532 + self.load_user(user_hash)? 533 + .map(|v| { 534 + Ok(UserCommsPrefs { 535 + email: v.email.clone(), 536 + handle: Handle::new(v.handle.clone()) 537 + .map_err(|_| MetastoreError::CorruptData("invalid user handle"))?, 538 + preferred_channel: Self::comms_channel(&v), 539 + preferred_locale: v.preferred_locale.clone(), 540 + telegram_chat_id: v.telegram_chat_id, 541 + discord_id: v.discord_id.clone(), 542 + signal_username: v.signal_username.clone(), 543 + }) 544 + }) 545 + .transpose() 546 + } 547 + 548 + pub fn get_id_by_did(&self, did: &Did) -> Result<Option<Uuid>, MetastoreError> { 549 + Ok(self.load_user_by_did(did.as_str())?.map(|v| v.id)) 550 + } 551 + 552 + pub fn get_user_key_by_id(&self, user_id: Uuid) -> Result<Option<UserKeyInfo>, MetastoreError> { 553 + let user_hash = self.resolve_hash_from_uuid(user_id)?; 554 + Ok(self.load_user(user_hash)?.map(|v| UserKeyInfo { 555 + key_bytes: v.key_bytes.clone(), 556 + encryption_version: Some(v.encryption_version), 557 + })) 558 + } 559 + 560 + pub fn get_id_and_handle_by_did( 561 + &self, 562 + did: &Did, 563 + ) -> Result<Option<UserIdAndHandle>, MetastoreError> { 564 + self.load_user_by_did(did.as_str())? 565 + .map(|v| { 566 + Ok(UserIdAndHandle { 567 + id: v.id, 568 + handle: Handle::new(v.handle.clone()) 569 + .map_err(|_| MetastoreError::CorruptData("invalid user handle"))?, 570 + }) 571 + }) 572 + .transpose() 573 + } 574 + 575 + pub fn get_did_web_info_by_handle( 576 + &self, 577 + handle: &Handle, 578 + ) -> Result<Option<UserDidWebInfo>, MetastoreError> { 579 + self.load_by_handle(handle.as_str())? 580 + .map(|v| { 581 + Ok(UserDidWebInfo { 582 + id: v.id, 583 + did: Did::new(v.did.clone()) 584 + .map_err(|_| MetastoreError::CorruptData("invalid user did"))?, 585 + migrated_to_pds: v.migrated_to_pds.clone(), 586 + }) 587 + }) 588 + .transpose() 589 + } 590 + 591 + pub fn get_did_web_overrides( 592 + &self, 593 + user_id: Uuid, 594 + ) -> Result<Option<DidWebOverrides>, MetastoreError> { 595 + let user_hash = self.resolve_hash_from_uuid(user_id)?; 596 + let key = did_web_overrides_key(user_hash); 597 + let val: Option<DidWebOverridesValue> = point_lookup( 598 + &self.users, 599 + key.as_slice(), 600 + DidWebOverridesValue::deserialize, 601 + "corrupt did_web_overrides", 602 + )?; 603 + 604 + Ok(val.map(|v| DidWebOverrides { 605 + verification_methods: v 606 + .verification_methods_json 607 + .and_then(|j| serde_json::from_str(&j).ok()) 608 + .unwrap_or(serde_json::Value::Null), 609 + also_known_as: v.also_known_as.unwrap_or_default(), 610 + })) 611 + } 612 + 613 + pub fn get_handle_by_did(&self, did: &Did) -> Result<Option<Handle>, MetastoreError> { 614 + self.load_user_by_did(did.as_str())? 615 + .map(|v| { 616 + Handle::new(v.handle.clone()) 617 + .map_err(|_| MetastoreError::CorruptData("invalid user handle")) 618 + }) 619 + .transpose() 620 + } 621 + 622 + pub fn is_account_active_by_did(&self, did: &Did) -> Result<Option<bool>, MetastoreError> { 623 + Ok(self.load_user_by_did(did.as_str())?.map(|v| v.is_active())) 624 + } 625 + 626 + pub fn get_user_for_deletion( 627 + &self, 628 + did: &Did, 629 + ) -> Result<Option<UserForDeletion>, MetastoreError> { 630 + self.load_user_by_did(did.as_str())? 631 + .map(|v| { 632 + Ok(UserForDeletion { 633 + id: v.id, 634 + password_hash: v.password_hash.clone(), 635 + handle: Handle::new(v.handle.clone()) 636 + .map_err(|_| MetastoreError::CorruptData("invalid user handle"))?, 637 + }) 638 + }) 639 + .transpose() 640 + } 641 + 642 + pub fn check_handle_exists( 643 + &self, 644 + handle: &Handle, 645 + exclude_user_id: Uuid, 646 + ) -> Result<bool, MetastoreError> { 647 + match self.load_by_handle(handle.as_str())? { 648 + Some(v) => Ok(v.id != exclude_user_id), 649 + None => Ok(false), 650 + } 651 + } 652 + 653 + pub fn update_handle(&self, user_id: Uuid, handle: &Handle) -> Result<(), MetastoreError> { 654 + let user_hash = self.resolve_hash_from_uuid(user_id)?; 655 + let val = self 656 + .load_user(user_hash)? 657 + .ok_or(MetastoreError::InvalidInput("user not found"))?; 658 + 659 + let old_handle = val.handle.clone(); 660 + let mut updated = val; 661 + updated.handle = handle.as_str().to_owned(); 662 + 663 + let mut batch = self.db.batch(); 664 + batch.remove(&self.users, user_by_handle_key(&old_handle).as_slice()); 665 + batch.insert( 666 + &self.users, 667 + user_by_handle_key(handle.as_str()).as_slice(), 668 + user_hash.raw().to_be_bytes(), 669 + ); 670 + batch.insert( 671 + &self.users, 672 + user_primary_key(user_hash).as_slice(), 673 + updated.serialize(), 674 + ); 675 + batch.commit().map_err(MetastoreError::Fjall) 676 + } 677 + 678 + pub fn get_user_with_key_by_did( 679 + &self, 680 + did: &Did, 681 + ) -> Result<Option<UserKeyWithId>, MetastoreError> { 682 + Ok(self.load_user_by_did(did.as_str())?.map(|v| UserKeyWithId { 683 + id: v.id, 684 + key_bytes: v.key_bytes.clone(), 685 + encryption_version: Some(v.encryption_version), 686 + })) 687 + } 688 + 689 + pub fn is_account_migrated(&self, did: &Did) -> Result<bool, MetastoreError> { 690 + Ok(self 691 + .load_user_by_did(did.as_str())? 692 + .map_or(false, |v| v.migrated_to_pds.is_some())) 693 + } 694 + 695 + pub fn has_verified_comms_channel(&self, did: &Did) -> Result<bool, MetastoreError> { 696 + Ok(self 697 + .load_user_by_did(did.as_str())? 698 + .map_or(false, |v| v.channel_verification() != 0)) 699 + } 700 + 701 + pub fn get_id_by_handle(&self, handle: &Handle) -> Result<Option<Uuid>, MetastoreError> { 702 + Ok(self.load_by_handle(handle.as_str())?.map(|v| v.id)) 703 + } 704 + 705 + pub fn get_email_info_by_did( 706 + &self, 707 + did: &Did, 708 + ) -> Result<Option<UserEmailInfo>, MetastoreError> { 709 + self.load_user_by_did(did.as_str())? 710 + .map(|v| { 711 + Ok(UserEmailInfo { 712 + id: v.id, 713 + handle: Handle::new(v.handle.clone()) 714 + .map_err(|_| MetastoreError::CorruptData("invalid user handle"))?, 715 + email: v.email.clone(), 716 + email_verified: v.email_verified, 717 + }) 718 + }) 719 + .transpose() 720 + } 721 + 722 + pub fn check_email_exists( 723 + &self, 724 + email: &str, 725 + exclude_user_id: Uuid, 726 + ) -> Result<bool, MetastoreError> { 727 + match self.load_by_email(email)? { 728 + Some(v) => Ok(v.id != exclude_user_id), 729 + None => Ok(false), 730 + } 731 + } 732 + 733 + pub fn update_email(&self, user_id: Uuid, email: &str) -> Result<(), MetastoreError> { 734 + let user_hash = self.resolve_hash_from_uuid(user_id)?; 735 + let val = self 736 + .load_user(user_hash)? 737 + .ok_or(MetastoreError::InvalidInput("user not found"))?; 738 + 739 + let mut batch = self.db.batch(); 740 + 741 + if let Some(old_email) = &val.email { 742 + batch.remove(&self.users, user_by_email_key(old_email).as_slice()); 743 + } 744 + 745 + batch.insert( 746 + &self.users, 747 + user_by_email_key(email).as_slice(), 748 + user_hash.raw().to_be_bytes(), 749 + ); 750 + 751 + let mut updated = val; 752 + updated.email = Some(email.to_owned()); 753 + updated.email_verified = false; 754 + 755 + batch.insert( 756 + &self.users, 757 + user_primary_key(user_hash).as_slice(), 758 + updated.serialize(), 759 + ); 760 + batch.commit().map_err(MetastoreError::Fjall) 761 + } 762 + 763 + pub fn set_email_verified(&self, user_id: Uuid, verified: bool) -> Result<(), MetastoreError> { 764 + self.mutate_user_by_uuid(user_id, |u| { 765 + u.email_verified = verified; 766 + })?; 767 + Ok(()) 768 + } 769 + 770 + pub fn check_email_verified_by_identifier( 771 + &self, 772 + identifier: &str, 773 + ) -> Result<Option<bool>, MetastoreError> { 774 + Ok(self 775 + .load_by_identifier(identifier)? 776 + .map(|v| v.email_verified)) 777 + } 778 + 779 + pub fn check_channel_verified_by_did( 780 + &self, 781 + did: &Did, 782 + channel: CommsChannel, 783 + ) -> Result<Option<bool>, MetastoreError> { 784 + Ok(self.load_user_by_did(did.as_str())?.map(|v| match channel { 785 + CommsChannel::Email => v.email_verified, 786 + CommsChannel::Discord => v.discord_verified, 787 + CommsChannel::Telegram => v.telegram_verified, 788 + CommsChannel::Signal => v.signal_verified, 789 + })) 790 + } 791 + 792 + pub fn admin_update_email(&self, did: &Did, email: &str) -> Result<u64, MetastoreError> { 793 + let user_hash = self.resolve_hash(did.as_str()); 794 + let val = match self.load_user(user_hash)? { 795 + Some(v) => v, 796 + None => return Ok(0), 797 + }; 798 + 799 + let mut batch = self.db.batch(); 800 + 801 + if let Some(old_email) = &val.email { 802 + batch.remove(&self.users, user_by_email_key(old_email).as_slice()); 803 + } 804 + 805 + batch.insert( 806 + &self.users, 807 + user_by_email_key(email).as_slice(), 808 + user_hash.raw().to_be_bytes(), 809 + ); 810 + 811 + let mut updated = val; 812 + updated.email = Some(email.to_owned()); 813 + 814 + batch.insert( 815 + &self.users, 816 + user_primary_key(user_hash).as_slice(), 817 + updated.serialize(), 818 + ); 819 + batch.commit().map_err(MetastoreError::Fjall)?; 820 + Ok(1) 821 + } 822 + 823 + pub fn admin_update_handle(&self, did: &Did, handle: &Handle) -> Result<u64, MetastoreError> { 824 + let user_hash = self.resolve_hash(did.as_str()); 825 + let val = match self.load_user(user_hash)? { 826 + Some(v) => v, 827 + None => return Ok(0), 828 + }; 829 + 830 + let old_handle = val.handle.clone(); 831 + let mut updated = val; 832 + updated.handle = handle.as_str().to_owned(); 833 + 834 + let mut batch = self.db.batch(); 835 + batch.remove(&self.users, user_by_handle_key(&old_handle).as_slice()); 836 + batch.insert( 837 + &self.users, 838 + user_by_handle_key(handle.as_str()).as_slice(), 839 + user_hash.raw().to_be_bytes(), 840 + ); 841 + batch.insert( 842 + &self.users, 843 + user_primary_key(user_hash).as_slice(), 844 + updated.serialize(), 845 + ); 846 + batch.commit().map_err(MetastoreError::Fjall)?; 847 + Ok(1) 848 + } 849 + 850 + pub fn admin_update_password( 851 + &self, 852 + did: &Did, 853 + password_hash: &str, 854 + ) -> Result<u64, MetastoreError> { 855 + let user_hash = self.resolve_hash(did.as_str()); 856 + match self.mutate_user(user_hash, |u| { 857 + u.password_hash = Some(password_hash.to_owned()); 858 + })? { 859 + true => Ok(1), 860 + false => Ok(0), 861 + } 862 + } 863 + 864 + pub fn get_notification_prefs( 865 + &self, 866 + did: &Did, 867 + ) -> Result<Option<NotificationPrefs>, MetastoreError> { 868 + self.load_user_by_did(did.as_str())? 869 + .map(|v| { 870 + Ok(NotificationPrefs { 871 + email: v.email.clone().unwrap_or_default(), 872 + preferred_channel: Self::comms_channel(&v), 873 + discord_id: v.discord_id.clone(), 874 + discord_username: v.discord_username.clone(), 875 + discord_verified: v.discord_verified, 876 + telegram_username: v.telegram_username.clone(), 877 + telegram_verified: v.telegram_verified, 878 + telegram_chat_id: v.telegram_chat_id, 879 + signal_username: v.signal_username.clone(), 880 + signal_verified: v.signal_verified, 881 + }) 882 + }) 883 + .transpose() 884 + } 885 + 886 + pub fn get_id_handle_email_by_did( 887 + &self, 888 + did: &Did, 889 + ) -> Result<Option<UserIdHandleEmail>, MetastoreError> { 890 + self.load_user_by_did(did.as_str())? 891 + .map(|v| { 892 + Ok(UserIdHandleEmail { 893 + id: v.id, 894 + handle: Handle::new(v.handle.clone()) 895 + .map_err(|_| MetastoreError::CorruptData("invalid user handle"))?, 896 + email: v.email.clone(), 897 + }) 898 + }) 899 + .transpose() 900 + } 901 + 902 + pub fn update_preferred_comms_channel( 903 + &self, 904 + did: &Did, 905 + channel: CommsChannel, 906 + ) -> Result<(), MetastoreError> { 907 + let user_hash = self.resolve_hash(did.as_str()); 908 + let channel_u8 = channel_to_u8(channel); 909 + self.mutate_user(user_hash, |u| { 910 + u.preferred_comms_channel = Some(channel_u8); 911 + })?; 912 + Ok(()) 913 + } 914 + 915 + pub fn clear_discord(&self, user_id: Uuid) -> Result<(), MetastoreError> { 916 + let user_hash = self.resolve_hash_from_uuid(user_id)?; 917 + let val = match self.load_user(user_hash)? { 918 + Some(v) => v, 919 + None => return Ok(()), 920 + }; 921 + 922 + let mut batch = self.db.batch(); 923 + 924 + if let Some(username) = &val.discord_username { 925 + batch.remove(&self.users, discord_lookup_key(username).as_slice()); 926 + } 927 + 928 + let mut updated = val; 929 + updated.discord_username = None; 930 + updated.discord_id = None; 931 + updated.discord_verified = false; 932 + 933 + batch.insert( 934 + &self.users, 935 + user_primary_key(user_hash).as_slice(), 936 + updated.serialize(), 937 + ); 938 + batch.commit().map_err(MetastoreError::Fjall) 939 + } 940 + 941 + pub fn clear_telegram(&self, user_id: Uuid) -> Result<(), MetastoreError> { 942 + let user_hash = self.resolve_hash_from_uuid(user_id)?; 943 + let val = match self.load_user(user_hash)? { 944 + Some(v) => v, 945 + None => return Ok(()), 946 + }; 947 + 948 + let mut batch = self.db.batch(); 949 + 950 + if let Some(username) = &val.telegram_username { 951 + batch.remove(&self.users, telegram_lookup_key(username).as_slice()); 952 + } 953 + 954 + let mut updated = val; 955 + updated.telegram_username = None; 956 + updated.telegram_chat_id = None; 957 + updated.telegram_verified = false; 958 + 959 + batch.insert( 960 + &self.users, 961 + user_primary_key(user_hash).as_slice(), 962 + updated.serialize(), 963 + ); 964 + batch.commit().map_err(MetastoreError::Fjall) 965 + } 966 + 967 + pub fn clear_signal(&self, user_id: Uuid) -> Result<(), MetastoreError> { 968 + self.mutate_user_by_uuid(user_id, |u| { 969 + u.signal_username = None; 970 + u.signal_verified = false; 971 + })?; 972 + Ok(()) 973 + } 974 + 975 + pub fn set_unverified_signal( 976 + &self, 977 + user_id: Uuid, 978 + signal_username: &str, 979 + ) -> Result<(), MetastoreError> { 980 + self.mutate_user_by_uuid(user_id, |u| { 981 + u.signal_username = Some(signal_username.to_owned()); 982 + u.signal_verified = false; 983 + })?; 984 + Ok(()) 985 + } 986 + 987 + pub fn set_unverified_telegram( 988 + &self, 989 + user_id: Uuid, 990 + telegram_username: &str, 991 + ) -> Result<(), MetastoreError> { 992 + let user_hash = self.resolve_hash_from_uuid(user_id)?; 993 + let val = match self.load_user(user_hash)? { 994 + Some(v) => v, 995 + None => return Ok(()), 996 + }; 997 + 998 + let mut batch = self.db.batch(); 999 + 1000 + if let Some(old_username) = &val.telegram_username { 1001 + batch.remove(&self.users, telegram_lookup_key(old_username).as_slice()); 1002 + } 1003 + 1004 + batch.insert( 1005 + &self.users, 1006 + telegram_lookup_key(telegram_username).as_slice(), 1007 + user_hash.raw().to_be_bytes(), 1008 + ); 1009 + 1010 + let mut updated = val; 1011 + updated.telegram_username = Some(telegram_username.to_owned()); 1012 + updated.telegram_verified = false; 1013 + updated.telegram_chat_id = None; 1014 + 1015 + batch.insert( 1016 + &self.users, 1017 + user_primary_key(user_hash).as_slice(), 1018 + updated.serialize(), 1019 + ); 1020 + batch.commit().map_err(MetastoreError::Fjall) 1021 + } 1022 + 1023 + pub fn store_telegram_chat_id( 1024 + &self, 1025 + telegram_username: &str, 1026 + chat_id: i64, 1027 + handle: Option<&str>, 1028 + ) -> Result<Option<Uuid>, MetastoreError> { 1029 + let idx_key = telegram_lookup_key(telegram_username); 1030 + let raw = match self 1031 + .users 1032 + .get(idx_key.as_slice()) 1033 + .map_err(MetastoreError::Fjall)? 1034 + { 1035 + Some(raw) if raw.len() >= 8 => raw, 1036 + _ => match handle { 1037 + Some(h) => { 1038 + let val = match self.load_by_handle(h)? { 1039 + Some(v) => v, 1040 + None => return Ok(None), 1041 + }; 1042 + let user_hash = UserHash::from_did(&val.did); 1043 + self.mutate_user(user_hash, |u| { 1044 + u.telegram_chat_id = Some(chat_id); 1045 + })?; 1046 + return Ok(Some(val.id)); 1047 + } 1048 + None => return Ok(None), 1049 + }, 1050 + }; 1051 + 1052 + let hash_raw = u64::from_be_bytes(raw[..8].try_into().unwrap()); 1053 + let user_hash = UserHash::from_raw(hash_raw); 1054 + let val = match self.load_user(user_hash)? { 1055 + Some(v) => v, 1056 + None => return Ok(None), 1057 + }; 1058 + let uid = val.id; 1059 + self.mutate_user(user_hash, |u| { 1060 + u.telegram_chat_id = Some(chat_id); 1061 + })?; 1062 + Ok(Some(uid)) 1063 + } 1064 + 1065 + pub fn get_telegram_chat_id(&self, user_id: Uuid) -> Result<Option<i64>, MetastoreError> { 1066 + let user_hash = self.resolve_hash_from_uuid(user_id)?; 1067 + Ok(self.load_user(user_hash)?.and_then(|v| v.telegram_chat_id)) 1068 + } 1069 + 1070 + pub fn set_unverified_discord( 1071 + &self, 1072 + user_id: Uuid, 1073 + discord_username: &str, 1074 + ) -> Result<(), MetastoreError> { 1075 + let user_hash = self.resolve_hash_from_uuid(user_id)?; 1076 + let val = match self.load_user(user_hash)? { 1077 + Some(v) => v, 1078 + None => return Ok(()), 1079 + }; 1080 + 1081 + let mut batch = self.db.batch(); 1082 + 1083 + if let Some(old_username) = &val.discord_username { 1084 + batch.remove(&self.users, discord_lookup_key(old_username).as_slice()); 1085 + } 1086 + 1087 + batch.insert( 1088 + &self.users, 1089 + discord_lookup_key(discord_username).as_slice(), 1090 + user_hash.raw().to_be_bytes(), 1091 + ); 1092 + 1093 + let mut updated = val; 1094 + updated.discord_username = Some(discord_username.to_owned()); 1095 + updated.discord_id = None; 1096 + updated.discord_verified = false; 1097 + 1098 + batch.insert( 1099 + &self.users, 1100 + user_primary_key(user_hash).as_slice(), 1101 + updated.serialize(), 1102 + ); 1103 + batch.commit().map_err(MetastoreError::Fjall) 1104 + } 1105 + 1106 + pub fn store_discord_user_id( 1107 + &self, 1108 + discord_username: &str, 1109 + discord_id: &str, 1110 + handle: Option<&str>, 1111 + ) -> Result<Option<Uuid>, MetastoreError> { 1112 + let idx_key = discord_lookup_key(discord_username); 1113 + let raw = match self 1114 + .users 1115 + .get(idx_key.as_slice()) 1116 + .map_err(MetastoreError::Fjall)? 1117 + { 1118 + Some(raw) if raw.len() >= 8 => raw, 1119 + _ => match handle { 1120 + Some(h) => { 1121 + let val = match self.load_by_handle(h)? { 1122 + Some(v) => v, 1123 + None => return Ok(None), 1124 + }; 1125 + let user_hash = UserHash::from_did(&val.did); 1126 + self.mutate_user(user_hash, |u| { 1127 + u.discord_id = Some(discord_id.to_owned()); 1128 + })?; 1129 + return Ok(Some(val.id)); 1130 + } 1131 + None => return Ok(None), 1132 + }, 1133 + }; 1134 + 1135 + let hash_raw = u64::from_be_bytes(raw[..8].try_into().unwrap()); 1136 + let user_hash = UserHash::from_raw(hash_raw); 1137 + let val = match self.load_user(user_hash)? { 1138 + Some(v) => v, 1139 + None => return Ok(None), 1140 + }; 1141 + let uid = val.id; 1142 + self.mutate_user(user_hash, |u| { 1143 + u.discord_id = Some(discord_id.to_owned()); 1144 + })?; 1145 + Ok(Some(uid)) 1146 + } 1147 + 1148 + pub fn get_verification_info( 1149 + &self, 1150 + did: &Did, 1151 + ) -> Result<Option<UserVerificationInfo>, MetastoreError> { 1152 + self.load_user_by_did(did.as_str())? 1153 + .map(|v| { 1154 + Ok(UserVerificationInfo { 1155 + id: v.id, 1156 + handle: Handle::new(v.handle.clone()) 1157 + .map_err(|_| MetastoreError::CorruptData("invalid user handle"))?, 1158 + email: v.email.clone(), 1159 + channel_verification: Self::channel_verification(&v), 1160 + }) 1161 + }) 1162 + .transpose() 1163 + } 1164 + 1165 + pub fn verify_email_channel(&self, user_id: Uuid, email: &str) -> Result<bool, MetastoreError> { 1166 + let user_hash = self.resolve_hash_from_uuid(user_id)?; 1167 + let val = match self.load_user(user_hash)? { 1168 + Some(v) => v, 1169 + None => return Ok(false), 1170 + }; 1171 + 1172 + match val.email.as_deref() == Some(email) { 1173 + true => { 1174 + self.mutate_user(user_hash, |u| { 1175 + u.email_verified = true; 1176 + })?; 1177 + Ok(true) 1178 + } 1179 + false => Ok(false), 1180 + } 1181 + } 1182 + 1183 + pub fn verify_discord_channel( 1184 + &self, 1185 + user_id: Uuid, 1186 + discord_id: &str, 1187 + ) -> Result<(), MetastoreError> { 1188 + self.mutate_user_by_uuid(user_id, |u| { 1189 + u.discord_id = Some(discord_id.to_owned()); 1190 + u.discord_verified = true; 1191 + })?; 1192 + Ok(()) 1193 + } 1194 + 1195 + pub fn verify_telegram_channel( 1196 + &self, 1197 + user_id: Uuid, 1198 + telegram_username: &str, 1199 + ) -> Result<(), MetastoreError> { 1200 + self.mutate_user_by_uuid(user_id, |u| { 1201 + match u.telegram_username.as_deref() == Some(telegram_username) { 1202 + true => u.telegram_verified = true, 1203 + false => {} 1204 + } 1205 + })?; 1206 + Ok(()) 1207 + } 1208 + 1209 + pub fn verify_signal_channel( 1210 + &self, 1211 + user_id: Uuid, 1212 + signal_username: &str, 1213 + ) -> Result<(), MetastoreError> { 1214 + self.mutate_user_by_uuid(user_id, |u| { 1215 + match u.signal_username.as_deref() == Some(signal_username) { 1216 + true => u.signal_verified = true, 1217 + false => {} 1218 + } 1219 + })?; 1220 + Ok(()) 1221 + } 1222 + 1223 + pub fn set_email_verified_flag(&self, user_id: Uuid) -> Result<(), MetastoreError> { 1224 + self.mutate_user_by_uuid(user_id, |u| { 1225 + u.email_verified = true; 1226 + })?; 1227 + Ok(()) 1228 + } 1229 + 1230 + pub fn set_discord_verified_flag(&self, user_id: Uuid) -> Result<(), MetastoreError> { 1231 + self.mutate_user_by_uuid(user_id, |u| { 1232 + u.discord_verified = true; 1233 + })?; 1234 + Ok(()) 1235 + } 1236 + 1237 + pub fn set_telegram_verified_flag(&self, user_id: Uuid) -> Result<(), MetastoreError> { 1238 + self.mutate_user_by_uuid(user_id, |u| { 1239 + u.telegram_verified = true; 1240 + })?; 1241 + Ok(()) 1242 + } 1243 + 1244 + pub fn set_signal_verified_flag(&self, user_id: Uuid) -> Result<(), MetastoreError> { 1245 + self.mutate_user_by_uuid(user_id, |u| { 1246 + u.signal_verified = true; 1247 + })?; 1248 + Ok(()) 1249 + } 1250 + 1251 + pub fn has_totp_enabled(&self, did: &Did) -> Result<bool, MetastoreError> { 1252 + Ok(self 1253 + .load_user_by_did(did.as_str())? 1254 + .map_or(false, |v| v.totp_enabled)) 1255 + } 1256 + 1257 + pub fn has_passkeys(&self, did: &Did) -> Result<bool, MetastoreError> { 1258 + let user_hash = self.resolve_hash(did.as_str()); 1259 + let prefix = passkey_prefix(user_hash); 1260 + match self.users.prefix(prefix.as_slice()).next() { 1261 + Some(guard) => { 1262 + guard.into_inner().map_err(MetastoreError::Fjall)?; 1263 + Ok(true) 1264 + } 1265 + None => Ok(false), 1266 + } 1267 + } 1268 + 1269 + pub fn get_password_hash_by_did(&self, did: &Did) -> Result<Option<String>, MetastoreError> { 1270 + Ok(self 1271 + .load_user_by_did(did.as_str())? 1272 + .and_then(|v| v.password_hash.clone())) 1273 + } 1274 + 1275 + pub fn get_passkeys_for_user(&self, did: &Did) -> Result<Vec<StoredPasskey>, MetastoreError> { 1276 + let user_hash = self.resolve_hash(did.as_str()); 1277 + let prefix = passkey_prefix(user_hash); 1278 + 1279 + self.users 1280 + .prefix(prefix.as_slice()) 1281 + .try_fold(Vec::new(), |mut acc, guard| { 1282 + let (_, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 1283 + let pv = PasskeyValue::deserialize(&val_bytes) 1284 + .ok_or(MetastoreError::CorruptData("corrupt passkey value"))?; 1285 + acc.push(Self::to_stored_passkey(&pv)?); 1286 + Ok::<_, MetastoreError>(acc) 1287 + }) 1288 + } 1289 + 1290 + pub fn get_passkey_by_credential_id( 1291 + &self, 1292 + credential_id: &[u8], 1293 + ) -> Result<Option<StoredPasskey>, MetastoreError> { 1294 + let idx_key = passkey_by_cred_key(credential_id); 1295 + let idx: Option<PasskeyIndexValue> = point_lookup( 1296 + &self.users, 1297 + idx_key.as_slice(), 1298 + PasskeyIndexValue::deserialize, 1299 + "corrupt passkey index", 1300 + )?; 1301 + 1302 + match idx { 1303 + Some(iv) => { 1304 + let pk_key = passkey_key(UserHash::from_raw(iv.user_hash), iv.passkey_id); 1305 + let pv: Option<PasskeyValue> = point_lookup( 1306 + &self.users, 1307 + pk_key.as_slice(), 1308 + PasskeyValue::deserialize, 1309 + "corrupt passkey value", 1310 + )?; 1311 + pv.map(|v| Self::to_stored_passkey(&v)).transpose() 1312 + } 1313 + None => Ok(None), 1314 + } 1315 + } 1316 + 1317 + pub fn save_passkey( 1318 + &self, 1319 + did: &Did, 1320 + credential_id: &[u8], 1321 + public_key: &[u8], 1322 + friendly_name: Option<&str>, 1323 + ) -> Result<Uuid, MetastoreError> { 1324 + let user_hash = self.resolve_hash(did.as_str()); 1325 + let id = Uuid::new_v4(); 1326 + let now_ms = Utc::now().timestamp_millis(); 1327 + 1328 + let value = PasskeyValue { 1329 + id, 1330 + did: did.to_string(), 1331 + credential_id: credential_id.to_vec(), 1332 + public_key: public_key.to_vec(), 1333 + sign_count: 0, 1334 + created_at_ms: now_ms, 1335 + last_used_at_ms: None, 1336 + friendly_name: friendly_name.map(str::to_owned), 1337 + aaguid: None, 1338 + transports: None, 1339 + }; 1340 + 1341 + let index = PasskeyIndexValue { 1342 + user_hash: user_hash.raw(), 1343 + passkey_id: id, 1344 + }; 1345 + 1346 + let mut batch = self.db.batch(); 1347 + batch.insert( 1348 + &self.users, 1349 + passkey_key(user_hash, id).as_slice(), 1350 + value.serialize(), 1351 + ); 1352 + batch.insert( 1353 + &self.users, 1354 + passkey_by_cred_key(credential_id).as_slice(), 1355 + index.serialize(), 1356 + ); 1357 + batch.commit().map_err(MetastoreError::Fjall)?; 1358 + 1359 + Ok(id) 1360 + } 1361 + 1362 + pub fn update_passkey_counter( 1363 + &self, 1364 + credential_id: &[u8], 1365 + new_counter: i32, 1366 + ) -> Result<bool, MetastoreError> { 1367 + let idx_key = passkey_by_cred_key(credential_id); 1368 + let idx: Option<PasskeyIndexValue> = point_lookup( 1369 + &self.users, 1370 + idx_key.as_slice(), 1371 + PasskeyIndexValue::deserialize, 1372 + "corrupt passkey index", 1373 + )?; 1374 + 1375 + let iv = match idx { 1376 + Some(iv) => iv, 1377 + None => return Ok(false), 1378 + }; 1379 + 1380 + let pk_key = passkey_key(UserHash::from_raw(iv.user_hash), iv.passkey_id); 1381 + let pv: Option<PasskeyValue> = point_lookup( 1382 + &self.users, 1383 + pk_key.as_slice(), 1384 + PasskeyValue::deserialize, 1385 + "corrupt passkey value", 1386 + )?; 1387 + 1388 + match pv { 1389 + Some(mut val) => { 1390 + val.sign_count = new_counter; 1391 + val.last_used_at_ms = Some(Utc::now().timestamp_millis()); 1392 + self.users 1393 + .insert(pk_key.as_slice(), val.serialize()) 1394 + .map_err(MetastoreError::Fjall)?; 1395 + Ok(true) 1396 + } 1397 + None => Ok(false), 1398 + } 1399 + } 1400 + 1401 + pub fn delete_passkey(&self, id: Uuid, did: &Did) -> Result<bool, MetastoreError> { 1402 + let user_hash = self.resolve_hash(did.as_str()); 1403 + let pk_key = passkey_key(user_hash, id); 1404 + 1405 + let pv: Option<PasskeyValue> = point_lookup( 1406 + &self.users, 1407 + pk_key.as_slice(), 1408 + PasskeyValue::deserialize, 1409 + "corrupt passkey value", 1410 + )?; 1411 + 1412 + match pv { 1413 + Some(val) => { 1414 + let mut batch = self.db.batch(); 1415 + batch.remove(&self.users, pk_key.as_slice()); 1416 + batch.remove( 1417 + &self.users, 1418 + passkey_by_cred_key(&val.credential_id).as_slice(), 1419 + ); 1420 + batch.commit().map_err(MetastoreError::Fjall)?; 1421 + Ok(true) 1422 + } 1423 + None => Ok(false), 1424 + } 1425 + } 1426 + 1427 + pub fn update_passkey_name( 1428 + &self, 1429 + id: Uuid, 1430 + did: &Did, 1431 + name: &str, 1432 + ) -> Result<bool, MetastoreError> { 1433 + let user_hash = self.resolve_hash(did.as_str()); 1434 + let pk_key = passkey_key(user_hash, id); 1435 + 1436 + let pv: Option<PasskeyValue> = point_lookup( 1437 + &self.users, 1438 + pk_key.as_slice(), 1439 + PasskeyValue::deserialize, 1440 + "corrupt passkey value", 1441 + )?; 1442 + 1443 + match pv { 1444 + Some(mut val) => { 1445 + val.friendly_name = Some(name.to_owned()); 1446 + self.users 1447 + .insert(pk_key.as_slice(), val.serialize()) 1448 + .map_err(MetastoreError::Fjall)?; 1449 + Ok(true) 1450 + } 1451 + None => Ok(false), 1452 + } 1453 + } 1454 + 1455 + pub fn save_webauthn_challenge( 1456 + &self, 1457 + did: &Did, 1458 + challenge_type: WebauthnChallengeType, 1459 + state_json: &str, 1460 + ) -> Result<Uuid, MetastoreError> { 1461 + let user_hash = self.resolve_hash(did.as_str()); 1462 + let type_u8 = challenge_type_to_u8(challenge_type); 1463 + let id = Uuid::new_v4(); 1464 + let now_ms = Utc::now().timestamp_millis(); 1465 + 1466 + let value = WebauthnChallengeValue { 1467 + id, 1468 + challenge_type: type_u8, 1469 + state_json: state_json.to_owned(), 1470 + created_at_ms: now_ms, 1471 + }; 1472 + 1473 + let key = webauthn_challenge_key(user_hash, type_u8); 1474 + self.auth 1475 + .insert(key.as_slice(), value.serialize_with_ttl()) 1476 + .map_err(MetastoreError::Fjall)?; 1477 + 1478 + Ok(id) 1479 + } 1480 + 1481 + pub fn load_webauthn_challenge( 1482 + &self, 1483 + did: &Did, 1484 + challenge_type: WebauthnChallengeType, 1485 + ) -> Result<Option<String>, MetastoreError> { 1486 + let user_hash = self.resolve_hash(did.as_str()); 1487 + let type_u8 = challenge_type_to_u8(challenge_type); 1488 + let key = webauthn_challenge_key(user_hash, type_u8); 1489 + 1490 + let val: Option<WebauthnChallengeValue> = point_lookup( 1491 + &self.auth, 1492 + key.as_slice(), 1493 + WebauthnChallengeValue::deserialize, 1494 + "corrupt webauthn challenge", 1495 + )?; 1496 + 1497 + Ok(val.map(|v| v.state_json)) 1498 + } 1499 + 1500 + pub fn delete_webauthn_challenge( 1501 + &self, 1502 + did: &Did, 1503 + challenge_type: WebauthnChallengeType, 1504 + ) -> Result<(), MetastoreError> { 1505 + let user_hash = self.resolve_hash(did.as_str()); 1506 + let type_u8 = challenge_type_to_u8(challenge_type); 1507 + let key = webauthn_challenge_key(user_hash, type_u8); 1508 + self.auth 1509 + .remove(key.as_slice()) 1510 + .map_err(MetastoreError::Fjall) 1511 + } 1512 + 1513 + pub fn get_totp_record(&self, did: &Did) -> Result<Option<TotpRecord>, MetastoreError> { 1514 + let user_hash = self.resolve_hash(did.as_str()); 1515 + let key = totp_key(user_hash); 1516 + let val: Option<TotpValue> = point_lookup( 1517 + &self.users, 1518 + key.as_slice(), 1519 + TotpValue::deserialize, 1520 + "corrupt totp value", 1521 + )?; 1522 + 1523 + Ok(val.map(|v| TotpRecord { 1524 + secret_encrypted: v.secret_encrypted, 1525 + encryption_version: v.encryption_version, 1526 + verified: v.verified, 1527 + })) 1528 + } 1529 + 1530 + pub fn get_totp_record_state( 1531 + &self, 1532 + did: &Did, 1533 + ) -> Result<Option<TotpRecordState>, MetastoreError> { 1534 + self.get_totp_record(did)? 1535 + .map(|r| Ok(TotpRecordState::from(r))) 1536 + .transpose() 1537 + } 1538 + 1539 + pub fn upsert_totp_secret( 1540 + &self, 1541 + did: &Did, 1542 + secret_encrypted: &[u8], 1543 + encryption_version: i32, 1544 + ) -> Result<(), MetastoreError> { 1545 + let user_hash = self.resolve_hash(did.as_str()); 1546 + let key = totp_key(user_hash); 1547 + 1548 + let value = TotpValue { 1549 + secret_encrypted: secret_encrypted.to_vec(), 1550 + encryption_version, 1551 + verified: false, 1552 + last_used_at_ms: None, 1553 + }; 1554 + 1555 + self.users 1556 + .insert(key.as_slice(), value.serialize()) 1557 + .map_err(MetastoreError::Fjall) 1558 + } 1559 + 1560 + pub fn set_totp_verified(&self, did: &Did) -> Result<(), MetastoreError> { 1561 + let user_hash = self.resolve_hash(did.as_str()); 1562 + let key = totp_key(user_hash); 1563 + 1564 + let mut val = match point_lookup( 1565 + &self.users, 1566 + key.as_slice(), 1567 + TotpValue::deserialize, 1568 + "corrupt totp value", 1569 + )? { 1570 + Some(v) => v, 1571 + None => return Ok(()), 1572 + }; 1573 + 1574 + val.verified = true; 1575 + 1576 + let mut batch = self.db.batch(); 1577 + batch.insert(&self.users, key.as_slice(), val.serialize()); 1578 + 1579 + let mut user = match self.load_user(user_hash)? { 1580 + Some(u) => u, 1581 + None => { 1582 + batch.commit().map_err(MetastoreError::Fjall)?; 1583 + return Ok(()); 1584 + } 1585 + }; 1586 + user.totp_enabled = true; 1587 + batch.insert( 1588 + &self.users, 1589 + user_primary_key(user_hash).as_slice(), 1590 + user.serialize(), 1591 + ); 1592 + batch.commit().map_err(MetastoreError::Fjall) 1593 + } 1594 + 1595 + pub fn update_totp_last_used(&self, did: &Did) -> Result<(), MetastoreError> { 1596 + let user_hash = self.resolve_hash(did.as_str()); 1597 + let key = totp_key(user_hash); 1598 + let mut val: TotpValue = match point_lookup( 1599 + &self.users, 1600 + key.as_slice(), 1601 + TotpValue::deserialize, 1602 + "corrupt totp value", 1603 + )? { 1604 + Some(v) => v, 1605 + None => return Ok(()), 1606 + }; 1607 + 1608 + val.last_used_at_ms = Some(Utc::now().timestamp_millis()); 1609 + self.users 1610 + .insert(key.as_slice(), val.serialize()) 1611 + .map_err(MetastoreError::Fjall) 1612 + } 1613 + 1614 + pub fn delete_totp(&self, did: &Did) -> Result<(), MetastoreError> { 1615 + let user_hash = self.resolve_hash(did.as_str()); 1616 + let key = totp_key(user_hash); 1617 + 1618 + let mut batch = self.db.batch(); 1619 + batch.remove(&self.users, key.as_slice()); 1620 + 1621 + if let Some(mut user) = self.load_user(user_hash)? { 1622 + user.totp_enabled = false; 1623 + batch.insert( 1624 + &self.users, 1625 + user_primary_key(user_hash).as_slice(), 1626 + user.serialize(), 1627 + ); 1628 + } 1629 + 1630 + batch.commit().map_err(MetastoreError::Fjall) 1631 + } 1632 + 1633 + pub fn get_unused_backup_codes( 1634 + &self, 1635 + did: &Did, 1636 + ) -> Result<Vec<StoredBackupCode>, MetastoreError> { 1637 + let user_hash = self.resolve_hash(did.as_str()); 1638 + let prefix = backup_code_prefix(user_hash); 1639 + 1640 + self.users 1641 + .prefix(prefix.as_slice()) 1642 + .try_fold(Vec::new(), |mut acc, guard| { 1643 + let (_, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 1644 + let val = BackupCodeValue::deserialize(&val_bytes) 1645 + .ok_or(MetastoreError::CorruptData("corrupt backup code"))?; 1646 + match val.used { 1647 + false => { 1648 + acc.push(StoredBackupCode { 1649 + id: val.id, 1650 + code_hash: val.code_hash, 1651 + }); 1652 + Ok(acc) 1653 + } 1654 + true => Ok(acc), 1655 + } 1656 + }) 1657 + } 1658 + 1659 + pub fn mark_backup_code_used(&self, code_id: Uuid) -> Result<bool, MetastoreError> { 1660 + let prefix = user_primary_prefix(); 1661 + let all_user_hashes: Vec<UserHash> = self 1662 + .users 1663 + .prefix(prefix.as_slice()) 1664 + .filter_map(|guard| { 1665 + let (key_bytes, _) = guard.into_inner().ok()?; 1666 + (key_bytes.len() >= 9).then(|| { 1667 + let hash_raw = u64::from_be_bytes(key_bytes[1..9].try_into().unwrap()); 1668 + UserHash::from_raw(hash_raw) 1669 + }) 1670 + }) 1671 + .collect(); 1672 + 1673 + all_user_hashes 1674 + .iter() 1675 + .try_fold(false, |found, user_hash| match found { 1676 + true => Ok(true), 1677 + false => { 1678 + let key = backup_code_key(*user_hash, code_id); 1679 + let val: Option<BackupCodeValue> = point_lookup( 1680 + &self.users, 1681 + key.as_slice(), 1682 + BackupCodeValue::deserialize, 1683 + "corrupt backup code", 1684 + )?; 1685 + match val { 1686 + Some(mut bc) => { 1687 + bc.used = true; 1688 + self.users 1689 + .insert(key.as_slice(), bc.serialize()) 1690 + .map_err(MetastoreError::Fjall)?; 1691 + Ok(true) 1692 + } 1693 + None => Ok(false), 1694 + } 1695 + } 1696 + }) 1697 + } 1698 + 1699 + pub fn count_unused_backup_codes(&self, did: &Did) -> Result<i64, MetastoreError> { 1700 + let user_hash = self.resolve_hash(did.as_str()); 1701 + let prefix = backup_code_prefix(user_hash); 1702 + 1703 + self.users 1704 + .prefix(prefix.as_slice()) 1705 + .try_fold(0i64, |acc, guard| { 1706 + let (_, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 1707 + let val = BackupCodeValue::deserialize(&val_bytes) 1708 + .ok_or(MetastoreError::CorruptData("corrupt backup code"))?; 1709 + match val.used { 1710 + false => Ok(acc.saturating_add(1)), 1711 + true => Ok(acc), 1712 + } 1713 + }) 1714 + } 1715 + 1716 + pub fn delete_backup_codes(&self, did: &Did) -> Result<u64, MetastoreError> { 1717 + let user_hash = self.resolve_hash(did.as_str()); 1718 + let prefix = backup_code_prefix(user_hash); 1719 + 1720 + let keys: Vec<Vec<u8>> = self 1721 + .users 1722 + .prefix(prefix.as_slice()) 1723 + .filter_map(|guard| { 1724 + let (key_bytes, _) = guard.into_inner().ok()?; 1725 + Some(key_bytes.to_vec()) 1726 + }) 1727 + .collect(); 1728 + 1729 + let count = u64::try_from(keys.len()).unwrap_or(u64::MAX); 1730 + match count { 1731 + 0 => Ok(0), 1732 + _ => { 1733 + let mut batch = self.db.batch(); 1734 + keys.iter().for_each(|key| { 1735 + batch.remove(&self.users, key); 1736 + }); 1737 + batch.commit().map_err(MetastoreError::Fjall)?; 1738 + Ok(count) 1739 + } 1740 + } 1741 + } 1742 + 1743 + fn insert_backup_codes_batch( 1744 + &self, 1745 + batch: &mut fjall::OwnedWriteBatch, 1746 + user_hash: UserHash, 1747 + code_hashes: &[String], 1748 + ) { 1749 + code_hashes.iter().for_each(|hash| { 1750 + let id = Uuid::new_v4(); 1751 + let value = BackupCodeValue { 1752 + id, 1753 + code_hash: hash.clone(), 1754 + used: false, 1755 + }; 1756 + batch.insert( 1757 + &self.users, 1758 + backup_code_key(user_hash, id).as_slice(), 1759 + value.serialize(), 1760 + ); 1761 + }); 1762 + } 1763 + 1764 + pub fn insert_backup_codes( 1765 + &self, 1766 + did: &Did, 1767 + code_hashes: &[String], 1768 + ) -> Result<(), MetastoreError> { 1769 + let user_hash = self.resolve_hash(did.as_str()); 1770 + let mut batch = self.db.batch(); 1771 + self.insert_backup_codes_batch(&mut batch, user_hash, code_hashes); 1772 + batch.commit().map_err(MetastoreError::Fjall) 1773 + } 1774 + 1775 + pub fn enable_totp_with_backup_codes( 1776 + &self, 1777 + did: &Did, 1778 + code_hashes: &[String], 1779 + ) -> Result<(), MetastoreError> { 1780 + let user_hash = self.resolve_hash(did.as_str()); 1781 + let mut batch = self.db.batch(); 1782 + 1783 + if let Some(mut user) = self.load_user(user_hash)? { 1784 + user.totp_enabled = true; 1785 + user.two_factor_enabled = true; 1786 + batch.insert( 1787 + &self.users, 1788 + user_primary_key(user_hash).as_slice(), 1789 + user.serialize(), 1790 + ); 1791 + } 1792 + 1793 + self.insert_backup_codes_batch(&mut batch, user_hash, code_hashes); 1794 + batch.commit().map_err(MetastoreError::Fjall) 1795 + } 1796 + 1797 + pub fn delete_totp_and_backup_codes(&self, did: &Did) -> Result<(), MetastoreError> { 1798 + let user_hash = self.resolve_hash(did.as_str()); 1799 + let mut batch = self.db.batch(); 1800 + 1801 + batch.remove(&self.users, totp_key(user_hash).as_slice()); 1802 + delete_all_by_prefix( 1803 + &self.users, 1804 + &mut batch, 1805 + backup_code_prefix(user_hash).as_slice(), 1806 + )?; 1807 + 1808 + if let Some(mut user) = self.load_user(user_hash)? { 1809 + user.totp_enabled = false; 1810 + batch.insert( 1811 + &self.users, 1812 + user_primary_key(user_hash).as_slice(), 1813 + user.serialize(), 1814 + ); 1815 + } 1816 + 1817 + batch.commit().map_err(MetastoreError::Fjall) 1818 + } 1819 + 1820 + pub fn replace_backup_codes( 1821 + &self, 1822 + did: &Did, 1823 + code_hashes: &[String], 1824 + ) -> Result<(), MetastoreError> { 1825 + let user_hash = self.resolve_hash(did.as_str()); 1826 + let mut batch = self.db.batch(); 1827 + delete_all_by_prefix( 1828 + &self.users, 1829 + &mut batch, 1830 + backup_code_prefix(user_hash).as_slice(), 1831 + )?; 1832 + self.insert_backup_codes_batch(&mut batch, user_hash, code_hashes); 1833 + batch.commit().map_err(MetastoreError::Fjall) 1834 + } 1835 + 1836 + pub fn get_session_info_by_did( 1837 + &self, 1838 + did: &Did, 1839 + ) -> Result<Option<UserSessionInfo>, MetastoreError> { 1840 + self.load_user_by_did(did.as_str())? 1841 + .map(|v| { 1842 + Ok(UserSessionInfo { 1843 + handle: Handle::new(v.handle.clone()) 1844 + .map_err(|_| MetastoreError::CorruptData("invalid user handle"))?, 1845 + email: v.email.clone(), 1846 + is_admin: v.is_admin, 1847 + deactivated_at: v 1848 + .deactivated_at_ms 1849 + .and_then(DateTime::from_timestamp_millis), 1850 + takedown_ref: v.takedown_ref.clone(), 1851 + preferred_locale: v.preferred_locale.clone(), 1852 + preferred_comms_channel: Self::comms_channel(&v), 1853 + channel_verification: Self::channel_verification(&v), 1854 + migrated_to_pds: v.migrated_to_pds.clone(), 1855 + migrated_at: v.migrated_at_ms.and_then(DateTime::from_timestamp_millis), 1856 + totp_enabled: v.totp_enabled, 1857 + email_2fa_enabled: v.email_2fa_enabled, 1858 + }) 1859 + }) 1860 + .transpose() 1861 + } 1862 + 1863 + pub fn get_legacy_login_pref( 1864 + &self, 1865 + did: &Did, 1866 + ) -> Result<Option<UserLegacyLoginPref>, MetastoreError> { 1867 + Ok(self 1868 + .load_user_by_did(did.as_str())? 1869 + .map(|v| UserLegacyLoginPref { 1870 + allow_legacy_login: v.allow_legacy_login, 1871 + has_mfa: v.two_factor_enabled || v.totp_enabled, 1872 + })) 1873 + } 1874 + 1875 + pub fn update_legacy_login(&self, did: &Did, allow: bool) -> Result<bool, MetastoreError> { 1876 + let user_hash = self.resolve_hash(did.as_str()); 1877 + self.mutate_user(user_hash, |u| { 1878 + u.allow_legacy_login = allow; 1879 + }) 1880 + } 1881 + 1882 + pub fn update_locale(&self, did: &Did, locale: &str) -> Result<bool, MetastoreError> { 1883 + let user_hash = self.resolve_hash(did.as_str()); 1884 + self.mutate_user(user_hash, |u| { 1885 + u.preferred_locale = Some(locale.to_owned()); 1886 + }) 1887 + } 1888 + 1889 + pub fn get_login_full_by_identifier( 1890 + &self, 1891 + identifier: &str, 1892 + ) -> Result<Option<UserLoginFull>, MetastoreError> { 1893 + self.load_by_identifier(identifier)? 1894 + .map(|v| { 1895 + Ok(UserLoginFull { 1896 + id: v.id, 1897 + did: Did::new(v.did.clone()) 1898 + .map_err(|_| MetastoreError::CorruptData("invalid user did"))?, 1899 + handle: Handle::new(v.handle.clone()) 1900 + .map_err(|_| MetastoreError::CorruptData("invalid user handle"))?, 1901 + password_hash: v.password_hash.clone(), 1902 + email: v.email.clone(), 1903 + deactivated_at: v 1904 + .deactivated_at_ms 1905 + .and_then(DateTime::from_timestamp_millis), 1906 + takedown_ref: v.takedown_ref.clone(), 1907 + channel_verification: Self::channel_verification(&v), 1908 + allow_legacy_login: v.allow_legacy_login, 1909 + migrated_to_pds: v.migrated_to_pds.clone(), 1910 + preferred_comms_channel: Self::comms_channel(&v), 1911 + key_bytes: v.key_bytes.clone(), 1912 + encryption_version: Some(v.encryption_version), 1913 + totp_enabled: v.totp_enabled, 1914 + email_2fa_enabled: v.email_2fa_enabled, 1915 + }) 1916 + }) 1917 + .transpose() 1918 + } 1919 + 1920 + pub fn get_confirm_signup_by_did( 1921 + &self, 1922 + did: &Did, 1923 + ) -> Result<Option<UserConfirmSignup>, MetastoreError> { 1924 + self.load_user_by_did(did.as_str())? 1925 + .map(|v| { 1926 + Ok(UserConfirmSignup { 1927 + id: v.id, 1928 + did: Did::new(v.did.clone()) 1929 + .map_err(|_| MetastoreError::CorruptData("invalid user did"))?, 1930 + handle: Handle::new(v.handle.clone()) 1931 + .map_err(|_| MetastoreError::CorruptData("invalid user handle"))?, 1932 + email: v.email.clone(), 1933 + channel: Self::comms_channel(&v), 1934 + discord_username: v.discord_username.clone(), 1935 + telegram_username: v.telegram_username.clone(), 1936 + signal_username: v.signal_username.clone(), 1937 + key_bytes: v.key_bytes.clone(), 1938 + encryption_version: Some(v.encryption_version), 1939 + }) 1940 + }) 1941 + .transpose() 1942 + } 1943 + 1944 + pub fn get_resend_verification_by_did( 1945 + &self, 1946 + did: &Did, 1947 + ) -> Result<Option<UserResendVerification>, MetastoreError> { 1948 + self.load_user_by_did(did.as_str())? 1949 + .map(|v| { 1950 + Ok(UserResendVerification { 1951 + id: v.id, 1952 + handle: Handle::new(v.handle.clone()) 1953 + .map_err(|_| MetastoreError::CorruptData("invalid user handle"))?, 1954 + email: v.email.clone(), 1955 + channel: Self::comms_channel(&v), 1956 + discord_username: v.discord_username.clone(), 1957 + telegram_username: v.telegram_username.clone(), 1958 + signal_username: v.signal_username.clone(), 1959 + channel_verification: Self::channel_verification(&v), 1960 + }) 1961 + }) 1962 + .transpose() 1963 + } 1964 + 1965 + pub fn set_channel_verified( 1966 + &self, 1967 + did: &Did, 1968 + channel: CommsChannel, 1969 + ) -> Result<(), MetastoreError> { 1970 + let user_hash = self.resolve_hash(did.as_str()); 1971 + self.mutate_user(user_hash, |u| match channel { 1972 + CommsChannel::Email => u.email_verified = true, 1973 + CommsChannel::Discord => u.discord_verified = true, 1974 + CommsChannel::Telegram => u.telegram_verified = true, 1975 + CommsChannel::Signal => u.signal_verified = true, 1976 + })?; 1977 + Ok(()) 1978 + } 1979 + 1980 + pub fn get_id_by_email_or_handle( 1981 + &self, 1982 + email: &str, 1983 + handle: &str, 1984 + ) -> Result<Option<Uuid>, MetastoreError> { 1985 + match self.load_by_email(email)? { 1986 + Some(v) => Ok(Some(v.id)), 1987 + None => Ok(self.load_by_handle(handle)?.map(|v| v.id)), 1988 + } 1989 + } 1990 + 1991 + pub fn count_accounts_by_email(&self, email: &str) -> Result<i64, MetastoreError> { 1992 + match self.load_by_email(email)? { 1993 + Some(_) => Ok(1), 1994 + None => Ok(0), 1995 + } 1996 + } 1997 + 1998 + pub fn get_handles_by_email(&self, email: &str) -> Result<Vec<Handle>, MetastoreError> { 1999 + match self.load_by_email(email)? { 2000 + Some(v) => Handle::new(v.handle.clone()) 2001 + .map(|h| vec![h]) 2002 + .map_err(|_| MetastoreError::CorruptData("invalid user handle")), 2003 + None => Ok(Vec::new()), 2004 + } 2005 + } 2006 + 2007 + pub fn set_password_reset_code( 2008 + &self, 2009 + user_id: Uuid, 2010 + code: &str, 2011 + expires_at: DateTime<Utc>, 2012 + ) -> Result<(), MetastoreError> { 2013 + let user_hash = self.resolve_hash_from_uuid(user_id)?; 2014 + let user = self 2015 + .load_user(user_hash)? 2016 + .ok_or(MetastoreError::InvalidInput("user not found"))?; 2017 + 2018 + let value = ResetCodeValue { 2019 + user_hash: user_hash.raw(), 2020 + user_id, 2021 + preferred_comms_channel: user.preferred_comms_channel, 2022 + code: code.to_owned(), 2023 + expires_at_ms: expires_at.timestamp_millis(), 2024 + }; 2025 + 2026 + let key = reset_code_key(code); 2027 + self.auth 2028 + .insert(key.as_slice(), value.serialize_with_ttl()) 2029 + .map_err(MetastoreError::Fjall) 2030 + } 2031 + 2032 + pub fn get_user_by_reset_code( 2033 + &self, 2034 + code: &str, 2035 + ) -> Result<Option<UserResetCodeInfo>, MetastoreError> { 2036 + let key = reset_code_key(code); 2037 + let val: Option<ResetCodeValue> = point_lookup( 2038 + &self.auth, 2039 + key.as_slice(), 2040 + ResetCodeValue::deserialize, 2041 + "corrupt reset code", 2042 + )?; 2043 + 2044 + match val { 2045 + Some(rc) => { 2046 + let now_ms = Utc::now().timestamp_millis(); 2047 + match rc.expires_at_ms > now_ms { 2048 + true => { 2049 + let user_hash = UserHash::from_raw(rc.user_hash); 2050 + let user = self.load_user(user_hash)?; 2051 + match user { 2052 + Some(u) => Ok(Some(UserResetCodeInfo { 2053 + id: u.id, 2054 + did: Did::new(u.did.clone()) 2055 + .map_err(|_| MetastoreError::CorruptData("invalid user did"))?, 2056 + preferred_comms_channel: Self::comms_channel(&u), 2057 + expires_at: DateTime::from_timestamp_millis(rc.expires_at_ms), 2058 + })), 2059 + None => Ok(None), 2060 + } 2061 + } 2062 + false => Ok(None), 2063 + } 2064 + } 2065 + None => Ok(None), 2066 + } 2067 + } 2068 + 2069 + pub fn clear_password_reset_code(&self, user_id: Uuid) -> Result<(), MetastoreError> { 2070 + let user_hash = self.resolve_hash_from_uuid(user_id)?; 2071 + let user = match self.load_user(user_hash)? { 2072 + Some(u) => u, 2073 + None => return Ok(()), 2074 + }; 2075 + let _ = user; 2076 + 2077 + let prefix = [super::keys::KeyTag::USER_RESET_CODE.raw()]; 2078 + let keys_to_remove: Vec<Vec<u8>> = self 2079 + .auth 2080 + .prefix(&prefix) 2081 + .filter_map(|guard| { 2082 + let (key_bytes, val_bytes) = guard.into_inner().ok()?; 2083 + let rc = ResetCodeValue::deserialize(&val_bytes)?; 2084 + match rc.user_id == user_id { 2085 + true => Some(key_bytes.to_vec()), 2086 + false => None, 2087 + } 2088 + }) 2089 + .collect(); 2090 + 2091 + match keys_to_remove.is_empty() { 2092 + true => Ok(()), 2093 + false => { 2094 + let mut batch = self.db.batch(); 2095 + keys_to_remove.iter().for_each(|key| { 2096 + batch.remove(&self.auth, key); 2097 + }); 2098 + batch.commit().map_err(MetastoreError::Fjall) 2099 + } 2100 + } 2101 + } 2102 + 2103 + pub fn get_id_and_password_hash_by_did( 2104 + &self, 2105 + did: &Did, 2106 + ) -> Result<Option<UserIdAndPasswordHash>, MetastoreError> { 2107 + self.load_user_by_did(did.as_str())? 2108 + .and_then(|v| v.password_hash.clone().map(|ph| (v.id, ph))) 2109 + .map(|(id, ph)| { 2110 + Ok(UserIdAndPasswordHash { 2111 + id, 2112 + password_hash: ph, 2113 + }) 2114 + }) 2115 + .transpose() 2116 + } 2117 + 2118 + pub fn update_password_hash( 2119 + &self, 2120 + user_id: Uuid, 2121 + password_hash: &str, 2122 + ) -> Result<(), MetastoreError> { 2123 + self.mutate_user_by_uuid(user_id, |u| { 2124 + u.password_hash = Some(password_hash.to_owned()); 2125 + })?; 2126 + Ok(()) 2127 + } 2128 + 2129 + pub fn reset_password_with_sessions( 2130 + &self, 2131 + user_id: Uuid, 2132 + password_hash: &str, 2133 + ) -> Result<PasswordResetResult, MetastoreError> { 2134 + let user_hash = self.resolve_hash_from_uuid(user_id)?; 2135 + let user = self 2136 + .load_user(user_hash)? 2137 + .ok_or(MetastoreError::InvalidInput("user not found"))?; 2138 + 2139 + let did = Did::new(user.did.clone()) 2140 + .map_err(|_| MetastoreError::CorruptData("invalid user did"))?; 2141 + 2142 + let prefix = super::sessions::session_by_did_prefix(user_hash); 2143 + let session_jtis: Vec<String> = self 2144 + .auth 2145 + .prefix(prefix.as_slice()) 2146 + .filter_map(|guard| { 2147 + let (key_bytes, _) = guard.into_inner().ok()?; 2148 + let remaining = key_bytes.get(9..13)?; 2149 + let sid = i32::from_be_bytes(remaining.try_into().ok()?); 2150 + let session_key = super::sessions::session_primary_key(sid); 2151 + self.auth 2152 + .get(session_key.as_slice()) 2153 + .ok() 2154 + .flatten() 2155 + .and_then(|raw| super::sessions::SessionTokenValue::deserialize(&raw)) 2156 + .map(|s| s.access_jti) 2157 + }) 2158 + .collect(); 2159 + 2160 + let mut updated = user; 2161 + updated.password_hash = Some(password_hash.to_owned()); 2162 + 2163 + self.save_user(user_hash, &updated)?; 2164 + 2165 + Ok(PasswordResetResult { did, session_jtis }) 2166 + } 2167 + 2168 + pub fn activate_account(&self, did: &Did) -> Result<bool, MetastoreError> { 2169 + let user_hash = self.resolve_hash(did.as_str()); 2170 + self.mutate_user(user_hash, |u| { 2171 + u.deactivated_at_ms = None; 2172 + u.delete_after_ms = None; 2173 + }) 2174 + } 2175 + 2176 + pub fn deactivate_account( 2177 + &self, 2178 + did: &Did, 2179 + delete_after: Option<DateTime<Utc>>, 2180 + ) -> Result<bool, MetastoreError> { 2181 + let user_hash = self.resolve_hash(did.as_str()); 2182 + let now_ms = Utc::now().timestamp_millis(); 2183 + self.mutate_user(user_hash, |u| { 2184 + u.deactivated_at_ms = Some(now_ms); 2185 + u.delete_after_ms = delete_after.map(|dt| dt.timestamp_millis()); 2186 + }) 2187 + } 2188 + 2189 + pub fn has_password_by_did(&self, did: &Did) -> Result<Option<bool>, MetastoreError> { 2190 + Ok(self 2191 + .load_user_by_did(did.as_str())? 2192 + .map(|v| v.password_hash.is_some())) 2193 + } 2194 + 2195 + pub fn get_password_info_by_did( 2196 + &self, 2197 + did: &Did, 2198 + ) -> Result<Option<UserPasswordInfo>, MetastoreError> { 2199 + Ok(self 2200 + .load_user_by_did(did.as_str())? 2201 + .map(|v| UserPasswordInfo { 2202 + id: v.id, 2203 + password_hash: v.password_hash.clone(), 2204 + })) 2205 + } 2206 + 2207 + pub fn remove_user_password(&self, user_id: Uuid) -> Result<(), MetastoreError> { 2208 + self.mutate_user_by_uuid(user_id, |u| { 2209 + u.password_hash = None; 2210 + u.password_required = false; 2211 + })?; 2212 + Ok(()) 2213 + } 2214 + 2215 + pub fn set_new_user_password( 2216 + &self, 2217 + user_id: Uuid, 2218 + password_hash: &str, 2219 + ) -> Result<(), MetastoreError> { 2220 + self.mutate_user_by_uuid(user_id, |u| { 2221 + u.password_hash = Some(password_hash.to_owned()); 2222 + u.password_required = true; 2223 + })?; 2224 + Ok(()) 2225 + } 2226 + 2227 + pub fn get_user_key_by_did(&self, did: &Did) -> Result<Option<UserKeyInfo>, MetastoreError> { 2228 + Ok(self.load_user_by_did(did.as_str())?.map(|v| UserKeyInfo { 2229 + key_bytes: v.key_bytes.clone(), 2230 + encryption_version: Some(v.encryption_version), 2231 + })) 2232 + } 2233 + 2234 + fn delete_user_data( 2235 + &self, 2236 + batch: &mut fjall::OwnedWriteBatch, 2237 + user_hash: UserHash, 2238 + user: &UserValue, 2239 + ) -> Result<(), MetastoreError> { 2240 + batch.remove(&self.users, user_primary_key(user_hash).as_slice()); 2241 + batch.remove(&self.users, user_by_handle_key(&user.handle).as_slice()); 2242 + 2243 + if let Some(email) = &user.email { 2244 + batch.remove(&self.users, user_by_email_key(email).as_slice()); 2245 + } 2246 + 2247 + if let Some(tg) = &user.telegram_username { 2248 + batch.remove(&self.users, telegram_lookup_key(tg).as_slice()); 2249 + } 2250 + 2251 + if let Some(dc) = &user.discord_username { 2252 + batch.remove(&self.users, discord_lookup_key(dc).as_slice()); 2253 + } 2254 + 2255 + self.users 2256 + .prefix(passkey_prefix(user_hash).as_slice()) 2257 + .try_for_each(|guard| { 2258 + let (key_bytes, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 2259 + batch.remove(&self.users, key_bytes.as_ref()); 2260 + if let Some(pv) = PasskeyValue::deserialize(&val_bytes) { 2261 + batch.remove( 2262 + &self.users, 2263 + passkey_by_cred_key(&pv.credential_id).as_slice(), 2264 + ); 2265 + } 2266 + Ok::<(), MetastoreError>(()) 2267 + })?; 2268 + 2269 + batch.remove(&self.users, totp_key(user_hash).as_slice()); 2270 + delete_all_by_prefix(&self.users, batch, backup_code_prefix(user_hash).as_slice())?; 2271 + batch.remove(&self.users, recovery_token_key(user_hash).as_slice()); 2272 + batch.remove(&self.users, did_web_overrides_key(user_hash).as_slice()); 2273 + 2274 + self.delete_auth_data_for_user(batch, user_hash)?; 2275 + 2276 + Ok(()) 2277 + } 2278 + 2279 + fn delete_auth_data_for_user( 2280 + &self, 2281 + batch: &mut fjall::OwnedWriteBatch, 2282 + user_hash: UserHash, 2283 + ) -> Result<(), MetastoreError> { 2284 + use super::sessions::{ 2285 + SessionTokenValue, session_app_password_prefix, session_by_access_key, 2286 + session_by_did_prefix, session_by_refresh_key, session_last_reauth_key, 2287 + session_primary_key, 2288 + }; 2289 + 2290 + let did_prefix = session_by_did_prefix(user_hash); 2291 + self.auth 2292 + .prefix(did_prefix.as_slice()) 2293 + .try_for_each(|guard| { 2294 + let (key_bytes, _) = guard.into_inner().map_err(MetastoreError::Fjall)?; 2295 + let mut reader = super::encoding::KeyReader::new(&key_bytes); 2296 + let _tag = reader.tag(); 2297 + let _hash = reader.u64(); 2298 + let sid_bytes: [u8; 4] = reader 2299 + .remaining() 2300 + .try_into() 2301 + .map_err(|_| MetastoreError::CorruptData("session_by_did key truncated"))?; 2302 + let sid = i32::from_be_bytes(sid_bytes); 2303 + 2304 + let primary = session_primary_key(sid); 2305 + if let Some(raw) = self 2306 + .auth 2307 + .get(primary.as_slice()) 2308 + .map_err(MetastoreError::Fjall)? 2309 + { 2310 + if let Some(session) = SessionTokenValue::deserialize(&raw) { 2311 + batch.remove( 2312 + &self.auth, 2313 + session_by_access_key(&session.access_jti).as_slice(), 2314 + ); 2315 + batch.remove( 2316 + &self.auth, 2317 + session_by_refresh_key(&session.refresh_jti).as_slice(), 2318 + ); 2319 + } 2320 + batch.remove(&self.auth, primary.as_slice()); 2321 + } 2322 + 2323 + batch.remove(&self.auth, key_bytes.as_ref()); 2324 + Ok::<(), MetastoreError>(()) 2325 + })?; 2326 + 2327 + delete_all_by_prefix( 2328 + &self.auth, 2329 + batch, 2330 + session_app_password_prefix(user_hash).as_slice(), 2331 + )?; 2332 + 2333 + batch.remove(&self.auth, session_last_reauth_key(user_hash).as_slice()); 2334 + 2335 + batch.remove(&self.auth, webauthn_challenge_key(user_hash, 0).as_slice()); 2336 + batch.remove(&self.auth, webauthn_challenge_key(user_hash, 1).as_slice()); 2337 + 2338 + Ok(()) 2339 + } 2340 + 2341 + pub fn delete_account_complete(&self, user_id: Uuid, did: &Did) -> Result<(), MetastoreError> { 2342 + let user_hash = self.resolve_hash(did.as_str()); 2343 + let user = match self.load_user(user_hash)? { 2344 + Some(u) => u, 2345 + None => return Ok(()), 2346 + }; 2347 + 2348 + let mut batch = self.db.batch(); 2349 + self.delete_user_data(&mut batch, user_hash, &user)?; 2350 + self.user_hashes.stage_remove(&mut batch, &user_id); 2351 + batch.commit().map_err(MetastoreError::Fjall) 2352 + } 2353 + 2354 + pub fn set_user_takedown( 2355 + &self, 2356 + did: &Did, 2357 + takedown_ref: Option<&str>, 2358 + ) -> Result<bool, MetastoreError> { 2359 + let user_hash = self.resolve_hash(did.as_str()); 2360 + self.mutate_user(user_hash, |u| { 2361 + u.takedown_ref = takedown_ref.map(str::to_owned); 2362 + }) 2363 + } 2364 + 2365 + pub fn admin_delete_account_complete( 2366 + &self, 2367 + user_id: Uuid, 2368 + did: &Did, 2369 + ) -> Result<(), MetastoreError> { 2370 + self.delete_account_complete(user_id, did) 2371 + } 2372 + 2373 + pub fn get_user_for_did_doc(&self, did: &Did) -> Result<Option<UserForDidDoc>, MetastoreError> { 2374 + self.load_user_by_did(did.as_str())? 2375 + .map(|v| { 2376 + Ok(UserForDidDoc { 2377 + id: v.id, 2378 + handle: Handle::new(v.handle.clone()) 2379 + .map_err(|_| MetastoreError::CorruptData("invalid user handle"))?, 2380 + deactivated_at: v 2381 + .deactivated_at_ms 2382 + .and_then(DateTime::from_timestamp_millis), 2383 + }) 2384 + }) 2385 + .transpose() 2386 + } 2387 + 2388 + pub fn get_user_for_did_doc_build( 2389 + &self, 2390 + did: &Did, 2391 + ) -> Result<Option<UserForDidDocBuild>, MetastoreError> { 2392 + self.load_user_by_did(did.as_str())? 2393 + .map(|v| { 2394 + Ok(UserForDidDocBuild { 2395 + id: v.id, 2396 + handle: Handle::new(v.handle.clone()) 2397 + .map_err(|_| MetastoreError::CorruptData("invalid user handle"))?, 2398 + migrated_to_pds: v.migrated_to_pds.clone(), 2399 + }) 2400 + }) 2401 + .transpose() 2402 + } 2403 + 2404 + pub fn upsert_did_web_overrides( 2405 + &self, 2406 + user_id: Uuid, 2407 + verification_methods: Option<serde_json::Value>, 2408 + also_known_as: Option<Vec<String>>, 2409 + ) -> Result<(), MetastoreError> { 2410 + let user_hash = self.resolve_hash_from_uuid(user_id)?; 2411 + let key = did_web_overrides_key(user_hash); 2412 + 2413 + let value = DidWebOverridesValue { 2414 + verification_methods_json: verification_methods 2415 + .map(|v| serde_json::to_string(&v).unwrap_or_default()), 2416 + also_known_as, 2417 + }; 2418 + 2419 + self.users 2420 + .insert(key.as_slice(), value.serialize()) 2421 + .map_err(MetastoreError::Fjall) 2422 + } 2423 + 2424 + pub fn update_migrated_to_pds(&self, did: &Did, endpoint: &str) -> Result<(), MetastoreError> { 2425 + let user_hash = self.resolve_hash(did.as_str()); 2426 + let now_ms = Utc::now().timestamp_millis(); 2427 + self.mutate_user(user_hash, |u| { 2428 + u.migrated_to_pds = Some(endpoint.to_owned()); 2429 + u.migrated_at_ms = Some(now_ms); 2430 + })?; 2431 + Ok(()) 2432 + } 2433 + 2434 + pub fn get_user_for_passkey_setup( 2435 + &self, 2436 + did: &Did, 2437 + ) -> Result<Option<UserForPasskeySetup>, MetastoreError> { 2438 + let user_hash = self.resolve_hash(did.as_str()); 2439 + let user = match self.load_user(user_hash)? { 2440 + Some(u) => u, 2441 + None => return Ok(None), 2442 + }; 2443 + 2444 + let recovery: Option<RecoveryTokenValue> = point_lookup( 2445 + &self.users, 2446 + recovery_token_key(user_hash).as_slice(), 2447 + RecoveryTokenValue::deserialize, 2448 + "corrupt recovery token", 2449 + )?; 2450 + 2451 + Ok(Some(UserForPasskeySetup { 2452 + id: user.id, 2453 + handle: Handle::new(user.handle.clone()) 2454 + .map_err(|_| MetastoreError::CorruptData("invalid user handle"))?, 2455 + recovery_token: recovery.as_ref().map(|r| r.token_hash.clone()), 2456 + recovery_token_expires_at: recovery 2457 + .as_ref() 2458 + .and_then(|r| DateTime::from_timestamp_millis(r.expires_at_ms)), 2459 + password_required: user.password_required, 2460 + })) 2461 + } 2462 + 2463 + pub fn get_user_for_passkey_recovery( 2464 + &self, 2465 + identifier: &str, 2466 + normalized_handle: &str, 2467 + ) -> Result<Option<UserForPasskeyRecovery>, MetastoreError> { 2468 + let val = match self.load_by_identifier(identifier)? { 2469 + Some(v) => v, 2470 + None => match self.load_by_handle(normalized_handle)? { 2471 + Some(v) => v, 2472 + None => return Ok(None), 2473 + }, 2474 + }; 2475 + 2476 + Ok(Some(UserForPasskeyRecovery { 2477 + id: val.id, 2478 + did: Did::new(val.did.clone()) 2479 + .map_err(|_| MetastoreError::CorruptData("invalid user did"))?, 2480 + handle: Handle::new(val.handle.clone()) 2481 + .map_err(|_| MetastoreError::CorruptData("invalid user handle"))?, 2482 + password_required: val.password_required, 2483 + })) 2484 + } 2485 + 2486 + pub fn set_recovery_token( 2487 + &self, 2488 + did: &Did, 2489 + token_hash: &str, 2490 + expires_at: DateTime<Utc>, 2491 + ) -> Result<(), MetastoreError> { 2492 + let user_hash = self.resolve_hash(did.as_str()); 2493 + let key = recovery_token_key(user_hash); 2494 + 2495 + let value = RecoveryTokenValue { 2496 + token_hash: token_hash.to_owned(), 2497 + expires_at_ms: expires_at.timestamp_millis(), 2498 + }; 2499 + 2500 + self.users 2501 + .insert(key.as_slice(), value.serialize()) 2502 + .map_err(MetastoreError::Fjall) 2503 + } 2504 + 2505 + pub fn get_user_for_recovery( 2506 + &self, 2507 + did: &Did, 2508 + ) -> Result<Option<UserForRecovery>, MetastoreError> { 2509 + let user_hash = self.resolve_hash(did.as_str()); 2510 + let user = match self.load_user(user_hash)? { 2511 + Some(u) => u, 2512 + None => return Ok(None), 2513 + }; 2514 + 2515 + let recovery: Option<RecoveryTokenValue> = point_lookup( 2516 + &self.users, 2517 + recovery_token_key(user_hash).as_slice(), 2518 + RecoveryTokenValue::deserialize, 2519 + "corrupt recovery token", 2520 + )?; 2521 + 2522 + Ok(Some(UserForRecovery { 2523 + id: user.id, 2524 + did: Did::new(user.did.clone()) 2525 + .map_err(|_| MetastoreError::CorruptData("invalid user did"))?, 2526 + preferred_comms_channel: Self::comms_channel(&user), 2527 + recovery_token: recovery.as_ref().map(|r| r.token_hash.clone()), 2528 + recovery_token_expires_at: recovery 2529 + .as_ref() 2530 + .and_then(|r| DateTime::from_timestamp_millis(r.expires_at_ms)), 2531 + })) 2532 + } 2533 + 2534 + pub fn get_accounts_scheduled_for_deletion( 2535 + &self, 2536 + limit: i64, 2537 + ) -> Result<Vec<ScheduledDeletionAccount>, MetastoreError> { 2538 + let prefix = user_primary_prefix(); 2539 + let limit = usize::try_from(limit).unwrap_or(0); 2540 + let now_ms = Utc::now().timestamp_millis(); 2541 + 2542 + self.users 2543 + .prefix(prefix.as_slice()) 2544 + .map( 2545 + |guard| -> Result<Option<ScheduledDeletionAccount>, MetastoreError> { 2546 + let (_, val_bytes) = guard.into_inner().map_err(MetastoreError::Fjall)?; 2547 + let val = UserValue::deserialize(&val_bytes) 2548 + .ok_or(MetastoreError::CorruptData("corrupt user value"))?; 2549 + match val.delete_after_ms { 2550 + Some(delete_at) if delete_at <= now_ms => { 2551 + Ok(Some(ScheduledDeletionAccount { 2552 + id: val.id, 2553 + did: Did::new(val.did.clone()) 2554 + .map_err(|_| MetastoreError::CorruptData("invalid user did"))?, 2555 + handle: Handle::new(val.handle.clone()).map_err(|_| { 2556 + MetastoreError::CorruptData("invalid user handle") 2557 + })?, 2558 + })) 2559 + } 2560 + _ => Ok(None), 2561 + } 2562 + }, 2563 + ) 2564 + .filter_map(Result::transpose) 2565 + .take(limit) 2566 + .collect() 2567 + } 2568 + 2569 + pub fn delete_account_with_firehose( 2570 + &self, 2571 + user_id: Uuid, 2572 + did: &Did, 2573 + ) -> Result<i64, MetastoreError> { 2574 + self.delete_account_complete(user_id, did)?; 2575 + tracing::warn!( 2576 + "delete_account_with_firehose: no firehose event emitted (not yet implemented for tranquil-store)" 2577 + ); 2578 + Ok(0) 2579 + } 2580 + 2581 + fn build_user_value( 2582 + &self, 2583 + did: &Did, 2584 + handle: &Handle, 2585 + email: Option<&str>, 2586 + password_hash: Option<&str>, 2587 + preferred_comms_channel: CommsChannel, 2588 + discord_username: Option<&str>, 2589 + telegram_username: Option<&str>, 2590 + signal_username: Option<&str>, 2591 + deactivated_at: Option<DateTime<Utc>>, 2592 + encrypted_key_bytes: &[u8], 2593 + encryption_version: i32, 2594 + account_type: AccountType, 2595 + password_required: bool, 2596 + is_admin: bool, 2597 + ) -> UserValue { 2598 + let now_ms = Utc::now().timestamp_millis(); 2599 + UserValue { 2600 + id: Uuid::new_v4(), 2601 + did: did.to_string(), 2602 + handle: handle.as_str().to_owned(), 2603 + email: email.map(str::to_owned), 2604 + email_verified: false, 2605 + password_hash: password_hash.map(str::to_owned), 2606 + created_at_ms: now_ms, 2607 + deactivated_at_ms: deactivated_at.map(|dt| dt.timestamp_millis()), 2608 + takedown_ref: None, 2609 + is_admin, 2610 + preferred_comms_channel: Some(channel_to_u8(preferred_comms_channel)), 2611 + key_bytes: encrypted_key_bytes.to_vec(), 2612 + encryption_version, 2613 + account_type: account_type_to_u8(account_type), 2614 + password_required, 2615 + two_factor_enabled: false, 2616 + email_2fa_enabled: false, 2617 + totp_enabled: false, 2618 + allow_legacy_login: false, 2619 + preferred_locale: None, 2620 + invites_disabled: false, 2621 + migrated_to_pds: None, 2622 + migrated_at_ms: None, 2623 + discord_username: discord_username.map(str::to_owned), 2624 + discord_id: None, 2625 + discord_verified: false, 2626 + telegram_username: telegram_username.map(str::to_owned), 2627 + telegram_chat_id: None, 2628 + telegram_verified: false, 2629 + signal_username: signal_username.map(str::to_owned), 2630 + signal_verified: false, 2631 + delete_after_ms: None, 2632 + } 2633 + } 2634 + 2635 + fn write_new_account( 2636 + &self, 2637 + user_value: &UserValue, 2638 + commit_cid: &str, 2639 + repo_rev: &str, 2640 + ) -> Result<CreatePasswordAccountResult, CreateAccountError> { 2641 + let user_hash = UserHash::from_did(&user_value.did); 2642 + let user_id = user_value.id; 2643 + 2644 + let cid_link = 2645 + CidLink::new(commit_cid).map_err(|e| CreateAccountError::Database(e.to_string()))?; 2646 + let cid_bytes = cid_link_to_bytes(&cid_link) 2647 + .map_err(|e| CreateAccountError::Database(e.to_string()))?; 2648 + 2649 + let handle_lower = user_value.handle.to_ascii_lowercase(); 2650 + 2651 + let repo_meta = RepoMetaValue { 2652 + repo_root_cid: cid_bytes, 2653 + repo_rev: repo_rev.to_string(), 2654 + handle: handle_lower.clone(), 2655 + status: RepoStatus::Active, 2656 + deactivated_at_ms: user_value 2657 + .deactivated_at_ms 2658 + .and_then(|ms| u64::try_from(ms).ok()), 2659 + takedown_ref: None, 2660 + did: Some(user_value.did.clone()), 2661 + }; 2662 + 2663 + let mut batch = self.db.batch(); 2664 + 2665 + self.user_hashes 2666 + .stage_insert(&mut batch, user_id, user_hash) 2667 + .map_err(|e| CreateAccountError::Database(e.to_string()))?; 2668 + 2669 + batch.insert( 2670 + &self.users, 2671 + user_primary_key(user_hash).as_slice(), 2672 + user_value.serialize(), 2673 + ); 2674 + batch.insert( 2675 + &self.users, 2676 + user_by_handle_key(user_value.handle.as_str()).as_slice(), 2677 + user_hash.raw().to_be_bytes(), 2678 + ); 2679 + 2680 + if let Some(email) = &user_value.email { 2681 + batch.insert( 2682 + &self.users, 2683 + user_by_email_key(email).as_slice(), 2684 + user_hash.raw().to_be_bytes(), 2685 + ); 2686 + } 2687 + 2688 + if let Some(tg) = &user_value.telegram_username { 2689 + batch.insert( 2690 + &self.users, 2691 + telegram_lookup_key(tg).as_slice(), 2692 + user_hash.raw().to_be_bytes(), 2693 + ); 2694 + } 2695 + 2696 + if let Some(dc) = &user_value.discord_username { 2697 + batch.insert( 2698 + &self.users, 2699 + discord_lookup_key(dc).as_slice(), 2700 + user_hash.raw().to_be_bytes(), 2701 + ); 2702 + } 2703 + 2704 + batch.insert( 2705 + &self.repo_data, 2706 + repo_meta_key(user_hash).as_slice(), 2707 + repo_meta.serialize(), 2708 + ); 2709 + batch.insert( 2710 + &self.repo_data, 2711 + handle_key(&handle_lower).as_slice(), 2712 + user_hash.raw().to_be_bytes(), 2713 + ); 2714 + 2715 + match batch.commit() { 2716 + Ok(()) => Ok(CreatePasswordAccountResult { 2717 + user_id, 2718 + is_admin: user_value.is_admin, 2719 + }), 2720 + Err(e) => { 2721 + self.user_hashes.rollback_insert(&user_id, &user_hash); 2722 + Err(CreateAccountError::Database(e.to_string())) 2723 + } 2724 + } 2725 + } 2726 + 2727 + fn check_availability( 2728 + &self, 2729 + did: &Did, 2730 + handle: &Handle, 2731 + email: Option<&str>, 2732 + ) -> Result<(), CreateAccountError> { 2733 + match self.load_user_by_did(did.as_str()) { 2734 + Ok(Some(_)) => return Err(CreateAccountError::DidExists), 2735 + Err(e) => return Err(CreateAccountError::Database(e.to_string())), 2736 + Ok(None) => {} 2737 + } 2738 + 2739 + match self.load_by_handle(handle.as_str()) { 2740 + Ok(Some(_)) => return Err(CreateAccountError::HandleTaken), 2741 + Err(e) => return Err(CreateAccountError::Database(e.to_string())), 2742 + Ok(None) => {} 2743 + } 2744 + 2745 + if let Some(email) = email { 2746 + match self.load_by_email(email) { 2747 + Ok(Some(_)) => return Err(CreateAccountError::EmailTaken), 2748 + Err(e) => return Err(CreateAccountError::Database(e.to_string())), 2749 + Ok(None) => {} 2750 + } 2751 + } 2752 + 2753 + Ok(()) 2754 + } 2755 + 2756 + pub fn create_password_account( 2757 + &self, 2758 + input: &CreatePasswordAccountInput, 2759 + ) -> Result<CreatePasswordAccountResult, CreateAccountError> { 2760 + self.check_availability(&input.did, &input.handle, input.email.as_deref())?; 2761 + 2762 + let is_admin = self 2763 + .is_first_account() 2764 + .map_err(|e| CreateAccountError::Database(e.to_string()))?; 2765 + 2766 + let user_value = self.build_user_value( 2767 + &input.did, 2768 + &input.handle, 2769 + input.email.as_deref(), 2770 + Some(&input.password_hash), 2771 + input.preferred_comms_channel, 2772 + input.discord_username.as_deref(), 2773 + input.telegram_username.as_deref(), 2774 + input.signal_username.as_deref(), 2775 + input.deactivated_at, 2776 + &input.encrypted_key_bytes, 2777 + input.encryption_version, 2778 + AccountType::Personal, 2779 + true, 2780 + is_admin, 2781 + ); 2782 + 2783 + self.write_new_account(&user_value, &input.commit_cid, &input.repo_rev) 2784 + } 2785 + 2786 + pub fn create_delegated_account( 2787 + &self, 2788 + input: &CreateDelegatedAccountInput, 2789 + ) -> Result<Uuid, CreateAccountError> { 2790 + self.check_availability(&input.did, &input.handle, input.email.as_deref())?; 2791 + 2792 + let user_value = self.build_user_value( 2793 + &input.did, 2794 + &input.handle, 2795 + input.email.as_deref(), 2796 + None, 2797 + CommsChannel::Email, 2798 + None, 2799 + None, 2800 + None, 2801 + None, 2802 + &input.encrypted_key_bytes, 2803 + input.encryption_version, 2804 + AccountType::Delegated, 2805 + false, 2806 + false, 2807 + ); 2808 + 2809 + let result = self.write_new_account(&user_value, &input.commit_cid, &input.repo_rev)?; 2810 + Ok(result.user_id) 2811 + } 2812 + 2813 + pub fn create_passkey_account( 2814 + &self, 2815 + input: &CreatePasskeyAccountInput, 2816 + ) -> Result<CreatePasswordAccountResult, CreateAccountError> { 2817 + self.check_availability(&input.did, &input.handle, Some(&input.email))?; 2818 + 2819 + let is_admin = self 2820 + .is_first_account() 2821 + .map_err(|e| CreateAccountError::Database(e.to_string()))?; 2822 + 2823 + let user_value = self.build_user_value( 2824 + &input.did, 2825 + &input.handle, 2826 + Some(&input.email), 2827 + None, 2828 + input.preferred_comms_channel, 2829 + input.discord_username.as_deref(), 2830 + input.telegram_username.as_deref(), 2831 + input.signal_username.as_deref(), 2832 + input.deactivated_at, 2833 + &input.encrypted_key_bytes, 2834 + input.encryption_version, 2835 + AccountType::Personal, 2836 + false, 2837 + is_admin, 2838 + ); 2839 + 2840 + let result = self.write_new_account(&user_value, &input.commit_cid, &input.repo_rev)?; 2841 + 2842 + let user_hash = UserHash::from_did(input.did.as_str()); 2843 + let recovery = RecoveryTokenValue { 2844 + token_hash: input.setup_token_hash.clone(), 2845 + expires_at_ms: input.setup_expires_at.timestamp_millis(), 2846 + }; 2847 + self.users 2848 + .insert( 2849 + recovery_token_key(user_hash).as_slice(), 2850 + recovery.serialize(), 2851 + ) 2852 + .map_err(|e| CreateAccountError::Database(e.to_string()))?; 2853 + 2854 + Ok(result) 2855 + } 2856 + 2857 + pub fn create_sso_account( 2858 + &self, 2859 + input: &CreateSsoAccountInput, 2860 + ) -> Result<CreatePasswordAccountResult, CreateAccountError> { 2861 + self.check_availability(&input.did, &input.handle, input.email.as_deref())?; 2862 + 2863 + let is_admin = self 2864 + .is_first_account() 2865 + .map_err(|e| CreateAccountError::Database(e.to_string()))?; 2866 + 2867 + let user_value = self.build_user_value( 2868 + &input.did, 2869 + &input.handle, 2870 + input.email.as_deref(), 2871 + None, 2872 + input.preferred_comms_channel, 2873 + input.discord_username.as_deref(), 2874 + input.telegram_username.as_deref(), 2875 + input.signal_username.as_deref(), 2876 + None, 2877 + &input.encrypted_key_bytes, 2878 + input.encryption_version, 2879 + AccountType::Personal, 2880 + false, 2881 + is_admin, 2882 + ); 2883 + 2884 + self.write_new_account(&user_value, &input.commit_cid, &input.repo_rev) 2885 + } 2886 + 2887 + pub fn reactivate_migration_account( 2888 + &self, 2889 + input: &MigrationReactivationInput, 2890 + ) -> Result<ReactivatedAccountInfo, MigrationReactivationError> { 2891 + let user_hash = UserHash::from_did(input.did.as_str()); 2892 + let user = self 2893 + .load_user(user_hash) 2894 + .map_err(|e| MigrationReactivationError::Database(e.to_string()))? 2895 + .ok_or(MigrationReactivationError::NotFound)?; 2896 + 2897 + match user.deactivated_at_ms { 2898 + None => return Err(MigrationReactivationError::NotDeactivated), 2899 + Some(_) => {} 2900 + } 2901 + 2902 + let existing_handle = self 2903 + .load_by_handle(input.new_handle.as_str()) 2904 + .map_err(|e| MigrationReactivationError::Database(e.to_string()))?; 2905 + match existing_handle { 2906 + Some(other) if other.id != user.id => { 2907 + return Err(MigrationReactivationError::HandleTaken); 2908 + } 2909 + _ => {} 2910 + } 2911 + 2912 + let old_handle = Handle::new(user.handle.clone()) 2913 + .map_err(|_| MigrationReactivationError::Database("invalid handle".to_owned()))?; 2914 + let user_id = user.id; 2915 + 2916 + let mut batch = self.db.batch(); 2917 + 2918 + batch.remove(&self.users, user_by_handle_key(&user.handle).as_slice()); 2919 + batch.insert( 2920 + &self.users, 2921 + user_by_handle_key(input.new_handle.as_str()).as_slice(), 2922 + user_hash.raw().to_be_bytes(), 2923 + ); 2924 + 2925 + if let Some(old_email) = &user.email { 2926 + batch.remove(&self.users, user_by_email_key(old_email).as_slice()); 2927 + } 2928 + if let Some(new_email) = &input.new_email { 2929 + batch.insert( 2930 + &self.users, 2931 + user_by_email_key(new_email).as_slice(), 2932 + user_hash.raw().to_be_bytes(), 2933 + ); 2934 + } 2935 + 2936 + let mut updated = user; 2937 + updated.handle = input.new_handle.as_str().to_owned(); 2938 + updated.email = input.new_email.clone(); 2939 + updated.email_verified = false; 2940 + updated.deactivated_at_ms = None; 2941 + updated.delete_after_ms = None; 2942 + updated.migrated_to_pds = None; 2943 + updated.migrated_at_ms = None; 2944 + 2945 + batch.insert( 2946 + &self.users, 2947 + user_primary_key(user_hash).as_slice(), 2948 + updated.serialize(), 2949 + ); 2950 + 2951 + batch 2952 + .commit() 2953 + .map_err(|e| MigrationReactivationError::Database(e.to_string()))?; 2954 + 2955 + Ok(ReactivatedAccountInfo { 2956 + user_id, 2957 + old_handle, 2958 + }) 2959 + } 2960 + 2961 + pub fn check_handle_available_for_new_account( 2962 + &self, 2963 + handle: &Handle, 2964 + ) -> Result<bool, MetastoreError> { 2965 + let existing = self.load_by_handle(handle.as_str())?; 2966 + let reserved_key = handle_reservation_key(handle.as_str()); 2967 + let reservation = self 2968 + .auth 2969 + .get(reserved_key.as_slice()) 2970 + .map_err(MetastoreError::Fjall)?; 2971 + 2972 + match (existing, reservation) { 2973 + (None, None) => Ok(true), 2974 + _ => Ok(false), 2975 + } 2976 + } 2977 + 2978 + pub fn reserve_handle( 2979 + &self, 2980 + handle: &Handle, 2981 + reserved_by: &str, 2982 + ) -> Result<bool, MetastoreError> { 2983 + let available = self.check_handle_available_for_new_account(handle)?; 2984 + match available { 2985 + false => Ok(false), 2986 + true => { 2987 + let now_ms = Utc::now().timestamp_millis(); 2988 + let expires_at_ms = now_ms.saturating_add(600_000); 2989 + let value = HandleReservationValue { 2990 + reserved_by: reserved_by.to_owned(), 2991 + created_at_ms: now_ms, 2992 + expires_at_ms, 2993 + }; 2994 + let key = handle_reservation_key(handle.as_str()); 2995 + self.auth 2996 + .insert(key.as_slice(), value.serialize_with_ttl()) 2997 + .map_err(MetastoreError::Fjall)?; 2998 + Ok(true) 2999 + } 3000 + } 3001 + } 3002 + 3003 + pub fn release_handle_reservation(&self, handle: &Handle) -> Result<(), MetastoreError> { 3004 + let key = handle_reservation_key(handle.as_str()); 3005 + self.auth 3006 + .remove(key.as_slice()) 3007 + .map_err(MetastoreError::Fjall) 3008 + } 3009 + 3010 + pub fn cleanup_expired_handle_reservations(&self) -> Result<u64, MetastoreError> { 3011 + let prefix = handle_reservation_prefix(); 3012 + let now_ms = Utc::now().timestamp_millis(); 3013 + 3014 + let expired_keys: Vec<Vec<u8>> = self 3015 + .auth 3016 + .prefix(prefix.as_slice()) 3017 + .filter_map(|guard| { 3018 + let (key_bytes, val_bytes) = guard.into_inner().ok()?; 3019 + let val = HandleReservationValue::deserialize(&val_bytes)?; 3020 + match val.expires_at_ms <= now_ms { 3021 + true => Some(key_bytes.to_vec()), 3022 + false => None, 3023 + } 3024 + }) 3025 + .collect(); 3026 + 3027 + let count = u64::try_from(expired_keys.len()).unwrap_or(u64::MAX); 3028 + match count { 3029 + 0 => Ok(0), 3030 + _ => { 3031 + let mut batch = self.db.batch(); 3032 + expired_keys.iter().for_each(|key| { 3033 + batch.remove(&self.auth, key); 3034 + }); 3035 + batch.commit().map_err(MetastoreError::Fjall)?; 3036 + Ok(count) 3037 + } 3038 + } 3039 + } 3040 + 3041 + pub fn complete_passkey_setup( 3042 + &self, 3043 + input: &CompletePasskeySetupInput, 3044 + ) -> Result<(), MetastoreError> { 3045 + let user_hash = self.resolve_hash(input.did.as_str()); 3046 + 3047 + self.mutate_user(user_hash, |u| { 3048 + u.password_hash = Some(input.app_password_hash.clone()); 3049 + u.password_required = false; 3050 + })?; 3051 + 3052 + Ok(()) 3053 + } 3054 + 3055 + pub fn recover_passkey_account( 3056 + &self, 3057 + input: &RecoverPasskeyAccountInput, 3058 + ) -> Result<RecoverPasskeyAccountResult, MetastoreError> { 3059 + let user_hash = self.resolve_hash(input.did.as_str()); 3060 + 3061 + let passkeys: Vec<(Vec<u8>, PasskeyValue)> = self 3062 + .users 3063 + .prefix(passkey_prefix(user_hash).as_slice()) 3064 + .filter_map(|guard| { 3065 + let (key_bytes, val_bytes) = guard.into_inner().ok()?; 3066 + let pv = PasskeyValue::deserialize(&val_bytes)?; 3067 + Some((key_bytes.to_vec(), pv)) 3068 + }) 3069 + .collect(); 3070 + 3071 + let passkeys_deleted = u64::try_from(passkeys.len()).unwrap_or(u64::MAX); 3072 + 3073 + let mut batch = self.db.batch(); 3074 + 3075 + passkeys.iter().for_each(|(key, pv)| { 3076 + batch.remove(&self.users, key); 3077 + batch.remove( 3078 + &self.users, 3079 + passkey_by_cred_key(&pv.credential_id).as_slice(), 3080 + ); 3081 + }); 3082 + 3083 + batch.remove(&self.users, recovery_token_key(user_hash).as_slice()); 3084 + 3085 + if let Some(mut user) = self.load_user(user_hash)? { 3086 + user.password_hash = Some(input.password_hash.clone()); 3087 + user.password_required = true; 3088 + batch.insert( 3089 + &self.users, 3090 + user_primary_key(user_hash).as_slice(), 3091 + user.serialize(), 3092 + ); 3093 + } 3094 + 3095 + batch.commit().map_err(MetastoreError::Fjall)?; 3096 + 3097 + Ok(RecoverPasskeyAccountResult { passkeys_deleted }) 3098 + } 3099 + }
+829
crates/tranquil-store/src/metastore/users.rs
··· 1 + use serde::{Deserialize, Serialize}; 2 + use smallvec::SmallVec; 3 + 4 + use super::encoding::KeyBuilder; 5 + use super::keys::{KeyTag, UserHash}; 6 + 7 + const USER_SCHEMA_VERSION: u8 = 1; 8 + const PASSKEY_SCHEMA_VERSION: u8 = 1; 9 + const TOTP_SCHEMA_VERSION: u8 = 1; 10 + const BACKUP_CODE_SCHEMA_VERSION: u8 = 1; 11 + const WEBAUTHN_CHALLENGE_SCHEMA_VERSION: u8 = 1; 12 + const RESET_CODE_SCHEMA_VERSION: u8 = 1; 13 + const RECOVERY_TOKEN_SCHEMA_VERSION: u8 = 1; 14 + const DID_WEB_OVERRIDES_SCHEMA_VERSION: u8 = 1; 15 + const HANDLE_RESERVATION_SCHEMA_VERSION: u8 = 1; 16 + 17 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 18 + pub struct UserValue { 19 + pub id: uuid::Uuid, 20 + pub did: String, 21 + pub handle: String, 22 + pub email: Option<String>, 23 + pub email_verified: bool, 24 + pub password_hash: Option<String>, 25 + pub created_at_ms: i64, 26 + pub deactivated_at_ms: Option<i64>, 27 + pub takedown_ref: Option<String>, 28 + pub is_admin: bool, 29 + pub preferred_comms_channel: Option<u8>, 30 + pub key_bytes: Vec<u8>, 31 + pub encryption_version: i32, 32 + pub account_type: u8, 33 + pub password_required: bool, 34 + pub two_factor_enabled: bool, 35 + pub email_2fa_enabled: bool, 36 + pub totp_enabled: bool, 37 + pub allow_legacy_login: bool, 38 + pub preferred_locale: Option<String>, 39 + pub invites_disabled: bool, 40 + pub migrated_to_pds: Option<String>, 41 + pub migrated_at_ms: Option<i64>, 42 + pub discord_username: Option<String>, 43 + pub discord_id: Option<String>, 44 + pub discord_verified: bool, 45 + pub telegram_username: Option<String>, 46 + pub telegram_chat_id: Option<i64>, 47 + pub telegram_verified: bool, 48 + pub signal_username: Option<String>, 49 + pub signal_verified: bool, 50 + pub delete_after_ms: Option<i64>, 51 + } 52 + 53 + impl UserValue { 54 + pub fn serialize(&self) -> Vec<u8> { 55 + let payload = postcard::to_allocvec(self).expect("UserValue serialization cannot fail"); 56 + let mut buf = Vec::with_capacity(1 + payload.len()); 57 + buf.push(USER_SCHEMA_VERSION); 58 + buf.extend_from_slice(&payload); 59 + buf 60 + } 61 + 62 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 63 + let (&version, payload) = bytes.split_first()?; 64 + match version { 65 + USER_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 66 + _ => None, 67 + } 68 + } 69 + 70 + pub fn is_active(&self) -> bool { 71 + self.deactivated_at_ms.is_none() && self.takedown_ref.is_none() 72 + } 73 + 74 + pub fn channel_verification(&self) -> u8 { 75 + let mut flags = 0u8; 76 + if self.email_verified { 77 + flags |= 1; 78 + } 79 + if self.discord_verified { 80 + flags |= 2; 81 + } 82 + if self.telegram_verified { 83 + flags |= 4; 84 + } 85 + if self.signal_verified { 86 + flags |= 8; 87 + } 88 + flags 89 + } 90 + } 91 + 92 + pub fn account_type_to_u8(t: tranquil_db_traits::AccountType) -> u8 { 93 + match t { 94 + tranquil_db_traits::AccountType::Personal => 0, 95 + tranquil_db_traits::AccountType::Delegated => 1, 96 + } 97 + } 98 + 99 + pub fn u8_to_account_type(v: u8) -> Option<tranquil_db_traits::AccountType> { 100 + match v { 101 + 0 => Some(tranquil_db_traits::AccountType::Personal), 102 + 1 => Some(tranquil_db_traits::AccountType::Delegated), 103 + _ => None, 104 + } 105 + } 106 + 107 + pub fn challenge_type_to_u8(t: tranquil_db_traits::WebauthnChallengeType) -> u8 { 108 + match t { 109 + tranquil_db_traits::WebauthnChallengeType::Registration => 0, 110 + tranquil_db_traits::WebauthnChallengeType::Authentication => 1, 111 + } 112 + } 113 + 114 + pub fn u8_to_challenge_type(v: u8) -> Option<tranquil_db_traits::WebauthnChallengeType> { 115 + match v { 116 + 0 => Some(tranquil_db_traits::WebauthnChallengeType::Registration), 117 + 1 => Some(tranquil_db_traits::WebauthnChallengeType::Authentication), 118 + _ => None, 119 + } 120 + } 121 + 122 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 123 + pub struct PasskeyValue { 124 + pub id: uuid::Uuid, 125 + pub did: String, 126 + pub credential_id: Vec<u8>, 127 + pub public_key: Vec<u8>, 128 + pub sign_count: i32, 129 + pub created_at_ms: i64, 130 + pub last_used_at_ms: Option<i64>, 131 + pub friendly_name: Option<String>, 132 + pub aaguid: Option<Vec<u8>>, 133 + pub transports: Option<Vec<String>>, 134 + } 135 + 136 + impl PasskeyValue { 137 + pub fn serialize(&self) -> Vec<u8> { 138 + let payload = postcard::to_allocvec(self).expect("PasskeyValue serialization cannot fail"); 139 + let mut buf = Vec::with_capacity(1 + payload.len()); 140 + buf.push(PASSKEY_SCHEMA_VERSION); 141 + buf.extend_from_slice(&payload); 142 + buf 143 + } 144 + 145 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 146 + let (&version, payload) = bytes.split_first()?; 147 + match version { 148 + PASSKEY_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 149 + _ => None, 150 + } 151 + } 152 + } 153 + 154 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 155 + pub struct TotpValue { 156 + pub secret_encrypted: Vec<u8>, 157 + pub encryption_version: i32, 158 + pub verified: bool, 159 + pub last_used_at_ms: Option<i64>, 160 + } 161 + 162 + impl TotpValue { 163 + pub fn serialize(&self) -> Vec<u8> { 164 + let payload = postcard::to_allocvec(self).expect("TotpValue serialization cannot fail"); 165 + let mut buf = Vec::with_capacity(1 + payload.len()); 166 + buf.push(TOTP_SCHEMA_VERSION); 167 + buf.extend_from_slice(&payload); 168 + buf 169 + } 170 + 171 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 172 + let (&version, payload) = bytes.split_first()?; 173 + match version { 174 + TOTP_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 175 + _ => None, 176 + } 177 + } 178 + } 179 + 180 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 181 + pub struct BackupCodeValue { 182 + pub id: uuid::Uuid, 183 + pub code_hash: String, 184 + pub used: bool, 185 + } 186 + 187 + impl BackupCodeValue { 188 + pub fn serialize(&self) -> Vec<u8> { 189 + let payload = 190 + postcard::to_allocvec(self).expect("BackupCodeValue serialization cannot fail"); 191 + let mut buf = Vec::with_capacity(1 + payload.len()); 192 + buf.push(BACKUP_CODE_SCHEMA_VERSION); 193 + buf.extend_from_slice(&payload); 194 + buf 195 + } 196 + 197 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 198 + let (&version, payload) = bytes.split_first()?; 199 + match version { 200 + BACKUP_CODE_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 201 + _ => None, 202 + } 203 + } 204 + } 205 + 206 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 207 + pub struct WebauthnChallengeValue { 208 + pub id: uuid::Uuid, 209 + pub challenge_type: u8, 210 + pub state_json: String, 211 + pub created_at_ms: i64, 212 + } 213 + 214 + impl WebauthnChallengeValue { 215 + pub fn serialize_with_ttl(&self) -> Vec<u8> { 216 + let expires_at_ms = self.created_at_ms.saturating_add(300_000); 217 + let ttl_bytes = u64::try_from(expires_at_ms).unwrap_or(0).to_be_bytes(); 218 + let payload = 219 + postcard::to_allocvec(self).expect("WebauthnChallengeValue serialization cannot fail"); 220 + let mut buf = Vec::with_capacity(8 + 1 + payload.len()); 221 + buf.extend_from_slice(&ttl_bytes); 222 + buf.push(WEBAUTHN_CHALLENGE_SCHEMA_VERSION); 223 + buf.extend_from_slice(&payload); 224 + buf 225 + } 226 + 227 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 228 + let rest = bytes.get(8..)?; 229 + let (&version, payload) = rest.split_first()?; 230 + match version { 231 + WEBAUTHN_CHALLENGE_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 232 + _ => None, 233 + } 234 + } 235 + } 236 + 237 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 238 + pub struct ResetCodeValue { 239 + pub user_hash: u64, 240 + pub user_id: uuid::Uuid, 241 + pub preferred_comms_channel: Option<u8>, 242 + pub code: String, 243 + pub expires_at_ms: i64, 244 + } 245 + 246 + impl ResetCodeValue { 247 + pub fn serialize_with_ttl(&self) -> Vec<u8> { 248 + let ttl_bytes = u64::try_from(self.expires_at_ms).unwrap_or(0).to_be_bytes(); 249 + let payload = 250 + postcard::to_allocvec(self).expect("ResetCodeValue serialization cannot fail"); 251 + let mut buf = Vec::with_capacity(8 + 1 + payload.len()); 252 + buf.extend_from_slice(&ttl_bytes); 253 + buf.push(RESET_CODE_SCHEMA_VERSION); 254 + buf.extend_from_slice(&payload); 255 + buf 256 + } 257 + 258 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 259 + let rest = bytes.get(8..)?; 260 + let (&version, payload) = rest.split_first()?; 261 + match version { 262 + RESET_CODE_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 263 + _ => None, 264 + } 265 + } 266 + } 267 + 268 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 269 + pub struct RecoveryTokenValue { 270 + pub token_hash: String, 271 + pub expires_at_ms: i64, 272 + } 273 + 274 + impl RecoveryTokenValue { 275 + pub fn serialize(&self) -> Vec<u8> { 276 + let payload = 277 + postcard::to_allocvec(self).expect("RecoveryTokenValue serialization cannot fail"); 278 + let mut buf = Vec::with_capacity(1 + payload.len()); 279 + buf.push(RECOVERY_TOKEN_SCHEMA_VERSION); 280 + buf.extend_from_slice(&payload); 281 + buf 282 + } 283 + 284 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 285 + let (&version, payload) = bytes.split_first()?; 286 + match version { 287 + RECOVERY_TOKEN_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 288 + _ => None, 289 + } 290 + } 291 + } 292 + 293 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 294 + pub struct DidWebOverridesValue { 295 + pub verification_methods_json: Option<String>, 296 + pub also_known_as: Option<Vec<String>>, 297 + } 298 + 299 + impl DidWebOverridesValue { 300 + pub fn serialize(&self) -> Vec<u8> { 301 + let payload = 302 + postcard::to_allocvec(self).expect("DidWebOverridesValue serialization cannot fail"); 303 + let mut buf = Vec::with_capacity(1 + payload.len()); 304 + buf.push(DID_WEB_OVERRIDES_SCHEMA_VERSION); 305 + buf.extend_from_slice(&payload); 306 + buf 307 + } 308 + 309 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 310 + let (&version, payload) = bytes.split_first()?; 311 + match version { 312 + DID_WEB_OVERRIDES_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 313 + _ => None, 314 + } 315 + } 316 + } 317 + 318 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 319 + pub struct HandleReservationValue { 320 + pub reserved_by: String, 321 + pub created_at_ms: i64, 322 + pub expires_at_ms: i64, 323 + } 324 + 325 + impl HandleReservationValue { 326 + pub fn serialize_with_ttl(&self) -> Vec<u8> { 327 + let ttl_bytes = u64::try_from(self.expires_at_ms).unwrap_or(0).to_be_bytes(); 328 + let payload = 329 + postcard::to_allocvec(self).expect("HandleReservationValue serialization cannot fail"); 330 + let mut buf = Vec::with_capacity(8 + 1 + payload.len()); 331 + buf.extend_from_slice(&ttl_bytes); 332 + buf.push(HANDLE_RESERVATION_SCHEMA_VERSION); 333 + buf.extend_from_slice(&payload); 334 + buf 335 + } 336 + 337 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 338 + let rest = bytes.get(8..)?; 339 + let (&version, payload) = rest.split_first()?; 340 + match version { 341 + HANDLE_RESERVATION_SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 342 + _ => None, 343 + } 344 + } 345 + } 346 + 347 + pub fn user_primary_key(user_hash: UserHash) -> SmallVec<[u8; 128]> { 348 + KeyBuilder::new() 349 + .tag(KeyTag::USER_PRIMARY) 350 + .u64(user_hash.raw()) 351 + .build() 352 + } 353 + 354 + pub fn user_primary_prefix() -> SmallVec<[u8; 128]> { 355 + KeyBuilder::new().tag(KeyTag::USER_PRIMARY).build() 356 + } 357 + 358 + pub fn user_by_handle_key(handle: &str) -> SmallVec<[u8; 128]> { 359 + KeyBuilder::new() 360 + .tag(KeyTag::USER_BY_HANDLE) 361 + .string(handle) 362 + .build() 363 + } 364 + 365 + pub fn user_by_email_key(email: &str) -> SmallVec<[u8; 128]> { 366 + KeyBuilder::new() 367 + .tag(KeyTag::USER_BY_EMAIL) 368 + .string(email) 369 + .build() 370 + } 371 + 372 + pub fn passkey_key(user_hash: UserHash, passkey_id: uuid::Uuid) -> SmallVec<[u8; 128]> { 373 + KeyBuilder::new() 374 + .tag(KeyTag::USER_PASSKEYS) 375 + .u64(user_hash.raw()) 376 + .bytes(passkey_id.as_bytes()) 377 + .build() 378 + } 379 + 380 + pub fn passkey_prefix(user_hash: UserHash) -> SmallVec<[u8; 128]> { 381 + KeyBuilder::new() 382 + .tag(KeyTag::USER_PASSKEYS) 383 + .u64(user_hash.raw()) 384 + .build() 385 + } 386 + 387 + pub fn passkey_by_cred_key(credential_id: &[u8]) -> SmallVec<[u8; 128]> { 388 + KeyBuilder::new() 389 + .tag(KeyTag::USER_PASSKEY_BY_CRED) 390 + .bytes(credential_id) 391 + .build() 392 + } 393 + 394 + pub fn totp_key(user_hash: UserHash) -> SmallVec<[u8; 128]> { 395 + KeyBuilder::new() 396 + .tag(KeyTag::USER_TOTP) 397 + .u64(user_hash.raw()) 398 + .build() 399 + } 400 + 401 + pub fn backup_code_key(user_hash: UserHash, code_id: uuid::Uuid) -> SmallVec<[u8; 128]> { 402 + KeyBuilder::new() 403 + .tag(KeyTag::USER_BACKUP_CODES) 404 + .u64(user_hash.raw()) 405 + .bytes(code_id.as_bytes()) 406 + .build() 407 + } 408 + 409 + pub fn backup_code_prefix(user_hash: UserHash) -> SmallVec<[u8; 128]> { 410 + KeyBuilder::new() 411 + .tag(KeyTag::USER_BACKUP_CODES) 412 + .u64(user_hash.raw()) 413 + .build() 414 + } 415 + 416 + pub fn webauthn_challenge_key(user_hash: UserHash, challenge_type: u8) -> SmallVec<[u8; 128]> { 417 + KeyBuilder::new() 418 + .tag(KeyTag::USER_WEBAUTHN_CHALLENGE) 419 + .u64(user_hash.raw()) 420 + .raw(&[challenge_type]) 421 + .build() 422 + } 423 + 424 + pub fn reset_code_key(code: &str) -> SmallVec<[u8; 128]> { 425 + KeyBuilder::new() 426 + .tag(KeyTag::USER_RESET_CODE) 427 + .string(code) 428 + .build() 429 + } 430 + 431 + pub fn recovery_token_key(user_hash: UserHash) -> SmallVec<[u8; 128]> { 432 + KeyBuilder::new() 433 + .tag(KeyTag::USER_RECOVERY_TOKEN) 434 + .u64(user_hash.raw()) 435 + .build() 436 + } 437 + 438 + pub fn did_web_overrides_key(user_hash: UserHash) -> SmallVec<[u8; 128]> { 439 + KeyBuilder::new() 440 + .tag(KeyTag::USER_DID_WEB_OVERRIDES) 441 + .u64(user_hash.raw()) 442 + .build() 443 + } 444 + 445 + pub fn handle_reservation_key(handle: &str) -> SmallVec<[u8; 128]> { 446 + KeyBuilder::new() 447 + .tag(KeyTag::USER_HANDLE_RESERVATION) 448 + .string(handle) 449 + .build() 450 + } 451 + 452 + pub fn handle_reservation_prefix() -> SmallVec<[u8; 128]> { 453 + KeyBuilder::new() 454 + .tag(KeyTag::USER_HANDLE_RESERVATION) 455 + .build() 456 + } 457 + 458 + pub fn telegram_lookup_key(telegram_username: &str) -> SmallVec<[u8; 128]> { 459 + KeyBuilder::new() 460 + .tag(KeyTag::USER_TELEGRAM_LOOKUP) 461 + .string(telegram_username) 462 + .build() 463 + } 464 + 465 + pub fn discord_lookup_key(discord_username: &str) -> SmallVec<[u8; 128]> { 466 + KeyBuilder::new() 467 + .tag(KeyTag::USER_DISCORD_LOOKUP) 468 + .string(discord_username) 469 + .build() 470 + } 471 + 472 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 473 + pub struct PasskeyIndexValue { 474 + pub user_hash: u64, 475 + pub passkey_id: uuid::Uuid, 476 + } 477 + 478 + impl PasskeyIndexValue { 479 + pub fn serialize(&self) -> Vec<u8> { 480 + let mut buf = Vec::with_capacity(24); 481 + buf.extend_from_slice(&self.user_hash.to_be_bytes()); 482 + buf.extend_from_slice(self.passkey_id.as_bytes()); 483 + buf 484 + } 485 + 486 + pub fn deserialize(bytes: &[u8]) -> Option<Self> { 487 + (bytes.len() == 24).then(|| { 488 + let user_hash = u64::from_be_bytes(bytes[..8].try_into().unwrap()); 489 + let passkey_id = uuid::Uuid::from_slice(&bytes[8..24]).unwrap(); 490 + Self { 491 + user_hash, 492 + passkey_id, 493 + } 494 + }) 495 + } 496 + } 497 + 498 + #[cfg(test)] 499 + mod tests { 500 + use super::*; 501 + 502 + #[test] 503 + fn user_value_roundtrip() { 504 + let val = UserValue { 505 + id: uuid::Uuid::new_v4(), 506 + did: "did:plc:test".to_owned(), 507 + handle: "test.example.com".to_owned(), 508 + email: Some("test@example.com".to_owned()), 509 + email_verified: true, 510 + password_hash: Some("hashed".to_owned()), 511 + created_at_ms: 1700000000000, 512 + deactivated_at_ms: None, 513 + takedown_ref: None, 514 + is_admin: false, 515 + preferred_comms_channel: None, 516 + key_bytes: vec![1, 2, 3], 517 + encryption_version: 1, 518 + account_type: 0, 519 + password_required: true, 520 + two_factor_enabled: false, 521 + email_2fa_enabled: false, 522 + totp_enabled: false, 523 + allow_legacy_login: false, 524 + preferred_locale: None, 525 + invites_disabled: false, 526 + migrated_to_pds: None, 527 + migrated_at_ms: None, 528 + discord_username: None, 529 + discord_id: None, 530 + discord_verified: false, 531 + telegram_username: None, 532 + telegram_chat_id: None, 533 + telegram_verified: false, 534 + signal_username: None, 535 + signal_verified: false, 536 + delete_after_ms: None, 537 + }; 538 + let bytes = val.serialize(); 539 + assert_eq!(bytes[0], USER_SCHEMA_VERSION); 540 + let decoded = UserValue::deserialize(&bytes).unwrap(); 541 + assert_eq!(val, decoded); 542 + } 543 + 544 + #[test] 545 + fn passkey_value_roundtrip() { 546 + let val = PasskeyValue { 547 + id: uuid::Uuid::new_v4(), 548 + did: "did:plc:test".to_owned(), 549 + credential_id: vec![1, 2, 3, 4], 550 + public_key: vec![5, 6, 7, 8], 551 + sign_count: 42, 552 + created_at_ms: 1700000000000, 553 + last_used_at_ms: None, 554 + friendly_name: Some("my key".to_owned()), 555 + aaguid: None, 556 + transports: Some(vec!["usb".to_owned()]), 557 + }; 558 + let bytes = val.serialize(); 559 + assert_eq!(bytes[0], PASSKEY_SCHEMA_VERSION); 560 + let decoded = PasskeyValue::deserialize(&bytes).unwrap(); 561 + assert_eq!(val, decoded); 562 + } 563 + 564 + #[test] 565 + fn totp_value_roundtrip() { 566 + let val = TotpValue { 567 + secret_encrypted: vec![10, 20, 30], 568 + encryption_version: 1, 569 + verified: true, 570 + last_used_at_ms: Some(1700000000000), 571 + }; 572 + let bytes = val.serialize(); 573 + let decoded = TotpValue::deserialize(&bytes).unwrap(); 574 + assert_eq!(val, decoded); 575 + } 576 + 577 + #[test] 578 + fn backup_code_value_roundtrip() { 579 + let val = BackupCodeValue { 580 + id: uuid::Uuid::new_v4(), 581 + code_hash: "hash123".to_owned(), 582 + used: false, 583 + }; 584 + let bytes = val.serialize(); 585 + let decoded = BackupCodeValue::deserialize(&bytes).unwrap(); 586 + assert_eq!(val, decoded); 587 + } 588 + 589 + #[test] 590 + fn webauthn_challenge_value_roundtrip() { 591 + let val = WebauthnChallengeValue { 592 + id: uuid::Uuid::new_v4(), 593 + challenge_type: 0, 594 + state_json: r#"{"challenge":"abc"}"#.to_owned(), 595 + created_at_ms: 1700000000000, 596 + }; 597 + let bytes = val.serialize_with_ttl(); 598 + let ttl = u64::from_be_bytes(bytes[..8].try_into().unwrap()); 599 + assert_eq!(ttl, 1700000300000); 600 + let decoded = WebauthnChallengeValue::deserialize(&bytes).unwrap(); 601 + assert_eq!(val, decoded); 602 + } 603 + 604 + #[test] 605 + fn reset_code_value_roundtrip() { 606 + let val = ResetCodeValue { 607 + user_hash: 0xDEAD, 608 + user_id: uuid::Uuid::new_v4(), 609 + preferred_comms_channel: Some(0), 610 + code: "abc123".to_owned(), 611 + expires_at_ms: 1700000600000, 612 + }; 613 + let bytes = val.serialize_with_ttl(); 614 + let ttl = u64::from_be_bytes(bytes[..8].try_into().unwrap()); 615 + assert_eq!(ttl, 1700000600000); 616 + let decoded = ResetCodeValue::deserialize(&bytes).unwrap(); 617 + assert_eq!(val, decoded); 618 + } 619 + 620 + #[test] 621 + fn recovery_token_value_roundtrip() { 622 + let val = RecoveryTokenValue { 623 + token_hash: "tokenhash".to_owned(), 624 + expires_at_ms: 1700000600000, 625 + }; 626 + let bytes = val.serialize(); 627 + let decoded = RecoveryTokenValue::deserialize(&bytes).unwrap(); 628 + assert_eq!(val, decoded); 629 + } 630 + 631 + #[test] 632 + fn did_web_overrides_value_roundtrip() { 633 + let val = DidWebOverridesValue { 634 + verification_methods_json: Some(r#"[{"id":"key-1"}]"#.to_owned()), 635 + also_known_as: Some(vec!["at://user.example.com".to_owned()]), 636 + }; 637 + let bytes = val.serialize(); 638 + let decoded = DidWebOverridesValue::deserialize(&bytes).unwrap(); 639 + assert_eq!(val, decoded); 640 + } 641 + 642 + #[test] 643 + fn handle_reservation_value_roundtrip() { 644 + let val = HandleReservationValue { 645 + reserved_by: "signup-flow".to_owned(), 646 + created_at_ms: 1700000000000, 647 + expires_at_ms: 1700000600000, 648 + }; 649 + let bytes = val.serialize_with_ttl(); 650 + let ttl = u64::from_be_bytes(bytes[..8].try_into().unwrap()); 651 + assert_eq!(ttl, 1700000600000); 652 + let decoded = HandleReservationValue::deserialize(&bytes).unwrap(); 653 + assert_eq!(val, decoded); 654 + } 655 + 656 + #[test] 657 + fn passkey_index_value_roundtrip() { 658 + let id = uuid::Uuid::new_v4(); 659 + let val = PasskeyIndexValue { 660 + user_hash: 0xCAFE_BABE, 661 + passkey_id: id, 662 + }; 663 + let bytes = val.serialize(); 664 + let decoded = PasskeyIndexValue::deserialize(&bytes).unwrap(); 665 + assert_eq!(val, decoded); 666 + } 667 + 668 + #[test] 669 + fn key_functions_produce_distinct_prefixes() { 670 + let uh = UserHash::from_did("did:plc:test"); 671 + let id = uuid::Uuid::new_v4(); 672 + let keys: Vec<SmallVec<[u8; 128]>> = vec![ 673 + user_primary_key(uh), 674 + user_by_handle_key("test.handle"), 675 + user_by_email_key("test@email.com"), 676 + passkey_key(uh, id), 677 + passkey_by_cred_key(&[1, 2, 3]), 678 + totp_key(uh), 679 + backup_code_key(uh, id), 680 + webauthn_challenge_key(uh, 0), 681 + reset_code_key("code123"), 682 + recovery_token_key(uh), 683 + did_web_overrides_key(uh), 684 + handle_reservation_key("reserved.handle"), 685 + telegram_lookup_key("tg_user"), 686 + discord_lookup_key("dc_user"), 687 + ]; 688 + let tags: Vec<u8> = keys.iter().map(|k| k[0]).collect(); 689 + let mut unique_tags = tags.clone(); 690 + unique_tags.sort(); 691 + unique_tags.dedup(); 692 + assert!(unique_tags.len() >= 12); 693 + } 694 + 695 + #[test] 696 + fn passkey_prefix_is_prefix_of_key() { 697 + let uh = UserHash::from_did("did:plc:test"); 698 + let prefix = passkey_prefix(uh); 699 + let key = passkey_key(uh, uuid::Uuid::new_v4()); 700 + assert!(key.starts_with(prefix.as_slice())); 701 + } 702 + 703 + #[test] 704 + fn backup_code_prefix_is_prefix_of_key() { 705 + let uh = UserHash::from_did("did:plc:test"); 706 + let prefix = backup_code_prefix(uh); 707 + let key = backup_code_key(uh, uuid::Uuid::new_v4()); 708 + assert!(key.starts_with(prefix.as_slice())); 709 + } 710 + 711 + #[test] 712 + fn account_type_roundtrip() { 713 + assert_eq!( 714 + u8_to_account_type(account_type_to_u8( 715 + tranquil_db_traits::AccountType::Personal 716 + )), 717 + Some(tranquil_db_traits::AccountType::Personal) 718 + ); 719 + assert_eq!( 720 + u8_to_account_type(account_type_to_u8( 721 + tranquil_db_traits::AccountType::Delegated 722 + )), 723 + Some(tranquil_db_traits::AccountType::Delegated) 724 + ); 725 + assert_eq!(u8_to_account_type(99), None); 726 + } 727 + 728 + #[test] 729 + fn challenge_type_roundtrip() { 730 + use tranquil_db_traits::WebauthnChallengeType; 731 + assert_eq!( 732 + u8_to_challenge_type(challenge_type_to_u8(WebauthnChallengeType::Registration)), 733 + Some(WebauthnChallengeType::Registration) 734 + ); 735 + assert_eq!( 736 + u8_to_challenge_type(challenge_type_to_u8(WebauthnChallengeType::Authentication)), 737 + Some(WebauthnChallengeType::Authentication) 738 + ); 739 + assert_eq!(u8_to_challenge_type(99), None); 740 + } 741 + 742 + #[test] 743 + fn channel_verification_flags() { 744 + let mut user = UserValue { 745 + id: uuid::Uuid::new_v4(), 746 + did: "did:plc:test".to_owned(), 747 + handle: "t.invalid".to_owned(), 748 + email: None, 749 + email_verified: false, 750 + password_hash: None, 751 + created_at_ms: 0, 752 + deactivated_at_ms: None, 753 + takedown_ref: None, 754 + is_admin: false, 755 + preferred_comms_channel: None, 756 + key_bytes: vec![], 757 + encryption_version: 1, 758 + account_type: 0, 759 + password_required: false, 760 + two_factor_enabled: false, 761 + email_2fa_enabled: false, 762 + totp_enabled: false, 763 + allow_legacy_login: false, 764 + preferred_locale: None, 765 + invites_disabled: false, 766 + migrated_to_pds: None, 767 + migrated_at_ms: None, 768 + discord_username: None, 769 + discord_id: None, 770 + discord_verified: false, 771 + telegram_username: None, 772 + telegram_chat_id: None, 773 + telegram_verified: false, 774 + signal_username: None, 775 + signal_verified: false, 776 + delete_after_ms: None, 777 + }; 778 + assert_eq!(user.channel_verification(), 0); 779 + user.email_verified = true; 780 + assert_eq!(user.channel_verification(), 1); 781 + user.discord_verified = true; 782 + assert_eq!(user.channel_verification(), 3); 783 + user.telegram_verified = true; 784 + assert_eq!(user.channel_verification(), 7); 785 + user.signal_verified = true; 786 + assert_eq!(user.channel_verification(), 15); 787 + } 788 + 789 + #[test] 790 + fn deserialize_unknown_version_returns_none() { 791 + let val = UserValue { 792 + id: uuid::Uuid::new_v4(), 793 + did: String::new(), 794 + handle: String::new(), 795 + email: None, 796 + email_verified: false, 797 + password_hash: None, 798 + created_at_ms: 0, 799 + deactivated_at_ms: None, 800 + takedown_ref: None, 801 + is_admin: false, 802 + preferred_comms_channel: None, 803 + key_bytes: vec![], 804 + encryption_version: 0, 805 + account_type: 0, 806 + password_required: false, 807 + two_factor_enabled: false, 808 + email_2fa_enabled: false, 809 + totp_enabled: false, 810 + allow_legacy_login: false, 811 + preferred_locale: None, 812 + invites_disabled: false, 813 + migrated_to_pds: None, 814 + migrated_at_ms: None, 815 + discord_username: None, 816 + discord_id: None, 817 + discord_verified: false, 818 + telegram_username: None, 819 + telegram_chat_id: None, 820 + telegram_verified: false, 821 + signal_username: None, 822 + signal_verified: false, 823 + delete_after_ms: None, 824 + }; 825 + let mut bytes = val.serialize(); 826 + bytes[0] = 99; 827 + assert!(UserValue::deserialize(&bytes).is_none()); 828 + } 829 + }