mirror of
https://tangled.org/tranquil.farm/tranquil-pds
synced 2026-08-18 15:26:08 +00:00
api: validated newtypes from their checked constructors
Lewis: May this revision serve well! <lu5a@proton.me>
This commit is contained in:
@@ -200,7 +200,8 @@ pub fn create_self_hosted_did_web(handle: &str) -> Result<Did, ApiError> {
|
||||
return Err(ApiError::SelfHostedDidWebDisabled);
|
||||
}
|
||||
let encoded_handle = handle.replace(':', "%3A");
|
||||
Ok(Did::from(format!("did:web:{}", encoded_handle)))
|
||||
Did::new(format!("did:web:{}", encoded_handle))
|
||||
.map_err(|_| ApiError::InvalidHandle(Some("Handle is not a valid did:web".into())))
|
||||
}
|
||||
|
||||
pub enum CredentialMatch {
|
||||
|
||||
@@ -392,7 +392,7 @@ pub async fn create_delegated_account(
|
||||
controller_scopes: input.controller_scopes.as_str().to_string(),
|
||||
encrypted_key_bytes: repo.encrypted_key_bytes,
|
||||
encryption_version: tranquil_pds::config::ENCRYPTION_VERSION,
|
||||
commit_cid: CidLink::from(repo.commit_cid.to_string()),
|
||||
commit_cid: CidLink::from(&repo.commit_cid),
|
||||
repo_rev: repo.repo_rev.clone(),
|
||||
genesis_block_cids: repo.genesis_block_cids,
|
||||
};
|
||||
|
||||
@@ -149,17 +149,21 @@ async fn handle_command(state: AppState, interaction: Interaction) -> Response {
|
||||
}
|
||||
};
|
||||
|
||||
let handle = parse_start_handle(interaction.data.as_ref().and_then(|d| d.options.as_deref()));
|
||||
|
||||
if let Some(ref h) = handle
|
||||
&& Handle::new(h).is_err()
|
||||
let handle = match parse_start_handle(
|
||||
interaction.data.as_ref().and_then(|d| d.options.as_deref()),
|
||||
)
|
||||
.map(Handle::new)
|
||||
.transpose()
|
||||
{
|
||||
return Json(json!({
|
||||
"type": 4,
|
||||
"data": {"content": "Invalid handle format. Handle should look like: alice.example.com", "flags": 64}
|
||||
}))
|
||||
.into_response();
|
||||
}
|
||||
Ok(h) => h,
|
||||
Err(_) => {
|
||||
return Json(json!({
|
||||
"type": 4,
|
||||
"data": {"content": "Invalid handle format. Handle should look like: nel.oyster.cafe", "flags": 64}
|
||||
}))
|
||||
.into_response();
|
||||
}
|
||||
};
|
||||
|
||||
debug!(
|
||||
discord_username = %discord_username,
|
||||
@@ -168,7 +172,6 @@ async fn handle_command(state: AppState, interaction: Interaction) -> Response {
|
||||
"Received /start from Discord user"
|
||||
);
|
||||
|
||||
let handle = handle.map(Handle::from);
|
||||
match state
|
||||
.repos
|
||||
.user
|
||||
|
||||
@@ -51,7 +51,9 @@ pub async fn resolve_handle(
|
||||
}
|
||||
};
|
||||
let cache_key = tranquil_pds::cache_keys::handle_key(&handle);
|
||||
if let Some(did) = state.cache.get(&cache_key).await {
|
||||
if let Some(cached) = state.cache.get(&cache_key).await
|
||||
&& let Ok(did) = tranquil_pds::Did::new(cached)
|
||||
{
|
||||
return DidResponse::response(did).into_response();
|
||||
}
|
||||
let user = state.repos.user.get_by_handle(&handle).await;
|
||||
|
||||
@@ -109,7 +109,7 @@ pub async fn init_genesis_repo(
|
||||
encrypted_key_bytes,
|
||||
commit_cid,
|
||||
mst_root_cid: mst_root,
|
||||
repo_rev: Tid::from(rev.as_ref().to_string()),
|
||||
repo_rev: Tid::from(rev.clone()),
|
||||
genesis_block_cids: vec![mst_root.to_bytes(), commit_cid.to_bytes()],
|
||||
})
|
||||
}
|
||||
@@ -198,7 +198,7 @@ pub async fn sequence_new_account(
|
||||
if let Err(e) = tranquil_pds::repo_ops::sequence_sync_event(
|
||||
state,
|
||||
did,
|
||||
&CidLink::from(repo.commit_cid.to_string()),
|
||||
&CidLink::from(&repo.commit_cid),
|
||||
Some(&repo.repo_rev),
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -6,6 +6,7 @@ use axum::{
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value, json};
|
||||
use std::sync::LazyLock;
|
||||
use tracing::{error, info, warn};
|
||||
use tranquil_pds::api::ApiError;
|
||||
use tranquil_pds::api::proxy_client::{is_ssrf_safe, proxy_client};
|
||||
@@ -13,6 +14,9 @@ use tranquil_pds::auth::{AnyUser, Auth};
|
||||
use tranquil_pds::state::AppState;
|
||||
use tranquil_pds::types::{Did, Nsid};
|
||||
|
||||
static CREATE_REPORT_NSID: LazyLock<Nsid> =
|
||||
LazyLock::new(|| "com.atproto.moderation.createReport".parse().unwrap());
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum ReportReasonType {
|
||||
#[serde(rename = "com.atproto.moderation.defs#reasonSpam")]
|
||||
@@ -145,11 +149,10 @@ async fn proxy_to_report_service(
|
||||
},
|
||||
};
|
||||
|
||||
let report_lxm = Nsid::from("com.atproto.moderation.createReport".to_string());
|
||||
let service_token = match tranquil_pds::auth::create_service_token(
|
||||
&auth_user.did,
|
||||
service_did,
|
||||
Some(&report_lxm),
|
||||
Some(&CREATE_REPORT_NSID),
|
||||
&key_bytes,
|
||||
) {
|
||||
Ok(t) => t,
|
||||
|
||||
@@ -11,6 +11,7 @@ use multihash::Multihash;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use std::pin::Pin;
|
||||
use std::sync::LazyLock;
|
||||
use tracing::{debug, error, info, warn};
|
||||
use tranquil_pds::api::error::{ApiError, DbResultExt};
|
||||
use tranquil_pds::auth::{Auth, AuthAny, NotTakendown, Permissive, VerifyScope};
|
||||
@@ -19,6 +20,9 @@ use tranquil_pds::state::AppState;
|
||||
use tranquil_pds::types::{CidLink, Did, Nsid};
|
||||
use tranquil_pds::util::get_header_str;
|
||||
|
||||
static UPLOAD_BLOB_NSID: LazyLock<Nsid> =
|
||||
LazyLock::new(|| "com.atproto.repo.uploadBlob".parse().unwrap());
|
||||
|
||||
fn detect_mime_type(data: &[u8], client_hint: &str) -> String {
|
||||
if let Some(kind) = infer::get(data) {
|
||||
let detected = kind.mime_type().to_string();
|
||||
@@ -48,7 +52,7 @@ pub async fn upload_blob(
|
||||
) -> Result<Response, ApiError> {
|
||||
let (did, controller_did): (Did, Option<Did>) = match &auth {
|
||||
AuthAny::Service(service) => {
|
||||
service.require_lxm(&Nsid::from("com.atproto.repo.uploadBlob".to_string()))?;
|
||||
service.require_lxm(&UPLOAD_BLOB_NSID)?;
|
||||
(service.did.clone(), None)
|
||||
}
|
||||
AuthAny::User(user) => {
|
||||
|
||||
@@ -69,7 +69,8 @@ pub async fn create_passkey_account(
|
||||
let token = extracted.token;
|
||||
if is_service_token(&token) {
|
||||
let verifier = ServiceTokenVerifier::new();
|
||||
let create_account_lxm = Nsid::from("com.atproto.server.createAccount".to_string());
|
||||
let create_account_lxm = Nsid::new("com.atproto.server.createAccount")
|
||||
.expect("com.atproto.server.createAccount is a valid NSID");
|
||||
match verifier
|
||||
.verify_service_token(&token, Some(&create_account_lxm))
|
||||
.await
|
||||
@@ -313,7 +314,7 @@ pub async fn create_passkey_account(
|
||||
encrypted_key_bytes: repo.encrypted_key_bytes,
|
||||
encryption_version: tranquil_pds::config::ENCRYPTION_VERSION,
|
||||
reserved_key_id,
|
||||
commit_cid: tranquil_types::CidLink::from(repo.commit_cid.to_string()),
|
||||
commit_cid: tranquil_types::CidLink::from(&repo.commit_cid),
|
||||
repo_rev: repo.repo_rev.clone(),
|
||||
genesis_block_cids: repo.genesis_block_cids,
|
||||
invite_code: invite_registration.into_invite_code(),
|
||||
|
||||
@@ -45,15 +45,23 @@ pub async fn request_password_reset(
|
||||
None
|
||||
};
|
||||
|
||||
let user_id = match state
|
||||
.repos
|
||||
.user
|
||||
.get_id_by_email_or_handle(
|
||||
normalized,
|
||||
&Handle::from(normalized_handle.as_str().to_string()),
|
||||
)
|
||||
.await
|
||||
{
|
||||
let lookup = match Handle::new(normalized_handle.as_str()) {
|
||||
Ok(handle) => {
|
||||
state
|
||||
.repos
|
||||
.user
|
||||
.get_id_by_email_or_handle(normalized, &handle)
|
||||
.await
|
||||
}
|
||||
Err(_) => state
|
||||
.repos
|
||||
.user
|
||||
.get_by_email(normalized)
|
||||
.await
|
||||
.map(|user| user.map(|user| user.id)),
|
||||
};
|
||||
|
||||
let user_id = match lookup {
|
||||
Ok(Some(id)) => id,
|
||||
Ok(None) => {
|
||||
info!("Password reset requested for unknown identifier");
|
||||
|
||||
@@ -21,7 +21,8 @@ fn public_key_to_did_key(signing_key: &SigningKey) -> Did {
|
||||
multicodec_key.extend_from_slice(&SECP256K1_MULTICODEC_PREFIX);
|
||||
multicodec_key.extend_from_slice(&compressed_pubkey);
|
||||
let encoded = multibase::encode(multibase::Base::Base58Btc, &multicodec_key);
|
||||
Did::from(format!("did:key:{}", encoded))
|
||||
Did::new(format!("did:key:{}", encoded))
|
||||
.expect("did:key with a multibase-encoded secp256k1 key is a valid DID")
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
||||
@@ -62,8 +62,20 @@ pub async fn handle_telegram_webhook(
|
||||
&& let Some(from) = message.from
|
||||
&& let Some(username) = from.username
|
||||
{
|
||||
let handle =
|
||||
parse_start_handle(message.text.as_deref()).map(tranquil_types::Handle::from);
|
||||
let handle = match parse_start_handle(message.text.as_deref())
|
||||
.map(tranquil_types::Handle::new)
|
||||
.transpose()
|
||||
{
|
||||
Ok(h) => h,
|
||||
Err(e) => {
|
||||
warn!(
|
||||
telegram_username = %username,
|
||||
error = %e,
|
||||
"Ignoring /start with an invalid handle"
|
||||
);
|
||||
return StatusCode::OK;
|
||||
}
|
||||
};
|
||||
|
||||
debug!(
|
||||
telegram_username = %username,
|
||||
|
||||
@@ -58,9 +58,16 @@ pub async fn dereference_scope(
|
||||
|
||||
for part in scope_parts {
|
||||
if let Some(cid_str) = part.strip_prefix("ref:") {
|
||||
let cache_key = tranquil_pds::cache_keys::scope_ref_key(
|
||||
&tranquil_types::CidLink::from(cid_str.to_string()),
|
||||
);
|
||||
let cid = match Cid::from_str(cid_str) {
|
||||
Ok(c) => c,
|
||||
Err(_) => {
|
||||
tracing::warn!("Invalid CID in scope ref: {}", cid_str);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let cache_key =
|
||||
tranquil_pds::cache_keys::scope_ref_key(&tranquil_types::CidLink::from(&cid));
|
||||
if let Some(cached) = state.cache.get(&cache_key).await {
|
||||
for s in cached.split_whitespace() {
|
||||
if !resolved_scopes.contains(&s.to_string()) {
|
||||
@@ -70,14 +77,6 @@ pub async fn dereference_scope(
|
||||
continue;
|
||||
}
|
||||
|
||||
let cid = match Cid::from_str(cid_str) {
|
||||
Ok(c) => c,
|
||||
Err(_) => {
|
||||
tracing::warn!("Invalid CID in scope ref: {}", cid_str);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let block_bytes = match state.block_store.get(&cid).await {
|
||||
Ok(Some(b)) => b,
|
||||
Ok(None) => {
|
||||
|
||||
@@ -12,7 +12,16 @@ pub async fn register_complete(
|
||||
_rate_limit: OAuthRateLimited<OAuthRegisterCompleteLimit>,
|
||||
Json(form): Json<RegisterCompleteInput>,
|
||||
) -> Response {
|
||||
let did = Did::from(form.did.clone());
|
||||
let Ok(did) = Did::new(form.did.as_str()) else {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(serde_json::json!({
|
||||
"error": "invalid_request",
|
||||
"error_description": "Invalid DID."
|
||||
})),
|
||||
)
|
||||
.into_response();
|
||||
};
|
||||
|
||||
let request_id = RequestId::from(form.request_uri.clone());
|
||||
let request_data = match state
|
||||
@@ -97,12 +106,12 @@ pub async fn register_complete(
|
||||
}
|
||||
|
||||
if let Some(existing_did) = &request_data.did
|
||||
&& existing_did != &form.did
|
||||
&& existing_did != &did
|
||||
{
|
||||
tracing::warn!(
|
||||
request_uri = %form.request_uri,
|
||||
existing_did = %existing_did,
|
||||
attempted_did = %form.did,
|
||||
attempted_did = %did,
|
||||
"register_complete attempted with different DID than already bound"
|
||||
);
|
||||
return (
|
||||
|
||||
@@ -1023,7 +1023,9 @@ pub async fn complete_registration(
|
||||
}
|
||||
let encoded_handle = handle.replace(':', "%3A");
|
||||
let self_hosted_did =
|
||||
tranquil_pds::types::Did::from(format!("did:web:{}", encoded_handle));
|
||||
tranquil_pds::types::Did::new(format!("did:web:{}", encoded_handle)).map_err(
|
||||
|_| ApiError::InvalidHandle(Some("Handle is not a valid did:web".into())),
|
||||
)?;
|
||||
tracing::info!(did = %self_hosted_did, "Creating self-hosted did:web SSO account");
|
||||
self_hosted_did
|
||||
}
|
||||
@@ -1151,8 +1153,8 @@ pub async fn complete_registration(
|
||||
.filter(|s| !s.is_empty()),
|
||||
encrypted_key_bytes: encrypted_key_bytes.clone(),
|
||||
encryption_version: tranquil_pds::config::ENCRYPTION_VERSION,
|
||||
commit_cid: tranquil_types::CidLink::from(commit_cid.to_string()),
|
||||
repo_rev: tranquil_types::Tid::from(rev.as_ref().to_string()),
|
||||
commit_cid: tranquil_types::CidLink::from_cid(&commit_cid),
|
||||
repo_rev: tranquil_types::Tid::from(rev.clone()),
|
||||
genesis_block_cids,
|
||||
invite_code: invite_registration.into_invite_code(),
|
||||
birthdate_pref,
|
||||
|
||||
@@ -28,8 +28,8 @@ pub struct DidResponse {
|
||||
}
|
||||
|
||||
impl DidResponse {
|
||||
pub fn response(did: impl Into<Did>) -> impl IntoResponse {
|
||||
Json(Self { did: did.into() })
|
||||
pub fn response(did: Did) -> impl IntoResponse {
|
||||
Json(Self { did })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -189,7 +189,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_create_and_validate_token() {
|
||||
let cache = MockCache::new();
|
||||
let did = Did::from("did:plc:teq".to_string());
|
||||
let did = Did::new("did:plc:teq").expect("valid DID");
|
||||
|
||||
let token = create_email_token(&cache, &did, EmailTokenPurpose::UpdateEmail)
|
||||
.await
|
||||
@@ -206,7 +206,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_token_consumed_after_use() {
|
||||
let cache = MockCache::new();
|
||||
let did = Did::from("did:plc:teq".to_string());
|
||||
let did = Did::new("did:plc:teq").expect("valid DID");
|
||||
|
||||
let token = create_email_token(&cache, &did, EmailTokenPurpose::UpdateEmail)
|
||||
.await
|
||||
@@ -224,7 +224,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_invalid_token_rejected() {
|
||||
let cache = MockCache::new();
|
||||
let did = Did::from("did:plc:teq".to_string());
|
||||
let did = Did::new("did:plc:teq").expect("valid DID");
|
||||
|
||||
let _token = create_email_token(&cache, &did, EmailTokenPurpose::UpdateEmail)
|
||||
.await
|
||||
@@ -238,7 +238,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_wrong_purpose_rejected() {
|
||||
let cache = MockCache::new();
|
||||
let did = Did::from("did:plc:teq".to_string());
|
||||
let did = Did::new("did:plc:teq").expect("valid DID");
|
||||
|
||||
let token = create_email_token(&cache, &did, EmailTokenPurpose::UpdateEmail)
|
||||
.await
|
||||
@@ -253,7 +253,7 @@ mod tests {
|
||||
async fn test_token_format() {
|
||||
// The emitted token is the display form: uppercase `XXXXX-XXXXX`.
|
||||
let cache = MockCache::new();
|
||||
let did = Did::from("did:plc:teq".to_string());
|
||||
let did = Did::new("did:plc:teq").expect("valid DID");
|
||||
(0..50).for_each(|_| {
|
||||
let token = futures::executor::block_on(create_email_token(
|
||||
&cache,
|
||||
@@ -270,7 +270,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_case_insensitive_validation() {
|
||||
let cache = MockCache::new();
|
||||
let did = Did::from("did:plc:teq".to_string());
|
||||
let did = Did::new("did:plc:teq").expect("valid DID");
|
||||
|
||||
let token = create_email_token(&cache, &did, EmailTokenPurpose::UpdateEmail)
|
||||
.await
|
||||
@@ -285,7 +285,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_hyphen_insensitive_validation() {
|
||||
let cache = MockCache::new();
|
||||
let did = Did::from("did:plc:teq".to_string());
|
||||
let did = Did::new("did:plc:teq").expect("valid DID");
|
||||
|
||||
let token = create_email_token(&cache, &did, EmailTokenPurpose::UpdateEmail)
|
||||
.await
|
||||
@@ -300,7 +300,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_noop_cache_returns_unavailable() {
|
||||
let cache = crate::cache::NoOpCache;
|
||||
let did = Did::from("did:plc:whelk".to_string());
|
||||
let did = Did::new("did:plc:whelk").expect("valid DID");
|
||||
|
||||
let result = create_email_token(&cache, &did, EmailTokenPurpose::UpdateEmail).await;
|
||||
assert_eq!(result.unwrap_err(), TokenError::CacheUnavailable);
|
||||
|
||||
@@ -624,7 +624,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
fn n(s: &str) -> crate::types::Nsid {
|
||||
crate::types::Nsid::from(s.to_string())
|
||||
crate::types::Nsid::new(s).expect("valid NSID")
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -561,7 +561,8 @@ pub fn did_for_genesis_op(signed_op: &Value) -> Result<Did, PlcError> {
|
||||
let hash = hasher.finalize();
|
||||
let encoded = base32::encode(Alphabet::Rfc4648Lower { padding: false }, &hash);
|
||||
let truncated = &encoded[..24];
|
||||
Ok(Did::from(format!("did:plc:{}", truncated)))
|
||||
Ok(Did::new(format!("did:plc:{}", truncated))
|
||||
.expect("did:plc with 24 base32 characters of a sha256 digest is a valid DID"))
|
||||
}
|
||||
|
||||
pub fn validate_plc_operation(op: &Value) -> Result<PlcOpType, PlcError> {
|
||||
@@ -784,7 +785,7 @@ mod tests {
|
||||
let result = create_genesis_operation(
|
||||
&key,
|
||||
Some(operator_key),
|
||||
&crate::types::Handle::from("whelk.nel.pet".to_string()),
|
||||
&crate::types::Handle::new("whelk.nel.pet").expect("valid handle"),
|
||||
"https://nel.pet",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -227,15 +227,23 @@ fn walk_mst_node(
|
||||
{
|
||||
let blob_refs = find_blob_refs_ipld(&record_value, 0);
|
||||
let parts: Vec<&str> = full_key.split('/').collect();
|
||||
if parts.len() >= 2 {
|
||||
let collection = Nsid::from(parts[..parts.len() - 1].join("/"));
|
||||
let rkey = Rkey::from(parts[parts.len() - 1].to_string());
|
||||
records.push(ImportedRecord {
|
||||
let parsed = match parts.len() >= 2 {
|
||||
true => Nsid::new(parts[..parts.len() - 1].join("/"))
|
||||
.ok()
|
||||
.zip(Rkey::new(parts[parts.len() - 1].to_string()).ok()),
|
||||
false => None,
|
||||
};
|
||||
match parsed {
|
||||
Some((collection, rkey)) => records.push(ImportedRecord {
|
||||
collection,
|
||||
rkey,
|
||||
cid: record_cid,
|
||||
blob_refs,
|
||||
});
|
||||
}),
|
||||
None => tracing::warn!(
|
||||
key = %full_key,
|
||||
"skipping a CAR record whose MST key isn't a valid collection/rkey pair"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,8 @@ impl CarVerifier {
|
||||
.ok_or_else(|| VerifyError::BlockNotFound(root_cid.to_string()))?;
|
||||
let commit =
|
||||
Commit::from_cbor(root_block).map_err(|e| VerifyError::InvalidCommit(e.to_string()))?;
|
||||
let commit_did = commit.did().to_string().into();
|
||||
let commit_did = tranquil_types::Did::new(commit.did().to_string())
|
||||
.map_err(|e| VerifyError::InvalidCommit(e.to_string()))?;
|
||||
let data_cid = commit.data();
|
||||
self.verify_mst_structure(data_cid, blocks)?;
|
||||
debug!("MST structure verified for commit: {:?}", commit);
|
||||
|
||||
@@ -145,7 +145,7 @@ async fn sync_event_carries_inline_commit_block() {
|
||||
let commit_bytes = b"synthetic sync commit block payload".to_vec();
|
||||
let commit_cid = synthetic_cid(&commit_bytes);
|
||||
let cid_link: CidLink = (&commit_cid).into();
|
||||
let rev = tranquil_types::Tid::from("3kabcdefghij2".to_string());
|
||||
let rev = tranquil_types::Tid::new("3kabcdefghij2").expect("valid TID");
|
||||
|
||||
let baseline = repos.repo.get_max_seq().await.expect("get_max_seq");
|
||||
repos
|
||||
@@ -192,7 +192,7 @@ async fn genesis_commit_event_carries_inline_blocks() {
|
||||
let mst_root_cid = synthetic_cid(&mst_root_bytes);
|
||||
let commit_link: CidLink = (&commit_cid).into();
|
||||
let mst_link: CidLink = (&mst_root_cid).into();
|
||||
let rev = tranquil_types::Tid::from("3kabcdefghij3".to_string());
|
||||
let rev = tranquil_types::Tid::new("3kabcdefghij3").expect("valid TID");
|
||||
|
||||
let baseline = repos.repo.get_max_seq().await.expect("get_max_seq");
|
||||
repos
|
||||
|
||||
@@ -40,7 +40,7 @@ fn create_unsigned_jwt(header: &Value, claims: &Value) -> String {
|
||||
#[test]
|
||||
fn test_signature_attacks() {
|
||||
let key_bytes = generate_user_key();
|
||||
let did = Did::from("did:plc:whelk".to_string());
|
||||
let did = Did::new("did:plc:whelk").expect("valid DID");
|
||||
let token = create_access_token(&did, &key_bytes).expect("create token");
|
||||
let parts: Vec<&str> = token.split('.').collect();
|
||||
|
||||
@@ -143,7 +143,7 @@ fn test_algorithm_substitution_attacks() {
|
||||
#[test]
|
||||
fn test_token_type_confusion() {
|
||||
let key_bytes = generate_user_key();
|
||||
let did = Did::from("did:plc:whelk".to_string());
|
||||
let did = Did::new("did:plc:whelk").expect("valid DID");
|
||||
|
||||
let refresh_token = create_refresh_token(&did, &key_bytes).expect("create refresh token");
|
||||
let result = verify_access_token(&refresh_token, &key_bytes);
|
||||
@@ -169,8 +169,8 @@ fn test_token_type_confusion() {
|
||||
|
||||
let service_token = create_service_token(
|
||||
&did,
|
||||
&Did::from("did:web:nel.pet".to_string()),
|
||||
Some(&Nsid::from("cafe.oyster.method".to_string())),
|
||||
&Did::new("did:web:nel.pet").expect("valid DID"),
|
||||
Some(&Nsid::new("cafe.oyster.method").expect("valid NSID")),
|
||||
&key_bytes,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -434,7 +434,7 @@ fn test_claim_validation() {
|
||||
#[test]
|
||||
fn test_did_and_jti_extraction() {
|
||||
let key_bytes = generate_user_key();
|
||||
let did = Did::from("did:plc:limpet".to_string());
|
||||
let did = Did::new("did:plc:limpet").expect("valid DID");
|
||||
let token = create_access_token(&did, &key_bytes).expect("create token");
|
||||
|
||||
assert_eq!(get_did_from_token(&token).unwrap(), did);
|
||||
@@ -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::from("did:plc:whelk".to_string());
|
||||
let did = Did::new("did:plc:whelk").expect("valid DID");
|
||||
|
||||
let header = json!({
|
||||
"alg": "ES256K", "typ": TokenType::Access.as_str(),
|
||||
@@ -501,7 +501,7 @@ async fn test_server_rejects_invalid_tokens() {
|
||||
|
||||
let key_bytes = generate_user_key();
|
||||
let forged_token =
|
||||
create_access_token(&Did::from("did:plc:lyna".to_string()), &key_bytes).unwrap();
|
||||
create_access_token(&Did::new("did:plc:lyna").expect("valid DID"), &key_bytes).unwrap();
|
||||
let res = http_client
|
||||
.get(format!("{}/xrpc/com.atproto.server.getSession", url))
|
||||
.header("Authorization", format!("Bearer {}", forged_token))
|
||||
|
||||
@@ -10,7 +10,7 @@ fn now() -> String {
|
||||
}
|
||||
|
||||
fn c(s: &str) -> Nsid {
|
||||
Nsid::from(s.to_string())
|
||||
Nsid::new(s).expect("valid NSID")
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -512,9 +512,9 @@ async fn follow_uris_pointing_to(repo_id: uuid::Uuid, target_did: &str) -> Vec<S
|
||||
let repos = get_test_repos().await;
|
||||
let probe = Backlink {
|
||||
uri: AtUri::from_parts(
|
||||
&Did::from("did:plc:periwinkle".to_string()),
|
||||
&Nsid::from("app.bsky.graph.follow".to_string()),
|
||||
&Rkey::from("probe".to_string()),
|
||||
&Did::new("did:plc:periwinkle").expect("valid DID"),
|
||||
&Nsid::new("app.bsky.graph.follow").expect("valid NSID"),
|
||||
&Rkey::new("probe").expect("valid rkey"),
|
||||
),
|
||||
path: BacklinkPath::Subject,
|
||||
link_to: target_did.to_string(),
|
||||
|
||||
@@ -6,7 +6,7 @@ use tranquil_pds::oauth::scopes::{
|
||||
use tranquil_types::Nsid;
|
||||
|
||||
fn c(s: &str) -> Nsid {
|
||||
Nsid::from(s.to_string())
|
||||
Nsid::new(s).expect("valid NSID")
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -48,7 +48,9 @@ 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(&tranquil_types::Did::from(signing_key.to_string()))
|
||||
.get_reserved_signing_key_full(
|
||||
&tranquil_types::Did::new(signing_key.to_string()).expect("valid DID"),
|
||||
)
|
||||
.await
|
||||
.expect("db error")
|
||||
.expect("Reserved key not found in database");
|
||||
@@ -75,7 +77,9 @@ 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(&tranquil_types::Did::from(signing_key.to_string()))
|
||||
.get_reserved_signing_key_full(
|
||||
&tranquil_types::Did::new(signing_key.to_string()).expect("valid DID"),
|
||||
)
|
||||
.await
|
||||
.expect("db error")
|
||||
.expect("Reserved key not found in database");
|
||||
@@ -185,7 +189,9 @@ async fn test_create_account_with_reserved_signing_key() {
|
||||
assert!(!access_jwt.is_empty());
|
||||
let reserved = repos
|
||||
.infra
|
||||
.get_reserved_signing_key_full(&tranquil_types::Did::from(signing_key.to_string()))
|
||||
.get_reserved_signing_key_full(
|
||||
&tranquil_types::Did::new(signing_key.to_string()).expect("valid DID"),
|
||||
)
|
||||
.await
|
||||
.expect("db error")
|
||||
.expect("Reserved key not found");
|
||||
|
||||
@@ -174,7 +174,8 @@ impl BacklinkOps {
|
||||
MetastoreError::CorruptData("corrupt backlink value in indexes partition"),
|
||||
)?;
|
||||
|
||||
let uri: AtUri = val.source_uri.into();
|
||||
let uri = AtUri::new(val.source_uri)
|
||||
.map_err(|_| MetastoreError::CorruptData("corrupt backlink source_uri"))?;
|
||||
let matches_collection = uri.collection().is_some_and(|c| c == collection_str);
|
||||
let matches_path = match discriminant_to_path(val.path) {
|
||||
Some(p) => p == bl.path,
|
||||
@@ -236,13 +237,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn test_cid_link(seed: u8) -> tranquil_types::CidLink {
|
||||
let digest: [u8; 32] = std::array::from_fn(|i| seed.wrapping_add(i as u8));
|
||||
let mh = multihash::Multihash::<64>::wrap(0x12, &digest).unwrap();
|
||||
let c = cid::Cid::new_v1(0x71, mh);
|
||||
tranquil_types::CidLink::from_cid(&c)
|
||||
}
|
||||
|
||||
fn test_rev(seq: u64) -> tranquil_types::Tid {
|
||||
const ALPHABET: &[u8] = b"234567abcdefghijklmnopqrstuvwxyz";
|
||||
let s: String = (0..13)
|
||||
@@ -252,10 +246,17 @@ mod tests {
|
||||
tranquil_types::Tid::new(s).expect("generated TID is valid")
|
||||
}
|
||||
|
||||
fn test_cid_link(seed: u8) -> tranquil_types::CidLink {
|
||||
let digest: [u8; 32] = std::array::from_fn(|i| seed.wrapping_add(i as u8));
|
||||
let mh = multihash::Multihash::<64>::wrap(0x12, &digest).unwrap();
|
||||
let c = cid::Cid::new_v1(0x71, mh);
|
||||
tranquil_types::CidLink::from_cid(&c)
|
||||
}
|
||||
|
||||
fn create_repo(h: &TestHarness, name: &str, seed: u8) -> (Uuid, UserHash) {
|
||||
let user_id = Uuid::new_v4();
|
||||
let did = Did::from(format!("did:plc:{name}"));
|
||||
let handle = Handle::from(format!("{name}.test.invalid"));
|
||||
let did = Did::new(format!("did:plc:{name}")).expect("test DID is well-formed");
|
||||
let handle = Handle::new(format!("{name}.oyster.cafe")).expect("test handle is valid");
|
||||
let cid = test_cid_link(seed);
|
||||
h.metastore
|
||||
.repo_ops()
|
||||
@@ -414,7 +415,7 @@ mod tests {
|
||||
link_to: "at://did:plc:someone/app.bsky.feed.post/3k2p1".to_string(),
|
||||
}];
|
||||
|
||||
let collection = Nsid::from("app.bsky.feed.like".to_string());
|
||||
let collection = Nsid::new("app.bsky.feed.like").expect("test collection is a valid NSID");
|
||||
let conflicts = ops
|
||||
.get_backlink_conflicts(user_id, &collection, &proposed)
|
||||
.unwrap();
|
||||
@@ -449,7 +450,8 @@ mod tests {
|
||||
link_to: "at://did:plc:someone/app.bsky.feed.post/3k2p1".to_string(),
|
||||
}];
|
||||
|
||||
let collection = Nsid::from("app.bsky.feed.repost".to_string());
|
||||
let collection =
|
||||
Nsid::new("app.bsky.feed.repost").expect("test collection is a valid NSID");
|
||||
let conflicts = ops
|
||||
.get_backlink_conflicts(user_id, &collection, &proposed)
|
||||
.unwrap();
|
||||
@@ -480,7 +482,8 @@ mod tests {
|
||||
link_to: "did:plc:target".to_string(),
|
||||
}];
|
||||
|
||||
let collection = Nsid::from("app.bsky.graph.follow".to_string());
|
||||
let collection =
|
||||
Nsid::new("app.bsky.graph.follow").expect("test collection is a valid NSID");
|
||||
let conflicts = ops
|
||||
.get_backlink_conflicts(user_id, &collection, &proposed)
|
||||
.unwrap();
|
||||
@@ -512,7 +515,7 @@ mod tests {
|
||||
link_to: "at://did:plc:target/app.bsky.feed.post/3k2p1".to_string(),
|
||||
}];
|
||||
|
||||
let collection = Nsid::from("app.bsky.feed.like".to_string());
|
||||
let collection = Nsid::new("app.bsky.feed.like").expect("test collection is a valid NSID");
|
||||
let conflicts = ops
|
||||
.get_backlink_conflicts(user_id_b, &collection, &proposed)
|
||||
.unwrap();
|
||||
@@ -543,7 +546,7 @@ mod tests {
|
||||
link_to: "at://did:plc:someone/app.bsky.feed.post/3k2p1".to_string(),
|
||||
}];
|
||||
|
||||
let collection = Nsid::from("app.bsky.feed.like".to_string());
|
||||
let collection = Nsid::new("app.bsky.feed.like").expect("test collection is a valid NSID");
|
||||
let conflicts = ops
|
||||
.get_backlink_conflicts(user_id, &collection, &proposed)
|
||||
.unwrap();
|
||||
@@ -557,7 +560,7 @@ mod tests {
|
||||
let ops = h.metastore.backlink_ops();
|
||||
let (user_id, _user_hash) = create_repo(&h, "bailey", 9);
|
||||
|
||||
let collection = Nsid::from("app.bsky.feed.like".to_string());
|
||||
let collection = Nsid::new("app.bsky.feed.like").expect("test collection is a valid NSID");
|
||||
let conflicts = ops
|
||||
.get_backlink_conflicts(user_id, &collection, &[])
|
||||
.unwrap();
|
||||
@@ -647,7 +650,7 @@ mod tests {
|
||||
},
|
||||
];
|
||||
|
||||
let collection = Nsid::from("app.bsky.feed.like".to_string());
|
||||
let collection = Nsid::new("app.bsky.feed.like").expect("test collection is a valid NSID");
|
||||
let conflicts = ops
|
||||
.get_backlink_conflicts(user_id, &collection, &proposed)
|
||||
.unwrap();
|
||||
|
||||
@@ -363,14 +363,19 @@ impl BlobOps {
|
||||
})?;
|
||||
|
||||
let start = cursor.map_or(Bound::Unbounded, Bound::Excluded);
|
||||
Ok(missing
|
||||
missing
|
||||
.range::<str, _>((start, Bound::Unbounded))
|
||||
.take(limit)
|
||||
.map(|(cid_str, uri)| tranquil_db_traits::MissingBlobInfo {
|
||||
blob_cid: CidLink::from(cid_str.clone()),
|
||||
record_uri: tranquil_types::AtUri::from(uri.clone()),
|
||||
.map(|(cid_str, uri)| {
|
||||
Ok(tranquil_db_traits::MissingBlobInfo {
|
||||
blob_cid: CidLink::new(cid_str.clone()).map_err(|_| {
|
||||
MetastoreError::CorruptData("corrupt record_blobs blob cid")
|
||||
})?,
|
||||
record_uri: tranquil_types::AtUri::new(uri.clone())
|
||||
.map_err(|_| MetastoreError::CorruptData("corrupt record_blobs uri"))?,
|
||||
})
|
||||
})
|
||||
.collect())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn collect_referenced_cid_bytes(
|
||||
@@ -445,7 +450,7 @@ fn parse_blob_cid_from_key(key: &[u8]) -> Result<CidLink, MetastoreError> {
|
||||
))?;
|
||||
reader
|
||||
.string()
|
||||
.map(CidLink::from)
|
||||
.and_then(|s| CidLink::new(s).ok())
|
||||
.ok_or(MetastoreError::CorruptData("corrupt blob key: missing cid"))
|
||||
}
|
||||
|
||||
|
||||
@@ -211,7 +211,8 @@ impl SessionOps {
|
||||
}
|
||||
|
||||
Ok(RefreshGraceLookup::Compromised {
|
||||
did: Did::from(session.did.clone()),
|
||||
did: Did::new(session.did.clone())
|
||||
.map_err(|_| MetastoreError::CorruptData("corrupt session did"))?,
|
||||
session_id: SessionId::new(session_id),
|
||||
key_bytes: user.key_bytes,
|
||||
encryption_version: user.encryption_version,
|
||||
|
||||
@@ -799,7 +799,7 @@ mod tests {
|
||||
let mut user = UserValue {
|
||||
id: uuid::Uuid::new_v4(),
|
||||
did: "did:plc:test".to_owned(),
|
||||
handle: "t.invalid".to_owned(),
|
||||
handle: "teq.oyster.cafe".to_owned(),
|
||||
email: None,
|
||||
email_verified: false,
|
||||
password_hash: None,
|
||||
|
||||
Reference in New Issue
Block a user