Fix genesis commit stuff, update deps

This commit is contained in:
lewis
2025-12-23 22:37:44 +02:00
parent cb98398494
commit 091d6d96a0
6 changed files with 333 additions and 506 deletions
Generated
+265 -425
View File
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -5,9 +5,9 @@ edition = "2024"
[dependencies]
anyhow = "1.0.100"
async-trait = "0.1.89"
aws-config = "1.8.11"
aws-sdk-s3 = "1.116.0"
axum = { version = "0.8.7", features = ["ws", "macros"] }
aws-config = "1.8.12"
aws-sdk-s3 = "1.118.0"
axum = { version = "0.8.8", features = ["ws", "macros"] }
base32 = "0.5"
base64 = "0.22.1"
bcrypt = "0.17.1"
@@ -20,20 +20,20 @@ governor = "0.10"
hkdf = "0.12"
hmac = "0.12"
aes-gcm = "0.10"
jacquard = { version = "0.9.3", default-features = false, features = ["api", "api_bluesky", "api_full", "derive", "dns"] }
jacquard-axum = "0.9.2"
jacquard-repo = "0.9.2"
jacquard = { version = "0.9.5", default-features = false, features = ["api", "api_bluesky", "api_full", "derive", "dns"] }
jacquard-axum = "0.9.6"
jacquard-repo = "0.9.6"
jsonwebtoken = { version = "10.2.0", features = ["rust_crypto"] }
k256 = { version = "0.13.3", features = ["ecdsa", "pem", "pkcs8"] }
multibase = "0.9.1"
multihash = "0.19.3"
rand = "0.8.5"
reqwest = { version = "0.12.24", features = ["json"] }
reqwest = { version = "0.12.28", features = ["json"] }
serde = { version = "1.0.228", features = ["derive"] }
serde_bytes = "0.11.14"
serde_ipld_dagcbor = "0.6.4"
ipld-core = "0.4.2"
serde_json = "1.0.145"
serde_json = "1.0.146"
serde_urlencoded = "0.7"
sha2 = "0.10.9"
subtle = "2.5"
@@ -49,21 +49,21 @@ tokio-tungstenite = { version = "0.28.0", features = ["native-tls"] }
urlencoding = "2.1"
uuid = { version = "1.19.0", features = ["v4", "v5", "fast-rng"] }
iroh-car = "0.5.1"
image = { version = "0.25", default-features = false, features = ["jpeg", "png", "gif", "webp"] }
redis = { version = "0.27", features = ["tokio-comp", "connection-manager"] }
tower-http = { version = "0.6", features = ["fs", "cors"] }
image = { version = "0.25.9", default-features = false, features = ["jpeg", "png", "gif", "webp"] }
redis = { version = "1.0.1", features = ["tokio-comp", "connection-manager"] }
tower-http = { version = "0.6.8", features = ["fs", "cors"] }
hickory-resolver = { version = "0.24", features = ["tokio-runtime"] }
metrics = "0.24"
metrics = "0.24.3"
metrics-exporter-prometheus = { version = "0.16", default-features = false, features = ["http-listener"] }
bs58 = "0.5.1"
totp-rs = { version = "5", features = ["qr"] }
webauthn-rs = { version = "0.5", features = ["danger-allow-state-serialisation", "danger-user-presence-only-security-keys"] }
webauthn-rs = { version = "0.5.4", features = ["danger-allow-state-serialisation", "danger-user-presence-only-security-keys"] }
webauthn-rs-proto = "0.5.4"
[features]
external-infra = []
[dev-dependencies]
ctor = "0.6.3"
testcontainers = "0.26.0"
testcontainers = "0.26.2"
testcontainers-modules = { version = "0.14.0", features = ["postgres"] }
wiremock = "0.6.5"
# urlencoding is also in dependencies, but tests use it directly
+6 -27
View File
@@ -1,4 +1,5 @@
use super::did::verify_did_web;
use crate::api::repo::record::utils::create_signed_commit;
use crate::auth::{ServiceTokenVerifier, extract_bearer_token_from_header, is_service_token};
use crate::plc::{PlcClient, create_genesis_operation, signing_key_to_did_key};
use crate::state::{AppState, RateLimitKind};
@@ -10,8 +11,8 @@ use axum::{
response::{IntoResponse, Response},
};
use bcrypt::{DEFAULT_COST, hash};
use jacquard::types::{did::Did, integer::LimitedU32, string::Tid};
use jacquard_repo::{commit::Commit, mst::Mst, storage::BlockStore};
use jacquard::types::{integer::LimitedU32, string::Tid};
use jacquard_repo::{mst::Mst, storage::BlockStore};
use k256::{SecretKey, ecdsa::SigningKey};
use rand::rngs::OsRng;
use serde::{Deserialize, Serialize};
@@ -864,33 +865,11 @@ pub async fn create_account(
.into_response();
}
};
let did_obj = match Did::new(&did) {
Ok(d) => d,
Err(_) => {
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": "InternalError", "message": "Invalid DID"})),
)
.into_response();
}
};
let rev = Tid::now(LimitedU32::MIN);
let unsigned_commit = Commit::new_unsigned(did_obj, mst_root, rev, None);
let signed_commit = match unsigned_commit.sign(&signing_key) {
Ok(c) => c,
let (commit_bytes, _sig) = match create_signed_commit(&did, mst_root, &rev.to_string(), None, &signing_key) {
Ok(result) => result,
Err(e) => {
error!("Error signing genesis commit: {:?}", e);
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": "InternalError"})),
)
.into_response();
}
};
let commit_bytes = match signed_commit.to_cbor() {
Ok(b) => b,
Err(e) => {
error!("Error serializing genesis commit: {:?}", e);
error!("Error creating genesis commit: {:?}", e);
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": "InternalError"})),
+11 -11
View File
@@ -3,23 +3,23 @@ use bytes::Bytes;
use cid::Cid;
use jacquard::types::{integer::LimitedU32, string::Tid};
use jacquard_repo::storage::BlockStore;
use k256::ecdsa::{Signature, SigningKey, signature::Signer};
use k256::ecdsa::{signature::Signer, Signature, SigningKey};
use serde::Serialize;
use serde_json::json;
use uuid::Uuid;
/*
* Why am I making custom commit objects instead of jacquard's Commit::sign(), you ask?
* Why custom commit signing instead of jacquard's Commit::sign()?
*
* At time of writing, jacquard has a bug in how it creates unsigned bytes for signing.
* Jacquard sets sig to empty bytes and serializes (6-field CBOR map)
* Indigo/ATProto creates a struct *without* the sig field (5-field CBOR map)
* Jacquard previously had a bug in how it created unsigned bytes for signing:
* it set sig to empty bytes and serialized (6-field CBOR map), while the
* ATProto spec creates a struct *without* the sig field (5-field CBOR map).
* These produce different CBOR bytes, so signatures didn't verify with relays.
*
* These produce different CBOR bytes, so signatures created with jacquard
* don't verify with the relay's algorithm. The relay silently rejects commits
* with invalid signatures.
*
* If you have it downloaded, see: reference-relay-indigo/atproto/repo/commit.go UnsignedBytes()
* The bug has been fixed in jacquard, but the fix is untested here.
* TODO: Switch back to jacquard's Commit::sign() and verify it works.
*/
#[derive(Serialize)]
struct UnsignedCommit<'a> {
data: Cid,
@@ -29,7 +29,7 @@ struct UnsignedCommit<'a> {
version: i64,
}
fn create_signed_commit(
pub fn create_signed_commit(
did: &str,
data: Cid,
rev: &str,
+36 -29
View File
@@ -6,8 +6,8 @@ use axum::{
};
use bcrypt::{DEFAULT_COST, hash};
use chrono::{Duration, Utc};
use jacquard::types::{did::Did, integer::LimitedU32, string::Tid};
use jacquard_repo::{commit::Commit, mst::Mst, storage::BlockStore};
use jacquard::types::{integer::LimitedU32, string::Tid};
use jacquard_repo::{mst::Mst, storage::BlockStore};
use rand::Rng;
use serde::{Deserialize, Serialize};
use serde_json::json;
@@ -15,6 +15,7 @@ use std::sync::Arc;
use tracing::{error, info, warn};
use uuid::Uuid;
use crate::api::repo::record::utils::create_signed_commit;
use crate::state::{AppState, RateLimitKind};
use crate::validation::validate_password;
@@ -391,13 +392,20 @@ pub async fn create_passkey_account(
}
};
let is_first_user = sqlx::query_scalar!("SELECT COUNT(*) as count FROM users")
.fetch_one(&mut *tx)
.await
.map(|c| c.unwrap_or(0) == 0)
.unwrap_or(false);
let user_insert: Result<(Uuid,), _> = sqlx::query_as(
r#"INSERT INTO users (
handle, email, did, password_hash, password_required,
preferred_comms_channel,
discord_id, telegram_username, signal_number,
recovery_token, recovery_token_expires_at
) VALUES ($1, $2, $3, NULL, FALSE, $4::comms_channel, $5, $6, $7, $8, $9) RETURNING id"#,
recovery_token, recovery_token_expires_at,
is_admin
) VALUES ($1, $2, $3, NULL, FALSE, $4::comms_channel, $5, $6, $7, $8, $9, $10) RETURNING id"#,
)
.bind(&handle)
.bind(&email)
@@ -426,6 +434,7 @@ pub async fn create_passkey_account(
)
.bind(&setup_token_hash)
.bind(setup_expires_at)
.bind(is_first_user)
.fetch_one(&mut *tx)
.await;
@@ -518,33 +527,11 @@ pub async fn create_passkey_account(
.into_response();
}
};
let did_obj = match Did::new(&did) {
Ok(d) => d,
Err(_) => {
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": "InternalError", "message": "Invalid DID"})),
)
.into_response();
}
};
let rev = Tid::now(LimitedU32::MIN);
let unsigned_commit = Commit::new_unsigned(did_obj, mst_root, rev, None);
let signed_commit = match unsigned_commit.sign(&secret_key) {
Ok(c) => c,
let (commit_bytes, _sig) = match create_signed_commit(&did, mst_root, &rev.to_string(), None, &secret_key) {
Ok(result) => result,
Err(e) => {
error!("Error signing genesis commit: {:?}", e);
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": "InternalError"})),
)
.into_response();
}
};
let commit_bytes = match signed_commit.to_cbor() {
Ok(b) => b,
Err(e) => {
error!("Error serializing genesis commit: {:?}", e);
error!("Error creating genesis commit: {:?}", e);
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": "InternalError"})),
@@ -630,6 +617,26 @@ pub async fn create_passkey_account(
{
warn!("Failed to sequence identity event for {}: {}", did, e);
}
if let Err(e) =
crate::api::repo::record::sequence_account_event(&state, &did, true, None).await
{
warn!("Failed to sequence account event for {}: {}", did, e);
}
let profile_record = serde_json::json!({
"$type": "app.bsky.actor.profile",
"displayName": handle
});
if let Err(e) = crate::api::repo::record::create_record_internal(
&state,
&did,
"app.bsky.actor.profile",
"self",
&profile_record,
)
.await
{
warn!("Failed to create default profile for {}: {}", did, e);
}
if let Err(e) = crate::comms::enqueue_signup_verification(
&state.db,
+1
View File
@@ -90,6 +90,7 @@ impl ClientMetadataCache {
if let Ok(url) = reqwest::Url::parse(client_id) {
url.scheme() == "http"
&& matches!(url.host_str(), Some("localhost") | Some("127.0.0.1"))
&& url.query().is_some()
} else {
false
}