From eb1a89dc58c608af08f7c6c76bb7e37e8ce4ac5b Mon Sep 17 00:00:00 2001 From: Lewis Date: Sun, 12 Jul 2026 08:03:14 +0200 Subject: [PATCH] db: Did type for repos and handlers Lewis: May this revision serve well! --- crates/tranquil-api/src/admin/account/info.rs | 2 +- crates/tranquil-api/src/admin/invite.rs | 8 +-- crates/tranquil-api/src/common.rs | 4 +- crates/tranquil-api/src/delegation.rs | 4 +- crates/tranquil-api/src/identity/account.rs | 16 ++--- crates/tranquil-api/src/identity/did.rs | 10 +-- crates/tranquil-api/src/identity/provision.rs | 4 +- crates/tranquil-api/src/moderation/mod.rs | 19 +++-- crates/tranquil-api/src/repo/import.rs | 17 ++--- .../tranquil-api/src/server/account_status.rs | 4 +- crates/tranquil-api/src/server/email.rs | 9 +-- crates/tranquil-api/src/server/invite.rs | 4 +- .../src/server/passkey_account.rs | 18 ++--- crates/tranquil-api/src/server/session.rs | 9 ++- crates/tranquil-api/src/server/signing_key.rs | 7 +- crates/tranquil-auth/src/token.rs | 68 +++++++++--------- crates/tranquil-auth/src/types.rs | 10 +-- crates/tranquil-auth/src/verify.rs | 2 +- crates/tranquil-db-traits/src/infra.rs | 8 +-- crates/tranquil-db-traits/src/session.rs | 9 +-- crates/tranquil-db-traits/src/user.rs | 6 +- crates/tranquil-db/src/postgres/infra.rs | 8 +-- crates/tranquil-db/src/postgres/session.rs | 11 ++- crates/tranquil-db/src/postgres/user.rs | 6 +- crates/tranquil-lexicon/src/resolve.rs | 33 ++++----- .../tests/resolve_integration.rs | 12 ++-- .../src/endpoints/authorize/consent.rs | 4 +- .../src/endpoints/delegation.rs | 6 +- .../src/endpoints/token/grants.rs | 35 ++++------ .../src/endpoints/token/helpers.rs | 11 +-- .../src/endpoints/token/types.rs | 2 +- .../src/sso_endpoints.rs | 31 ++++----- crates/tranquil-oauth/src/types.rs | 2 +- crates/tranquil-pds/src/api/proxy.rs | 15 ++-- crates/tranquil-pds/src/auth/email_token.rs | 55 +++++++-------- crates/tranquil-pds/src/auth/legacy_2fa.rs | 16 ++--- crates/tranquil-pds/src/auth/mod.rs | 17 ++--- crates/tranquil-pds/src/auth/service.rs | 20 +++--- crates/tranquil-pds/src/cache_keys.rs | 14 ++-- crates/tranquil-pds/src/delegation/mod.rs | 2 +- crates/tranquil-pds/src/did.rs | 69 +++++++++++-------- crates/tranquil-pds/src/handle/mod.rs | 18 ++--- crates/tranquil-pds/src/oauth/client.rs | 4 +- crates/tranquil-pds/src/oauth/verify.rs | 6 +- crates/tranquil-pds/src/plc/mod.rs | 20 +++--- crates/tranquil-pds/src/sync/verify.rs | 17 +++-- crates/tranquil-pds/tests/commit_signing.rs | 12 ++-- crates/tranquil-pds/tests/jwt_security.rs | 25 +++---- crates/tranquil-pds/tests/scope_edge_cases.rs | 6 +- crates/tranquil-pds/tests/signing_key.rs | 6 +- crates/tranquil-pds/tests/store_parity.rs | 2 +- crates/tranquil-scopes/src/permission_set.rs | 7 +- crates/tranquil-scopes/src/permissions.rs | 44 ++++++------ crates/tranquil-store/benches/metastore.rs | 6 +- .../src/metastore/backlink_ops.rs | 36 +++++----- crates/tranquil-store/src/metastore/client.rs | 17 +++-- .../tranquil-store/src/metastore/handler.rs | 30 ++++---- .../tranquil-store/src/metastore/infra_ops.rs | 2 +- 58 files changed, 421 insertions(+), 444 deletions(-) diff --git a/crates/tranquil-api/src/admin/account/info.rs b/crates/tranquil-api/src/admin/account/info.rs index b5b7732..f340c14 100644 --- a/crates/tranquil-api/src/admin/account/info.rs +++ b/crates/tranquil-api/src/admin/account/info.rs @@ -200,7 +200,7 @@ pub async fn get_account_infos( return Err(ApiError::InvalidRequest("dids is required".into())); } - let dids_typed: Vec = dids.iter().filter_map(|d| d.parse().ok()).collect(); + let dids: Vec = dids.iter().filter_map(|d| d.parse().ok()).collect(); let accounts = state .repos .infra diff --git a/crates/tranquil-api/src/admin/invite.rs b/crates/tranquil-api/src/admin/invite.rs index 43ef76b..a5415f4 100644 --- a/crates/tranquil-api/src/admin/invite.rs +++ b/crates/tranquil-api/src/admin/invite.rs @@ -15,7 +15,7 @@ use tranquil_pds::state::AppState; #[serde(rename_all = "camelCase")] pub struct DisableInviteCodesInput { pub codes: Option>, - pub accounts: Option>, + pub accounts: Option>, } pub async fn disable_invite_codes( @@ -97,7 +97,7 @@ pub async fn get_invite_codes( let user_ids: Vec = codes_rows.iter().map(|r| r.created_by_user).collect(); let code_strings: Vec = codes_rows.iter().map(|r| r.code.clone()).collect(); - let creator_dids: std::collections::HashMap = state + let creator_dids: std::collections::HashMap = state .repos .infra .get_user_dids_by_ids(&user_ids) @@ -167,7 +167,7 @@ pub async fn disable_account_invites( if account.is_empty() { return Err(ApiError::InvalidRequest("account is required".into())); } - let account_did: tranquil_types::Did = account + let account_did: Did = account .parse() .map_err(|_| ApiError::InvalidDid("Invalid DID format".into()))?; @@ -200,7 +200,7 @@ pub async fn enable_account_invites( if account.is_empty() { return Err(ApiError::InvalidRequest("account is required".into())); } - let account_did: tranquil_types::Did = account + let account_did: Did = account .parse() .map_err(|_| ApiError::InvalidDid("Invalid DID format".into()))?; diff --git a/crates/tranquil-api/src/common.rs b/crates/tranquil-api/src/common.rs index fde79f8..c7f4c00 100644 --- a/crates/tranquil-api/src/common.rs +++ b/crates/tranquil-api/src/common.rs @@ -195,12 +195,12 @@ pub fn extract_verification_recipient( } } -pub fn create_self_hosted_did_web(handle: &str) -> Result { +pub fn create_self_hosted_did_web(handle: &str) -> Result { if !tranquil_pds::util::is_self_hosted_did_web_enabled() { return Err(ApiError::SelfHostedDidWebDisabled); } let encoded_handle = handle.replace(':', "%3A"); - Ok(format!("did:web:{}", encoded_handle)) + Ok(Did::from(format!("did:web:{}", encoded_handle))) } pub enum CredentialMatch { diff --git a/crates/tranquil-api/src/delegation.rs b/crates/tranquil-api/src/delegation.rs index 1bfd0e0..e56e95c 100644 --- a/crates/tranquil-api/src/delegation.rs +++ b/crates/tranquil-api/src/delegation.rs @@ -38,7 +38,7 @@ pub async fn list_controllers( async move { if c.handle.is_none() { c.handle = did_resolver - .fetch_did_document(c.did.as_str()) + .fetch_did_document(&c.did) .await .ok() .and_then(|doc| tranquil_types::did_doc::extract_handle(&doc)); @@ -77,7 +77,7 @@ pub async fn add_controller( } match state .cross_pds_oauth - .check_remote_is_delegated(pds_url, input.controller_did.as_str()) + .check_remote_is_delegated(pds_url, &input.controller_did) .await { Some(true) => { diff --git a/crates/tranquil-api/src/identity/account.rs b/crates/tranquil-api/src/identity/account.rs index 4d5635c..6c4b8cb 100644 --- a/crates/tranquil-api/src/identity/account.rs +++ b/crates/tranquil-api/src/identity/account.rs @@ -25,7 +25,7 @@ pub struct CreateAccountInput { pub invite_code: Option, pub did: Option, pub did_type: Option, - pub signing_key: Option, + pub signing_key: Option, pub verification_channel: Option, pub discord_username: Option, pub telegram_username: Option, @@ -47,7 +47,7 @@ pub struct CreateAccountOutput { async fn try_reactivate_migration( state: &AppState, - did: &str, + did: &Did, handle: &Handle, email: &Option, verification_channel: tranquil_db_traits::CommsChannel, @@ -112,7 +112,7 @@ async fn try_reactivate_migration( } }; let session_data = tranquil_db_traits::SessionTokenCreate { - did: did_typed.clone(), + did: did.clone(), access_jti: access_meta.jti.clone(), refresh_jti: refresh_meta.jti.clone(), access_expires_at: access_meta.expires_at, @@ -132,7 +132,7 @@ async fn try_reactivate_migration( super::provision::enqueue_migration_verification( state, reactivated.user_id, - &did_typed, + did, verification_channel, recipient, ) @@ -309,7 +309,7 @@ pub async fn create_account( let signing_key = key_result.signing_key; let reserved_key_id = key_result.reserved_key_id; let did_type = input.did_type.as_deref().unwrap_or("plc"); - let did = match did_type { + let did: Did = match did_type { "web" => { let self_hosted_did = match common::create_self_hosted_did_web(&handle) { Ok(d) => d, @@ -456,7 +456,7 @@ pub async fn create_account( let create_input = tranquil_db_traits::CreatePasswordAccountInput { handle: handle.clone(), email: email.clone(), - did: did_for_commit.clone(), + did: did.clone(), password_hash, preferred_comms_channel, discord_username: comms.discord, @@ -508,7 +508,7 @@ pub async fn create_account( super::provision::enqueue_signup_verification( &state, user_id, - &did_for_commit, + &did, verification_channel, recipient, ) @@ -518,7 +518,7 @@ pub async fn create_account( super::provision::enqueue_migration_verification( &state, user_id, - &did_for_commit, + &did, verification_channel, recipient, ) diff --git a/crates/tranquil-api/src/identity/did.rs b/crates/tranquil-api/src/identity/did.rs index be17182..f712728 100644 --- a/crates/tranquil-api/src/identity/did.rs +++ b/crates/tranquil-api/src/identity/did.rs @@ -44,10 +44,6 @@ pub async fn resolve_handle( if handle_str.is_empty() { return ApiError::InvalidRequest("handle is required".into()).into_response(); } - let cache_key = tranquil_pds::cache_keys::handle_key(handle_str); - if let Some(did) = state.cache.get(&cache_key).await { - return DidResponse::response(did).into_response(); - } let handle: Handle = match handle_str.parse() { Ok(h) => h, Err(_) => { @@ -163,8 +159,8 @@ pub async fn well_known_did(State(state): State, headers: HeaderMap) - async fn serve_handle_did_doc(state: &AppState, handle: &str, hostname: &str) -> Response { let encoded_handle = handle.replace(':', "%3A"); - let expected_did = format!("did:web:{}", encoded_handle); - let expected_did_typed: tranquil_pds::types::Did = match expected_did.parse() { + let expected_did: tranquil_pds::types::Did = match format!("did:web:{}", encoded_handle).parse() + { Ok(d) => d, Err(_) => return ApiError::InvalidRequest("Invalid DID format".into()).into_response(), }; @@ -350,7 +346,7 @@ pub async fn verify_did_web( did: &str, hostname: &str, handle: &str, - expected_signing_key: Option<&str>, + expected_signing_key: Option<&Did>, ) -> Result<(), DidWebVerifyError> { let hostname_for_handles = hostname.split(':').next().unwrap_or(hostname); let subdomain_host = format!("{}.{}", handle, hostname_for_handles); diff --git a/crates/tranquil-api/src/identity/provision.rs b/crates/tranquil-api/src/identity/provision.rs index d5923d9..feeb19f 100644 --- a/crates/tranquil-api/src/identity/provision.rs +++ b/crates/tranquil-api/src/identity/provision.rs @@ -38,7 +38,7 @@ pub async fn submit_plc_genesis( state: &AppState, signing_key: &SigningKey, handle: &Handle, -) -> Result { +) -> Result { let hostname = &tranquil_config::get().server.hostname; let pds_endpoint = format!("https://{}", hostname); @@ -122,7 +122,7 @@ pub struct SigningKeyResult { pub async fn resolve_signing_key( state: &AppState, - signing_key_did: Option<&str>, + signing_key_did: Option<&Did>, ) -> Result { match signing_key_did { Some(key_did) => { diff --git a/crates/tranquil-api/src/moderation/mod.rs b/crates/tranquil-api/src/moderation/mod.rs index 6b7e84d..c9592d1 100644 --- a/crates/tranquil-api/src/moderation/mod.rs +++ b/crates/tranquil-api/src/moderation/mod.rs @@ -66,17 +66,26 @@ pub struct CreateReportOutput { struct ReportServiceConfig { url: String, - did: String, + did: Did, } fn get_report_service_config() -> Option { let cfg = tranquil_config::get(); let url = cfg.moderation.report_service_url.clone()?; - let did = cfg.moderation.report_service_did.clone()?; - if url.is_empty() || did.is_empty() { + let did_str = cfg.moderation.report_service_did.as_deref()?; + if url.is_empty() || did_str.is_empty() { return None; } - Some(ReportServiceConfig { url, did }) + match did_str.parse::() { + Ok(did) => Some(ReportServiceConfig { url, did }), + Err(_) => { + warn!( + report_service_did = did_str, + "invalid report_service_did, handling reports locally" + ); + None + } + } } pub async fn create_report( @@ -97,7 +106,7 @@ async fn proxy_to_report_service( state: &AppState, auth_user: &tranquil_pds::auth::AuthenticatedUser, service_url: &str, - service_did: &str, + service_did: &Did, input: &CreateReportInput, ) -> Response { if let Err(e) = is_ssrf_safe(service_url) { diff --git a/crates/tranquil-api/src/repo/import.rs b/crates/tranquil-api/src/repo/import.rs index 077a4e1..fde45a0 100644 --- a/crates/tranquil-api/src/repo/import.rs +++ b/crates/tranquil-api/src/repo/import.rs @@ -221,17 +221,12 @@ pub async fn import_repo( })?; let new_rev = Tid::now(LimitedU32::MIN); let new_rev_str = new_rev.to_string(); - let (commit_bytes, _sig) = create_signed_commit( - did, - import_result.data_cid, - &new_rev_str, - None, - &signing_key, - ) - .map_err(|e| { - error!("Failed to create new commit: {}", e); - ApiError::InternalError(None) - })?; + let (commit_bytes, _sig) = + create_signed_commit(did, import_result.data_cid, &new_rev, None, &signing_key) + .map_err(|e| { + error!("Failed to create new commit: {}", e); + ApiError::InternalError(None) + })?; let new_root_cid: cid::Cid = state.block_store.put(&commit_bytes).await.map_err(|e| { error!("Failed to store new commit block: {:?}", e); diff --git a/crates/tranquil-api/src/server/account_status.rs b/crates/tranquil-api/src/server/account_status.rs index 2994919..5f4f94d 100644 --- a/crates/tranquil-api/src/server/account_status.rs +++ b/crates/tranquil-api/src/server/account_status.rs @@ -133,7 +133,7 @@ async fn assert_valid_did_document_for_service( if did.as_str().starts_with("did:plc:") { let max_attempts = if with_retry { 5 } else { 1 }; let cache_for_retry = cache.clone(); - let did_owned = did.as_str().to_string(); + let did_owned = did.clone(); let expected_owned = expected_endpoint.clone(); let attempt_counter = Arc::new(AtomicUsize::new(0)); @@ -387,7 +387,7 @@ pub async fn activate_account( .cache .delete(&tranquil_pds::cache_keys::plc_data_key(&did)) .await; - if state.did_resolver.refresh_did(did.as_str()).await.is_err() { + if state.did_resolver.refresh_did(&did).await.is_err() { warn!( "[MIGRATION] activateAccount: Failed to refresh DID cache for {}", did diff --git a/crates/tranquil-api/src/server/email.rs b/crates/tranquil-api/src/server/email.rs index 64121cf..6237785 100644 --- a/crates/tranquil-api/src/server/email.rs +++ b/crates/tranquil-api/src/server/email.rs @@ -19,6 +19,7 @@ use tranquil_pds::auth::{Auth, NotTakendown}; use tranquil_pds::oauth::scopes::{AccountAction, AccountAttr}; use tranquil_pds::rate_limit::{EmailUpdateLimit, RateLimited, VerificationCheckLimit}; use tranquil_pds::state::AppState; +use tranquil_pds::types::{AtIdentifier, Did}; const EMAIL_UPDATE_TTL: Duration = Duration::from_secs(30 * 60); @@ -37,7 +38,7 @@ struct PendingEmailUpdate { async fn get_pending_email_update( cache: &dyn tranquil_pds::cache::Cache, - did: &str, + did: &Did, ) -> Option { cache .get(&tranquil_pds::cache_keys::email_update_key(did)) @@ -79,7 +80,7 @@ pub async fn request_email_update( if token_required { let token = tranquil_pds::auth::email_token::create_email_token( state.cache.as_ref(), - auth.did.as_str(), + &auth.did, tranquil_pds::auth::email_token::EmailTokenPurpose::UpdateEmail, ) .await @@ -249,7 +250,7 @@ pub async fn update_email( tranquil_pds::auth::email_token::validate_email_token( state.cache.as_ref(), - did.as_str(), + did, tranquil_pds::auth::email_token::EmailTokenPurpose::UpdateEmail, token, ) @@ -298,7 +299,7 @@ pub async fn update_email( let short_token_result = tranquil_pds::auth::email_token::validate_email_token( state.cache.as_ref(), - did.as_str(), + did, tranquil_pds::auth::email_token::EmailTokenPurpose::UpdateEmail, token, ) diff --git a/crates/tranquil-api/src/server/invite.rs b/crates/tranquil-api/src/server/invite.rs index 4169a7b..c93625c 100644 --- a/crates/tranquil-api/src/server/invite.rs +++ b/crates/tranquil-api/src/server/invite.rs @@ -72,8 +72,8 @@ pub struct CreateInviteCodesOutput { #[derive(Serialize)] pub struct AccountCodes { - pub account: String, - pub codes: Vec, + pub account: Did, + pub codes: Vec, } pub async fn create_invite_codes( diff --git a/crates/tranquil-api/src/server/passkey_account.rs b/crates/tranquil-api/src/server/passkey_account.rs index e13e0b9..5f4dc2e 100644 --- a/crates/tranquil-api/src/server/passkey_account.rs +++ b/crates/tranquil-api/src/server/passkey_account.rs @@ -39,7 +39,7 @@ pub struct CreatePasskeyAccountInput { pub invite_code: Option, pub did: Option, pub did_type: Option, - pub signing_key: Option, + pub signing_key: Option, pub verification_channel: Option, pub discord_username: Option, pub telegram_username: Option, @@ -154,7 +154,7 @@ pub async fn create_passkey_account( let secret_key = key_result.signing_key; let reserved_key_id = key_result.reserved_key_id; - let did = match did_type { + let did: Did = match did_type { "web" => { let self_hosted_did = match common::create_self_hosted_did_web(&handle) { Ok(d) => d, @@ -269,13 +269,9 @@ pub async fn create_passkey_account( None }; - let did_typed: Did = match did.parse() { - Ok(d) => d, - Err(_) => return Err(ApiError::InternalError(Some("Invalid DID".into()))), - }; let repo = match crate::identity::provision::init_genesis_repo( &state, - &did_typed, + &did, &secret_key, &secret_key_bytes, ) @@ -303,7 +299,7 @@ pub async fn create_passkey_account( let create_input = tranquil_db_traits::CreatePasskeyAccountInput { handle: handle.clone(), email: email.clone().unwrap_or_default(), - did: did_typed.clone(), + did: did.clone(), preferred_comms_channel: verification_channel, discord_username: comms.discord, telegram_username: comms.telegram, @@ -353,7 +349,7 @@ pub async fn create_passkey_account( crate::identity::provision::enqueue_signup_verification( &state, user_id, - &did_typed, + &did, verification_channel, &verification_recipient, ) @@ -367,7 +363,7 @@ pub async fn create_passkey_account( let refresh_jti = Jti::from(uuid::Uuid::new_v4().to_string()); let refresh_expires = chrono::Utc::now() + chrono::Duration::hours(24); let session_data = tranquil_db_traits::SessionTokenCreate { - did: did_typed.clone(), + did: did.clone(), access_jti: token_meta.jti.clone(), refresh_jti, access_expires_at: token_meta.expires_at, @@ -688,7 +684,7 @@ pub async fn request_passkey_recovery( if let Err(e) = state .repos .user - .set_recovery_token(&user.did, &recovery_token_hash, expires_at) + .set_recovery_token(&user.did, recovery_token_hash.as_str(), expires_at) .await { error!("Error updating recovery token: {:?}", e); diff --git a/crates/tranquil-api/src/server/session.rs b/crates/tranquil-api/src/server/session.rs index c38874e..96d8d40 100644 --- a/crates/tranquil-api/src/server/session.rs +++ b/crates/tranquil-api/src/server/session.rs @@ -575,7 +575,7 @@ pub async fn refresh_session( &session_row.did, &key_bytes, session_row.scope.as_deref(), - session_row.controller_did.as_deref(), + session_row.controller_did.as_ref(), None, ) { Ok(m) => m, @@ -747,7 +747,7 @@ fn remint_grace_tokens( &replay.did, key_bytes, replay.scope.as_deref(), - replay.controller_did.as_deref(), + replay.controller_did.as_ref(), None, &replay.access_jti, replay.access_expires_at, @@ -915,7 +915,6 @@ pub async fn confirm_signup( let session = match crate::identity::provision::create_and_store_session( &state, &row.did, - &row.did, &key_bytes, "transition:generic transition:chat.bsky", None, @@ -957,7 +956,7 @@ pub struct AutoResendResult { } pub async fn auto_resend_verification(state: &AppState, did: &Did) -> Option { - let debounce_key = tranquil_pds::cache_keys::auto_verify_sent_key(did.as_str()); + let debounce_key = tranquil_pds::cache_keys::auto_verify_sent_key(did); let debounced = state.cache.get(&debounce_key).await.is_some(); let row = match state.repos.user.get_resend_verification_by_did(did).await { Ok(Some(row)) => row, @@ -1224,7 +1223,7 @@ pub async fn revoke_all_sessions( state .repos .oauth - .delete_sessions_by_did_except(&auth.did, &jti_typed) + .delete_sessions_by_did_except(&auth.did, &token_id) .await .log_db_err("revoking OAuth sessions")?; } else { diff --git a/crates/tranquil-api/src/server/signing_key.rs b/crates/tranquil-api/src/server/signing_key.rs index e618e86..0ac514f 100644 --- a/crates/tranquil-api/src/server/signing_key.rs +++ b/crates/tranquil-api/src/server/signing_key.rs @@ -10,17 +10,18 @@ use serde::{Deserialize, Serialize}; use tracing::{error, info}; use tranquil_pds::api::error::ApiError; use tranquil_pds::state::AppState; +use tranquil_pds::types::Did; const SECP256K1_MULTICODEC_PREFIX: [u8; 2] = [0xe7, 0x01]; -fn public_key_to_did_key(signing_key: &SigningKey) -> String { +fn public_key_to_did_key(signing_key: &SigningKey) -> Did { let verifying_key = signing_key.verifying_key(); let compressed_pubkey = verifying_key.to_sec1_bytes(); let mut multicodec_key = Vec::with_capacity(2 + compressed_pubkey.len()); multicodec_key.extend_from_slice(&SECP256K1_MULTICODEC_PREFIX); multicodec_key.extend_from_slice(&compressed_pubkey); let encoded = multibase::encode(multibase::Base::Base58Btc, &multicodec_key); - format!("did:key:{}", encoded) + Did::from(format!("did:key:{}", encoded)) } #[derive(Deserialize)] @@ -31,7 +32,7 @@ pub struct ReserveSigningKeyInput { #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct ReserveSigningKeyOutput { - pub signing_key: String, + pub signing_key: Did, } pub async fn reserve_signing_key( diff --git a/crates/tranquil-auth/src/token.rs b/crates/tranquil-auth/src/token.rs index 360e47d..9088b67 100644 --- a/crates/tranquil-auth/src/token.rs +++ b/crates/tranquil-auth/src/token.rs @@ -12,20 +12,20 @@ use tranquil_types::{Did, Jti, Nsid}; type HmacSha256 = Hmac; -pub fn create_access_token(did: &str, key_bytes: &[u8]) -> Result { +pub fn create_access_token(did: &Did, key_bytes: &[u8]) -> Result { Ok(create_access_token_with_metadata(did, key_bytes)?.token) } -pub fn create_refresh_token(did: &str, key_bytes: &[u8]) -> Result { +pub fn create_refresh_token(did: &Did, key_bytes: &[u8]) -> Result { Ok(create_refresh_token_with_metadata(did, key_bytes)?.token) } -pub fn create_access_token_with_metadata(did: &str, key_bytes: &[u8]) -> Result { +pub fn create_access_token_with_metadata(did: &Did, key_bytes: &[u8]) -> Result { create_access_token_with_scope_metadata(did, key_bytes, None, None) } pub fn create_access_token_with_scope_metadata( - did: &str, + did: &Did, key_bytes: &[u8], scopes: Option<&str>, hostname: Option<&str>, @@ -42,14 +42,14 @@ pub fn create_access_token_with_scope_metadata( } pub fn create_access_token_with_delegation( - did: &str, + did: &Did, key_bytes: &[u8], scopes: Option<&str>, - controller_did: Option<&str>, + controller_did: Option<&Did>, hostname: Option<&str>, ) -> Result { let scope = scopes.unwrap_or(TokenScope::Access.as_str()); - let act = controller_did.map(|c| ActClaim { sub: c.to_string() }); + let act = controller_did.map(|c| ActClaim { sub: c.clone() }); create_signed_token_with_act( did, scope, @@ -62,7 +62,7 @@ pub fn create_access_token_with_delegation( } pub fn create_refresh_token_with_metadata( - did: &str, + did: &Did, key_bytes: &[u8], ) -> Result { create_signed_token_with_metadata( @@ -79,16 +79,16 @@ pub fn create_refresh_token_with_metadata( /// refresh grace window to reproduce a session's current access token without /// persisting the signed JWT itself. pub fn create_access_token_with_jti( - did: &str, + did: &Did, key_bytes: &[u8], scopes: Option<&str>, - controller_did: Option<&str>, + controller_did: Option<&Did>, hostname: Option<&str>, jti: &Jti, expires_at: DateTime, ) -> Result { let scope = scopes.unwrap_or(TokenScope::Access.as_str()); - let act = controller_did.map(|c| ActClaim { sub: c.to_string() }); + let act = controller_did.map(|c| ActClaim { sub: c.clone() }); Ok(create_signed_token_pinned( did, scope, @@ -105,7 +105,7 @@ pub fn create_access_token_with_jti( /// Re-mint a refresh token carrying a specific `jti` and expiry. Counterpart to /// [`create_access_token_with_jti`] for the refresh grace window. pub fn create_refresh_token_with_jti( - did: &str, + did: &Did, key_bytes: &[u8], jti: &Jti, expires_at: DateTime, @@ -124,8 +124,8 @@ pub fn create_refresh_token_with_jti( } pub fn create_service_token( - did: &str, - aud: &str, + did: &Did, + aud: &Did, lxm: Option<&Nsid>, key_bytes: &[u8], ) -> Result { @@ -137,9 +137,9 @@ pub fn create_service_token( .timestamp(); let claims = Claims { - iss: did.to_owned(), - sub: did.to_owned(), - aud: aud.to_owned(), + iss: did.clone(), + sub: did.clone(), + aud: aud.to_string(), exp: expiration, iat: Utc::now().timestamp(), scope: None, @@ -152,7 +152,7 @@ pub fn create_service_token( } fn create_signed_token_with_metadata( - did: &str, + did: &Did, scope: &str, typ: TokenType, key_bytes: &[u8], @@ -163,7 +163,7 @@ fn create_signed_token_with_metadata( } fn create_signed_token_with_act( - did: &str, + did: &Did, scope: &str, typ: TokenType, key_bytes: &[u8], @@ -180,7 +180,7 @@ fn create_signed_token_with_act( #[allow(clippy::too_many_arguments)] fn create_signed_token_pinned( - did: &str, + did: &Did, scope: &str, typ: TokenType, key_bytes: &[u8], @@ -200,8 +200,8 @@ fn create_signed_token_pinned( }); let claims = Claims { - iss: did.to_owned(), - sub: did.to_owned(), + iss: did.clone(), + sub: did.clone(), aud: format!("did:web:{}", aud_hostname), exp: expiration, iat: Utc::now().timestamp(), @@ -243,16 +243,16 @@ fn sign_claims_with_type(claims: Claims, key: &SigningKey, typ: TokenType) -> Re Ok(format!("{}.{}", message, signature_b64)) } -pub fn create_access_token_hs256(did: &str, secret: &[u8]) -> Result { +pub fn create_access_token_hs256(did: &Did, secret: &[u8]) -> Result { Ok(create_access_token_hs256_with_metadata(did, secret)?.token) } -pub fn create_refresh_token_hs256(did: &str, secret: &[u8]) -> Result { +pub fn create_refresh_token_hs256(did: &Did, secret: &[u8]) -> Result { Ok(create_refresh_token_hs256_with_metadata(did, secret)?.token) } pub fn create_access_token_hs256_with_metadata( - did: &str, + did: &Did, secret: &[u8], ) -> Result { create_hs256_token_with_metadata( @@ -265,7 +265,7 @@ pub fn create_access_token_hs256_with_metadata( } pub fn create_refresh_token_hs256_with_metadata( - did: &str, + did: &Did, secret: &[u8], ) -> Result { create_hs256_token_with_metadata( @@ -278,8 +278,8 @@ pub fn create_refresh_token_hs256_with_metadata( } pub fn create_service_token_hs256( - did: &str, - aud: &str, + did: &Did, + aud: &Did, lxm: &Nsid, secret: &[u8], ) -> Result { @@ -289,9 +289,9 @@ pub fn create_service_token_hs256( .timestamp(); let claims = Claims { - iss: did.to_owned(), - sub: did.to_owned(), - aud: aud.to_owned(), + iss: did.clone(), + sub: did.clone(), + aud: aud.to_string(), exp: expiration, iat: Utc::now().timestamp(), scope: None, @@ -304,7 +304,7 @@ pub fn create_service_token_hs256( } fn create_hs256_token_with_metadata( - did: &str, + did: &Did, scope: &str, typ: TokenType, secret: &[u8], @@ -318,8 +318,8 @@ fn create_hs256_token_with_metadata( let jti = Jti::new(uuid::Uuid::new_v4().to_string()); let claims = Claims { - iss: did.to_owned(), - sub: did.to_owned(), + iss: did.clone(), + sub: did.clone(), aud: format!( "did:web:{}", tranquil_config::try_get() diff --git a/crates/tranquil-auth/src/types.rs b/crates/tranquil-auth/src/types.rs index 04cbbed..a41146d 100644 --- a/crates/tranquil-auth/src/types.rs +++ b/crates/tranquil-auth/src/types.rs @@ -204,13 +204,13 @@ impl std::error::Error for TokenDecodeError {} #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ActClaim { - pub sub: String, + pub sub: Did, } #[derive(Debug, Serialize, Deserialize)] pub struct Claims { - pub iss: String, - pub sub: String, + pub iss: Did, + pub sub: Did, pub aud: String, pub exp: i64, pub iat: i64, @@ -231,8 +231,8 @@ pub struct Header { #[derive(Debug, Serialize, Deserialize)] pub struct UnsafeClaims { - pub iss: String, - pub sub: Option, + pub iss: Did, + pub sub: Option, } pub struct TokenData { diff --git a/crates/tranquil-auth/src/verify.rs b/crates/tranquil-auth/src/verify.rs index 0db95ab..fb59fc3 100644 --- a/crates/tranquil-auth/src/verify.rs +++ b/crates/tranquil-auth/src/verify.rs @@ -14,7 +14,7 @@ use tranquil_types::{Did, Jti}; type HmacSha256 = Hmac; -pub fn get_did_from_token(token: &str) -> Result { +pub fn get_did_from_token(token: &str) -> Result { let parts: Vec<&str> = token.split('.').collect(); if parts.len() != 3 { return Err(TokenDecodeError::InvalidFormat); diff --git a/crates/tranquil-db-traits/src/infra.rs b/crates/tranquil-db-traits/src/infra.rs index 8e68ced..70e1bfa 100644 --- a/crates/tranquil-db-traits/src/infra.rs +++ b/crates/tranquil-db-traits/src/infra.rs @@ -189,7 +189,7 @@ pub struct ReservedSigningKey { pub struct ReservedSigningKeyFull { pub id: Uuid, pub did: Option, - pub public_key_did_key: String, + pub public_key_did_key: Did, pub private_key_bytes: Vec, pub expires_at: DateTime, pub used_at: Option>, @@ -314,14 +314,14 @@ pub trait InfraRepository: Send + Sync { async fn reserve_signing_key( &self, did: Option<&Did>, - public_key_did_key: &str, + public_key_did_key: &Did, private_key_bytes: &[u8], expires_at: DateTime, ) -> Result; async fn get_reserved_signing_key( &self, - public_key_did_key: &str, + public_key_did_key: &Did, ) -> Result, DbError>; async fn mark_signing_key_used(&self, key_id: Uuid) -> Result<(), DbError>; @@ -455,7 +455,7 @@ pub trait InfraRepository: Send + Sync { async fn get_reserved_signing_key_full( &self, - public_key_did_key: &str, + public_key_did_key: &Did, ) -> Result, DbError>; async fn get_plc_tokens_by_did(&self, did: &Did) -> Result, DbError>; diff --git a/crates/tranquil-db-traits/src/session.rs b/crates/tranquil-db-traits/src/session.rs index beeed75..9102dbd 100644 --- a/crates/tranquil-db-traits/src/session.rs +++ b/crates/tranquil-db-traits/src/session.rs @@ -233,11 +233,7 @@ pub trait SessionRepository: Send + Sync { did: &Did, ) -> Result; - async fn delete_session_by_id( - &self, - session_id: SessionId, - did: &Did, - ) -> Result; + async fn delete_session_by_id(&self, session_id: SessionId, did: &Did) -> Result; async fn delete_sessions_by_did(&self, did: &Did) -> Result; @@ -300,7 +296,8 @@ pub trait SessionRepository: Send + Sync { async fn update_mfa_verified(&self, did: &Did) -> Result<(), DbError>; - async fn get_app_password_hashes_by_did(&self, did: &Did) -> Result, DbError>; + async fn get_app_password_hashes_by_did(&self, did: &Did) + -> Result, DbError>; async fn refresh_session_atomic( &self, diff --git a/crates/tranquil-db-traits/src/user.rs b/crates/tranquil-db-traits/src/user.rs index 68a8a6b..b853a8a 100644 --- a/crates/tranquil-db-traits/src/user.rs +++ b/crates/tranquil-db-traits/src/user.rs @@ -224,7 +224,11 @@ pub trait UserRepository: Send + Sync { async fn admin_update_handle(&self, did: &Did, handle: &Handle) -> Result; - async fn admin_update_password(&self, did: &Did, password_hash: &str) -> Result; + async fn admin_update_password( + &self, + did: &Did, + password_hash: &PasswordHash, + ) -> Result; async fn set_admin_status(&self, did: &Did, is_admin: bool) -> Result<(), DbError>; diff --git a/crates/tranquil-db/src/postgres/infra.rs b/crates/tranquil-db/src/postgres/infra.rs index 6f58844..e9bfeb9 100644 --- a/crates/tranquil-db/src/postgres/infra.rs +++ b/crates/tranquil-db/src/postgres/infra.rs @@ -531,7 +531,7 @@ impl InfraRepository for PostgresInfraRepository { async fn reserve_signing_key( &self, did: Option<&Did>, - public_key_did_key: &str, + public_key_did_key: &Did, private_key_bytes: &[u8], expires_at: DateTime, ) -> Result { @@ -554,7 +554,7 @@ impl InfraRepository for PostgresInfraRepository { async fn get_reserved_signing_key( &self, - public_key_did_key: &str, + public_key_did_key: &Did, ) -> Result, DbError> { let result = sqlx::query!( r#"SELECT id, private_key_bytes @@ -1123,7 +1123,7 @@ impl InfraRepository for PostgresInfraRepository { async fn get_reserved_signing_key_full( &self, - public_key_did_key: &str, + public_key_did_key: &Did, ) -> Result, DbError> { let row = sqlx::query!( r#"SELECT id, did, public_key_did_key, private_key_bytes, expires_at, used_at @@ -1137,7 +1137,7 @@ impl InfraRepository for PostgresInfraRepository { Ok(row.map(|r| ReservedSigningKeyFull { id: r.id, did: r.did.map(|d| Did::new(d).expect("valid DID in database")), - public_key_did_key: r.public_key_did_key, + public_key_did_key: Did::from(r.public_key_did_key), private_key_bytes: r.private_key_bytes, expires_at: r.expires_at, used_at: r.used_at, diff --git a/crates/tranquil-db/src/postgres/session.rs b/crates/tranquil-db/src/postgres/session.rs index a36cbc3..2bed5bf 100644 --- a/crates/tranquil-db/src/postgres/session.rs +++ b/crates/tranquil-db/src/postgres/session.rs @@ -131,11 +131,7 @@ impl SessionRepository for PostgresSessionRepository { Ok(result.rows_affected()) } - async fn delete_session_by_id( - &self, - session_id: SessionId, - did: &Did, - ) -> Result { + async fn delete_session_by_id(&self, session_id: SessionId, did: &Did) -> Result { let result = sqlx::query!( "DELETE FROM session_tokens WHERE id = $1 AND did = $2", session_id.as_i32(), @@ -499,7 +495,10 @@ impl SessionRepository for PostgresSessionRepository { Ok(()) } - async fn get_app_password_hashes_by_did(&self, did: &Did) -> Result, DbError> { + async fn get_app_password_hashes_by_did( + &self, + did: &Did, + ) -> Result, DbError> { let rows = sqlx::query_scalar!( r#"SELECT ap.password_hash FROM app_passwords ap JOIN users u ON ap.user_id = u.id diff --git a/crates/tranquil-db/src/postgres/user.rs b/crates/tranquil-db/src/postgres/user.rs index 1d650aa..9802931 100644 --- a/crates/tranquil-db/src/postgres/user.rs +++ b/crates/tranquil-db/src/postgres/user.rs @@ -651,7 +651,11 @@ impl UserRepository for PostgresUserRepository { Ok(result.rows_affected()) } - async fn admin_update_password(&self, did: &Did, password_hash: &str) -> Result { + async fn admin_update_password( + &self, + did: &Did, + password_hash: &PasswordHash, + ) -> Result { let result = sqlx::query!( "UPDATE users SET password_hash = $1 WHERE did = $2", password_hash.as_str(), diff --git a/crates/tranquil-lexicon/src/resolve.rs b/crates/tranquil-lexicon/src/resolve.rs index e1c41b6..d5546bf 100644 --- a/crates/tranquil-lexicon/src/resolve.rs +++ b/crates/tranquil-lexicon/src/resolve.rs @@ -60,9 +60,9 @@ pub enum ResolveError { #[error("no DID found in DNS TXT records for {domain}")] NoDid { domain: String }, #[error("DID document fetch failed for {did}: {reason}")] - DidResolution { did: String, reason: String }, + DidResolution { did: Did, reason: String }, #[error("no PDS endpoint found in DID document for {did}")] - NoPdsEndpoint { did: String }, + NoPdsEndpoint { did: Did }, #[error("schema fetch failed from {url}: {reason}")] SchemaFetch { url: String, reason: String }, #[error("schema deserialization failed: {0}")] @@ -82,22 +82,21 @@ pub fn nsid_to_authority(nsid: &Nsid) -> String { Ok(segments.join(".")) } -pub async fn resolve_did_from_dns(authority: &str) -> Result { +pub async fn resolve_did_from_dns(authority: &str) -> Result { let resolver = 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 extract_did = |lookup: hickory_resolver::lookup::TxtLookup| -> Option { + let extract_did = |lookup: hickory_resolver::lookup::TxtLookup| -> Option { lookup .iter() .flat_map(|record| record.txt_data()) .find_map(|txt| { let txt_str = String::from_utf8_lossy(txt); - txt_str.strip_prefix("did=").and_then(|did| { - let did = did.trim(); - did.starts_with("did:").then(|| did.to_string()) - }) + txt_str + .strip_prefix("did=") + .and_then(|did| Did::new(did.trim()).ok()) }) }; @@ -124,7 +123,7 @@ pub async fn resolve_did_from_dns(authority: &str) -> Result, ) -> Result { let plc_base = plc_directory_url.unwrap_or(DEFAULT_PLC_DIRECTORY); @@ -137,7 +136,7 @@ pub async fn resolve_pds_endpoint( Some(("web", domain)) => format!("https://{}/.well-known/did.json", domain), _ => { return Err(ResolveError::DidResolution { - did: did.to_string(), + did: did.clone(), reason: "unsupported DID method".to_string(), }); } @@ -148,26 +147,24 @@ pub async fn resolve_pds_endpoint( .send() .await .map_err(|e| ResolveError::DidResolution { - did: did.to_string(), + did: did.clone(), reason: e.to_string(), })?; let body = read_body_limited(resp, MAX_RESPONSE_BYTES) .await .map_err(|reason| ResolveError::DidResolution { - did: did.to_string(), + did: did.clone(), reason, })?; let doc: serde_json::Value = serde_json::from_slice(&body).map_err(|e| ResolveError::DidResolution { - did: did.to_string(), + did: did.clone(), reason: e.to_string(), })?; - extract_pds_endpoint(&doc).ok_or(ResolveError::NoPdsEndpoint { - did: did.to_string(), - }) + extract_pds_endpoint(&doc).ok_or_else(|| ResolveError::NoPdsEndpoint { did: did.clone() }) } fn extract_pds_endpoint(doc: &serde_json::Value) -> Option { @@ -188,7 +185,7 @@ fn extract_pds_endpoint(doc: &serde_json::Value) -> Option { pub async fn fetch_schema_from_pds( pds_endpoint: &str, - did: &str, + did: &Did, nsid: &Nsid, ) -> Result { let url = format!( @@ -280,7 +277,7 @@ pub async fn resolve_lexicon_with_config( pub async fn resolve_lexicon_from_did( nsid: &Nsid, - did: &str, + did: &Did, plc_directory_url: Option<&str>, ) -> Result { let pds_endpoint = resolve_pds_endpoint(did, plc_directory_url).await?; diff --git a/crates/tranquil-lexicon/tests/resolve_integration.rs b/crates/tranquil-lexicon/tests/resolve_integration.rs index eb5bb3e..68158ee 100644 --- a/crates/tranquil-lexicon/tests/resolve_integration.rs +++ b/crates/tranquil-lexicon/tests/resolve_integration.rs @@ -71,7 +71,7 @@ async fn test_resolve_pds_endpoint_from_plc() { .mount(&plc_server) .await; - let endpoint = resolve_pds_endpoint(did, Some(&plc_server.uri())) + let endpoint = resolve_pds_endpoint(&did.parse().unwrap(), Some(&plc_server.uri())) .await .unwrap(); assert_eq!(endpoint, "https://pds.example.com"); @@ -94,7 +94,7 @@ async fn test_resolve_pds_endpoint_no_pds_service() { .mount(&plc_server) .await; - let result = resolve_pds_endpoint(did, Some(&plc_server.uri())).await; + let result = resolve_pds_endpoint(&did.parse().unwrap(), Some(&plc_server.uri())).await; assert!(matches!(result, Err(ResolveError::NoPdsEndpoint { .. }))); } @@ -109,13 +109,13 @@ async fn test_resolve_pds_endpoint_plc_not_found() { .mount(&plc_server) .await; - let result = resolve_pds_endpoint(did, Some(&plc_server.uri())).await; + let result = resolve_pds_endpoint(&did.parse().unwrap(), Some(&plc_server.uri())).await; assert!(result.is_err()); } #[tokio::test] async fn test_resolve_pds_endpoint_unsupported_did_method() { - let result = resolve_pds_endpoint("did:key:z6MkTest", None).await; + let result = resolve_pds_endpoint(&"did:key:z6MkTest".parse().unwrap(), None).await; assert!(matches!(result, Err(ResolveError::DidResolution { .. }))); } @@ -146,7 +146,7 @@ async fn test_resolve_pds_endpoint_multiple_services_picks_pds() { .mount(&plc_server) .await; - let endpoint = resolve_pds_endpoint(did, Some(&plc_server.uri())) + let endpoint = resolve_pds_endpoint(&did.parse().unwrap(), Some(&plc_server.uri())) .await .unwrap(); assert_eq!(endpoint, "https://pds.example.com"); @@ -402,6 +402,6 @@ async fn test_plc_server_timeout() { .mount(&plc_server) .await; - let result = resolve_pds_endpoint(did, Some(&plc_server.uri())).await; + let result = resolve_pds_endpoint(&did.parse().unwrap(), Some(&plc_server.uri())).await; assert!(result.is_err()); } diff --git a/crates/tranquil-oauth-server/src/endpoints/authorize/consent.rs b/crates/tranquil-oauth-server/src/endpoints/authorize/consent.rs index 60851a6..2ac5ff2 100644 --- a/crates/tranquil-oauth-server/src/endpoints/authorize/consent.rs +++ b/crates/tranquil-oauth-server/src/endpoints/authorize/consent.rs @@ -19,7 +19,7 @@ pub struct ConsentResponse { pub logo_uri: Option, pub scopes: Vec, pub show_consent: bool, - pub did: String, + pub did: Did, #[serde(skip_serializing_if = "Option::is_none")] pub handle: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -256,7 +256,7 @@ pub async fn consent_get( logo_uri: client_metadata.as_ref().and_then(|m| m.logo_uri.clone()), scopes, show_consent, - did: did.to_string(), + did: did.clone(), handle: account_handle, is_delegation, controller_did: controller_did_resp, diff --git a/crates/tranquil-oauth-server/src/endpoints/delegation.rs b/crates/tranquil-oauth-server/src/endpoints/delegation.rs index b52b90b..8062577 100644 --- a/crates/tranquil-oauth-server/src/endpoints/delegation.rs +++ b/crates/tranquil-oauth-server/src/endpoints/delegation.rs @@ -224,11 +224,7 @@ pub async fn delegation_auth( .flatten(); if is_cross_pds || controller_local.is_none() { - let did_doc = match state - .plc_client() - .get_document(controller_did.as_str()) - .await - { + let did_doc = match state.plc_client().get_document(&controller_did).await { Ok(doc) => doc, Err(_) => { return DelegationAuthResponse::err("Failed to resolve controller DID"); diff --git a/crates/tranquil-oauth-server/src/endpoints/token/grants.rs b/crates/tranquil-oauth-server/src/endpoints/token/grants.rs index b8a508d..23388ed 100644 --- a/crates/tranquil-oauth-server/src/endpoints/token/grants.rs +++ b/crates/tranquil-oauth-server/src/endpoints/token/grants.rs @@ -65,7 +65,7 @@ pub async fn handle_authorization_code_grant( { return Err(OAuthError::InvalidGrant("client_id mismatch".to_string())); } - let did = authorized.did.to_string(); + let did = authorized.did.clone(); let client_metadata_cache = ClientMetadataCache::new(3600); let client_metadata = client_metadata_cache.get(&authorized.client_id).await?; let client_auth = match &request.client_auth { @@ -134,16 +134,10 @@ pub async fn handle_authorization_code_grant( let now = Utc::now(); let (raw_scope, controller_did) = if let Some(ref controller) = authorized.controller_did { - let did_parsed: Did = did - .parse() - .map_err(|_| OAuthError::InvalidRequest("Invalid DID format".to_string()))?; - let controller_parsed: Did = controller - .parse() - .map_err(|_| OAuthError::InvalidRequest("Invalid controller DID format".to_string()))?; let grant = state .repos .delegation - .get_delegation(&did_parsed, &controller_parsed) + .get_delegation(&did, controller) .await .ok() .flatten(); @@ -179,7 +173,7 @@ pub async fn handle_authorization_code_grant( &did, dpop_jkt.as_deref(), final_scope.as_deref(), - controller_did.as_deref(), + controller_did.as_ref(), )?; let stored_client_auth = authorized.client_auth.unwrap_or(ClientAuth::None); let refresh_expiry_days = if matches!(stored_client_auth, ClientAuth::None) { @@ -189,11 +183,8 @@ pub async fn handle_authorization_code_grant( }; let mut stored_parameters = authorized.parameters.clone(); stored_parameters.dpop_jkt = dpop_jkt.clone(); - let did_typed: Did = did - .parse() - .map_err(|_| OAuthError::InvalidRequest("Invalid DID format".to_string()))?; let token_data = TokenData { - did: did_typed, + did: did.clone(), token_id: token_id.clone(), created_at: now, updated_at: now, @@ -295,11 +286,11 @@ pub async fn handle_refresh_token_grant( ); let dpop_jkt = token_data.parameters.dpop_jkt.as_deref(); let access_token = create_access_token_with_delegation( - &token_data.token_id.0, - token_data.did.as_str(), + &token_data.token_id, + &token_data.did, dpop_jkt, token_data.scope.as_deref(), - token_data.controller_did.as_ref().map(|d| d.as_str()), + token_data.controller_did.as_ref(), )?; let mut response_headers = HeaderMap::new(); let config = AuthConfig::get(); @@ -320,7 +311,7 @@ pub async fn handle_refresh_token_grant( expires_in: ACCESS_TOKEN_EXPIRY_SECONDS, refresh_token: token_data.current_refresh_token, scope: token_data.scope, - sub: Some(token_data.did.to_string()), + sub: Some(token_data.did), }), )); } @@ -409,11 +400,11 @@ pub async fn handle_refresh_token_grant( "Refresh token rotated successfully" ); let access_token = create_access_token_with_delegation( - &token_data.token_id.0, - token_data.did.as_str(), - dpop_jkt.as_deref(), + &token_data.token_id, + &token_data.did, + dpop_jkt.as_ref(), token_data.scope.as_deref(), - token_data.controller_did.as_ref().map(|d| d.as_str()), + token_data.controller_did.as_ref(), )?; let mut response_headers = HeaderMap::new(); let config = AuthConfig::get(); @@ -434,7 +425,7 @@ pub async fn handle_refresh_token_grant( expires_in: ACCESS_TOKEN_EXPIRY_SECONDS, refresh_token: Some(new_refresh_token), scope: token_data.scope, - sub: Some(token_data.did.to_string()), + sub: Some(token_data.did), }), )) } diff --git a/crates/tranquil-oauth-server/src/endpoints/token/helpers.rs b/crates/tranquil-oauth-server/src/endpoints/token/helpers.rs index 8af930a..68c311b 100644 --- a/crates/tranquil-oauth-server/src/endpoints/token/helpers.rs +++ b/crates/tranquil-oauth-server/src/endpoints/token/helpers.rs @@ -35,16 +35,7 @@ pub fn create_access_token_with_delegation( sub: &tranquil_types::Did, dpop_jkt: Option<&tranquil_types::JwkThumbprint>, scope: Option<&str>, -) -> Result { - create_access_token_with_delegation(session_id, sub, dpop_jkt, scope, None) -} - -pub fn create_access_token_with_delegation( - session_id: &str, - sub: &str, - dpop_jkt: Option<&str>, - scope: Option<&str>, - controller_did: Option<&str>, + controller_did: Option<&tranquil_types::Did>, ) -> Result { use serde_json::json; let jti = uuid::Uuid::new_v4().to_string(); diff --git a/crates/tranquil-oauth-server/src/endpoints/token/types.rs b/crates/tranquil-oauth-server/src/endpoints/token/types.rs index 7bff327..7091d0e 100644 --- a/crates/tranquil-oauth-server/src/endpoints/token/types.rs +++ b/crates/tranquil-oauth-server/src/endpoints/token/types.rs @@ -184,5 +184,5 @@ pub struct TokenResponse { #[serde(skip_serializing_if = "Option::is_none")] pub scope: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub sub: Option, + pub sub: Option, } diff --git a/crates/tranquil-oauth-server/src/sso_endpoints.rs b/crates/tranquil-oauth-server/src/sso_endpoints.rs index 0c34ff2..d32731a 100644 --- a/crates/tranquil-oauth-server/src/sso_endpoints.rs +++ b/crates/tranquil-oauth-server/src/sso_endpoints.rs @@ -839,7 +839,7 @@ pub struct CompleteRegistrationInput { #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct CompleteRegistrationResponse { - pub did: String, + pub did: tranquil_pds::types::Did, pub handle: tranquil_pds::types::Handle, pub redirect_url: String, #[serde(skip_serializing_if = "Option::is_none")] @@ -1016,13 +1016,14 @@ pub async fn complete_registration( let pds_endpoint = format!("https://{}", hostname); let did_type = input.did_type.as_deref().unwrap_or("plc"); - let did = match did_type { + let did: tranquil_pds::types::Did = match did_type { "web" => { if !tranquil_pds::util::is_self_hosted_did_web_enabled() { return Err(ApiError::SelfHostedDidWebDisabled); } let encoded_handle = handle.replace(':', "%3A"); - let self_hosted_did = format!("did:web:{}", encoded_handle); + let self_hosted_did = + tranquil_pds::types::Did::from(format!("did:web:{}", encoded_handle)); tracing::info!(did = %self_hosted_did, "Creating self-hosted did:web SSO account"); self_hosted_did } @@ -1094,11 +1095,8 @@ pub async fn complete_registration( }; let rev = Tid::now(LimitedU32::MIN); - let did_typed: tranquil_pds::types::Did = did - .parse() - .map_err(|_| ApiError::InternalError(Some("Invalid DID".into())))?; let (commit_bytes, _sig) = match tranquil_pds::repo_ops::create_signed_commit( - &did_typed, + &did, mst_root, &rev, None, @@ -1133,7 +1131,7 @@ pub async fn complete_registration( let create_input = tranquil_db_traits::CreateSsoAccountInput { handle: handle.clone(), email: email.clone(), - did: did_typed.clone(), + did: did.clone(), preferred_comms_channel: verification_channel, discord_username: input .discord_username @@ -1200,7 +1198,7 @@ pub async fn complete_registration( } if let Err(e) = tranquil_pds::repo_ops::sequence_account_event( &state, - &did_typed, + &did, tranquil_db_traits::AccountStatus::Active, ) .await @@ -1214,7 +1212,7 @@ pub async fn complete_registration( }); if let Err(e) = tranquil_pds::repo_ops::create_record_internal( &state, - &did_typed, + &did, &tranquil_pds::types::PROFILE_COLLECTION, &tranquil_pds::types::PROFILE_RKEY, &profile_record, @@ -1269,12 +1267,7 @@ pub async fn complete_registration( "SSO registration completed successfully" ); - let user_id = state - .repos - .user - .get_id_by_did(&did_typed) - .await - .unwrap_or(None); + let user_id = state.repos.user.get_id_by_did(&did).await.unwrap_or(None); let channel_auto_verified = verification_channel == tranquil_db_traits::CommsChannel::Email && pending_preview.provider_email_verified @@ -1284,7 +1277,7 @@ pub async fn complete_registration( let _ = state .repos .user - .set_channel_verified(&did_typed, tranquil_db_traits::CommsChannel::Email) + .set_channel_verified(&did, tranquil_db_traits::CommsChannel::Email) .await; tracing::info!(did = %did, "Auto-verified email from SSO provider"); @@ -1318,7 +1311,7 @@ pub async fn complete_registration( }; let session_data = tranquil_db_traits::SessionTokenCreate { - did: did_typed.clone(), + did: did.clone(), access_jti: access_meta.jti.clone(), refresh_jti: refresh_meta.jti.clone(), access_expires_at: access_meta.expires_at, @@ -1373,7 +1366,7 @@ pub async fn complete_registration( if let Some(uid) = user_id { let verification_token = tranquil_pds::auth::verification_token::generate_signup_token( - &did_typed, + &did, verification_channel, &verification_recipient, ); diff --git a/crates/tranquil-oauth/src/types.rs b/crates/tranquil-oauth/src/types.rs index a17e767..f397bfe 100644 --- a/crates/tranquil-oauth/src/types.rs +++ b/crates/tranquil-oauth/src/types.rs @@ -257,7 +257,7 @@ pub struct TokenResponse { #[serde(skip_serializing_if = "Option::is_none")] pub scope: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub sub: Option, + pub sub: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/crates/tranquil-pds/src/api/proxy.rs b/crates/tranquil-pds/src/api/proxy.rs index 7ee8b68..388f488 100644 --- a/crates/tranquil-pds/src/api/proxy.rs +++ b/crates/tranquil-pds/src/api/proxy.rs @@ -111,7 +111,7 @@ fn is_protected_method(method: &str) -> bool { } /// Fetch the `feed` generator record from the AppView and return its `did`. -async fn resolve_feed_generator_did(appview_url: &str, query: Option<&str>) -> Option { +async fn resolve_feed_generator_did(appview_url: &str, query: Option<&str>) -> Option { #[derive(serde::Deserialize)] struct GetFeedQuery { feed: String, @@ -136,7 +136,10 @@ async fn resolve_feed_generator_did(appview_url: &str, query: Option<&str>) -> O return None; } let body: serde_json::Value = resp.json().await.ok()?; - body.get("value")?.get("did")?.as_str().map(str::to_string) + body.get("value")? + .get("did")? + .as_str() + .and_then(|s| Did::new(s).ok()) } pub struct XrpcProxyLayer { @@ -241,7 +244,11 @@ async fn proxy_handler( ) .into_response(); }; - let Ok(resolved) = state.did_resolver.resolve_service(did, service_id).await else { + let Ok(did) = did.parse::() else { + return ApiError::InvalidRequest("Invalid DID in atproto-proxy header".into()) + .into_response(); + }; + let Ok(resolved) = state.did_resolver.resolve_service(&did, service_id).await else { error!(did = %did, service_id = %service_id, "Could not resolve service DID"); return ApiError::UpstreamFailure.into_response(); }; @@ -340,7 +347,7 @@ async fn proxy_handler( } } } else { - (resolved.did.clone(), method) + (resolved.did.clone(), method_nsid.clone()) }; match crate::auth::create_service_token( diff --git a/crates/tranquil-pds/src/auth/email_token.rs b/crates/tranquil-pds/src/auth/email_token.rs index 1a0a650..2522473 100644 --- a/crates/tranquil-pds/src/auth/email_token.rs +++ b/crates/tranquil-pds/src/auth/email_token.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; use std::time::Duration; use crate::cache::Cache; +use crate::types::Did; use crate::util::{generate_token_code, normalize_token_code}; const TOKEN_TTL_SECS: u64 = 900; @@ -41,7 +42,7 @@ pub enum TokenError { ExpiredToken, } -fn cache_key(did: &str, purpose: EmailTokenPurpose) -> String { +fn cache_key(did: &Did, purpose: EmailTokenPurpose) -> String { format!("email_token:{}:{}", purpose.as_str(), did) } @@ -51,7 +52,7 @@ fn current_timestamp() -> u64 { pub async fn create_email_token( cache: &dyn Cache, - did: &str, + did: &Did, purpose: EmailTokenPurpose, ) -> Result { if !cache.is_available() { @@ -80,7 +81,7 @@ pub async fn create_email_token( pub async fn validate_email_token( cache: &dyn Cache, - did: &str, + did: &Did, purpose: EmailTokenPurpose, token: &str, ) -> Result<(), TokenError> { @@ -110,7 +111,7 @@ pub async fn validate_email_token( Ok(()) } -pub async fn delete_email_token(cache: &dyn Cache, did: &str, purpose: EmailTokenPurpose) { +pub async fn delete_email_token(cache: &dyn Cache, did: &Did, purpose: EmailTokenPurpose) { let _ = cache.delete(&cache_key(did, purpose)).await; } @@ -188,9 +189,9 @@ mod tests { #[tokio::test] async fn test_create_and_validate_token() { let cache = MockCache::new(); - let did = "did:plc:test123"; + let did = Did::from("did:plc:teq".to_string()); - let token = create_email_token(&cache, did, EmailTokenPurpose::UpdateEmail) + let token = create_email_token(&cache, &did, EmailTokenPurpose::UpdateEmail) .await .unwrap(); @@ -198,53 +199,53 @@ mod tests { assert!(token.contains('-')); let result = - validate_email_token(&cache, did, EmailTokenPurpose::UpdateEmail, &token).await; + validate_email_token(&cache, &did, EmailTokenPurpose::UpdateEmail, &token).await; assert!(result.is_ok()); } #[tokio::test] async fn test_token_consumed_after_use() { let cache = MockCache::new(); - let did = "did:plc:test123"; + let did = Did::from("did:plc:teq".to_string()); - let token = create_email_token(&cache, did, EmailTokenPurpose::UpdateEmail) + let token = create_email_token(&cache, &did, EmailTokenPurpose::UpdateEmail) .await .unwrap(); - validate_email_token(&cache, did, EmailTokenPurpose::UpdateEmail, &token) + validate_email_token(&cache, &did, EmailTokenPurpose::UpdateEmail, &token) .await .unwrap(); let result = - validate_email_token(&cache, did, EmailTokenPurpose::UpdateEmail, &token).await; + validate_email_token(&cache, &did, EmailTokenPurpose::UpdateEmail, &token).await; assert_eq!(result.unwrap_err(), TokenError::InvalidToken); } #[tokio::test] async fn test_invalid_token_rejected() { let cache = MockCache::new(); - let did = "did:plc:test123"; + let did = Did::from("did:plc:teq".to_string()); - let _token = create_email_token(&cache, did, EmailTokenPurpose::UpdateEmail) + let _token = create_email_token(&cache, &did, EmailTokenPurpose::UpdateEmail) .await .unwrap(); let result = - validate_email_token(&cache, did, EmailTokenPurpose::UpdateEmail, "XXXXX-XXXXX").await; + validate_email_token(&cache, &did, EmailTokenPurpose::UpdateEmail, "XXXXX-XXXXX").await; assert_eq!(result.unwrap_err(), TokenError::InvalidToken); } #[tokio::test] async fn test_wrong_purpose_rejected() { let cache = MockCache::new(); - let did = "did:plc:test123"; + let did = Did::from("did:plc:teq".to_string()); - let token = create_email_token(&cache, did, EmailTokenPurpose::UpdateEmail) + let token = create_email_token(&cache, &did, EmailTokenPurpose::UpdateEmail) .await .unwrap(); let result = - validate_email_token(&cache, did, EmailTokenPurpose::ConfirmEmail, &token).await; + validate_email_token(&cache, &did, EmailTokenPurpose::ConfirmEmail, &token).await; assert_eq!(result.unwrap_err(), TokenError::InvalidToken); } @@ -252,11 +253,11 @@ mod tests { async fn test_token_format() { // The emitted token is the display form: uppercase `XXXXX-XXXXX`. let cache = MockCache::new(); - let did = "did:plc:test123"; + let did = Did::from("did:plc:teq".to_string()); (0..50).for_each(|_| { let token = futures::executor::block_on(create_email_token( &cache, - did, + &did, EmailTokenPurpose::UpdateEmail, )) .unwrap(); @@ -269,39 +270,39 @@ mod tests { #[tokio::test] async fn test_case_insensitive_validation() { let cache = MockCache::new(); - let did = "did:plc:test123"; + let did = Did::from("did:plc:teq".to_string()); - let token = create_email_token(&cache, did, EmailTokenPurpose::UpdateEmail) + let token = create_email_token(&cache, &did, EmailTokenPurpose::UpdateEmail) .await .unwrap(); let lowercase = token.to_lowercase(); let result = - validate_email_token(&cache, did, EmailTokenPurpose::UpdateEmail, &lowercase).await; + validate_email_token(&cache, &did, EmailTokenPurpose::UpdateEmail, &lowercase).await; assert!(result.is_ok()); } #[tokio::test] async fn test_hyphen_insensitive_validation() { let cache = MockCache::new(); - let did = "did:plc:test123"; + let did = Did::from("did:plc:teq".to_string()); - let token = create_email_token(&cache, did, EmailTokenPurpose::UpdateEmail) + let token = create_email_token(&cache, &did, EmailTokenPurpose::UpdateEmail) .await .unwrap(); let no_hyphen = token.replace('-', ""); let result = - validate_email_token(&cache, did, EmailTokenPurpose::UpdateEmail, &no_hyphen).await; + validate_email_token(&cache, &did, EmailTokenPurpose::UpdateEmail, &no_hyphen).await; assert!(result.is_ok()); } #[tokio::test] async fn test_noop_cache_returns_unavailable() { let cache = crate::cache::NoOpCache; - let did = "did:plc:test"; + let did = Did::from("did:plc:whelk".to_string()); - let result = create_email_token(&cache, did, EmailTokenPurpose::UpdateEmail).await; + let result = create_email_token(&cache, &did, EmailTokenPurpose::UpdateEmail).await; assert_eq!(result.unwrap_err(), TokenError::CacheUnavailable); } } diff --git a/crates/tranquil-pds/src/auth/legacy_2fa.rs b/crates/tranquil-pds/src/auth/legacy_2fa.rs index 9748823..e8d3aba 100644 --- a/crates/tranquil-pds/src/auth/legacy_2fa.rs +++ b/crates/tranquil-pds/src/auth/legacy_2fa.rs @@ -58,13 +58,13 @@ pub async fn create_challenge( } pub async fn clear_challenge(cache: &dyn Cache, did: &Did) { - let _ = cache.delete(&challenge_key(did.as_str())).await; - let _ = cache.delete(&cooldown_key(did.as_str())).await; + let _ = cache.delete(&challenge_key(did)).await; + let _ = cache.delete(&cooldown_key(did)).await; } async fn validate_challenge_internal( cache: &dyn Cache, - did: &str, + did: &Did, code: &str, ) -> Result<(), ValidationError> { if !cache.is_available() { @@ -119,11 +119,11 @@ async fn validate_challenge_internal( Ok(()) } -fn challenge_key(did: &str) -> String { +fn challenge_key(did: &Did) -> String { format!("legacy_2fa:{}", did) } -fn cooldown_key(did: &str) -> String { +fn cooldown_key(did: &Did) -> String { format!("legacy_2fa_cooldown:{}", did) } @@ -215,7 +215,7 @@ pub async fn validate_challenge( did: &Did, code: &str, ) -> Result<(), ValidationError> { - validate_challenge_internal(cache, did.as_str(), code).await + validate_challenge_internal(cache, did, code).await } async fn create_challenge_code( @@ -226,7 +226,7 @@ async fn create_challenge_code( return Err(ChallengeError::CacheUnavailable); } - let cooldown = cooldown_key(did.as_str()); + let cooldown = cooldown_key(did); if cache.get(&cooldown).await.is_some() { return Err(ChallengeError::RateLimited); } @@ -244,7 +244,7 @@ async fn create_challenge_code( cache .set( - &challenge_key(did.as_str()), + &challenge_key(did), &json, Duration::from_secs(CHALLENGE_TTL_SECS), ) diff --git a/crates/tranquil-pds/src/auth/mod.rs b/crates/tranquil-pds/src/auth/mod.rs index 48cf852..1c92e53 100644 --- a/crates/tranquil-pds/src/auth/mod.rs +++ b/crates/tranquil-pds/src/auth/mod.rs @@ -372,7 +372,7 @@ async fn validate_bearer_token_with_options_internal( ) .await; - let status_cache_key = crate::cache_keys::user_status_key(did.as_ref()); + let status_cache_key = crate::cache_keys::user_status_key(&did); let cached = CachedUserStatus { deactivated: user.deactivated_at.is_some(), takendown: user.takedown_ref.is_some(), @@ -504,22 +504,13 @@ async fn validate_bearer_token_with_options_internal( oauth_token.key_bytes.as_deref(), oauth_token.encryption_version, ); - let did: Did = oauth_token - .did - .parse() - .map_err(|_| TokenValidationError::InvalidToken)?; - let controller_did: Option = oauth_info - .controller_did - .map(|d| d.parse()) - .transpose() - .map_err(|_| TokenValidationError::InvalidToken)?; return Ok(AuthenticatedUser { - did, + did: oauth_token.did, key_bytes, is_admin: oauth_token.is_admin, status, scope: oauth_info.scope, - controller_did, + controller_did: oauth_info.controller_did, auth_source: AuthSource::OAuth, }); } else { @@ -530,7 +521,7 @@ async fn validate_bearer_token_with_options_internal( Err(TokenValidationError::AuthenticationFailed) } -pub async fn invalidate_auth_cache(cache: &dyn Cache, did: &str) { +pub async fn invalidate_auth_cache(cache: &dyn Cache, did: &Did) { let key_cache_key = crate::cache_keys::signing_key_key(did); let status_cache_key = crate::cache_keys::user_status_key(did); let _ = cache.delete(&key_cache_key).await; diff --git a/crates/tranquil-pds/src/auth/service.rs b/crates/tranquil-pds/src/auth/service.rs index 11cd8f1..d9de672 100644 --- a/crates/tranquil-pds/src/auth/service.rs +++ b/crates/tranquil-pds/src/auth/service.rs @@ -219,7 +219,7 @@ impl ServiceTokenVerifier { } } - let did = claims.iss.as_str(); + let did = &claims.iss; let public_key = self.resolve_signing_key(did).await?; let signature_bytes = URL_SAFE_NO_PAD @@ -240,7 +240,7 @@ impl ServiceTokenVerifier { Ok(claims) } - async fn resolve_signing_key(&self, did: &str) -> Result { + async fn resolve_signing_key(&self, did: &Did) -> Result { let did_doc = self.resolve_did_document(did).await?; let atproto_key = did_doc @@ -257,18 +257,22 @@ impl ServiceTokenVerifier { parse_did_key_multibase(multibase) } - async fn resolve_did_document(&self, did: &str) -> Result { - if did.starts_with("did:plc:") { + async fn resolve_did_document(&self, did: &Did) -> Result { + if did.is_plc() { self.resolve_did_plc(did).await - } else if did.starts_with("did:web:") { + } else if did.is_web() { self.resolve_did_web(did).await } else { Err(ServiceTokenError::UnsupportedDidMethod) } } - async fn resolve_did_plc(&self, did: &str) -> Result { - let url = format!("{}/{}", self.plc_directory_url, urlencoding::encode(did)); + async fn resolve_did_plc(&self, did: &Did) -> Result { + let url = format!( + "{}/{}", + self.plc_directory_url, + urlencoding::encode(did.as_str()) + ); debug!("Resolving did:plc {} via {}", did, url); let resp = self @@ -291,7 +295,7 @@ impl ServiceTokenVerifier { .map_err(ServiceTokenError::InvalidDidDocument) } - async fn resolve_did_web(&self, did: &str) -> Result { + async fn resolve_did_web(&self, did: &Did) -> Result { let host = did .strip_prefix("did:web:") .ok_or(ServiceTokenError::InvalidFormat)?; diff --git a/crates/tranquil-pds/src/cache_keys.rs b/crates/tranquil-pds/src/cache_keys.rs index b5f3951..9fd03d5 100644 --- a/crates/tranquil-pds/src/cache_keys.rs +++ b/crates/tranquil-pds/src/cache_keys.rs @@ -4,11 +4,11 @@ pub fn session_key(did: &Did, jti: &Jti) -> String { format!("auth:session:{}:{}", did, jti) } -pub fn signing_key_key(did: &str) -> String { +pub fn signing_key_key(did: &Did) -> String { format!("auth:key:{}", did) } -pub fn user_status_key(did: &str) -> String { +pub fn user_status_key(did: &Did) -> String { format!("auth:status:{}", did) } @@ -16,19 +16,19 @@ pub fn handle_key(handle: &Handle) -> String { format!("handle:{}", handle) } -pub fn reauth_key(did: &str) -> String { +pub fn reauth_key(did: &Did) -> String { format!("reauth:{}", did) } -pub fn plc_doc_key(did: &str) -> String { +pub fn plc_doc_key(did: &Did) -> String { format!("plc:doc:{}", did) } -pub fn plc_data_key(did: &str) -> String { +pub fn plc_data_key(did: &Did) -> String { format!("plc:data:{}", did) } -pub fn email_update_key(did: &str) -> String { +pub fn email_update_key(did: &Did) -> String { format!("email_update:{}", did) } @@ -36,6 +36,6 @@ pub fn scope_ref_key(cid: &CidLink) -> String { format!("scope_ref:{}", cid) } -pub fn auto_verify_sent_key(did: &str) -> String { +pub fn auto_verify_sent_key(did: &Did) -> String { format!("auto_verify_sent:{}", did) } diff --git a/crates/tranquil-pds/src/delegation/mod.rs b/crates/tranquil-pds/src/delegation/mod.rs index 968f552..37f144d 100644 --- a/crates/tranquil-pds/src/delegation/mod.rs +++ b/crates/tranquil-pds/src/delegation/mod.rs @@ -38,7 +38,7 @@ pub async fn resolve_identity( .flatten() .is_some(); - let did_doc = state.did_resolver.resolve_did(did.as_str()).await?; + let did_doc = state.did_resolver.resolve_did(did).await?; let pds_url = did_doc.services.iter().find_map(|svc| { if (svc.id == "#atproto_pds" || svc.id.ends_with("#atproto_pds")) diff --git a/crates/tranquil-pds/src/did.rs b/crates/tranquil-pds/src/did.rs index 0fe5107..ac41376 100644 --- a/crates/tranquil-pds/src/did.rs +++ b/crates/tranquil-pds/src/did.rs @@ -1,3 +1,4 @@ +use crate::types::Did; use reqwest::Client; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -30,7 +31,7 @@ pub enum ServiceResolutionError { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DidDocument { - pub id: String, + pub id: Did, #[serde(default)] #[serde(rename = "service")] pub services: Vec, @@ -51,7 +52,7 @@ pub struct DidService { #[derive(Debug, Clone)] pub struct ResolvedService { pub url: String, - pub did: String, + pub did: Did, pub service_id: String, } @@ -94,7 +95,7 @@ impl DidResolver { pub async fn resolve_service( &self, - did: &str, + did: &Did, service_id: &str, ) -> Result, ServiceResolutionError> { { @@ -117,7 +118,7 @@ impl DidResolver { let resolved = Arc::new(ResolvedService { url: service.service_endpoint.clone(), - did: did.into(), + did: did.clone(), service_id: service_id.into(), }); @@ -132,10 +133,10 @@ impl DidResolver { Ok(resolved) } - pub async fn resolve_did(&self, did: &str) -> Result, DidResolutionError> { + pub async fn resolve_did(&self, did: &Did) -> Result, DidResolutionError> { { let cache = self.parsed_did_doc_cache.read().await; - if let Some(cached) = cache.get(did) + if let Some(cached) = cache.get(did.as_str()) && cached.0.elapsed() < self.cache_ttl { return Ok(cached.1.clone()); @@ -146,34 +147,34 @@ impl DidResolver { { let mut cache = self.parsed_did_doc_cache.write().await; - cache.insert(did.into(), (Instant::now(), resolved.clone())); + cache.insert(did.as_str().into(), (Instant::now(), resolved.clone())); } Ok(resolved) } - pub async fn refresh_did(&self, did: &str) -> Result, DidResolutionError> { + pub async fn refresh_did(&self, did: &Did) -> Result, DidResolutionError> { { let mut cache = self.parsed_did_doc_cache.write().await; - cache.remove(did); + cache.remove(did.as_str()); let mut cache = self.service_cache.write().await; - cache.retain(|k, _| !k.starts_with(did)); + cache.retain(|k, _| !k.starts_with(did.as_str())); } self.resolve_did(did).await } - async fn resolve_did_uncached(&self, did: &str) -> Result { - if did.starts_with("did:web:") { + async fn resolve_did_uncached(&self, did: &Did) -> Result { + if did.is_web() { self.resolve_did_web(did).await - } else if did.starts_with("did:plc:") { + } else if did.is_plc() { self.resolve_did_plc(did).await } else { warn!("Unsupported DID method: {}", did); - Err(DidResolutionError::UnsupportedDidMethod(did.into())) + Err(DidResolutionError::UnsupportedDidMethod(did.to_string())) } } - async fn resolve_did_web(&self, did: &str) -> Result { + async fn resolve_did_web(&self, did: &Did) -> Result { let url = build_did_web_url(did)?; debug!("Resolving did:web {} via {}", did, url); @@ -197,8 +198,12 @@ impl DidResolver { .map_err(|e| DidResolutionError::InvalidDocument(e.to_string())) } - async fn resolve_did_plc(&self, did: &str) -> Result { - let url = format!("{}/{}", self.plc_directory_url, urlencoding::encode(did)); + async fn resolve_did_plc(&self, did: &Did) -> Result { + let url = format!( + "{}/{}", + self.plc_directory_url, + urlencoding::encode(did.as_str()) + ); debug!("Resolving did:plc {} via {}", did, url); @@ -227,11 +232,11 @@ impl DidResolver { pub async fn fetch_did_document( &self, - did: &str, + did: &Did, ) -> Result, DidResolutionError> { { let cache = self.did_doc_cache.read().await; - if let Some(cached) = cache.get(did) + if let Some(cached) = cache.get(did.as_str()) && cached.0.elapsed() < self.cache_ttl { return Ok(cached.1.clone()); @@ -242,7 +247,7 @@ impl DidResolver { { let mut cache = self.did_doc_cache.write().await; - cache.insert(did.into(), (Instant::now(), resolved.clone())); + cache.insert(did.as_str().into(), (Instant::now(), resolved.clone())); } Ok(resolved) @@ -251,21 +256,21 @@ impl DidResolver { // TODO: make cached version async fn fetch_did_document_uncached( &self, - did: &str, + did: &Did, ) -> Result { - if did.starts_with("did:web:") { + if did.is_web() { self.fetch_did_document_web(did).await - } else if did.starts_with("did:plc:") { + } else if did.is_plc() { self.fetch_did_document_plc(did).await } else { warn!("Unsupported DID method: {}", did); - Err(DidResolutionError::UnsupportedDidMethod(did.into())) + Err(DidResolutionError::UnsupportedDidMethod(did.to_string())) } } async fn fetch_did_document_web( &self, - did: &str, + did: &Did, ) -> Result { let url = build_did_web_url(did)?; @@ -290,9 +295,13 @@ impl DidResolver { async fn fetch_did_document_plc( &self, - did: &str, + did: &Did, ) -> Result { - let url = format!("{}/{}", self.plc_directory_url, urlencoding::encode(did)); + let url = format!( + "{}/{}", + self.plc_directory_url, + urlencoding::encode(did.as_str()) + ); let resp = self .client @@ -317,9 +326,9 @@ impl DidResolver { .map_err(|e| DidResolutionError::InvalidDocument(e.to_string())) } - pub async fn invalidate_cache(&self, did: &str) { + pub async fn invalidate_cache(&self, did: &Did) { let mut doc_cache = self.parsed_did_doc_cache.write().await; - doc_cache.remove(did); + doc_cache.remove(did.as_str()); } } @@ -333,7 +342,7 @@ pub fn create_did_resolver() -> Arc { Arc::new(DidResolver::new()) } -fn build_did_web_url(did: &str) -> Result { +fn build_did_web_url(did: &Did) -> Result { let host = did .strip_prefix("did:web:") .ok_or(DidResolutionError::InvalidDidWeb)?; diff --git a/crates/tranquil-pds/src/handle/mod.rs b/crates/tranquil-pds/src/handle/mod.rs index 37c2440..e43fbad 100644 --- a/crates/tranquil-pds/src/handle/mod.rs +++ b/crates/tranquil-pds/src/handle/mod.rs @@ -16,7 +16,7 @@ pub enum HandleResolutionError { #[error("Invalid DID format in record")] InvalidDid, #[error("DID mismatch: expected {expected}, got {actual}")] - DidMismatch { expected: String, actual: String }, + DidMismatch { expected: Did, actual: Did }, } pub async fn resolve_handle_dns(handle: &Handle) -> Result { @@ -34,10 +34,9 @@ pub async fn resolve_handle_dns(handle: &Handle) -> Result Result { @@ -78,7 +72,7 @@ pub async fn resolve_handle(handle: &Handle) -> Result Result<(), HandleResolutionError> { let resolved_did = resolve_handle(handle).await?; if resolved_did == expected_did { diff --git a/crates/tranquil-pds/src/oauth/client.rs b/crates/tranquil-pds/src/oauth/client.rs index 9717b15..2f37144 100644 --- a/crates/tranquil-pds/src/oauth/client.rs +++ b/crates/tranquil-pds/src/oauth/client.rs @@ -110,11 +110,11 @@ impl CrossPdsOAuthClient { }) } - pub async fn check_remote_is_delegated(&self, pds_url: &str, did: &str) -> Option { + pub async fn check_remote_is_delegated(&self, pds_url: &str, did: &Did) -> Option { let url = format!( "{}/oauth/security-status?identifier={}", pds_url.trim_end_matches('/'), - urlencoding::encode(did) + urlencoding::encode(did.as_str()) ); let resp = self.http.get(&url).send().await.ok()?; if !resp.status().is_success() { diff --git a/crates/tranquil-pds/src/oauth/verify.rs b/crates/tranquil-pds/src/oauth/verify.rs index 9d7e66f..63738e5 100644 --- a/crates/tranquil-pds/src/oauth/verify.rs +++ b/crates/tranquil-pds/src/oauth/verify.rs @@ -93,12 +93,8 @@ pub async fn verify_oauth_access_token( )); } } - let did: Did = token_data - .did - .parse() - .map_err(|_| OAuthError::InvalidToken("Invalid DID in token".to_string()))?; Ok(VerifyResult { - did, + did: token_data.did, token_id, client_id: token_data.client_id, scope: token_data.scope, diff --git a/crates/tranquil-pds/src/plc/mod.rs b/crates/tranquil-pds/src/plc/mod.rs index 2253e26..8422407 100644 --- a/crates/tranquil-pds/src/plc/mod.rs +++ b/crates/tranquil-pds/src/plc/mod.rs @@ -202,11 +202,11 @@ impl PlcClient { } } - fn encode_did(did: &str) -> String { - urlencoding::encode(did).to_string() + fn encode_did(did: &Did) -> String { + urlencoding::encode(did.as_str()).to_string() } - pub async fn get_document(&self, did: &str) -> Result { + pub async fn get_document(&self, did: &Did) -> Result { let cache_key = crate::cache_keys::plc_doc_key(did); if let Some(ref cache) = self.cache && let Some(cached) = cache.get(&cache_key).await @@ -245,7 +245,7 @@ impl PlcClient { Ok(value) } - pub async fn get_document_data(&self, did: &str) -> Result { + pub async fn get_document_data(&self, did: &Did) -> Result { let cache_key = crate::cache_keys::plc_data_key(did); if let Some(ref cache) = self.cache && let Some(cached) = cache.get(&cache_key).await @@ -284,7 +284,7 @@ impl PlcClient { Ok(value) } - pub async fn get_last_op(&self, did: &str) -> Result { + pub async fn get_last_op(&self, did: &Did) -> Result { let url = format!("{}/{}/log/last", self.base_url, Self::encode_did(did)); let response = self.client.get(&url).send().await?; if response.status() == reqwest::StatusCode::NOT_FOUND { @@ -304,7 +304,7 @@ impl PlcClient { .map_err(|e| PlcError::InvalidResponse(e.to_string())) } - pub async fn get_audit_log(&self, did: &str) -> Result, PlcError> { + pub async fn get_audit_log(&self, did: &Did) -> Result, PlcError> { let url = format!("{}/{}/log/audit", self.base_url, Self::encode_did(did)); let response = self.client.get(&url).send().await?; if response.status() == reqwest::StatusCode::NOT_FOUND { @@ -324,7 +324,7 @@ impl PlcClient { .map_err(|e| PlcError::InvalidResponse(e.to_string())) } - pub async fn send_operation(&self, did: &str, operation: &Value) -> Result<(), PlcError> { + pub async fn send_operation(&self, did: &Did, operation: &Value) -> Result<(), PlcError> { let url = format!("{}/{}", self.base_url, Self::encode_did(did)); let response = self.client.post(&url).json(operation).send().await?; if !response.status().is_success() { @@ -512,7 +512,7 @@ pub fn validate_rotation_did_key(did_key: &str) -> Result<(), String> { } pub struct GenesisResult { - pub did: String, + pub did: Did, pub signed_operation: Value, } @@ -553,7 +553,7 @@ pub fn create_genesis_operation( }) } -pub fn did_for_genesis_op(signed_op: &Value) -> Result { +pub fn did_for_genesis_op(signed_op: &Value) -> Result { let cbor_bytes = serde_ipld_dagcbor::to_vec(signed_op) .map_err(|e| PlcError::Serialization(e.to_string()))?; let mut hasher = Sha256::new(); @@ -561,7 +561,7 @@ pub fn did_for_genesis_op(signed_op: &Value) -> Result { let hash = hasher.finalize(); let encoded = base32::encode(Alphabet::Rfc4648Lower { padding: false }, &hash); let truncated = &encoded[..24]; - Ok(format!("did:plc:{}", truncated)) + Ok(Did::from(format!("did:plc:{}", truncated))) } pub fn validate_plc_operation(op: &Value) -> Result { diff --git a/crates/tranquil-pds/src/sync/verify.rs b/crates/tranquil-pds/src/sync/verify.rs index 16aa225..6c11579 100644 --- a/crates/tranquil-pds/src/sync/verify.rs +++ b/crates/tranquil-pds/src/sync/verify.rs @@ -124,23 +124,22 @@ impl CarVerifier { &self, did: &Did, ) -> Result, VerifyError> { - let did_str = did.as_str(); - if did_str.starts_with("did:plc:") { - self.resolve_plc_did(did_str).await - } else if did_str.starts_with("did:web:") { - self.resolve_web_did(did_str).await + if did.is_plc() { + self.resolve_plc_did(did).await + } else if did.is_web() { + self.resolve_web_did(did).await } else { Err(VerifyError::DidResolutionFailed(format!( "Unsupported DID method: {}", - did_str + did ))) } } - async fn resolve_plc_did(&self, did: &str) -> Result, VerifyError> { + async fn resolve_plc_did(&self, did: &Did) -> Result, VerifyError> { let plc_url = std::env::var("PLC_DIRECTORY_URL") .unwrap_or_else(|_| tranquil_config::get().plc.directory_url.clone()); - let url = format!("{}/{}", plc_url, urlencoding::encode(did)); + let url = format!("{}/{}", plc_url, urlencoding::encode(did.as_str())); let response = self .http_client .get(&url) @@ -162,7 +161,7 @@ impl CarVerifier { Ok(doc.into_static()) } - async fn resolve_web_did(&self, did: &str) -> Result, VerifyError> { + async fn resolve_web_did(&self, did: &Did) -> Result, VerifyError> { let domain = did.strip_prefix("did:web:").ok_or_else(|| { VerifyError::DidResolutionFailed("Invalid did:web format".to_string()) })?; diff --git a/crates/tranquil-pds/tests/commit_signing.rs b/crates/tranquil-pds/tests/commit_signing.rs index 3ec6717..ca47b3c 100644 --- a/crates/tranquil-pds/tests/commit_signing.rs +++ b/crates/tranquil-pds/tests/commit_signing.rs @@ -14,8 +14,8 @@ fn test_commit_signing_produces_valid_signature() { Cid::from_str("bafyreib2rxk3ryblouj3fxza5jvx6psmwewwessc4m6g6e7pqhhkwqomfi").unwrap(); let rev = Tid::now(LimitedU32::MIN); - let did_typed = jacquard_common::types::string::Did::new(did).unwrap(); - let unsigned = Commit::new_unsigned(did_typed, data_cid, rev, None); + let did = jacquard_common::types::string::Did::new(did).unwrap(); + let unsigned = Commit::new_unsigned(did, data_cid, rev, None); let signed = unsigned.sign(&signing_key).unwrap(); let pubkey_bytes = signing_key.verifying_key().to_encoded_point(true); @@ -38,8 +38,8 @@ fn test_commit_signing_with_prev() { Cid::from_str("bafyreigxmvutyl3k5m4guzwxv3xf34gfxjlykgfdqkjmf32vwb5vcjxlui").unwrap(); let rev = Tid::now(LimitedU32::MIN); - let did_typed = jacquard_common::types::string::Did::new(did).unwrap(); - let unsigned = Commit::new_unsigned(did_typed, data_cid, rev, Some(prev_cid)); + let did = jacquard_common::types::string::Did::new(did).unwrap(); + let unsigned = Commit::new_unsigned(did, data_cid, rev, Some(prev_cid)); let signed = unsigned.sign(&signing_key).unwrap(); let pubkey_bytes = signing_key.verifying_key().to_encoded_point(true); @@ -58,8 +58,8 @@ fn test_unsigned_commit_has_5_fields() { Cid::from_str("bafyreib2rxk3ryblouj3fxza5jvx6psmwewwessc4m6g6e7pqhhkwqomfi").unwrap(); let rev = Tid::from_str("3masrxv55po22").unwrap(); - let did_typed = jacquard_common::types::string::Did::new(did).unwrap(); - let unsigned = Commit::new_unsigned(did_typed, data_cid, rev, None); + let did = jacquard_common::types::string::Did::new(did).unwrap(); + let unsigned = Commit::new_unsigned(did, data_cid, rev, None); let unsigned_bytes = serde_ipld_dagcbor::to_vec(&unsigned).unwrap(); diff --git a/crates/tranquil-pds/tests/jwt_security.rs b/crates/tranquil-pds/tests/jwt_security.rs index e785824..1d3fd70 100644 --- a/crates/tranquil-pds/tests/jwt_security.rs +++ b/crates/tranquil-pds/tests/jwt_security.rs @@ -40,8 +40,8 @@ fn create_unsigned_jwt(header: &Value, claims: &Value) -> String { #[test] fn test_signature_attacks() { let key_bytes = generate_user_key(); - let did = "did:plc:test"; - let token = create_access_token(did, &key_bytes).expect("create token"); + let did = Did::from("did:plc:whelk".to_string()); + let token = create_access_token(&did, &key_bytes).expect("create token"); let parts: Vec<&str> = token.split('.').collect(); let forged_signature = URL_SAFE_NO_PAD.encode([0u8; 64]); @@ -143,7 +143,7 @@ fn test_algorithm_substitution_attacks() { #[test] fn test_token_type_confusion() { let key_bytes = generate_user_key(); - let did = "did:plc:test"; + let did = Did::from("did:plc:whelk".to_string()); let refresh_token = create_refresh_token(&did, &key_bytes).expect("create refresh token"); let result = verify_access_token(&refresh_token, &key_bytes); @@ -156,7 +156,7 @@ fn test_token_type_confusion() { .contains("Invalid token type") ); - let access_token = create_access_token(did, &key_bytes).expect("create access token"); + let access_token = create_access_token(&did, &key_bytes).expect("create access token"); let result = verify_refresh_token(&access_token, &key_bytes); assert!(result.is_err(), "Access token as refresh must be rejected"); assert!( @@ -168,9 +168,9 @@ fn test_token_type_confusion() { ); let service_token = create_service_token( - did, - "did:web:target", - Some("com.example.method"), + &did, + &Did::from("did:web:nel.pet".to_string()), + Some(&Nsid::from("cafe.oyster.method".to_string())), &key_bytes, ) .unwrap(); @@ -434,8 +434,8 @@ fn test_claim_validation() { #[test] fn test_did_and_jti_extraction() { let key_bytes = generate_user_key(); - let did = "did:plc:legitimate"; - let token = create_access_token(did, &key_bytes).expect("create token"); + let did = Did::from("did:plc:limpet".to_string()); + let token = create_access_token(&did, &key_bytes).expect("create token"); assert_eq!(get_did_from_token(&token).unwrap(), did); assert!(get_did_from_token("invalid").is_err()); @@ -459,7 +459,7 @@ fn test_did_and_jti_extraction() { #[test] fn test_header_injection_and_constant_time() { let key_bytes = generate_user_key(); - let did = "did:plc:test"; + let did = Did::from("did:plc:whelk".to_string()); let header = json!({ "alg": "ES256K", "typ": TokenType::Access.as_str(), @@ -474,7 +474,7 @@ fn test_header_injection_and_constant_time() { verify_access_token(&create_custom_jwt(&header, &claims, &key_bytes), &key_bytes).is_ok() ); - let valid_token = create_access_token(did, &key_bytes).expect("create token"); + let valid_token = create_access_token(&did, &key_bytes).expect("create token"); let parts: Vec<&str> = valid_token.split('.').collect(); let mut almost_valid = URL_SAFE_NO_PAD.decode(parts[2]).unwrap(); almost_valid[0] ^= 1; @@ -500,7 +500,8 @@ async fn test_server_rejects_invalid_tokens() { let http_client = client(); let key_bytes = generate_user_key(); - let forged_token = create_access_token("did:plc:fake-user", &key_bytes).unwrap(); + let forged_token = + create_access_token(&Did::from("did:plc:lyna".to_string()), &key_bytes).unwrap(); let res = http_client .get(format!("{}/xrpc/com.atproto.server.getSession", url)) .header("Authorization", format!("Bearer {}", forged_token)) diff --git a/crates/tranquil-pds/tests/scope_edge_cases.rs b/crates/tranquil-pds/tests/scope_edge_cases.rs index c9d7ed3..0956d3f 100644 --- a/crates/tranquil-pds/tests/scope_edge_cases.rs +++ b/crates/tranquil-pds/tests/scope_edge_cases.rs @@ -176,9 +176,9 @@ fn test_permissions_repo_collection_wildcard_prefix() { fn test_permissions_rpc_lxm_wildcard_prefix() { let perms = ScopePermissions::from_scope_string(Some("rpc:app.bsky.feed.*?aud=did:web:api.bsky.app")); - assert!(perms.allows_rpc("did:web:api.bsky.app", "app.bsky.feed.getTimeline")); - assert!(perms.allows_rpc("did:web:api.bsky.app", "app.bsky.feed.getAuthorFeed")); - assert!(!perms.allows_rpc("did:web:api.bsky.app", "app.bsky.actor.getProfile")); + assert!(perms.allows_rpc("did:web:api.bsky.app", &c("app.bsky.feed.getTimeline"))); + assert!(perms.allows_rpc("did:web:api.bsky.app", &c("app.bsky.feed.getAuthorFeed"))); + assert!(!perms.allows_rpc("did:web:api.bsky.app", &c("app.bsky.actor.getProfile"))); } #[test] diff --git a/crates/tranquil-pds/tests/signing_key.rs b/crates/tranquil-pds/tests/signing_key.rs index 662c77b..2eaaae6 100644 --- a/crates/tranquil-pds/tests/signing_key.rs +++ b/crates/tranquil-pds/tests/signing_key.rs @@ -48,7 +48,7 @@ async fn test_reserve_signing_key_with_did() { assert!(signing_key.starts_with("did:key:z")); let row = repos .infra - .get_reserved_signing_key_full(signing_key) + .get_reserved_signing_key_full(&tranquil_types::Did::from(signing_key.to_string())) .await .expect("db error") .expect("Reserved key not found in database"); @@ -75,7 +75,7 @@ async fn test_reserve_signing_key_stores_private_key() { let signing_key = body["signingKey"].as_str().unwrap(); let row = repos .infra - .get_reserved_signing_key_full(signing_key) + .get_reserved_signing_key_full(&tranquil_types::Did::from(signing_key.to_string())) .await .expect("db error") .expect("Reserved key not found in database"); @@ -185,7 +185,7 @@ async fn test_create_account_with_reserved_signing_key() { assert!(!access_jwt.is_empty()); let reserved = repos .infra - .get_reserved_signing_key_full(signing_key) + .get_reserved_signing_key_full(&tranquil_types::Did::from(signing_key.to_string())) .await .expect("db error") .expect("Reserved key not found"); diff --git a/crates/tranquil-pds/tests/store_parity.rs b/crates/tranquil-pds/tests/store_parity.rs index 0d8fa8e..59739c9 100644 --- a/crates/tranquil-pds/tests/store_parity.rs +++ b/crates/tranquil-pds/tests/store_parity.rs @@ -1359,7 +1359,7 @@ async fn parity_signing_key_reservation() { let f = ParityFixture::new().await; let did = test_did("sigkey"); let expires = chrono::Utc::now() + chrono::Duration::hours(1); - let pub_key = format!("did:key:z6Mk{}", Uuid::new_v4().simple()); + let pub_key = Did::from(format!("did:key:z6Mk{}", Uuid::new_v4().simple())); let priv_bytes = vec![1u8, 2, 3, 4, 5, 6, 7, 8]; f.pg.infra diff --git a/crates/tranquil-scopes/src/permission_set.rs b/crates/tranquil-scopes/src/permission_set.rs index 0f77d32..921c994 100644 --- a/crates/tranquil-scopes/src/permission_set.rs +++ b/crates/tranquil-scopes/src/permission_set.rs @@ -220,7 +220,7 @@ async fn fetch_lexicon_via_atproto(nsid: &Nsid) -> Result Result { +async fn resolve_lexicon_did_authority(authority: &str) -> Result { let resolver = TokioAsyncResolver::tokio_from_system_conf().unwrap_or_else(|e| { tracing::warn!("falling back to default DNS resolvers: {}", e); TokioAsyncResolver::tokio(ResolverConfig::default(), ResolverOpts::default()) @@ -239,7 +239,8 @@ async fn resolve_lexicon_did_authority(authority: &str) -> Result = (0..5) .map(|i| Backlink { - uri: AtUri::from_parts("did:plc:nel", "app.bsky.feed.like", &format!("3k2r{i}")), + uri: test_uri("did:plc:nel", "app.bsky.feed.like", &format!("3k2r{i}")), path: BacklinkPath::SubjectUri, link_to: format!("at://did:plc:target{i}/app.bsky.feed.post/3k2p{i}"), }) @@ -382,7 +382,7 @@ mod tests { let (user_id, user_hash) = create_repo(&h, "lyna", 4); let existing = Backlink { - uri: AtUri::from_parts("did:plc:lyna", "app.bsky.feed.like", "3k2old"), + uri: test_uri("did:plc:lyna", "app.bsky.feed.like", "3k2old"), path: BacklinkPath::SubjectUri, link_to: "at://did:plc:someone/app.bsky.feed.post/3k2p1".to_string(), }; @@ -393,7 +393,7 @@ mod tests { batch.commit().unwrap(); let proposed = vec![Backlink { - uri: AtUri::from_parts("did:plc:lyna", "app.bsky.feed.like", "3k2new"), + uri: test_uri("did:plc:lyna", "app.bsky.feed.like", "3k2new"), path: BacklinkPath::SubjectUri, link_to: "at://did:plc:someone/app.bsky.feed.post/3k2p1".to_string(), }]; @@ -417,7 +417,7 @@ mod tests { let (user_id, user_hash) = create_repo(&h, "bailey", 5); let existing = Backlink { - uri: AtUri::from_parts("did:plc:bailey", "app.bsky.feed.like", "3k2old"), + uri: test_uri("did:plc:bailey", "app.bsky.feed.like", "3k2old"), path: BacklinkPath::SubjectUri, link_to: "at://did:plc:someone/app.bsky.feed.post/3k2p1".to_string(), }; @@ -428,7 +428,7 @@ mod tests { batch.commit().unwrap(); let proposed = vec![Backlink { - uri: AtUri::from_parts("did:plc:bailey", "app.bsky.feed.repost", "3k2new"), + uri: test_uri("did:plc:bailey", "app.bsky.feed.repost", "3k2new"), path: BacklinkPath::SubjectUri, link_to: "at://did:plc:someone/app.bsky.feed.post/3k2p1".to_string(), }]; @@ -448,7 +448,7 @@ mod tests { let (user_id, user_hash) = create_repo(&h, "olaren", 6); let existing = Backlink { - uri: AtUri::from_parts("did:plc:olaren", "app.bsky.graph.follow", "3k2old"), + uri: test_uri("did:plc:olaren", "app.bsky.graph.follow", "3k2old"), path: BacklinkPath::Subject, link_to: "did:plc:target".to_string(), }; @@ -459,7 +459,7 @@ mod tests { batch.commit().unwrap(); let proposed = vec![Backlink { - uri: AtUri::from_parts("did:plc:olaren", "app.bsky.graph.follow", "3k2new"), + uri: test_uri("did:plc:olaren", "app.bsky.graph.follow", "3k2new"), path: BacklinkPath::SubjectUri, link_to: "did:plc:target".to_string(), }]; @@ -480,7 +480,7 @@ mod tests { let (user_id_b, _user_hash_b) = create_repo(&h, "nel", 8); let existing = Backlink { - uri: AtUri::from_parts("did:plc:teq", "app.bsky.feed.like", "3k2old"), + uri: test_uri("did:plc:teq", "app.bsky.feed.like", "3k2old"), path: BacklinkPath::SubjectUri, link_to: "at://did:plc:target/app.bsky.feed.post/3k2p1".to_string(), }; @@ -491,7 +491,7 @@ mod tests { batch.commit().unwrap(); let proposed = vec![Backlink { - uri: AtUri::from_parts("did:plc:nel", "app.bsky.feed.like", "3k2new"), + uri: test_uri("did:plc:nel", "app.bsky.feed.like", "3k2new"), path: BacklinkPath::SubjectUri, link_to: "at://did:plc:target/app.bsky.feed.post/3k2p1".to_string(), }]; @@ -511,7 +511,7 @@ mod tests { let (user_id, user_hash) = create_repo(&h, "lyna", 12); let existing = Backlink { - uri: AtUri::from_parts("did:plc:lyna", "app.bsky.feed.like", "3k2same"), + uri: test_uri("did:plc:lyna", "app.bsky.feed.like", "3k2same"), path: BacklinkPath::SubjectUri, link_to: "at://did:plc:someone/app.bsky.feed.post/3k2p1".to_string(), }; @@ -522,7 +522,7 @@ mod tests { batch.commit().unwrap(); let proposed = vec![Backlink { - uri: AtUri::from_parts("did:plc:lyna", "app.bsky.feed.like", "3k2same"), + uri: test_uri("did:plc:lyna", "app.bsky.feed.like", "3k2same"), path: BacklinkPath::SubjectUri, link_to: "at://did:plc:someone/app.bsky.feed.post/3k2p1".to_string(), }]; @@ -555,12 +555,12 @@ mod tests { let (_user_id, user_hash) = create_repo(&h, "bailey", 10); let bl1 = Backlink { - uri: AtUri::from_parts("did:plc:bailey", "app.bsky.feed.like", "3k2aaa"), + uri: test_uri("did:plc:bailey", "app.bsky.feed.like", "3k2aaa"), path: BacklinkPath::SubjectUri, link_to: "at://did:plc:t1/app.bsky.feed.post/p1".to_string(), }; let bl2 = Backlink { - uri: AtUri::from_parts("did:plc:bailey", "app.bsky.feed.like", "3k2bbb"), + uri: test_uri("did:plc:bailey", "app.bsky.feed.like", "3k2bbb"), path: BacklinkPath::SubjectUri, link_to: "at://did:plc:t2/app.bsky.feed.post/p2".to_string(), }; @@ -608,7 +608,7 @@ mod tests { let (user_id, user_hash) = create_repo(&h, "kate", 11); let existing = Backlink { - uri: AtUri::from_parts("did:plc:kate", "app.bsky.feed.like", "3k2old"), + uri: test_uri("did:plc:kate", "app.bsky.feed.like", "3k2old"), path: BacklinkPath::SubjectUri, link_to: "at://did:plc:someone/app.bsky.feed.post/3k2p1".to_string(), }; @@ -620,12 +620,12 @@ mod tests { let proposed = vec![ Backlink { - uri: AtUri::from_parts("did:plc:kate", "app.bsky.feed.like", "3k2new1"), + uri: test_uri("did:plc:kate", "app.bsky.feed.like", "3k2new1"), path: BacklinkPath::SubjectUri, link_to: "at://did:plc:someone/app.bsky.feed.post/3k2p1".to_string(), }, Backlink { - uri: AtUri::from_parts("did:plc:kate", "app.bsky.feed.like", "3k2new2"), + uri: test_uri("did:plc:kate", "app.bsky.feed.like", "3k2new2"), path: BacklinkPath::SubjectUri, link_to: "at://did:plc:someone/app.bsky.feed.post/3k2p1".to_string(), }, diff --git a/crates/tranquil-store/src/metastore/client.rs b/crates/tranquil-store/src/metastore/client.rs index b254088..221933b 100644 --- a/crates/tranquil-store/src/metastore/client.rs +++ b/crates/tranquil-store/src/metastore/client.rs @@ -1726,7 +1726,10 @@ impl tranquil_db_traits::SessionRepository for Metastore recv(rx).await } - async fn get_app_password_hashes_by_did(&self, did: &Did) -> Result, DbError> { + async fn get_app_password_hashes_by_did( + &self, + did: &Did, + ) -> Result, DbError> { let (tx, rx) = oneshot::channel(); self.pool.send(MetastoreRequest::Session( SessionRequest::GetAppPasswordHashesByDid { @@ -2041,7 +2044,7 @@ impl tranquil_db_traits::InfraRepository for MetastoreCl async fn reserve_signing_key( &self, did: Option<&Did>, - public_key_did_key: &str, + public_key_did_key: &Did, private_key_bytes: &[u8], expires_at: DateTime, ) -> Result { @@ -2059,7 +2062,7 @@ impl tranquil_db_traits::InfraRepository for MetastoreCl async fn get_reserved_signing_key( &self, - public_key_did_key: &str, + public_key_did_key: &Did, ) -> Result, DbError> { let (tx, rx) = oneshot::channel(); self.pool.send(MetastoreRequest::Infra( @@ -2466,7 +2469,7 @@ impl tranquil_db_traits::InfraRepository for MetastoreCl async fn get_reserved_signing_key_full( &self, - public_key_did_key: &str, + public_key_did_key: &Did, ) -> Result, DbError> { let (tx, rx) = oneshot::channel(); self.pool.send(MetastoreRequest::Infra( @@ -3730,7 +3733,11 @@ impl tranquil_db_traits::UserRepository for MetastoreCli recv(rx).await } - async fn admin_update_password(&self, did: &Did, password_hash: &str) -> Result { + async fn admin_update_password( + &self, + did: &Did, + password_hash: &PasswordHash, + ) -> Result { let (tx, rx) = oneshot::channel(); self.pool .send(MetastoreRequest::User(UserRequest::AdminUpdatePassword { diff --git a/crates/tranquil-store/src/metastore/handler.rs b/crates/tranquil-store/src/metastore/handler.rs index 0557003..7bb185b 100644 --- a/crates/tranquil-store/src/metastore/handler.rs +++ b/crates/tranquil-store/src/metastore/handler.rs @@ -253,7 +253,7 @@ pub enum RepoRequest { impl RepoRequest { fn routing(&self, user_hashes: &UserHashMap) -> Routing { match self { - Self::CreateRepoFull { did, .. } => did_to_routing(did.as_str()), + Self::CreateRepoFull { did, .. } => did_to_routing(did), Self::UpdateRepoRoot { user_id, .. } | Self::UpdateRepoRev { user_id, .. } | Self::DeleteRepo { user_id, .. } @@ -262,7 +262,7 @@ impl RepoRequest { | Self::GetRepoRootCidByUserId { user_id, .. } => uuid_to_routing(user_hashes, user_id), Self::GetRepoRootByDid { did, .. } | Self::GetAccountWithRepo { did, .. } - | Self::UpdateRepoStatus { did, .. } => did_to_routing(did.as_str()), + | Self::UpdateRepoStatus { did, .. } => did_to_routing(did), Self::CountRepos { .. } | Self::GetReposWithoutRev { .. } | Self::ListReposPaginated { .. } => Routing::Global, @@ -503,7 +503,7 @@ pub enum CommitRequest { impl CommitRequest { fn routing(&self, user_hashes: &UserHashMap) -> Routing { match self { - Self::ApplyCommit { input, .. } => did_to_routing(input.did.as_str()), + Self::ApplyCommit { input, .. } => did_to_routing(&input.did), Self::ImportRepoData { user_id, .. } | Self::InsertRecordBlobs { repo_id: user_id, .. @@ -644,7 +644,7 @@ impl BlobRequest { Self::ListMissingBlobs { repo_id, .. } | Self::CountDistinctRecordBlobs { repo_id, .. } | Self::GetBlobsForExport { repo_id, .. } => uuid_to_routing(user_hashes, repo_id), - Self::ListBlobsSinceRev { did, .. } => did_to_routing(did.as_str()), + Self::ListBlobsSinceRev { did, .. } => did_to_routing(did), } } } @@ -731,7 +731,7 @@ impl DelegationRequest { } | Self::CountAuditLogEntries { delegated_did: did, .. - } => did_to_routing(did.as_str()), + } => did_to_routing(did), Self::CreateDelegation { delegated_did, .. } | Self::RevokeDelegation { delegated_did, .. } | Self::UpdateDelegationScopes { delegated_did, .. } @@ -743,7 +743,7 @@ impl DelegationRequest { | Self::ControlsAnyAccounts { did: controller_did, .. - } => did_to_routing(controller_did.as_str()), + } => did_to_routing(controller_did), } } } @@ -822,7 +822,7 @@ impl SsoRequest { match self { Self::CreateExternalIdentity { did, .. } | Self::GetExternalIdentitiesByDid { did, .. } - | Self::DeleteExternalIdentity { did, .. } => did_to_routing(did.as_str()), + | Self::DeleteExternalIdentity { did, .. } => did_to_routing(did), Self::GetExternalIdentityByProvider { .. } | Self::UpdateExternalIdentityLogin { .. } | Self::ConsumeSsoAuthState { .. } @@ -964,7 +964,7 @@ impl SessionRequest { | Self::UpdateLastReauth { did, .. } | Self::GetSessionMfaStatus { did, .. } | Self::UpdateMfaVerified { did, .. } - | Self::GetAppPasswordHashesByDid { did, .. } => did_to_routing(did.as_str()), + | Self::GetAppPasswordHashesByDid { did, .. } => did_to_routing(did), Self::RefreshSessionAtomic { data, .. } => did_to_routing(data.did.as_str()), Self::ListAppPasswords { user_id, .. } | Self::GetAppPasswordsForLogin { user_id, .. } @@ -1721,7 +1721,7 @@ impl UserRequest { } | Self::SetRecoveryToken { did, .. } | Self::EnableTotpVerified { did, .. } - | Self::SetTwoFactorEnabled { did, .. } => did_to_routing(did.as_str()), + | Self::SetTwoFactorEnabled { did, .. } => did_to_routing(did), Self::GetCommsPrefs { user_id, .. } | Self::GetUserKeyById { user_id, .. } @@ -2101,7 +2101,7 @@ impl InfraRequest { | Self::GetAdminAccountInfoByDid { did, .. } | Self::GetDeletionRequestByDid { did, .. } | Self::GetPlcTokensByDid { did, .. } - | Self::CountPlcTokensByDid { did, .. } => did_to_routing(did.as_str()), + | Self::CountPlcTokensByDid { did, .. } => did_to_routing(did), Self::GetBlobStorageKeyByCid { cid, .. } | Self::DeleteBlobByCid { cid, .. } => { cid_to_routing(cid) } @@ -2405,14 +2405,12 @@ impl OAuthRequest { | Self::ListSessionsByDid { did, .. } | Self::DeleteSessionsByDid { did, .. } | Self::DeleteSessionsByDidExcept { did, .. } - | Self::DeleteSessionById { did, .. } => did_to_routing(did.as_str()), - Self::RevokeTokensForController { delegated_did, .. } => { - did_to_routing(delegated_did.as_str()) - } + | Self::DeleteSessionById { did, .. } => did_to_routing(did), + Self::RevokeTokensForController { delegated_did, .. } => did_to_routing(delegated_did), Self::SetAuthorizationDid { did, .. } | Self::UpdateAuthorizationRequest { did, .. } | Self::MarkRequestAuthenticated { did, .. } - | Self::SetRequestDid { did, .. } => did_to_routing(did.as_str()), + | Self::SetRequestDid { did, .. } => did_to_routing(did), Self::CreateToken { .. } | Self::GetTokenById { .. } | Self::GetTokenByRefreshToken { .. } @@ -6284,7 +6282,7 @@ mod tests { .unwrap(); let user_hashes = ms.user_hashes().as_ref(); let did = Did::from("did:plc:limpet".to_string()); - let expected = did_to_routing(did.as_str()); + let expected = did_to_routing(&did); let sid = SessionId::new(7); let (tx, _rx) = oneshot::channel(); diff --git a/crates/tranquil-store/src/metastore/infra_ops.rs b/crates/tranquil-store/src/metastore/infra_ops.rs index bbdef7d..decd2c8 100644 --- a/crates/tranquil-store/src/metastore/infra_ops.rs +++ b/crates/tranquil-store/src/metastore/infra_ops.rs @@ -1461,7 +1461,7 @@ impl InfraOps { Ok(val.map(|v| ReservedSigningKeyFull { id: v.id, did: v.did.and_then(|d| Did::new(d).ok()), - public_key_did_key: v.public_key_did_key, + public_key_did_key: Did::from(v.public_key_did_key), private_key_bytes: v.private_key_bytes, expires_at: DateTime::from_timestamp_millis(v.expires_at_ms).unwrap_or_default(), used_at: v.used_at_ms.and_then(DateTime::from_timestamp_millis),