From e41f34746a2e823e339dd181f3d84e9f5c41e0cc Mon Sep 17 00:00:00 2001 From: Lewis Date: Sun, 12 Jul 2026 08:03:14 +0200 Subject: [PATCH] db: newtype Rkey and final newtype touches for now Lewis: May this revision serve well! --- crates/tranquil-api/src/admin/account/info.rs | 2 +- crates/tranquil-api/src/admin/invite.rs | 6 +- crates/tranquil-api/src/common.rs | 2 +- crates/tranquil-api/src/delegation.rs | 2 - crates/tranquil-api/src/identity/account.rs | 26 ++++++-- crates/tranquil-api/src/identity/did.rs | 8 +-- crates/tranquil-api/src/identity/provision.rs | 3 +- crates/tranquil-api/src/moderation/mod.rs | 4 +- crates/tranquil-api/src/repo/import.rs | 4 +- .../tranquil-api/src/server/app_password.rs | 2 +- crates/tranquil-api/src/server/email.rs | 2 +- crates/tranquil-api/src/server/invite.rs | 9 +-- .../src/server/passkey_account.rs | 15 +++-- crates/tranquil-api/src/server/reauth.rs | 2 +- .../tranquil-api/src/server/service_auth.rs | 2 +- crates/tranquil-api/src/server/session.rs | 4 +- crates/tranquil-auth/src/types.rs | 4 +- crates/tranquil-comms/src/email/mod.rs | 10 +-- crates/tranquil-db-traits/src/repo.rs | 9 +-- crates/tranquil-db-traits/src/user.rs | 8 +-- crates/tranquil-db/src/postgres/blob.rs | 2 +- crates/tranquil-db/src/postgres/infra.rs | 63 ++++++++++++++----- crates/tranquil-db/src/postgres/oauth.rs | 13 ++-- crates/tranquil-db/src/postgres/repo.rs | 16 +++-- crates/tranquil-db/src/postgres/session.rs | 2 +- crates/tranquil-db/src/postgres/user.rs | 22 +++---- crates/tranquil-lexicon/src/dynamic.rs | 8 +-- crates/tranquil-lexicon/src/registry.rs | 2 +- crates/tranquil-lexicon/src/resolve.rs | 5 +- .../src/endpoints/authorize/consent.rs | 2 +- .../src/endpoints/authorize/login.rs | 30 ++++----- .../src/endpoints/authorize/passkey.rs | 31 +++++---- .../src/endpoints/authorize/registration.rs | 6 +- .../src/endpoints/authorize/two_factor.rs | 4 +- .../src/endpoints/delegation.rs | 2 +- .../src/endpoints/par.rs | 2 +- .../src/endpoints/token/grants.rs | 21 +++---- .../src/endpoints/token/helpers.rs | 8 +-- .../src/endpoints/token/mod.rs | 2 +- .../src/sso_endpoints.rs | 9 +-- crates/tranquil-oauth/src/types.rs | 59 +++++------------ crates/tranquil-pds/src/api/proxy.rs | 4 +- crates/tranquil-pds/src/api/validation.rs | 2 +- crates/tranquil-pds/src/auth/mfa_verified.rs | 2 +- crates/tranquil-pds/src/auth/mod.rs | 17 ++--- crates/tranquil-pds/src/handle/mod.rs | 14 ++--- crates/tranquil-pds/src/oauth/verify.rs | 4 +- crates/tranquil-pds/src/repo_ops.rs | 2 - crates/tranquil-pds/src/sync/frame.rs | 13 ++-- crates/tranquil-pds/src/sync/import.rs | 6 +- crates/tranquil-pds/src/sync/util.rs | 4 +- crates/tranquil-pds/src/validation/mod.rs | 4 +- .../tests/oauth_token_eviction.rs | 6 +- .../tranquil-pds/tests/record_validation.rs | 22 ++++--- crates/tranquil-pds/tests/scope_edge_cases.rs | 8 +-- crates/tranquil-pds/tests/store_parity.rs | 4 +- crates/tranquil-scopes/src/permission_set.rs | 5 -- crates/tranquil-scopes/src/permissions.rs | 20 +++--- crates/tranquil-store/src/eventlog/payload.rs | 5 +- crates/tranquil-store/src/metastore/client.rs | 42 ++++++------- .../src/metastore/commit_ops.rs | 2 +- .../tranquil-store/src/metastore/event_ops.rs | 9 +-- .../tranquil-store/src/metastore/handler.rs | 4 +- .../tranquil-store/src/metastore/infra_ops.rs | 4 +- .../tranquil-store/src/metastore/oauth_ops.rs | 6 +- .../tranquil-store/src/metastore/user_ops.rs | 2 +- crates/tranquil-sync/src/blob.rs | 8 +-- crates/tranquil-sync/src/commit.rs | 5 +- 68 files changed, 326 insertions(+), 331 deletions(-) diff --git a/crates/tranquil-api/src/admin/account/info.rs b/crates/tranquil-api/src/admin/account/info.rs index 268aec2..2400a5d 100644 --- a/crates/tranquil-api/src/admin/account/info.rs +++ b/crates/tranquil-api/src/admin/account/info.rs @@ -204,7 +204,7 @@ pub async fn get_account_infos( let accounts = state .repos .infra - .get_admin_account_infos_by_dids(&dids_typed) + .get_admin_account_infos_by_dids(&dids) .await .log_db_err("fetching account infos")?; diff --git a/crates/tranquil-api/src/admin/invite.rs b/crates/tranquil-api/src/admin/invite.rs index 1de24d7..e36dcec 100644 --- a/crates/tranquil-api/src/admin/invite.rs +++ b/crates/tranquil-api/src/admin/invite.rs @@ -29,10 +29,8 @@ pub async fn disable_invite_codes( { error!("DB error disabling invite codes: {:?}", e); } - if let Some(accounts) = &input.accounts { - let accounts_typed: Vec = - accounts.iter().filter_map(|a| a.parse().ok()).collect(); - if let Err(e) = state + if let Some(accounts) = &input.accounts + && let Err(e) = state .repos .infra .disable_invite_codes_by_account(accounts) diff --git a/crates/tranquil-api/src/common.rs b/crates/tranquil-api/src/common.rs index 45f446c..c5df7c4 100644 --- a/crates/tranquil-api/src/common.rs +++ b/crates/tranquil-api/src/common.rs @@ -219,7 +219,7 @@ pub async fn verify_credential( password_hash: Option<&PasswordHash>, ) -> Option { let main_valid = password_hash - .map(|h| bcrypt::verify(password, h).unwrap_or(false)) + .map(|h| bcrypt::verify(password, h.as_str()).unwrap_or(false)) .unwrap_or(false); if main_valid { return Some(CredentialMatch::MainPassword); diff --git a/crates/tranquil-api/src/delegation.rs b/crates/tranquil-api/src/delegation.rs index e56e95c..f4390af 100644 --- a/crates/tranquil-api/src/delegation.rs +++ b/crates/tranquil-api/src/delegation.rs @@ -471,8 +471,6 @@ pub async fn resolve_controller( Some(user) => user.did, None => tranquil_pds::handle::resolve_handle(&handle) .await - .map_err(|_| ApiError::ControllerNotFound)? - .parse() .map_err(|_| ApiError::ControllerNotFound)?, } }; diff --git a/crates/tranquil-api/src/identity/account.rs b/crates/tranquil-api/src/identity/account.rs index 6c4b8cb..2eb9d70 100644 --- a/crates/tranquil-api/src/identity/account.rs +++ b/crates/tranquil-api/src/identity/account.rs @@ -204,7 +204,7 @@ pub async fn create_account( let verifier = ServiceTokenVerifier::new(); let create_account_lxm = Nsid::from("com.atproto.server.createAccount".to_string()); match verifier - .verify_service_token(&token, Some("com.atproto.server.createAccount")) + .verify_service_token(&token, Some(&create_account_lxm)) .await { Ok(claims) => { @@ -301,7 +301,7 @@ pub async fn create_account( }; let hostname = &cfg.server.hostname; let key_result = - match super::provision::resolve_signing_key(&state, input.signing_key.as_deref()).await { + match super::provision::resolve_signing_key(&state, input.signing_key.as_ref()).await { Ok(k) => k, Err(e) => return e.into_response(), }; @@ -339,13 +339,22 @@ pub async fn create_account( return ApiError::InvalidDid(e.to_string()).into_response(); } info!(did = %d, "Creating external did:web account"); - d.clone() + match d.parse() { + Ok(d) => d, + Err(_) => return ApiError::InvalidDid("Invalid DID format".into()).into_response(), + } } _ => { if let Some(d) = &input.did { if d.starts_with("did:plc:") && is_migration { info!(did = %d, "Migration with existing did:plc"); - d.clone() + match d.parse() { + Ok(d) => d, + Err(_) => { + return ApiError::InvalidDid("Invalid DID format".into()) + .into_response(); + } + } } else if d.starts_with("did:web:") { if !is_did_web_byod && let Err(e) = @@ -354,7 +363,13 @@ pub async fn create_account( { return ApiError::InvalidDid(e.to_string()).into_response(); } - d.clone() + match d.parse() { + Ok(d) => d, + Err(_) => { + return ApiError::InvalidDid("Invalid DID format".into()) + .into_response(); + } + } } else if !d.trim().is_empty() { return ApiError::InvalidDid( "Only did:web DIDs can be provided; leave empty for did:plc. For migration with existing did:plc, provide service auth.".into() @@ -528,7 +543,6 @@ pub async fn create_account( let session = match super::provision::create_and_store_session( &state, &did, - &did_for_commit, &secret_key_bytes, "transition:generic transition:chat.bsky", None, diff --git a/crates/tranquil-api/src/identity/did.rs b/crates/tranquil-api/src/identity/did.rs index f712728..b2d2551 100644 --- a/crates/tranquil-api/src/identity/did.rs +++ b/crates/tranquil-api/src/identity/did.rs @@ -167,7 +167,7 @@ async fn serve_handle_did_doc(state: &AppState, handle: &str, hostname: &str) -> let user = match state .repos .user - .get_user_for_did_doc_build(&expected_did_typed) + .get_user_for_did_doc_build(&expected_did) .await { Ok(Some(u)) => u, @@ -221,7 +221,7 @@ pub async fn user_did_doc(State(state): State, Path(handle): Path u, @@ -627,10 +627,6 @@ pub async fn update_handle( .parse() .map_err(|_| ApiError::InvalidHandle(Some("Invalid handle format".into())))?; if new_handle == current_handle { - let handle_typed: Handle = match new_handle.parse() { - Ok(h) => h, - Err(_) => return Err(ApiError::InvalidHandle(None)), - }; if let Err(e) = tranquil_pds::repo_ops::sequence_identity_event(&state, &did, Some(&handle)).await { diff --git a/crates/tranquil-api/src/identity/provision.rs b/crates/tranquil-api/src/identity/provision.rs index feeb19f..3e1c7dd 100644 --- a/crates/tranquil-api/src/identity/provision.rs +++ b/crates/tranquil-api/src/identity/provision.rs @@ -255,7 +255,6 @@ pub struct SessionResult { pub async fn create_and_store_session( state: &AppState, - did_str: &str, did: &Did, signing_key_bytes: &[u8], scope: &str, @@ -269,7 +268,7 @@ pub async fn create_and_store_session( let refresh_meta = tranquil_pds::auth::create_refresh_token_with_metadata(did, signing_key_bytes).map_err( |e| { - tracing::error!("Error creating access token: {:?}", e); + tracing::error!("Error creating refresh token: {:?}", e); ApiError::InternalError(None) }, )?; diff --git a/crates/tranquil-api/src/moderation/mod.rs b/crates/tranquil-api/src/moderation/mod.rs index c9592d1..ae6d604 100644 --- a/crates/tranquil-api/src/moderation/mod.rs +++ b/crates/tranquil-api/src/moderation/mod.rs @@ -6,7 +6,7 @@ use axum::{ }; use serde::{Deserialize, Serialize}; use serde_json::{Value, json}; -use tracing::{error, info}; +use tracing::{error, info, warn}; use tranquil_pds::api::ApiError; use tranquil_pds::api::proxy_client::{is_ssrf_safe, proxy_client}; use tranquil_pds::auth::{AnyUser, Auth}; @@ -149,7 +149,7 @@ async fn proxy_to_report_service( let service_token = match tranquil_pds::auth::create_service_token( &auth_user.did, service_did, - Some("com.atproto.moderation.createReport"), + Some(&report_lxm), &key_bytes, ) { Ok(t) => t, diff --git a/crates/tranquil-api/src/repo/import.rs b/crates/tranquil-api/src/repo/import.rs index fde45a0..f5f281a 100644 --- a/crates/tranquil-api/src/repo/import.rs +++ b/crates/tranquil-api/src/repo/import.rs @@ -237,7 +237,7 @@ pub async fn import_repo( state .repos .repo - .update_repo_root(user_id, &new_root_cid_link, &new_rev_str) + .update_repo_root(user_id, &new_root_cid_link, &new_rev_tid) .await .map_err(|e| { error!("Failed to update repo root: {:?}", e); @@ -248,7 +248,7 @@ pub async fn import_repo( state .repos .repo - .insert_user_blocks(user_id, &all_block_cids, &new_rev_str) + .insert_user_blocks(user_id, &all_block_cids, &new_rev_tid) .await .map_err(|e| { error!("Failed to insert user_blocks: {:?}", e); diff --git a/crates/tranquil-api/src/server/app_password.rs b/crates/tranquil-api/src/server/app_password.rs index dacee00..291cca5 100644 --- a/crates/tranquil-api/src/server/app_password.rs +++ b/crates/tranquil-api/src/server/app_password.rs @@ -191,7 +191,7 @@ pub async fn create_app_password( } Ok(Json(CreateAppPasswordOutput { name: name.to_string(), - password, + password: password.into_inner(), created_at: created_at.to_rfc3339(), privileged: privilege.is_privileged(), scopes: final_scopes, diff --git a/crates/tranquil-api/src/server/email.rs b/crates/tranquil-api/src/server/email.rs index 6237785..401ae06 100644 --- a/crates/tranquil-api/src/server/email.rs +++ b/crates/tranquil-api/src/server/email.rs @@ -395,7 +395,7 @@ pub async fn update_email( #[derive(Deserialize)] pub struct CheckEmailVerifiedInput { - pub identifier: String, + pub identifier: AtIdentifier, } pub async fn check_email_verified( diff --git a/crates/tranquil-api/src/server/invite.rs b/crates/tranquil-api/src/server/invite.rs index b435b1d..28cdb3b 100644 --- a/crates/tranquil-api/src/server/invite.rs +++ b/crates/tranquil-api/src/server/invite.rs @@ -18,7 +18,7 @@ pub struct CreateInviteCodeInput { #[derive(Serialize)] pub struct CreateInviteCodeOutput { - pub code: String, + pub code: InviteCodeValue, } pub async fn create_invite_code( @@ -117,10 +117,7 @@ pub async fn create_invite_codes( infra_repo .create_invite_codes_batch(&codes, use_count, admin_user_id, Some(&account)) .await - .map(|_| AccountCodes { - account: account.to_string(), - codes, - }) + .map(|_| AccountCodes { account, codes }) } })) .await; @@ -146,7 +143,7 @@ pub struct GetAccountInviteCodesParams { #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct InviteCode { - pub code: String, + pub code: InviteCodeValue, pub available: i32, pub disabled: bool, pub for_account: String, diff --git a/crates/tranquil-api/src/server/passkey_account.rs b/crates/tranquil-api/src/server/passkey_account.rs index 5f4dc2e..bddfb30 100644 --- a/crates/tranquil-api/src/server/passkey_account.rs +++ b/crates/tranquil-api/src/server/passkey_account.rs @@ -71,7 +71,7 @@ pub async fn create_passkey_account( let verifier = ServiceTokenVerifier::new(); let create_account_lxm = Nsid::from("com.atproto.server.createAccount".to_string()); match verifier - .verify_service_token(&token, Some("com.atproto.server.createAccount")) + .verify_service_token(&token, Some(&create_account_lxm)) .await { Ok(claims) => { @@ -144,7 +144,7 @@ pub async fn create_passkey_account( let did_type = input.did_type.as_deref().unwrap_or("plc"); let key_result = - match crate::identity::provision::resolve_signing_key(&state, input.signing_key.as_deref()) + match crate::identity::provision::resolve_signing_key(&state, input.signing_key.as_ref()) .await { Ok(k) => k, @@ -192,7 +192,7 @@ pub async fn create_passkey_account( d, hostname, &input.handle, - input.signing_key.as_deref(), + input.signing_key.as_ref(), ) .await { @@ -200,7 +200,8 @@ pub async fn create_passkey_account( } info!(did = %d, "Creating external did:web passkey account (reserved key)"); } - d.to_string() + d.parse() + .map_err(|_| ApiError::InvalidDid("Invalid DID format".into()))? } _ => { if let Some(ref auth_did) = byod_auth { @@ -213,7 +214,9 @@ pub async fn create_passkey_account( ))); } info!(did = %provided_did, "Creating BYOD did:plc passkey account (migration)"); - provided_did.clone() + provided_did + .parse() + .map_err(|_| ApiError::InvalidDid("Invalid DID format".into()))? } else { return Err(ApiError::InvalidRequest( "BYOD migration requires a did:plc or did:web DID".into(), @@ -545,7 +548,7 @@ pub async fn complete_passkey_setup( Ok(Json(CompletePasskeySetupOutput { did: input.did.clone(), handle: user.handle, - app_password, + app_password: app_password.into_inner(), app_password_name, })) } diff --git a/crates/tranquil-api/src/server/reauth.rs b/crates/tranquil-api/src/server/reauth.rs index 0b721b5..154f958 100644 --- a/crates/tranquil-api/src/server/reauth.rs +++ b/crates/tranquil-api/src/server/reauth.rs @@ -85,7 +85,7 @@ pub async fn reauth_password( .unwrap_or_default(); let app_password_valid = app_password_hashes.iter().fold(false, |acc, h| { - acc | bcrypt::verify(&input.password, h).unwrap_or(false) + acc | bcrypt::verify(&input.password, h.as_str()).unwrap_or(false) }); if !app_password_valid { diff --git a/crates/tranquil-api/src/server/service_auth.rs b/crates/tranquil-api/src/server/service_auth.rs index d9f48ce..da08672 100644 --- a/crates/tranquil-api/src/server/service_auth.rs +++ b/crates/tranquil-api/src/server/service_auth.rs @@ -118,7 +118,7 @@ pub async fn get_service_auth( &auth.auth_source, auth.scope.as_deref(), params.aud.as_str(), - method.as_str(), + method, ) { return e.into_response(); } diff --git a/crates/tranquil-api/src/server/session.rs b/crates/tranquil-api/src/server/session.rs index 96d8d40..e772f40 100644 --- a/crates/tranquil-api/src/server/session.rs +++ b/crates/tranquil-api/src/server/session.rs @@ -91,7 +91,7 @@ pub async fn create_session( let row = match state .repos .user - .get_login_full_by_identifier(normalized_identifier.as_str()) + .get_login_full_by_identifier(&login_identifier) .await { Ok(Some(row)) => row, @@ -277,7 +277,7 @@ pub async fn create_session( &row.did, &key_bytes, app_password_scopes.as_deref(), - app_password_controller.as_deref(), + app_password_controller.as_ref(), None, ) { Ok(m) => m, diff --git a/crates/tranquil-auth/src/types.rs b/crates/tranquil-auth/src/types.rs index a41146d..f0c87d6 100644 --- a/crates/tranquil-auth/src/types.rs +++ b/crates/tranquil-auth/src/types.rs @@ -248,14 +248,14 @@ pub struct TokenWithMetadata { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TokenVerifyError { Expired, - Invalid, + Invalid(&'static str), } impl fmt::Display for TokenVerifyError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Expired => write!(f, "Token expired"), - Self::Invalid => write!(f, "Token invalid"), + Self::Invalid(reason) => write!(f, "{}", reason), } } } diff --git a/crates/tranquil-comms/src/email/mod.rs b/crates/tranquil-comms/src/email/mod.rs index b611a6c..91a309b 100644 --- a/crates/tranquil-comms/src/email/mod.rs +++ b/crates/tranquil-comms/src/email/mod.rs @@ -129,10 +129,12 @@ fn build_smarthost( fn build_direct_mx(cfg: &tranquil_config::TranquilConfig) -> Result { let helo = resolve_helo(cfg)?; - let resolver = Arc::new(TokioAsyncResolver::tokio_from_system_conf().unwrap_or_else(|e| { - tracing::warn!("falling back to default DNS resolvers: {}", e); - TokioAsyncResolver::tokio(ResolverConfig::default(), ResolverOpts::default()) - })); + let resolver = Arc::new( + TokioAsyncResolver::tokio_from_system_conf().unwrap_or_else(|e| { + tracing::warn!("falling back to default DNS resolvers: {}", e); + TokioAsyncResolver::tokio(ResolverConfig::default(), ResolverOpts::default()) + }), + ); let max_concurrent = cfg.email.direct_mx.max_concurrent_sends.max(1); Ok(SendMode::DirectMx { resolver, diff --git a/crates/tranquil-db-traits/src/repo.rs b/crates/tranquil-db-traits/src/repo.rs index 4a324c3..b7dc58b 100644 --- a/crates/tranquil-db-traits/src/repo.rs +++ b/crates/tranquil-db-traits/src/repo.rs @@ -51,14 +51,7 @@ impl AccountStatus { } } - pub fn for_firehose(&self) -> Option<&'static str> { - match self { - Self::Active => None, - other => Some(other.as_str()), - } - } - - pub fn for_firehose_typed(&self) -> Option { + pub fn for_firehose(&self) -> Option { match self { Self::Active => None, other => Some(*other), diff --git a/crates/tranquil-db-traits/src/user.rs b/crates/tranquil-db-traits/src/user.rs index a3598e6..3ef0ec8 100644 --- a/crates/tranquil-db-traits/src/user.rs +++ b/crates/tranquil-db-traits/src/user.rs @@ -149,12 +149,12 @@ pub trait UserRepository: Send + Sync { async fn get_login_check_by_identifier( &self, - identifier: &str, + identifier: &AtIdentifier, ) -> Result, DbError>; async fn get_login_info_by_identifier( &self, - identifier: &str, + identifier: &AtIdentifier, ) -> Result, DbError>; async fn get_2fa_status_by_did(&self, did: &Did) -> Result, DbError>; @@ -211,7 +211,7 @@ pub trait UserRepository: Send + Sync { async fn check_email_verified_by_identifier( &self, - identifier: &str, + identifier: &AtIdentifier, ) -> Result, DbError>; async fn check_channel_verified_by_did( @@ -428,7 +428,7 @@ pub trait UserRepository: Send + Sync { async fn get_login_full_by_identifier( &self, - identifier: &str, + identifier: &AtIdentifier, ) -> Result, DbError>; async fn get_confirm_signup_by_did( diff --git a/crates/tranquil-db/src/postgres/blob.rs b/crates/tranquil-db/src/postgres/blob.rs index d7cfd4c..f5428b4 100644 --- a/crates/tranquil-db/src/postgres/blob.rs +++ b/crates/tranquil-db/src/postgres/blob.rs @@ -118,7 +118,7 @@ impl BlobRepository for PostgresBlobRepository { FROM repo_seq WHERE did = $1 AND rev > $2 AND blobs IS NOT NULL"#, did.as_str(), - since + since.as_str() ) .fetch_all(&self.pool) .await diff --git a/crates/tranquil-db/src/postgres/infra.rs b/crates/tranquil-db/src/postgres/infra.rs index 9f6130a..fe8e3a3 100644 --- a/crates/tranquil-db/src/postgres/infra.rs +++ b/crates/tranquil-db/src/postgres/infra.rs @@ -159,7 +159,7 @@ impl InfraRepository for PostgresInfraRepository { let result = sqlx::query!( r#"INSERT INTO invite_codes (code, available_uses, created_by_user, for_account) SELECT $1, $2, id, $3 FROM users WHERE is_admin = true LIMIT 1"#, - code, + code.as_str(), use_count, for_account_str ) @@ -200,7 +200,7 @@ impl InfraRepository for PostgresInfraRepository { ) -> Result, DbError> { let result = sqlx::query_scalar!( "SELECT available_uses FROM invite_codes WHERE code = $1 FOR UPDATE", - code + code.as_str() ) .fetch_optional(&self.pool) .await @@ -215,7 +215,7 @@ impl InfraRepository for PostgresInfraRepository { ) -> Result, InviteCodeError> { let result = sqlx::query!( r#"SELECT available_uses, COALESCE(disabled, false) as "disabled!" FROM invite_codes WHERE code = $1"#, - code + code.as_str() ) .fetch_optional(&self.pool) .await @@ -270,7 +270,7 @@ impl InfraRepository for PostgresInfraRepository { JOIN users u ON icu.used_by_user = u.id WHERE icu.code = $1 ORDER BY icu.used_at DESC"#, - code + code.as_str() ) .fetch_all(&self.pool) .await @@ -279,7 +279,7 @@ impl InfraRepository for PostgresInfraRepository { Ok(results .into_iter() .map(|r| InviteCodeUse { - code: code.to_string(), + code: code.clone(), used_by_did: Did::from(r.did), used_by_handle: Some(Handle::from(r.handle)), used_at: r.used_at, @@ -348,9 +348,19 @@ impl InfraRepository for PostgresInfraRepository { ) .fetch_all(&self.pool) .await - .map_err(map_sqlx_error)?, - (None, InviteCodeSortOrder::Recent) => sqlx::query_as!( - InviteCodeRow, + .map_err(map_sqlx_error)? + .into_iter() + .map(|r| { + to_row( + r.code, + r.available_uses, + r.disabled, + r.created_by_user, + r.created_at, + ) + }) + .collect(), + (None, InviteCodeSortOrder::Recent) => sqlx::query!( r#"SELECT ic.code, ic.available_uses, ic.disabled, ic.created_by_user, ic.created_at FROM invite_codes ic ORDER BY created_at DESC @@ -382,9 +392,19 @@ impl InfraRepository for PostgresInfraRepository { ) .fetch_all(&self.pool) .await - .map_err(map_sqlx_error)?, - (None, InviteCodeSortOrder::Usage) => sqlx::query_as!( - InviteCodeRow, + .map_err(map_sqlx_error)? + .into_iter() + .map(|r| { + to_row( + r.code, + r.available_uses, + r.disabled, + r.created_by_user, + r.created_at, + ) + }) + .collect(), + (None, InviteCodeSortOrder::Usage) => sqlx::query!( r#"SELECT ic.code, ic.available_uses, ic.disabled, ic.created_by_user, ic.created_at FROM invite_codes ic ORDER BY available_uses DESC @@ -393,7 +413,18 @@ impl InfraRepository for PostgresInfraRepository { ) .fetch_all(&self.pool) .await - .map_err(map_sqlx_error)?, + .map_err(map_sqlx_error)? + .into_iter() + .map(|r| { + to_row( + r.code, + r.available_uses, + r.disabled, + r.created_by_user, + r.created_at, + ) + }) + .collect(), }; Ok(results) @@ -476,7 +507,7 @@ impl InfraRepository for PostgresInfraRepository { FROM invite_codes ic JOIN users u ON ic.created_by_user = u.id WHERE ic.code = $1"#, - code + code.as_str() ) .fetch_optional(&self.pool) .await @@ -578,7 +609,7 @@ impl InfraRepository for PostgresInfraRepository { VALUES ($1, $2, $3, $4) RETURNING id"#, did_str, - public_key_did_key, + public_key_did_key.as_str(), private_key_bytes, expires_at ) @@ -600,7 +631,7 @@ impl InfraRepository for PostgresInfraRepository { AND used_at IS NULL AND expires_at > NOW() FOR UPDATE"#, - public_key_did_key + public_key_did_key.as_str() ) .fetch_optional(&self.pool) .await @@ -1165,7 +1196,7 @@ impl InfraRepository for PostgresInfraRepository { let row = sqlx::query!( r#"SELECT id, did, public_key_did_key, private_key_bytes, expires_at, used_at FROM reserved_signing_keys WHERE public_key_did_key = $1"#, - public_key_did_key + public_key_did_key.as_str() ) .fetch_optional(&self.pool) .await diff --git a/crates/tranquil-db/src/postgres/oauth.rs b/crates/tranquil-db/src/postgres/oauth.rs index dfcc9d9..d26aa84 100644 --- a/crates/tranquil-db/src/postgres/oauth.rs +++ b/crates/tranquil-db/src/postgres/oauth.rs @@ -7,9 +7,8 @@ use tranquil_db_traits::{ ScopePreference, TokenFamilyId, TrustedDeviceRow, TwoFactorChallenge, }; use tranquil_oauth::{ - AuthorizationRequestParameters, AuthorizedClientData, ClientAuth, Code as OAuthCode, - DeviceData, DeviceId as OAuthDeviceId, RefreshToken as OAuthRefreshToken, RequestData, - SessionId as OAuthSessionId, TokenData, TokenId as OAuthTokenId, + AuthorizationRequestParameters, AuthorizedClientData, ClientAuth, DeviceData, RequestData, + SessionId as OAuthSessionId, TokenData, }; use tranquil_types::{ AuthorizationCode, ClientId, DPoPProofId, DeviceId, Did, Handle, RefreshToken, RequestId, @@ -61,7 +60,7 @@ impl OAuthRepository for PostgresOAuthRepository { RETURNING id "#, data.did.as_str(), - &data.token_id.0, + data.token_id.as_str(), data.created_at, data.updated_at, data.expires_at, @@ -442,7 +441,7 @@ impl OAuthRepository for PostgresOAuthRepository { client_auth_json, parameters_json, data.expires_at, - data.code.as_ref().map(|c| c.0.as_str()), + data.code.as_deref(), ) .execute(&self.pool) .await @@ -720,7 +719,7 @@ impl OAuthRepository for PostgresOAuthRepository { VALUES ($1, $2, $3, $4, $5) "#, device_id.as_str(), - &data.session_id.0, + data.session_id.as_str(), data.user_agent, data.ip_address, data.last_seen_at, @@ -744,7 +743,7 @@ impl OAuthRepository for PostgresOAuthRepository { .await .map_err(map_sqlx_error)?; Ok(row.map(|r| DeviceData { - session_id: OAuthSessionId(r.session_id), + session_id: OAuthSessionId::from(r.session_id), user_agent: r.user_agent, ip_address: r.ip_address, last_seen_at: r.last_seen_at, diff --git a/crates/tranquil-db/src/postgres/repo.rs b/crates/tranquil-db/src/postgres/repo.rs index 607d952..c63cc29 100644 --- a/crates/tranquil-db/src/postgres/repo.rs +++ b/crates/tranquil-db/src/postgres/repo.rs @@ -770,7 +770,7 @@ impl RepoRepository for PostgresRepoRepository { "#, ) .bind(user_id) - .bind(since_rev) + .bind(since_rev.as_str()) .fetch_all(&self.pool) .await .map_err(map_sqlx_error)?; @@ -780,6 +780,10 @@ impl RepoRepository for PostgresRepoRepository { async fn insert_commit_event(&self, data: &CommitEventData) -> Result<(), DbError> { let (block_cids, block_data) = inline_to_paired_blocks(data.blocks.as_deref()); + let blob_strs: Option> = data + .blobs + .as_ref() + .map(|blobs| blobs.iter().map(|c| c.to_string()).collect()); sqlx::query!( r#" INSERT INTO repo_seq (did, event_type, commit_cid, prev_cid, ops, blobs, block_cids, block_data, prev_data_cid, rev) @@ -790,7 +794,7 @@ impl RepoRepository for PostgresRepoRepository { data.commit_cid.as_ref().map(|c| c.as_str()), data.prev_cid.as_ref().map(|c| c.as_str()), data.ops, - data.blobs.as_deref(), + blob_strs.as_deref(), &block_cids as &[Vec], &block_data as &[Vec], data.prev_data_cid.as_ref().map(|c| c.as_str()), @@ -828,7 +832,7 @@ impl RepoRepository for PostgresRepoRepository { async fn insert_account_event(&self, did: &Did, status: AccountStatus) -> Result<(), DbError> { let active = status.is_active(); - let status_str = status.for_firehose(); + let status_str = status.for_firehose().map(|s| s.as_str()); sqlx::query!( r#" INSERT INTO repo_seq (did, event_type, active, status) @@ -1467,6 +1471,10 @@ impl RepoRepository for PostgresRepoRepository { let event = input.commit_event; let (event_block_cids, event_block_data) = inline_into_paired_blocks(event.blocks); + let event_blob_strs: Option> = event + .blobs + .as_ref() + .map(|blobs| blobs.iter().map(|c| c.to_string()).collect()); sqlx::query!( r#" INSERT INTO repo_seq (did, event_type, commit_cid, prev_cid, ops, blobs, block_cids, block_data, prev_data_cid, rev) @@ -1477,7 +1485,7 @@ impl RepoRepository for PostgresRepoRepository { event.commit_cid.as_ref().map(|c| c.as_str()), event.prev_cid.as_ref().map(|c| c.as_str()), event.ops, - event.blobs.as_deref(), + event_blob_strs.as_deref(), &event_block_cids as &[Vec], &event_block_data as &[Vec], event.prev_data_cid.as_ref().map(|c| c.as_str()), diff --git a/crates/tranquil-db/src/postgres/session.rs b/crates/tranquil-db/src/postgres/session.rs index 2bed5bf..9a5a381 100644 --- a/crates/tranquil-db/src/postgres/session.rs +++ b/crates/tranquil-db/src/postgres/session.rs @@ -161,7 +161,7 @@ impl SessionRepository for PostgresSessionRepository { let result = sqlx::query!( "DELETE FROM session_tokens WHERE did = $1 AND access_jti != $2", did.as_str(), - except_jti + except_jti.as_str() ) .execute(&self.pool) .await diff --git a/crates/tranquil-db/src/postgres/user.rs b/crates/tranquil-db/src/postgres/user.rs index 9802931..ab8999c 100644 --- a/crates/tranquil-db/src/postgres/user.rs +++ b/crates/tranquil-db/src/postgres/user.rs @@ -201,7 +201,7 @@ impl UserRepository for PostgresUserRepository { JOIN users u ON t.did = u.did LEFT JOIN user_keys k ON u.id = k.user_id WHERE t.token_id = $1"#, - token_id + token_id.as_str() ) .fetch_optional(&self.pool) .await @@ -588,11 +588,11 @@ impl UserRepository for PostgresUserRepository { async fn check_email_verified_by_identifier( &self, - identifier: &str, + identifier: &AtIdentifier, ) -> Result, DbError> { let row = sqlx::query_scalar!( "SELECT email_verified FROM users WHERE did = $1 OR email = $1 OR handle = $1", - identifier + identifier.as_str() ) .fetch_optional(&self.pool) .await @@ -1423,11 +1423,11 @@ impl UserRepository for PostgresUserRepository { async fn get_login_check_by_identifier( &self, - identifier: &str, + identifier: &AtIdentifier, ) -> Result, DbError> { sqlx::query!( "SELECT did, password_hash FROM users WHERE handle = $1 OR did = $1", - identifier + identifier.as_str() ) .fetch_optional(&self.pool) .await @@ -1442,7 +1442,7 @@ impl UserRepository for PostgresUserRepository { async fn get_login_info_by_identifier( &self, - identifier: &str, + identifier: &AtIdentifier, ) -> Result, DbError> { sqlx::query!( r#" @@ -1454,7 +1454,7 @@ impl UserRepository for PostgresUserRepository { FROM users WHERE handle = $1 OR did = $1 "#, - identifier + identifier.as_str() ) .fetch_optional(&self.pool) .await @@ -1602,7 +1602,7 @@ impl UserRepository for PostgresUserRepository { async fn get_login_full_by_identifier( &self, - identifier: &str, + identifier: &AtIdentifier, ) -> Result, DbError> { sqlx::query!( r#"SELECT @@ -1616,7 +1616,7 @@ impl UserRepository for PostgresUserRepository { FROM users u JOIN user_keys k ON u.id = k.user_id WHERE u.handle = $1 OR u.did = $1"#, - identifier + identifier.as_str() ) .fetch_optional(&self.pool) .await @@ -3236,7 +3236,7 @@ impl UserRepository for PostgresUserRepository { "UPDATE users SET discord_id = $2, discord_verified = TRUE, updated_at = NOW() WHERE LOWER(discord_username) = LOWER($1) AND discord_username IS NOT NULL AND handle = $3 RETURNING id", discord_username, discord_id, - h + h.as_str() ) .fetch_optional(&self.pool) .await @@ -3298,7 +3298,7 @@ impl UserRepository for PostgresUserRepository { "UPDATE users SET telegram_chat_id = $2, telegram_verified = TRUE, updated_at = NOW() WHERE LOWER(telegram_username) = LOWER($1) AND telegram_username IS NOT NULL AND handle = $3 RETURNING id", telegram_username, chat_id, - h + h.as_str() ) .fetch_optional(&self.pool) .await diff --git a/crates/tranquil-lexicon/src/dynamic.rs b/crates/tranquil-lexicon/src/dynamic.rs index 7130ae4..96581e2 100644 --- a/crates/tranquil-lexicon/src/dynamic.rs +++ b/crates/tranquil-lexicon/src/dynamic.rs @@ -341,8 +341,8 @@ mod tests { .unwrap_err(); assert!( - !matches!(err, ResolveError::InvalidNsid(_)), - "negative cache hit should not return InvalidNsid - the NSID is valid, it just failed resolution recently. got: {}", + matches!(err, ResolveError::NegativelyCached { .. }), + "negative cache hit must surface as NegativelyCached, got: {}", err ); } @@ -438,7 +438,7 @@ mod tests { let result = registry .resolve_and_cache_with(&nsid("pet.nel.flaky"), |n| async move { Err::(ResolveError::DnsLookup { - domain: n, + domain: n.into_inner(), reason: "simulated failure".to_string(), }) }) @@ -597,7 +597,7 @@ mod tests { calls.fetch_add(1, Ordering::SeqCst); tokio::time::sleep(Duration::from_millis(50)).await; Err::(ResolveError::DnsLookup { - domain: n, + domain: n.into_inner(), reason: "simulated".to_string(), }) } diff --git a/crates/tranquil-lexicon/src/registry.rs b/crates/tranquil-lexicon/src/registry.rs index ff6d68a..2fa209d 100644 --- a/crates/tranquil-lexicon/src/registry.rs +++ b/crates/tranquil-lexicon/src/registry.rs @@ -70,7 +70,7 @@ impl LexiconRegistry { pub fn resolve_ref(&self, reference: &str, context_nsid: &str) -> Option { match parse_ref(reference) { ParsedRef::Local(local) => { - let doc = self.get_doc(context_nsid)?; + let doc = self.get_doc_by_key(context_nsid)?; Self::def_to_resolved(&doc, local) } ParsedRef::Qualified { nsid, fragment } => { diff --git a/crates/tranquil-lexicon/src/resolve.rs b/crates/tranquil-lexicon/src/resolve.rs index d5546bf..4662223 100644 --- a/crates/tranquil-lexicon/src/resolve.rs +++ b/crates/tranquil-lexicon/src/resolve.rs @@ -53,8 +53,6 @@ async fn read_body_limited(resp: reqwest::Response, max_bytes: usize) -> Result< #[derive(Debug, thiserror::Error)] pub enum ResolveError { - #[error("failed to derive authority from NSID: {0}")] - InvalidNsid(String), #[error("DNS lookup failed for {domain}: {reason}")] DnsLookup { domain: String, reason: String }, #[error("no DID found in DNS TXT records for {domain}")] @@ -79,7 +77,7 @@ pub fn nsid_to_authority(nsid: &Nsid) -> String { let mut segments: Vec<&str> = nsid.split('.').collect(); segments.pop(); segments.reverse(); - Ok(segments.join(".")) + segments.join(".") } pub async fn resolve_did_from_dns(authority: &str) -> Result { @@ -308,7 +306,6 @@ mod tests { nsid_to_authority(&nsid("com.germnetwork.social.post")), "social.germnetwork.com" ); - assert!(nsid_to_authority("tooShort").is_err()); } #[test] diff --git a/crates/tranquil-oauth-server/src/endpoints/authorize/consent.rs b/crates/tranquil-oauth-server/src/endpoints/authorize/consent.rs index 2ac5ff2..7b4176c 100644 --- a/crates/tranquil-oauth-server/src/endpoints/authorize/consent.rs +++ b/crates/tranquil-oauth-server/src/endpoints/authorize/consent.rs @@ -420,7 +420,7 @@ pub async fn consent_post( let redirect_uri = &request_data.parameters.redirect_uri; let intermediate_url = build_intermediate_redirect_url( redirect_uri, - &code.0, + code.as_str(), request_data.parameters.state.as_deref(), request_data.parameters.response_mode.map(|m| m.as_str()), ); diff --git a/crates/tranquil-oauth-server/src/endpoints/authorize/login.rs b/crates/tranquil-oauth-server/src/endpoints/authorize/login.rs index 01afa28..0e3bd8d 100644 --- a/crates/tranquil-oauth-server/src/endpoints/authorize/login.rs +++ b/crates/tranquil-oauth-server/src/endpoints/authorize/login.rs @@ -105,12 +105,12 @@ pub async fn authorize_get( let normalized = NormalizedLoginIdentifier::normalize(login_hint, hostname_for_handles); tracing::info!(normalized = %normalized, "Normalized login_hint"); - match state - .repos - .user - .get_login_check_by_identifier(normalized.as_str()) - .await - { + let hint_identifier = tranquil_types::AtIdentifier::new(normalized.as_str()).ok(); + let hint_lookup = match hint_identifier { + Some(ref id) => state.repos.user.get_login_check_by_identifier(id).await, + None => Ok(None), + }; + match hint_lookup { Ok(Some(user)) => { tracing::info!(did = %user.did, has_password = user.password_hash.is_some(), "Found user for login_hint"); let is_delegated = state @@ -399,12 +399,12 @@ pub async fn authorize_post( pds_hostname = %tranquil_config::get().server.hostname, "Normalized username for lookup" ); - let user = match state - .repos - .user - .get_login_info_by_identifier(normalized_username.as_str()) - .await - { + let login_identifier = tranquil_types::AtIdentifier::new(normalized_username.as_str()).ok(); + let login_lookup = match login_identifier { + Some(ref id) => state.repos.user.get_login_info_by_identifier(id).await, + None => Ok(None), + }; + let user = match login_lookup { Ok(Some(u)) => u, Ok(None) => { let _ = bcrypt::verify( @@ -478,7 +478,7 @@ pub async fn authorize_post( } let password_valid = match &user.password_hash { - Some(hash) => match bcrypt::verify(&form.password, hash) { + Some(hash) => match bcrypt::verify(&form.password, hash.as_str()) { Ok(valid) => valid, Err(_) => { return show_login_error("An error occurred. Please try again.", json_response); @@ -708,7 +708,7 @@ pub async fn authorize_post( if json_response { let redirect_url = build_intermediate_redirect_url( &request_data.parameters.redirect_uri, - &code.0, + code.as_str(), request_data.parameters.state.as_deref(), request_data.parameters.response_mode.map(|m| m.as_str()), ); @@ -725,7 +725,7 @@ pub async fn authorize_post( } else { let redirect_url = build_success_redirect( &request_data.parameters.redirect_uri, - &code.0, + code.as_str(), request_data.parameters.state.as_deref(), request_data.parameters.response_mode.map(|m| m.as_str()), ); diff --git a/crates/tranquil-oauth-server/src/endpoints/authorize/passkey.rs b/crates/tranquil-oauth-server/src/endpoints/authorize/passkey.rs index 6eebc22..d114c20 100644 --- a/crates/tranquil-oauth-server/src/endpoints/authorize/passkey.rs +++ b/crates/tranquil-oauth-server/src/endpoints/authorize/passkey.rs @@ -19,11 +19,10 @@ pub async fn check_user_has_passkeys( let bare_identifier = BareLoginIdentifier::from_identifier(&query.identifier, hostname_for_handles); - let user = state - .repos - .user - .get_login_check_by_identifier(bare_identifier.as_str()) - .await; + let user = match tranquil_types::AtIdentifier::new(bare_identifier.as_str()) { + Ok(ref id) => state.repos.user.get_login_check_by_identifier(id).await, + Err(_) => Ok(None), + }; let has_passkeys = match user { Ok(Some(u)) => tranquil_api::server::has_passkeys_for_user(&state, &u.did).await, @@ -52,11 +51,10 @@ pub async fn check_user_security_status( let normalized_identifier = NormalizedLoginIdentifier::normalize(&query.identifier, hostname_for_handles); - let user = state - .repos - .user - .get_login_check_by_identifier(normalized_identifier.as_str()) - .await; + let user = match tranquil_types::AtIdentifier::new(normalized_identifier.as_str()) { + Ok(ref id) => state.repos.user.get_login_check_by_identifier(id).await, + Err(_) => Ok(None), + }; let (has_passkeys, has_totp, has_password, is_delegated, did): ( bool, @@ -238,12 +236,11 @@ async fn passkey_start_named( let normalized_username = NormalizedLoginIdentifier::normalize(&identifier, hostname_for_handles); - let user = match state - .repos - .user - .get_login_info_by_identifier(normalized_username.as_str()) - .await - { + let passkey_lookup = match tranquil_types::AtIdentifier::new(normalized_username.as_str()) { + Ok(ref id) => state.repos.user.get_login_info_by_identifier(id).await, + Err(_) => Ok(None), + }; + let user = match passkey_lookup { Ok(Some(u)) => u, Ok(None) => { return ( @@ -670,7 +667,7 @@ pub async fn passkey_finish( let redirect_url = build_intermediate_redirect_url( &request_data.parameters.redirect_uri, - &code.0, + code.as_str(), request_data.parameters.state.as_deref(), request_data.parameters.response_mode.map(|m| m.as_str()), ); diff --git a/crates/tranquil-oauth-server/src/endpoints/authorize/registration.rs b/crates/tranquil-oauth-server/src/endpoints/authorize/registration.rs index b1a8a88..9e8cea1 100644 --- a/crates/tranquil-oauth-server/src/endpoints/authorize/registration.rs +++ b/crates/tranquil-oauth-server/src/endpoints/authorize/registration.rs @@ -140,13 +140,13 @@ pub async fn register_complete( }; let mut password_valid = password_hashes.iter().fold(false, |acc, hash| { - acc | bcrypt::verify(&form.app_password, hash).unwrap_or(false) + acc | bcrypt::verify(&form.app_password, hash.as_str()).unwrap_or(false) }); if !password_valid && let Ok(Some(account_hash)) = state.repos.user.get_password_hash_by_did(&did).await { - password_valid = bcrypt::verify(&form.app_password, &account_hash).unwrap_or(false); + password_valid = bcrypt::verify(&form.app_password, account_hash.as_str()).unwrap_or(false); } if !password_valid { @@ -290,7 +290,7 @@ pub async fn register_complete( let redirect_url = build_intermediate_redirect_url( &request_data.parameters.redirect_uri, - &code.0, + code.as_str(), request_data.parameters.state.as_deref(), request_data.parameters.response_mode.map(|m| m.as_str()), ); diff --git a/crates/tranquil-oauth-server/src/endpoints/authorize/two_factor.rs b/crates/tranquil-oauth-server/src/endpoints/authorize/two_factor.rs index 4af40ce..28c84d0 100644 --- a/crates/tranquil-oauth-server/src/endpoints/authorize/two_factor.rs +++ b/crates/tranquil-oauth-server/src/endpoints/authorize/two_factor.rs @@ -185,7 +185,7 @@ pub async fn authorize_2fa_post( } let redirect_url = build_intermediate_redirect_url( &request_data.parameters.redirect_uri, - &code.0, + code.as_str(), request_data.parameters.state.as_deref(), request_data.parameters.response_mode.map(|m| m.as_str()), ); @@ -329,7 +329,7 @@ pub async fn authorize_2fa_post( } let redirect_url = build_intermediate_redirect_url( &request_data.parameters.redirect_uri, - &code.0, + code.as_str(), request_data.parameters.state.as_deref(), request_data.parameters.response_mode.map(|m| m.as_str()), ); diff --git a/crates/tranquil-oauth-server/src/endpoints/delegation.rs b/crates/tranquil-oauth-server/src/endpoints/delegation.rs index 8062577..748f90a 100644 --- a/crates/tranquil-oauth-server/src/endpoints/delegation.rs +++ b/crates/tranquil-oauth-server/src/endpoints/delegation.rs @@ -294,7 +294,7 @@ pub async fn delegation_auth( let password_valid = controller .password_hash .as_ref() - .map(|hash| bcrypt::verify(password, hash).unwrap_or_default()) + .map(|hash| bcrypt::verify(password, hash.as_str()).unwrap_or_default()) .unwrap_or_default(); if !password_valid { diff --git a/crates/tranquil-oauth-server/src/endpoints/par.rs b/crates/tranquil-oauth-server/src/endpoints/par.rs index 2c1e18c..dcaa075 100644 --- a/crates/tranquil-oauth-server/src/endpoints/par.rs +++ b/crates/tranquil-oauth-server/src/endpoints/par.rs @@ -31,7 +31,7 @@ pub struct ParRequest { #[serde(default)] pub login_hint: Option, #[serde(default)] - pub dpop_jkt: Option, + pub dpop_jkt: Option, #[serde(default)] pub client_secret: Option, #[serde(default)] diff --git a/crates/tranquil-oauth-server/src/endpoints/token/grants.rs b/crates/tranquil-oauth-server/src/endpoints/token/grants.rs index 23388ed..275a754 100644 --- a/crates/tranquil-oauth-server/src/endpoints/token/grants.rs +++ b/crates/tranquil-oauth-server/src/endpoints/token/grants.rs @@ -16,7 +16,6 @@ use tranquil_pds::oauth::{ verify_client_auth, }; use tranquil_pds::state::AppState; -use tranquil_types::{AuthorizationCode, Did, RefreshToken as RefreshTokenType}; const ACCESS_TOKEN_EXPIRY_SECONDS: u64 = 300; const REFRESH_TOKEN_EXPIRY_DAYS_CONFIDENTIAL: i64 = 60; @@ -115,13 +114,13 @@ pub async fn handle_authorization_code_grant( )); } if let Some(expected_jkt) = &authorized.parameters.dpop_jkt - && result.jkt.as_str() != expected_jkt + && result.jkt != *expected_jkt { return Err(OAuthError::InvalidDpopProof( "DPoP key binding mismatch".to_string(), )); } - Some(result.jkt.as_str().to_string()) + Some(result.jkt.clone()) } else if authorized.parameters.dpop_jkt.is_some() || client_metadata.requires_dpop() { return Err(OAuthError::UseDpopNonce( DPoPVerifier::new(AuthConfig::get().dpop_secret().as_bytes()).generate_nonce(), @@ -169,9 +168,9 @@ pub async fn handle_authorization_code_grant( }; let access_token = create_access_token_with_delegation( - &token_id.0, + &token_id, &did, - dpop_jkt.as_deref(), + dpop_jkt.as_ref(), final_scope.as_deref(), controller_did.as_ref(), )?; @@ -207,7 +206,7 @@ pub async fn handle_authorization_code_grant( .map_err(tranquil_pds::oauth::db_err_to_oauth)?; tracing::info!( did = %did, - token_id = %token_id.0, + token_id = %token_id, client_id = %authorized.client_id, "Authorization code grant completed, token created" ); @@ -215,9 +214,7 @@ pub async fn handle_authorization_code_grant( let oauth_repo = state.repos.oauth.clone(); let did_clone = did.clone(); async move { - if let Ok(did_typed) = did_clone.parse::() - && let Err(e) = enforce_token_limit_for_user(oauth_repo.as_ref(), &did_typed).await - { + if let Err(e) = enforce_token_limit_for_user(oauth_repo.as_ref(), &did_clone).await { tracing::warn!("Failed to enforce token limit for user: {:?}", e); } } @@ -284,7 +281,7 @@ pub async fn handle_refresh_token_grant( rotated_at = %rotated_at, "Refresh token reuse within grace period, returning existing tokens" ); - let dpop_jkt = token_data.parameters.dpop_jkt.as_deref(); + let dpop_jkt = token_data.parameters.dpop_jkt.as_ref(); let access_token = create_access_token_with_delegation( &token_data.token_id, &token_data.did, @@ -367,13 +364,13 @@ pub async fn handle_refresh_token_grant( )); } if let Some(expected_jkt) = &token_data.parameters.dpop_jkt - && result.jkt.as_str() != expected_jkt + && result.jkt != *expected_jkt { return Err(OAuthError::InvalidDpopProof( "DPoP key binding mismatch".to_string(), )); } - Some(result.jkt.as_str().to_string()) + Some(result.jkt.clone()) } else if token_data.parameters.dpop_jkt.is_some() { return Err(OAuthError::InvalidRequest( "DPoP proof required".to_string(), diff --git a/crates/tranquil-oauth-server/src/endpoints/token/helpers.rs b/crates/tranquil-oauth-server/src/endpoints/token/helpers.rs index 68c311b..ae0df2d 100644 --- a/crates/tranquil-oauth-server/src/endpoints/token/helpers.rs +++ b/crates/tranquil-oauth-server/src/endpoints/token/helpers.rs @@ -46,19 +46,19 @@ pub fn create_access_token_with_delegation( let actual_scope = scope.unwrap_or("atproto"); let mut payload = json!({ "iss": issuer, - "sub": sub, + "sub": sub.as_str(), "aud": issuer, "iat": now, "exp": exp, "jti": jti, - "sid": session_id, + "sid": session_id.as_str(), "scope": actual_scope }); if let Some(jkt) = dpop_jkt { - payload["cnf"] = json!({ "jkt": jkt }); + payload["cnf"] = json!({ "jkt": jkt.as_str() }); } if let Some(controller) = controller_did { - payload["act"] = json!({ "sub": controller }); + payload["act"] = json!({ "sub": controller.as_str() }); } let header = json!({ "alg": "HS256", diff --git a/crates/tranquil-oauth-server/src/endpoints/token/mod.rs b/crates/tranquil-oauth-server/src/endpoints/token/mod.rs index 616474c..23ee9a2 100644 --- a/crates/tranquil-oauth-server/src/endpoints/token/mod.rs +++ b/crates/tranquil-oauth-server/src/endpoints/token/mod.rs @@ -10,7 +10,7 @@ use tranquil_pds::rate_limit::{OAuthRateLimited, OAuthTokenLimit}; use tranquil_pds::state::AppState; pub use grants::{handle_authorization_code_grant, handle_refresh_token_grant}; -pub use helpers::{TokenClaims, create_access_token, extract_token_claims, verify_pkce}; +pub use helpers::{TokenClaims, extract_token_claims, verify_pkce}; pub use introspect::{ IntrospectRequest, IntrospectResponse, RevokeRequest, introspect_token, revoke_token, }; diff --git a/crates/tranquil-oauth-server/src/sso_endpoints.rs b/crates/tranquil-oauth-server/src/sso_endpoints.rs index d32731a..a4c3662 100644 --- a/crates/tranquil-oauth-server/src/sso_endpoints.rs +++ b/crates/tranquil-oauth-server/src/sso_endpoints.rs @@ -1042,7 +1042,8 @@ pub async fn complete_registration( )); } tracing::info!(did = %d, "Creating external did:web SSO account"); - d.to_string() + d.parse() + .map_err(|_| ApiError::InvalidDid("Invalid DID format".into()))? } _ => { let genesis_result = match tranquil_pds::plc::create_genesis_operation( @@ -1345,7 +1346,7 @@ pub async fn complete_registration( redirect_url: "/app/dashboard".to_string(), access_jwt: Some(access_meta.token), refresh_jwt: Some(refresh_meta.token), - app_password: Some(app_password), + app_password: Some(app_password.into_inner()), app_password_name: Some(app_password_name), })); } @@ -1359,7 +1360,7 @@ pub async fn complete_registration( ), access_jwt: None, refresh_jwt: None, - app_password: Some(app_password), + app_password: Some(app_password.into_inner()), app_password_name: Some(app_password_name), })); } @@ -1403,7 +1404,7 @@ pub async fn complete_registration( redirect_url, access_jwt: None, refresh_jwt: None, - app_password: Some(app_password), + app_password: Some(app_password.into_inner()), app_password_name: Some(app_password_name), })) } diff --git a/crates/tranquil-oauth/src/types.rs b/crates/tranquil-oauth/src/types.rs index f397bfe..806268f 100644 --- a/crates/tranquil-oauth/src/types.rs +++ b/crates/tranquil-oauth/src/types.rs @@ -8,60 +8,31 @@ pub use tranquil_types::{AuthorizationCode, DeviceId, RefreshToken, RequestId, T #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::Type)] #[serde(transparent)] #[sqlx(transparent)] -pub struct RequestId(pub String); +pub struct SessionId(String); -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::Type)] -#[serde(transparent)] -#[sqlx(transparent)] -pub struct TokenId(pub String); +impl SessionId { + pub fn new(s: impl Into) -> Self { + Self(s.into()) + } -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::Type)] -#[serde(transparent)] -#[sqlx(transparent)] -pub struct DeviceId(pub String); + pub fn generate() -> Self { + Self(uuid::Uuid::new_v4().to_string()) + } pub fn as_str(&self) -> &str { &self.0 } } -impl TokenId { - pub fn generate() -> Self { - Self(uuid::Uuid::new_v4().to_string()) +impl From for SessionId { + fn from(s: String) -> Self { + Self(s) } } -impl DeviceId { - pub fn generate() -> Self { - Self(uuid::Uuid::new_v4().to_string()) - } -} - -impl SessionId { - pub fn generate() -> Self { - Self(uuid::Uuid::new_v4().to_string()) - } -} - -impl Code { - pub fn generate() -> Self { - use rand::Rng; - let bytes: [u8; 32] = rand::thread_rng().r#gen(); - Self(base64::Engine::encode( - &base64::engine::general_purpose::URL_SAFE_NO_PAD, - bytes, - )) - } -} - -impl RefreshToken { - pub fn generate() -> Self { - use rand::Rng; - let bytes: [u8; 32] = rand::thread_rng().r#gen(); - Self(base64::Engine::encode( - &base64::engine::general_purpose::URL_SAFE_NO_PAD, - bytes, - )) +impl std::fmt::Display for SessionId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) } } @@ -146,7 +117,7 @@ pub struct AuthorizationRequestParameters { pub code_challenge_method: CodeChallengeMethod, pub response_mode: Option, pub login_hint: Option, - pub dpop_jkt: Option, + pub dpop_jkt: Option, pub prompt: Option, #[serde(flatten)] pub extra: Option, diff --git a/crates/tranquil-pds/src/api/proxy.rs b/crates/tranquil-pds/src/api/proxy.rs index 388f488..bb2dadd 100644 --- a/crates/tranquil-pds/src/api/proxy.rs +++ b/crates/tranquil-pds/src/api/proxy.rs @@ -291,7 +291,7 @@ async fn proxy_handler( &auth_user.auth_source, auth_user.scope.as_deref(), &resolved.did, - method, + &method_nsid, ) { return e.into_response(); } @@ -353,7 +353,7 @@ async fn proxy_handler( match crate::auth::create_service_token( &auth_user.did, &token_aud, - Some(token_lxm), + Some(&token_lxm), &key_bytes, ) { Ok(new_token) => { diff --git a/crates/tranquil-pds/src/api/validation.rs b/crates/tranquil-pds/src/api/validation.rs index 6975141..edeac38 100644 --- a/crates/tranquil-pds/src/api/validation.rs +++ b/crates/tranquil-pds/src/api/validation.rs @@ -214,7 +214,7 @@ pub fn resolve_handle_input(input: &str) -> Result( match hash { Some(h) => { - if bcrypt::verify(password, &h).unwrap_or(false) { + if bcrypt::verify(password, h.as_str()).unwrap_or(false) { Ok(MfaVerified::from_password(user)) } else { Err(crate::api::error::ApiError::InvalidPassword( diff --git a/crates/tranquil-pds/src/auth/mod.rs b/crates/tranquil-pds/src/auth/mod.rs index 1c92e53..5a6e64b 100644 --- a/crates/tranquil-pds/src/auth/mod.rs +++ b/crates/tranquil-pds/src/auth/mod.rs @@ -82,7 +82,7 @@ pub fn decrypt_totp_secret( crate::config::decrypt_key(encrypted, Some(version)) } -pub fn generate_app_password() -> String { +pub fn generate_app_password() -> crate::types::PlainPassword { use rand::Rng; let chars: &[u8] = b"abcdefghijklmnopqrstuvwxyz234567"; let mut rng = rand::thread_rng(); @@ -93,7 +93,7 @@ pub fn generate_app_password() -> String { .collect() }) .collect(); - segments.join("-") + crate::types::PlainPassword::new(segments.join("-")) } const KEY_CACHE_TTL_SECS: u64 = 300; @@ -631,26 +631,29 @@ mod tests { fn test_lxm_permits_exact_match() { assert!(lxm_permits( "com.atproto.repo.uploadBlob", - "com.atproto.repo.uploadBlob" + &n("com.atproto.repo.uploadBlob") )); } #[test] fn test_lxm_permits_wildcard() { - assert!(lxm_permits("*", "com.atproto.repo.uploadBlob")); - assert!(lxm_permits("*", "anything.at.all")); + assert!(lxm_permits("*", &n("com.atproto.repo.uploadBlob"))); + assert!(lxm_permits("*", &n("anything.at.all"))); } #[test] fn test_lxm_permits_mismatch() { assert!(!lxm_permits( "com.atproto.repo.uploadBlob", - "com.atproto.repo.createRecord" + &n("com.atproto.repo.createRecord") )); } #[test] fn test_lxm_permits_partial_not_wildcard() { - assert!(!lxm_permits("com.atproto.*", "com.atproto.repo.uploadBlob")); + assert!(!lxm_permits( + "com.atproto.*", + &n("com.atproto.repo.uploadBlob") + )); } } diff --git a/crates/tranquil-pds/src/handle/mod.rs b/crates/tranquil-pds/src/handle/mod.rs index e43fbad..0592430 100644 --- a/crates/tranquil-pds/src/handle/mod.rs +++ b/crates/tranquil-pds/src/handle/mod.rs @@ -75,11 +75,11 @@ pub async fn verify_handle_ownership( expected_did: &Did, ) -> Result<(), HandleResolutionError> { let resolved_did = resolve_handle(handle).await?; - if resolved_did == expected_did { + if resolved_did == *expected_did { Ok(()) } else { Err(HandleResolutionError::DidMismatch { - expected: expected_did.to_string(), + expected: expected_did.clone(), actual: resolved_did, }) } @@ -103,10 +103,10 @@ mod tests { #[test] fn test_is_service_domain_handle() { - assert!(is_service_domain_handle("user.example.com", "example.com")); - assert!(is_service_domain_handle("example.com", "example.com")); - assert!(is_service_domain_handle("myhandle", "example.com")); - assert!(!is_service_domain_handle("user.other.com", "example.com")); - assert!(!is_service_domain_handle("myhandle.xyz", "example.com")); + assert!(is_service_domain_handle("nel.oyster.cafe", "oyster.cafe")); + assert!(is_service_domain_handle("oyster.cafe", "oyster.cafe")); + assert!(is_service_domain_handle("myhandle", "oyster.cafe")); + assert!(!is_service_domain_handle("lyna.nel.pet", "oyster.cafe")); + assert!(!is_service_domain_handle("myhandle.xyz", "oyster.cafe")); } } diff --git a/crates/tranquil-pds/src/oauth/verify.rs b/crates/tranquil-pds/src/oauth/verify.rs index 63738e5..9dc1c1d 100644 --- a/crates/tranquil-pds/src/oauth/verify.rs +++ b/crates/tranquil-pds/src/oauth/verify.rs @@ -24,7 +24,6 @@ pub struct OAuthTokenInfo { pub did: Did, pub token_id: TokenId, pub scope: Option, - pub dpop_jkt: Option, pub controller_did: Option, } @@ -87,7 +86,7 @@ pub async fn verify_oauth_access_token( "DPoP proof has already been used".to_string(), )); } - if result.jkt.as_str() != expected_jkt { + if result.jkt != *expected_jkt { return Err(OAuthError::InvalidDpopProof( "DPoP key binding mismatch".to_string(), )); @@ -179,7 +178,6 @@ pub fn extract_oauth_token_info(token: &str) -> Result) -> std::fmt::Result { match self { Self::InvalidDid(e) => write!(f, "Invalid DID: {}", e), - Self::InvalidTid(e) => write!(f, "Invalid TID: {}", e), Self::SigningFailed(e) => write!(f, "Failed to sign commit: {}", e), Self::SerializationFailed(e) => write!(f, "Failed to serialize signed commit: {}", e), Self::KeyNotFound => write!(f, "Signing key not found"), diff --git a/crates/tranquil-pds/src/sync/frame.rs b/crates/tranquil-pds/src/sync/frame.rs index 865fe3e..d29fe86 100644 --- a/crates/tranquil-pds/src/sync/frame.rs +++ b/crates/tranquil-pds/src/sync/frame.rs @@ -2,6 +2,7 @@ use crate::sync::firehose::SequencedEvent; use cid::Cid; use serde::{Deserialize, Serialize}; use std::str::FromStr; +use tranquil_db_traits::SequenceNumber; use tranquil_scopes::RepoAction; use tranquil_types::{CidLink, Did, Handle, Tid}; @@ -27,7 +28,7 @@ pub struct FrameHeader { #[derive(Debug, Serialize, Deserialize)] pub struct CommitFrame { - pub seq: i64, + pub seq: SequenceNumber, pub rebase: bool, #[serde(rename = "tooBig")] pub too_big: bool, @@ -76,7 +77,7 @@ pub struct AccountFrame { pub active: bool, #[serde(skip_serializing_if = "Option::is_none")] pub status: Option, - pub seq: i64, + pub seq: SequenceNumber, pub time: String, } @@ -86,7 +87,7 @@ pub struct SyncFrame { pub rev: Tid, #[serde(with = "serde_bytes")] pub blocks: Vec, - pub seq: i64, + pub seq: SequenceNumber, pub time: String, } @@ -139,7 +140,7 @@ impl std::fmt::Display for CommitFrameError { impl std::error::Error for CommitFrameError {} pub struct CommitFrameBuilder { - seq: i64, + seq: SequenceNumber, did: Did, commit_cid: Cid, ops_json: serde_json::Value, @@ -150,7 +151,7 @@ pub struct CommitFrameBuilder { impl CommitFrameBuilder { pub fn new( - seq: i64, + seq: SequenceNumber, did: Did, commit_cid: &CidLink, ops_json: serde_json::Value, @@ -222,7 +223,7 @@ impl TryFrom for CommitFrame { CommitFrameError::InvalidCommitCid("Missing commit_cid in event".to_string()) })?; let builder = CommitFrameBuilder::new( - event.seq.as_i64(), + event.seq, event.did.clone(), &commit_cid, event.ops.unwrap_or_default(), diff --git a/crates/tranquil-pds/src/sync/import.rs b/crates/tranquil-pds/src/sync/import.rs index d567977..7512c45 100644 --- a/crates/tranquil-pds/src/sync/import.rs +++ b/crates/tranquil-pds/src/sync/import.rs @@ -143,7 +143,7 @@ pub fn find_blob_refs(value: &JsonValue, depth: usize) -> Vec { .and_then(|v| v.as_str()) .map(String::from); return vec![BlobRef { - cid: link.clone(), + cid, mime_type: mime, }]; } @@ -173,7 +173,7 @@ pub fn extract_links(value: &Ipld, links: &mut Vec) { #[derive(Debug)] pub struct ImportedRecord { pub collection: Nsid, - pub rkey: String, + pub rkey: Rkey, pub cid: Cid, pub blob_refs: Vec, } @@ -229,7 +229,7 @@ fn walk_mst_node( let parts: Vec<&str> = full_key.split('/').collect(); if parts.len() >= 2 { let collection = Nsid::from(parts[..parts.len() - 1].join("/")); - let rkey = parts[parts.len() - 1].to_string(); + let rkey = Rkey::from(parts[parts.len() - 1].to_string()); records.push(ImportedRecord { collection, rkey, diff --git a/crates/tranquil-pds/src/sync/util.rs b/crates/tranquil-pds/src/sync/util.rs index 2aa36d1..1d4b07c 100644 --- a/crates/tranquil-pds/src/sync/util.rs +++ b/crates/tranquil-pds/src/sync/util.rs @@ -258,7 +258,7 @@ fn format_account_event(event: &SequencedEvent) -> Result, SyncFrameErro did: event.did.clone(), active: event.active.unwrap_or(true), status: event.status.filter(|s| !s.is_active()), - seq: event.seq.as_i64(), + seq: event.seq, time: format_atproto_time(event.created_at), }; let bytes = serialize_event_frame(FrameType::Account, &frame, 256)?; @@ -355,7 +355,7 @@ async fn format_sync_event( did: event.did.clone(), rev, blocks: car_bytes, - seq: event.seq.as_i64(), + seq: event.seq, time: format_atproto_time(event.created_at), }, 512, diff --git a/crates/tranquil-pds/src/validation/mod.rs b/crates/tranquil-pds/src/validation/mod.rs index e626b44..1b858c2 100644 --- a/crates/tranquil-pds/src/validation/mod.rs +++ b/crates/tranquil-pds/src/validation/mod.rs @@ -75,7 +75,7 @@ impl RecordValidator { &self, record: &Value, collection: &Nsid, - rkey: Option<&str>, + rkey: Option<&Rkey>, ) -> Result { let (record_type, obj) = validate_preamble(record, collection)?; let registry = tranquil_lexicon::LexiconRegistry::global(); @@ -135,7 +135,7 @@ fn validate_preamble<'a>( fn check_banned_content( record_type: &str, obj: &serde_json::Map, - rkey: Option<&str>, + rkey: Option<&Rkey>, ) -> Result<(), ValidationError> { match record_type { "app.bsky.feed.post" => { diff --git a/crates/tranquil-pds/tests/oauth_token_eviction.rs b/crates/tranquil-pds/tests/oauth_token_eviction.rs index 9ae1123..78c47e9 100644 --- a/crates/tranquil-pds/tests/oauth_token_eviction.rs +++ b/crates/tranquil-pds/tests/oauth_token_eviction.rs @@ -121,8 +121,10 @@ async fn delete_oldest_tokens_evicts_lowest_created_at() { .expect("list failed"); assert_eq!(remaining.len(), 3, "three newest tokens should remain"); - let remaining_ids: std::collections::HashSet = - remaining.iter().map(|t| t.token_id.0.clone()).collect(); + let remaining_ids: std::collections::HashSet = remaining + .iter() + .map(|t| t.token_id.as_str().to_string()) + .collect(); let expected_ids: std::collections::HashSet = token_ids[2..].iter().cloned().collect(); assert_eq!( remaining_ids, expected_ids, diff --git a/crates/tranquil-pds/tests/record_validation.rs b/crates/tranquil-pds/tests/record_validation.rs index 11ba1e6..102b875 100644 --- a/crates/tranquil-pds/tests/record_validation.rs +++ b/crates/tranquil-pds/tests/record_validation.rs @@ -17,13 +17,13 @@ fn c(s: &str) -> Nsid { fn test_type_mismatch() { let validator = RecordValidator::new(); let record = json!({ - "$type": "com.example.other", + "$type": "cafe.oyster.other", "createdAt": now() }); assert!(matches!( - validator.validate(&record, "com.example.expected"), + validator.validate(&record, &c("cafe.oyster.expected")), Err(ValidationError::TypeMismatch { expected, actual }) - if expected == "com.example.expected" && actual == "com.example.other" + if expected == "cafe.oyster.expected" && actual == "cafe.oyster.other" )); } @@ -32,7 +32,7 @@ fn test_missing_type() { let validator = RecordValidator::new(); let record = json!({"text": "Hello"}); assert!(matches!( - validator.validate(&record, "com.example.test"), + validator.validate(&record, &c("cafe.oyster.record")), Err(ValidationError::MissingType) )); } @@ -42,7 +42,7 @@ fn test_not_object() { let validator = RecordValidator::new(); let record = json!("just a string"); assert!(matches!( - validator.validate(&record, "com.example.test"), + validator.validate(&record, &c("cafe.oyster.record")), Err(ValidationError::InvalidRecord(_)) )); } @@ -52,7 +52,9 @@ fn test_unknown_type_lenient() { let validator = RecordValidator::new(); let record = json!({"$type": "com.custom.record", "data": "test"}); assert_eq!( - validator.validate(&record, "com.custom.record").unwrap(), + validator + .validate(&record, &c("com.custom.record")) + .unwrap(), ValidationStatus::Unknown ); } @@ -62,7 +64,7 @@ fn test_unknown_type_strict() { let validator = RecordValidator::new().require_lexicon(true); let record = json!({"$type": "com.custom.record", "data": "test"}); assert!(matches!( - validator.validate(&record, "com.custom.record"), + validator.validate(&record, &c("com.custom.record")), Err(ValidationError::UnknownType(_)) )); } @@ -73,7 +75,7 @@ fn test_datetime_validation() { let valid = json!({"$type": "com.custom.record", "createdAt": "2024-01-15T10:30:00.000Z"}); assert_eq!( - validator.validate(&valid, "com.custom.record").unwrap(), + validator.validate(&valid, &c("com.custom.record")).unwrap(), ValidationStatus::Unknown ); @@ -81,14 +83,14 @@ fn test_datetime_validation() { json!({"$type": "com.custom.record", "createdAt": "2024-01-15T10:30:00+05:30"}); assert_eq!( validator - .validate(&with_offset, "com.custom.record") + .validate(&with_offset, &c("com.custom.record")) .unwrap(), ValidationStatus::Unknown ); let invalid = json!({"$type": "com.custom.record", "createdAt": "2024/01/15"}); assert!(matches!( - validator.validate(&invalid, "com.custom.record"), + validator.validate(&invalid, &c("com.custom.record")), Err(ValidationError::InvalidDatetime { .. }) )); } diff --git a/crates/tranquil-pds/tests/scope_edge_cases.rs b/crates/tranquil-pds/tests/scope_edge_cases.rs index 0956d3f..457b846 100644 --- a/crates/tranquil-pds/tests/scope_edge_cases.rs +++ b/crates/tranquil-pds/tests/scope_edge_cases.rs @@ -166,10 +166,10 @@ fn test_permissions_whitespace_only() { #[test] fn test_permissions_repo_collection_wildcard_prefix() { let perms = ScopePermissions::from_scope_string(Some("repo:app.bsky.*?action=create")); - assert!(perms.allows_repo(RepoAction::Create, "app.bsky.feed.post")); - assert!(perms.allows_repo(RepoAction::Create, "app.bsky.actor.profile")); - assert!(!perms.allows_repo(RepoAction::Create, "com.atproto.repo.blob")); - assert!(!perms.allows_repo(RepoAction::Update, "app.bsky.feed.post")); + assert!(perms.allows_repo(RepoAction::Create, &c("app.bsky.feed.post"))); + assert!(perms.allows_repo(RepoAction::Create, &c("app.bsky.actor.profile"))); + assert!(!perms.allows_repo(RepoAction::Create, &c("com.atproto.repo.blob"))); + assert!(!perms.allows_repo(RepoAction::Update, &c("app.bsky.feed.post"))); } #[test] diff --git a/crates/tranquil-pds/tests/store_parity.rs b/crates/tranquil-pds/tests/store_parity.rs index 3247c55..15a5773 100644 --- a/crates/tranquil-pds/tests/store_parity.rs +++ b/crates/tranquil-pds/tests/store_parity.rs @@ -1420,12 +1420,12 @@ async fn parity_repo_root_operations() { let new_root = test_cid(99); let rev1 = Tid::from("rev1".to_string()); f.pg.repo - .update_repo_root(pg_uid, &new_root, "rev1") + .update_repo_root(pg_uid, &new_root, &rev1) .await .unwrap(); f.store .repo - .update_repo_root(store_uid, &new_root, "rev1") + .update_repo_root(store_uid, &new_root, &rev1) .await .unwrap(); diff --git a/crates/tranquil-scopes/src/permission_set.rs b/crates/tranquil-scopes/src/permission_set.rs index 921c994..7c60469 100644 --- a/crates/tranquil-scopes/src/permission_set.rs +++ b/crates/tranquil-scopes/src/permission_set.rs @@ -405,11 +405,6 @@ mod tests { ); } - #[test] - fn test_extract_namespace_authority_single_segment() { - assert_eq!(extract_namespace_authority("single"), "single"); - } - #[test] fn test_is_under_authority() { assert!(is_under_authority("io.atcr.manifest", "io.atcr")); diff --git a/crates/tranquil-scopes/src/permissions.rs b/crates/tranquil-scopes/src/permissions.rs index 76a4bb6..519fa26 100644 --- a/crates/tranquil-scopes/src/permissions.rs +++ b/crates/tranquil-scopes/src/permissions.rs @@ -350,7 +350,7 @@ mod tests { fn test_atproto_scope_is_identity_only() { let perms = ScopePermissions::from_scope_string(Some("atproto")); assert!(!perms.has_full_access()); - assert!(!perms.allows_repo(RepoAction::Create, "app.bsky.feed.post")); + assert!(!perms.allows_repo(RepoAction::Create, &c("app.bsky.feed.post"))); assert!(!perms.allows_blob("image/png")); assert!(!perms.allows_rpc("did:web:api.bsky.app", &c("app.bsky.feed.getTimeline"))); assert!(!perms.allows_account(AccountAttr::Email, AccountAction::Manage)); @@ -359,7 +359,7 @@ mod tests { #[test] fn test_transition_generic_allows_everything() { let perms = ScopePermissions::from_scope_string(Some("transition:generic")); - assert!(perms.allows_repo(RepoAction::Create, "app.bsky.feed.post")); + assert!(perms.allows_repo(RepoAction::Create, &c("app.bsky.feed.post"))); assert!(perms.allows_blob("image/png")); } @@ -410,7 +410,7 @@ mod tests { assert!(perms.allows_email_read()); assert!(perms.allows_account(AccountAttr::Email, AccountAction::Read)); assert!(!perms.allows_account(AccountAttr::Email, AccountAction::Manage)); - assert!(!perms.allows_repo(RepoAction::Create, "app.bsky.feed.post")); + assert!(!perms.allows_repo(RepoAction::Create, &c("app.bsky.feed.post"))); } #[test] @@ -427,10 +427,10 @@ mod tests { let perms = ScopePermissions::from_scope_string(Some( "repo:app.bsky.feed.post?action=create&action=delete", )); - assert!(perms.allows_repo(RepoAction::Create, "app.bsky.feed.post")); - assert!(perms.allows_repo(RepoAction::Delete, "app.bsky.feed.post")); - assert!(!perms.allows_repo(RepoAction::Update, "app.bsky.feed.post")); - assert!(!perms.allows_repo(RepoAction::Create, "app.bsky.feed.like")); + assert!(perms.allows_repo(RepoAction::Create, &c("app.bsky.feed.post"))); + assert!(perms.allows_repo(RepoAction::Delete, &c("app.bsky.feed.post"))); + assert!(!perms.allows_repo(RepoAction::Update, &c("app.bsky.feed.post"))); + assert!(!perms.allows_repo(RepoAction::Create, &c("app.bsky.feed.like"))); } #[test] @@ -561,11 +561,11 @@ mod tests { assert!(perms.allows_rpc("did:web:api.bsky.app", &c("app.bsky.feed.getAuthorFeed"))); assert!(perms.allows_rpc( "did:web:api.bsky.app#bsky_appview", - "app.bsky.feed.getAuthorFeed" + &c("app.bsky.feed.getAuthorFeed") )); assert!(perms.allows_rpc( "did:web:api.bsky.app#other_service", - "app.bsky.feed.getAuthorFeed" + &c("app.bsky.feed.getAuthorFeed") )); assert!(!perms.allows_rpc("did:web:other.app", &c("app.bsky.feed.getAuthorFeed"))); assert!(!perms.allows_rpc("did:web:api.bsky.app", &c("app.bsky.feed.getTimeline"))); @@ -579,7 +579,7 @@ mod tests { assert!(perms.allows_rpc("did:web:api.bsky.app", &c("app.bsky.feed.getAuthorFeed"))); assert!(perms.allows_rpc( "did:web:api.bsky.app#bsky_appview", - "app.bsky.feed.getAuthorFeed" + &c("app.bsky.feed.getAuthorFeed") )); } } diff --git a/crates/tranquil-store/src/eventlog/payload.rs b/crates/tranquil-store/src/eventlog/payload.rs index 8758695..319893f 100644 --- a/crates/tranquil-store/src/eventlog/payload.rs +++ b/crates/tranquil-store/src/eventlog/payload.rs @@ -106,7 +106,10 @@ pub fn encode_payload_with_mutations( prev_cid: event.prev_cid.as_ref().and_then(cid_link_to_bytes), prev_data_cid: event.prev_data_cid.as_ref().and_then(cid_link_to_bytes), ops: ops_bytes, - blobs: event.blobs.clone(), + blobs: event + .blobs + .as_ref() + .map(|v| v.iter().map(|c| c.as_str().to_owned()).collect()), blocks: match event.blocks.as_ref() { Some(EventBlocks::Inline(v)) => Some(v.clone()), Some(EventBlocks::LegacyCids(_)) | None => None, diff --git a/crates/tranquil-store/src/metastore/client.rs b/crates/tranquil-store/src/metastore/client.rs index f563250..6c7af6a 100644 --- a/crates/tranquil-store/src/metastore/client.rs +++ b/crates/tranquil-store/src/metastore/client.rs @@ -916,7 +916,7 @@ impl tranquil_db_traits::BlobRepository for MetastoreCli self.pool .send(MetastoreRequest::Blob(BlobRequest::ListBlobsSinceRev { did: did.clone(), - since: since.to_owned(), + since: since.to_string(), tx, }))?; recv(rx).await @@ -1515,7 +1515,7 @@ impl tranquil_db_traits::SessionRepository for Metastore self.pool.send(MetastoreRequest::Session( SessionRequest::DeleteSessionsByDidExceptJti { did: did.clone(), - except_jti: except_jti.to_owned(), + except_jti: except_jti.to_string(), tx, }, ))?; @@ -1841,7 +1841,7 @@ impl tranquil_db_traits::InfraRepository for MetastoreCl let (tx, rx) = oneshot::channel(); self.pool .send(MetastoreRequest::Infra(InfraRequest::CreateInviteCode { - code: code.to_owned(), + code: code.clone(), use_count, for_account: for_account.cloned(), tx, @@ -1876,7 +1876,7 @@ impl tranquil_db_traits::InfraRepository for MetastoreCl let (tx, rx) = oneshot::channel(); self.pool.send(MetastoreRequest::Infra( InfraRequest::GetInviteCodeAvailableUses { - code: code.to_owned(), + code: code.clone(), tx, }, ))?; @@ -1890,7 +1890,7 @@ impl tranquil_db_traits::InfraRepository for MetastoreCl let (tx, rx) = oneshot::channel(); self.pool .send(MetastoreRequest::Infra(InfraRequest::ValidateInviteCode { - code: code.to_owned(), + code: code.clone(), tx, })) .map_err(|e| InviteCodeError::DatabaseError(DbError::Connection(e.to_string())))?; @@ -1916,7 +1916,7 @@ impl tranquil_db_traits::InfraRepository for MetastoreCl let (tx, rx) = oneshot::channel(); self.pool .send(MetastoreRequest::Infra(InfraRequest::GetInviteCodeUses { - code: code.to_owned(), + code: code.to_string(), tx, }))?; recv(rx).await @@ -1926,7 +1926,7 @@ impl tranquil_db_traits::InfraRepository for MetastoreCl let (tx, rx) = oneshot::channel(); self.pool.send(MetastoreRequest::Infra( InfraRequest::DisableInviteCodesByCode { - codes: codes.to_vec(), + codes: codes.iter().map(|c| c.to_string()).collect(), tx, }, ))?; @@ -1978,7 +1978,7 @@ impl tranquil_db_traits::InfraRepository for MetastoreCl let (tx, rx) = oneshot::channel(); self.pool.send(MetastoreRequest::Infra( InfraRequest::GetInviteCodeUsesBatch { - codes: codes.to_vec(), + codes: codes.iter().map(|c| c.to_string()).collect(), tx, }, ))?; @@ -2003,7 +2003,7 @@ impl tranquil_db_traits::InfraRepository for MetastoreCl let (tx, rx) = oneshot::channel(); self.pool .send(MetastoreRequest::Infra(InfraRequest::GetInviteCodeInfo { - code: code.to_owned(), + code: code.to_string(), tx, }))?; recv(rx).await @@ -2063,7 +2063,7 @@ impl tranquil_db_traits::InfraRepository for MetastoreCl self.pool .send(MetastoreRequest::Infra(InfraRequest::ReserveSigningKey { did: did.cloned(), - public_key_did_key: public_key_did_key.to_owned(), + public_key_did_key: public_key_did_key.to_string(), private_key_bytes: private_key_bytes.to_vec(), expires_at, tx, @@ -2078,7 +2078,7 @@ impl tranquil_db_traits::InfraRepository for MetastoreCl let (tx, rx) = oneshot::channel(); self.pool.send(MetastoreRequest::Infra( InfraRequest::GetReservedSigningKey { - public_key_did_key: public_key_did_key.to_owned(), + public_key_did_key: public_key_did_key.to_string(), tx, }, ))?; @@ -2489,7 +2489,7 @@ impl tranquil_db_traits::InfraRepository for MetastoreCl let (tx, rx) = oneshot::channel(); self.pool.send(MetastoreRequest::Infra( InfraRequest::GetReservedSigningKeyFull { - public_key_did_key: public_key_did_key.to_owned(), + public_key_did_key: public_key_did_key.to_string(), tx, }, ))?; @@ -3370,7 +3370,7 @@ impl tranquil_db_traits::UserRepository for MetastoreCli let (tx, rx) = oneshot::channel(); self.pool .send(MetastoreRequest::User(UserRequest::GetOAuthTokenWithUser { - token_id: token_id.to_owned(), + token_id: token_id.to_string(), tx, }))?; recv(rx).await @@ -3447,12 +3447,12 @@ impl tranquil_db_traits::UserRepository for MetastoreCli async fn get_login_check_by_identifier( &self, - identifier: &str, + identifier: &AtIdentifier, ) -> Result, DbError> { let (tx, rx) = oneshot::channel(); self.pool.send(MetastoreRequest::User( UserRequest::GetLoginCheckByIdentifier { - identifier: identifier.to_owned(), + identifier: identifier.to_string(), tx, }, ))?; @@ -3461,12 +3461,12 @@ impl tranquil_db_traits::UserRepository for MetastoreCli async fn get_login_info_by_identifier( &self, - identifier: &str, + identifier: &AtIdentifier, ) -> Result, DbError> { let (tx, rx) = oneshot::channel(); self.pool.send(MetastoreRequest::User( UserRequest::GetLoginInfoByIdentifier { - identifier: identifier.to_owned(), + identifier: identifier.to_string(), tx, }, ))?; @@ -3698,12 +3698,12 @@ impl tranquil_db_traits::UserRepository for MetastoreCli async fn check_email_verified_by_identifier( &self, - identifier: &str, + identifier: &AtIdentifier, ) -> Result, DbError> { let (tx, rx) = oneshot::channel(); self.pool.send(MetastoreRequest::User( UserRequest::CheckEmailVerifiedByIdentifier { - identifier: identifier.to_owned(), + identifier: identifier.to_string(), tx, }, ))?; @@ -4442,12 +4442,12 @@ impl tranquil_db_traits::UserRepository for MetastoreCli async fn get_login_full_by_identifier( &self, - identifier: &str, + identifier: &AtIdentifier, ) -> Result, DbError> { let (tx, rx) = oneshot::channel(); self.pool.send(MetastoreRequest::User( UserRequest::GetLoginFullByIdentifier { - identifier: identifier.to_owned(), + identifier: identifier.to_string(), tx, }, ))?; diff --git a/crates/tranquil-store/src/metastore/commit_ops.rs b/crates/tranquil-store/src/metastore/commit_ops.rs index cca622c..30e1741 100644 --- a/crates/tranquil-store/src/metastore/commit_ops.rs +++ b/crates/tranquil-store/src/metastore/commit_ops.rs @@ -203,7 +203,7 @@ impl CommitOps { let mutation_set = CommitMutationSet { new_root_cid: new_cid_bytes.clone(), - new_rev: input.new_rev.clone(), + new_rev: input.new_rev.to_string(), record_upserts: input .record_upserts .iter() diff --git a/crates/tranquil-store/src/metastore/event_ops.rs b/crates/tranquil-store/src/metastore/event_ops.rs index 0a6777f..ce6e701 100644 --- a/crates/tranquil-store/src/metastore/event_ops.rs +++ b/crates/tranquil-store/src/metastore/event_ops.rs @@ -304,7 +304,7 @@ impl EventOps { let user_seqs = self.scan_did_events(user_hash, start_seq)?; - let mut seen = std::collections::BTreeSet::new(); + let mut seen = std::collections::HashSet::new(); user_seqs .into_iter() .try_fold(Vec::new(), |mut acc, seq_u64| { @@ -318,12 +318,7 @@ impl EventOps { match self.bridge.get_event_by_seq(seq_sn)? { Some(event) if event.rev.is_some() => { if let Some(blobs) = event.blobs { - acc.extend( - blobs - .into_iter() - .filter(|b| seen.insert(b.clone())) - .map(CidLink::from), - ); + acc.extend(blobs.into_iter().filter(|b| seen.insert(b.clone()))); } Ok(acc) } diff --git a/crates/tranquil-store/src/metastore/handler.rs b/crates/tranquil-store/src/metastore/handler.rs index baa68d7..d2d0ee6 100644 --- a/crates/tranquil-store/src/metastore/handler.rs +++ b/crates/tranquil-store/src/metastore/handler.rs @@ -736,9 +736,7 @@ impl DelegationRequest { | Self::RevokeDelegation { delegated_did, .. } | Self::UpdateDelegationScopes { delegated_did, .. } | Self::GetDelegation { delegated_did, .. } - | Self::LogDelegationAction { delegated_did, .. } => { - did_to_routing(delegated_did.as_str()) - } + | Self::LogDelegationAction { delegated_did, .. } => did_to_routing(delegated_did), Self::GetAccountsControlledBy { controller_did, .. } | Self::ControlsAnyAccounts { did: controller_did, diff --git a/crates/tranquil-store/src/metastore/infra_ops.rs b/crates/tranquil-store/src/metastore/infra_ops.rs index 3020112..6f1c056 100644 --- a/crates/tranquil-store/src/metastore/infra_ops.rs +++ b/crates/tranquil-store/src/metastore/infra_ops.rs @@ -374,7 +374,7 @@ impl InfraOps { } let value = InviteCodeValue { - code: code.to_owned(), + code: code.as_str().to_owned(), available_uses: use_count, disabled: false, for_account: for_account.map(|d| d.to_string()), @@ -400,7 +400,7 @@ impl InfraOps { codes.iter().try_for_each(|code| { let value = InviteCodeValue { - code: code.clone(), + code: code.as_str().to_owned(), available_uses: use_count, disabled: false, for_account: for_account.map(|d| d.to_string()), diff --git a/crates/tranquil-store/src/metastore/oauth_ops.rs b/crates/tranquil-store/src/metastore/oauth_ops.rs index 842f575..2d234b3 100644 --- a/crates/tranquil-store/src/metastore/oauth_ops.rs +++ b/crates/tranquil-store/src/metastore/oauth_ops.rs @@ -247,7 +247,7 @@ impl OAuthOps { refresh_token: data .current_refresh_token .as_ref() - .map(|r| r.0.clone()) + .map(|r| r.to_string()) .unwrap_or_default(), previous_refresh_token: None, scope: data.scope.clone().unwrap_or_default(), @@ -877,7 +877,7 @@ impl OAuthOps { ) -> Result<(), MetastoreError> { let now_ms = Utc::now().timestamp_millis(); let value = OAuthDeviceValue { - session_id: data.session_id.0.clone(), + session_id: data.session_id.as_str().to_owned(), user_agent: data.user_agent.clone(), ip_address: data.ip_address.clone(), last_seen_at_ms: data.last_seen_at.timestamp_millis(), @@ -900,7 +900,7 @@ impl OAuthOps { )?; Ok(val.map(|v| DeviceData { - session_id: tranquil_oauth::SessionId(v.session_id), + session_id: tranquil_oauth::SessionId::from(v.session_id), user_agent: v.user_agent, ip_address: v.ip_address, last_seen_at: DateTime::from_timestamp_millis(v.last_seen_at_ms).unwrap_or_default(), diff --git a/crates/tranquil-store/src/metastore/user_ops.rs b/crates/tranquil-store/src/metastore/user_ops.rs index b9209e1..17cbb97 100644 --- a/crates/tranquil-store/src/metastore/user_ops.rs +++ b/crates/tranquil-store/src/metastore/user_ops.rs @@ -2931,7 +2931,7 @@ impl UserOps { let user_hash = UserHash::from_did(input.did.as_str()); let recovery = RecoveryTokenValue { - token_hash: input.setup_token_hash.clone(), + token_hash: input.setup_token_hash.as_str().to_owned(), expires_at_ms: input.setup_expires_at.timestamp_millis(), }; self.users diff --git a/crates/tranquil-sync/src/blob.rs b/crates/tranquil-sync/src/blob.rs index ed1fd3d..bd5ac90 100644 --- a/crates/tranquil-sync/src/blob.rs +++ b/crates/tranquil-sync/src/blob.rs @@ -98,10 +98,9 @@ pub async fn list_blobs( .list_blobs_since_rev(&did, &Tid::from(since.clone())) .await .map(|cids| { - let mut cid_strs: Vec = cids.into_iter().map(|c| c.to_string()).collect(); - cid_strs.sort(); - cid_strs - .into_iter() + let mut cids = cids; + cids.sort(); + cids.into_iter() .filter(|c| c.as_str() > cursor_cid) .take(usize::try_from(limit + 1).unwrap_or(0)) .collect() @@ -112,7 +111,6 @@ pub async fn list_blobs( .blob .list_blobs_by_user(user_id, Some(cursor_cid), limit + 1) .await - .map(|cids| cids.into_iter().map(|c| c.to_string()).collect()) }; match cids_result { Ok(cids) => { diff --git a/crates/tranquil-sync/src/commit.rs b/crates/tranquil-sync/src/commit.rs index 528b96e..287c8a4 100644 --- a/crates/tranquil-sync/src/commit.rs +++ b/crates/tranquil-sync/src/commit.rs @@ -118,6 +118,7 @@ pub async fn list_repos( let rev = get_rev_from_commit(&state, &cid_str) .await .or_else(|| row.repo_rev.clone()) + .map(|t| t.into_inner()) .unwrap_or_default(); let status = if row.takedown_ref.is_some() { AccountStatus::Takendown @@ -131,7 +132,7 @@ pub async fn list_repos( head: cid_str, rev, active: status.is_active(), - status: status.for_firehose_typed(), + status: status.for_firehose(), }); } let next_cursor = if has_more { @@ -203,7 +204,7 @@ pub async fn get_repo_status( Json(GetRepoStatusOutput { did: account.did, active: account.status.is_active(), - status: account.status.for_firehose_typed(), + status: account.status.for_firehose(), rev, }), )