mirror of
https://tangled.org/tranquil.farm/tranquil-pds
synced 2026-09-21 09:44:14 +00:00
Investigation/warning about inbound migration
This commit is contained in:
@@ -468,7 +468,7 @@ pub async fn get_audit_log(
|
||||
auth: BearerAuth,
|
||||
Query(params): Query<AuditLogParams>,
|
||||
) -> Response {
|
||||
let limit = params.limit.min(100).max(1);
|
||||
let limit = params.limit.clamp(1, 100);
|
||||
let offset = params.offset.max(0);
|
||||
|
||||
let entries =
|
||||
@@ -489,10 +489,9 @@ pub async fn get_audit_log(
|
||||
}
|
||||
};
|
||||
|
||||
let total = match delegation::audit::count_audit_log_entries(&state.db, &auth.0.did).await {
|
||||
Ok(t) => t,
|
||||
Err(_) => 0,
|
||||
};
|
||||
let total = delegation::audit::count_audit_log_entries(&state.db, &auth.0.did)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
Json(GetAuditLogResponse {
|
||||
entries: entries
|
||||
|
||||
+71
-27
@@ -69,7 +69,19 @@ pub async fn create_account(
|
||||
headers: HeaderMap,
|
||||
Json(input): Json<CreateAccountInput>,
|
||||
) -> Response {
|
||||
info!("create_account called");
|
||||
let is_potential_migration = input
|
||||
.did
|
||||
.as_ref()
|
||||
.map(|d| d.starts_with("did:plc:"))
|
||||
.unwrap_or(false);
|
||||
if is_potential_migration {
|
||||
info!(
|
||||
"[MIGRATION] createAccount called for potential migration did={:?} handle={}",
|
||||
input.did, input.handle
|
||||
);
|
||||
} else {
|
||||
info!("create_account called");
|
||||
}
|
||||
let client_ip = extract_client_ip(&headers);
|
||||
if !state
|
||||
.check_rate_limit(RateLimitKind::AccountCreation, &client_ip)
|
||||
@@ -136,6 +148,10 @@ pub async fn create_account(
|
||||
&& let (Some(provided_did), Some(auth_did)) = (input.did.as_ref(), migration_auth.as_ref())
|
||||
{
|
||||
if provided_did != auth_did {
|
||||
info!(
|
||||
"[MIGRATION] createAccount: Service token mismatch - token_did={} provided_did={}",
|
||||
auth_did, provided_did
|
||||
);
|
||||
return (
|
||||
StatusCode::FORBIDDEN,
|
||||
Json(json!({
|
||||
@@ -148,7 +164,10 @@ pub async fn create_account(
|
||||
if is_did_web_byod {
|
||||
info!(did = %provided_did, "Processing did:web BYOD account creation");
|
||||
} else {
|
||||
info!(did = %provided_did, "Processing account migration");
|
||||
info!(
|
||||
"[MIGRATION] createAccount: Service token verified, processing migration for did={}",
|
||||
provided_did
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1005,30 +1024,44 @@ pub async fn create_account(
|
||||
}
|
||||
|
||||
let (access_jwt, refresh_jwt) = if is_migration {
|
||||
let access_meta =
|
||||
match crate::auth::create_access_token_with_metadata(&did, &secret_key_bytes) {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
error!("Error creating access token for migration: {:?}", e);
|
||||
return (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({"error": "InternalError"})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
};
|
||||
let refresh_meta =
|
||||
match crate::auth::create_refresh_token_with_metadata(&did, &secret_key_bytes) {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
error!("Error creating refresh token for migration: {:?}", e);
|
||||
return (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({"error": "InternalError"})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
};
|
||||
info!(
|
||||
"[MIGRATION] createAccount: Creating session tokens for migration did={}",
|
||||
did
|
||||
);
|
||||
let access_meta = match crate::auth::create_access_token_with_metadata(
|
||||
&did,
|
||||
&secret_key_bytes,
|
||||
) {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"[MIGRATION] createAccount: Error creating access token for migration: {:?}",
|
||||
e
|
||||
);
|
||||
return (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({"error": "InternalError"})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
};
|
||||
let refresh_meta = match crate::auth::create_refresh_token_with_metadata(
|
||||
&did,
|
||||
&secret_key_bytes,
|
||||
) {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"[MIGRATION] createAccount: Error creating refresh token for migration: {:?}",
|
||||
e
|
||||
);
|
||||
return (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({"error": "InternalError"})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
};
|
||||
if let Err(e) = sqlx::query!(
|
||||
"INSERT INTO session_tokens (did, access_jti, refresh_jti, access_expires_at, refresh_expires_at) VALUES ($1, $2, $3, $4, $5)",
|
||||
did,
|
||||
@@ -1040,18 +1073,29 @@ pub async fn create_account(
|
||||
.execute(&state.db)
|
||||
.await
|
||||
{
|
||||
error!("Error creating session for migration: {:?}", e);
|
||||
error!("[MIGRATION] createAccount: Error creating session for migration: {:?}", e);
|
||||
return (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({"error": "InternalError"})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
info!(
|
||||
"[MIGRATION] createAccount: Session created successfully for did={}",
|
||||
did
|
||||
);
|
||||
(Some(access_meta.token), Some(refresh_meta.token))
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
if is_migration {
|
||||
info!(
|
||||
"[MIGRATION] createAccount: SUCCESS - Account ready for migration did={} handle={}",
|
||||
did, handle
|
||||
);
|
||||
}
|
||||
|
||||
(
|
||||
StatusCode::OK,
|
||||
Json(CreateAccountOutput {
|
||||
|
||||
@@ -23,22 +23,34 @@ pub async fn submit_plc_operation(
|
||||
headers: axum::http::HeaderMap,
|
||||
Json(input): Json<SubmitPlcOperationInput>,
|
||||
) -> Response {
|
||||
info!("[MIGRATION] submitPlcOperation called");
|
||||
let bearer = match crate::auth::extract_bearer_token_from_header(
|
||||
headers.get("Authorization").and_then(|h| h.to_str().ok()),
|
||||
) {
|
||||
Some(t) => t,
|
||||
None => return ApiError::AuthenticationRequired.into_response(),
|
||||
None => {
|
||||
info!("[MIGRATION] submitPlcOperation: No bearer token");
|
||||
return ApiError::AuthenticationRequired.into_response();
|
||||
}
|
||||
};
|
||||
let auth_user =
|
||||
match crate::auth::validate_bearer_token_allow_deactivated(&state.db, &bearer).await {
|
||||
Ok(user) => user,
|
||||
Err(e) => return ApiError::from(e).into_response(),
|
||||
Err(e) => {
|
||||
info!("[MIGRATION] submitPlcOperation: Auth failed: {:?}", e);
|
||||
return ApiError::from(e).into_response();
|
||||
}
|
||||
};
|
||||
info!(
|
||||
"[MIGRATION] submitPlcOperation: Authenticated user did={}",
|
||||
auth_user.did
|
||||
);
|
||||
if let Err(e) = crate::auth::scope_check::check_identity_scope(
|
||||
auth_user.is_oauth,
|
||||
auth_user.scope.as_deref(),
|
||||
crate::oauth::scopes::IdentityAttr::Wildcard,
|
||||
) {
|
||||
info!("[MIGRATION] submitPlcOperation: Scope check failed");
|
||||
return e;
|
||||
}
|
||||
let did = &auth_user.did;
|
||||
@@ -188,6 +200,11 @@ pub async fn submit_plc_operation(
|
||||
let plc_client = PlcClient::new(None);
|
||||
let operation_clone = input.operation.clone();
|
||||
let did_clone = did.clone();
|
||||
info!(
|
||||
"[MIGRATION] submitPlcOperation: Sending operation to PLC directory for did={}",
|
||||
did
|
||||
);
|
||||
let plc_start = std::time::Instant::now();
|
||||
let result: Result<(), CircuitBreakerError<PlcError>> =
|
||||
with_circuit_breaker(&state.circuit_breakers.plc_directory, || async {
|
||||
plc_client
|
||||
@@ -196,9 +213,17 @@ pub async fn submit_plc_operation(
|
||||
})
|
||||
.await;
|
||||
match result {
|
||||
Ok(()) => {}
|
||||
Ok(()) => {
|
||||
info!(
|
||||
"[MIGRATION] submitPlcOperation: PLC directory accepted operation in {:?}",
|
||||
plc_start.elapsed()
|
||||
);
|
||||
}
|
||||
Err(CircuitBreakerError::CircuitOpen(e)) => {
|
||||
warn!("PLC directory circuit breaker open: {}", e);
|
||||
warn!(
|
||||
"[MIGRATION] submitPlcOperation: PLC directory circuit breaker open: {}",
|
||||
e
|
||||
);
|
||||
return (
|
||||
StatusCode::SERVICE_UNAVAILABLE,
|
||||
Json(json!({
|
||||
@@ -209,7 +234,10 @@ pub async fn submit_plc_operation(
|
||||
.into_response();
|
||||
}
|
||||
Err(CircuitBreakerError::OperationFailed(e)) => {
|
||||
error!("Failed to submit PLC operation: {:?}", e);
|
||||
error!(
|
||||
"[MIGRATION] submitPlcOperation: PLC operation failed: {:?}",
|
||||
e
|
||||
);
|
||||
return (
|
||||
StatusCode::BAD_GATEWAY,
|
||||
Json(json!({
|
||||
@@ -220,6 +248,10 @@ pub async fn submit_plc_operation(
|
||||
.into_response();
|
||||
}
|
||||
}
|
||||
info!(
|
||||
"[MIGRATION] submitPlcOperation: Sequencing identity event for did={}",
|
||||
did
|
||||
);
|
||||
match sqlx::query!(
|
||||
"INSERT INTO repo_seq (did, event_type) VALUES ($1, 'identity') RETURNING seq",
|
||||
did
|
||||
@@ -228,17 +260,27 @@ pub async fn submit_plc_operation(
|
||||
.await
|
||||
{
|
||||
Ok(row) => {
|
||||
info!(
|
||||
"[MIGRATION] submitPlcOperation: Identity event sequenced with seq={}",
|
||||
row.seq
|
||||
);
|
||||
if let Err(e) = sqlx::query(&format!("NOTIFY repo_updates, '{}'", row.seq))
|
||||
.execute(&state.db)
|
||||
.await
|
||||
{
|
||||
warn!("Failed to notify identity event: {:?}", e);
|
||||
warn!(
|
||||
"[MIGRATION] submitPlcOperation: Failed to notify identity event: {:?}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Failed to sequence identity event: {:?}", e);
|
||||
warn!(
|
||||
"[MIGRATION] submitPlcOperation: Failed to sequence identity event: {:?}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
info!("Submitted PLC operation for user {}", did);
|
||||
info!("[MIGRATION] submitPlcOperation: SUCCESS for did={}", did);
|
||||
(StatusCode::OK, Json(json!({}))).into_response()
|
||||
}
|
||||
|
||||
+22
-36
@@ -1,6 +1,7 @@
|
||||
use crate::auth::{ServiceTokenVerifier, is_service_token};
|
||||
use crate::delegation::{self, DelegationActionType};
|
||||
use crate::state::AppState;
|
||||
use crate::sync::import::find_blob_refs_ipld;
|
||||
use axum::body::Bytes;
|
||||
use axum::{
|
||||
Json,
|
||||
@@ -9,13 +10,14 @@ use axum::{
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use cid::Cid;
|
||||
use ipld_core::ipld::Ipld;
|
||||
use jacquard_repo::storage::BlockStore;
|
||||
use multihash::Multihash;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::str::FromStr;
|
||||
use tracing::{debug, error};
|
||||
use tracing::{debug, error, warn};
|
||||
|
||||
const MAX_BLOB_SIZE: usize = 10_000_000_000;
|
||||
const MAX_VIDEO_BLOB_SIZE: usize = 10_000_000_000;
|
||||
@@ -258,26 +260,6 @@ pub struct ListMissingBlobsOutput {
|
||||
pub blobs: Vec<RecordBlob>,
|
||||
}
|
||||
|
||||
fn find_blobs(val: &serde_json::Value, blobs: &mut Vec<String>) {
|
||||
if let Some(obj) = val.as_object() {
|
||||
if let Some(type_val) = obj.get("$type")
|
||||
&& type_val == "blob"
|
||||
&& let Some(r) = obj.get("ref")
|
||||
&& let Some(link) = r.get("$link")
|
||||
&& let Some(s) = link.as_str()
|
||||
{
|
||||
blobs.push(s.to_string());
|
||||
}
|
||||
for (_, v) in obj {
|
||||
find_blobs(v, blobs);
|
||||
}
|
||||
} else if let Some(arr) = val.as_array() {
|
||||
for v in arr {
|
||||
find_blobs(v, blobs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_missing_blobs(
|
||||
State(state): State<AppState>,
|
||||
headers: axum::http::HeaderMap,
|
||||
@@ -295,16 +277,17 @@ pub async fn list_missing_blobs(
|
||||
.into_response();
|
||||
}
|
||||
};
|
||||
let auth_user = match crate::auth::validate_bearer_token_allow_deactivated(&state.db, &token).await {
|
||||
Ok(user) => user,
|
||||
Err(_) => {
|
||||
return (
|
||||
StatusCode::UNAUTHORIZED,
|
||||
Json(json!({"error": "AuthenticationFailed"})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
};
|
||||
let auth_user =
|
||||
match crate::auth::validate_bearer_token_allow_deactivated(&state.db, &token).await {
|
||||
Ok(user) => user,
|
||||
Err(_) => {
|
||||
return (
|
||||
StatusCode::UNAUTHORIZED,
|
||||
Json(json!({"error": "AuthenticationFailed"})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
};
|
||||
let did = auth_user.did;
|
||||
let user_query = sqlx::query!("SELECT id FROM users WHERE did = $1", did)
|
||||
.fetch_optional(&state.db)
|
||||
@@ -362,13 +345,16 @@ pub async fn list_missing_blobs(
|
||||
Ok(Some(b)) => b,
|
||||
_ => continue,
|
||||
};
|
||||
let record_val: serde_json::Value = match serde_ipld_dagcbor::from_slice(&block_bytes) {
|
||||
let record_ipld: Ipld = match serde_ipld_dagcbor::from_slice(&block_bytes) {
|
||||
Ok(v) => v,
|
||||
Err(_) => continue,
|
||||
Err(e) => {
|
||||
warn!("Failed to parse record {} as IPLD: {:?}", record_cid_str, e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let mut blobs = Vec::new();
|
||||
find_blobs(&record_val, &mut blobs);
|
||||
for blob_cid_str in blobs {
|
||||
let blob_refs = find_blob_refs_ipld(&record_ipld, 0);
|
||||
for blob_ref in blob_refs {
|
||||
let blob_cid_str = blob_ref.cid;
|
||||
let exists = sqlx::query!(
|
||||
"SELECT 1 as one FROM blobs WHERE cid = $1 AND created_by_user = $2",
|
||||
blob_cid_str,
|
||||
|
||||
+15
-15
@@ -350,17 +350,18 @@ pub async fn import_repo(
|
||||
.into_response();
|
||||
}
|
||||
};
|
||||
let key_bytes = match crate::config::decrypt_key(&key_row.key_bytes, key_row.encryption_version) {
|
||||
Ok(k) => k,
|
||||
Err(e) => {
|
||||
error!("Failed to decrypt signing key: {}", e);
|
||||
return (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({"error": "InternalError"})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
};
|
||||
let key_bytes =
|
||||
match crate::config::decrypt_key(&key_row.key_bytes, key_row.encryption_version) {
|
||||
Ok(k) => k,
|
||||
Err(e) => {
|
||||
error!("Failed to decrypt signing key: {}", e);
|
||||
return (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({"error": "InternalError"})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
};
|
||||
let signing_key = match SigningKey::from_slice(&key_bytes) {
|
||||
Ok(k) => k,
|
||||
Err(e) => {
|
||||
@@ -422,10 +423,9 @@ pub async fn import_repo(
|
||||
"Created new commit for imported repo: cid={}, rev={}",
|
||||
new_root_str, new_rev_str
|
||||
);
|
||||
if !is_migration {
|
||||
if let Err(e) = sequence_import_event(&state, did, &new_root_str).await {
|
||||
warn!("Failed to sequence import event: {:?}", e);
|
||||
}
|
||||
if !is_migration && let Err(e) = sequence_import_event(&state, did, &new_root_str).await
|
||||
{
|
||||
warn!("Failed to sequence import event: {:?}", e);
|
||||
}
|
||||
(StatusCode::OK, Json(json!({}))).into_response()
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use cid::Cid;
|
||||
use ipld_core::ipld::Ipld;
|
||||
use jacquard_repo::storage::BlockStore;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Map, Value};
|
||||
use serde_json::{Map, Value, json};
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
use tracing::{error, info};
|
||||
@@ -37,10 +37,8 @@ fn ipld_to_json(ipld: Ipld) -> Value {
|
||||
}
|
||||
Ipld::List(arr) => Value::Array(arr.into_iter().map(ipld_to_json).collect()),
|
||||
Ipld::Map(map) => {
|
||||
let obj: Map<String, Value> = map
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, ipld_to_json(v)))
|
||||
.collect();
|
||||
let obj: Map<String, Value> =
|
||||
map.into_iter().map(|(k, v)| (k, ipld_to_json(v))).collect();
|
||||
Value::Object(obj)
|
||||
}
|
||||
Ipld::Link(cid) => json!({ "$link": cid.to_string() }),
|
||||
|
||||
@@ -5,7 +5,7 @@ use jacquard::types::{integer::LimitedU32, string::Tid};
|
||||
use jacquard_repo::commit::Commit;
|
||||
use jacquard_repo::storage::BlockStore;
|
||||
use k256::ecdsa::SigningKey;
|
||||
use serde_json::{json, Value};
|
||||
use serde_json::{Value, json};
|
||||
use std::str::FromStr;
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -18,12 +18,11 @@ pub fn extract_blob_cids(record: &Value) -> Vec<String> {
|
||||
fn extract_blob_cids_recursive(value: &Value, blobs: &mut Vec<String>) {
|
||||
match value {
|
||||
Value::Object(map) => {
|
||||
if map.get("$type").and_then(|v| v.as_str()) == Some("blob") {
|
||||
if let Some(ref_obj) = map.get("ref") {
|
||||
if let Some(link) = ref_obj.get("$link").and_then(|v| v.as_str()) {
|
||||
blobs.push(link.to_string());
|
||||
}
|
||||
}
|
||||
if map.get("$type").and_then(|v| v.as_str()) == Some("blob")
|
||||
&& let Some(ref_obj) = map.get("ref")
|
||||
&& let Some(link) = ref_obj.get("$link").and_then(|v| v.as_str())
|
||||
{
|
||||
blobs.push(link.to_string());
|
||||
}
|
||||
for v in map.values() {
|
||||
extract_blob_cids_recursive(v, blobs);
|
||||
|
||||
@@ -8,8 +8,8 @@ use axum::{
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use bcrypt::verify;
|
||||
use cid::Cid;
|
||||
use chrono::{Duration, Utc};
|
||||
use cid::Cid;
|
||||
use jacquard_repo::commit::Commit;
|
||||
use jacquard_repo::storage::BlockStore;
|
||||
use k256::ecdsa::SigningKey;
|
||||
@@ -216,8 +216,8 @@ async fn assert_valid_did_document_for_service(
|
||||
})?;
|
||||
|
||||
if let Some(row) = user_row {
|
||||
let key_bytes =
|
||||
crate::config::decrypt_key(&row.key_bytes, row.encryption_version).map_err(|e| {
|
||||
let key_bytes = crate::config::decrypt_key(&row.key_bytes, row.encryption_version)
|
||||
.map_err(|e| {
|
||||
error!("Failed to decrypt user key: {}", e);
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
@@ -247,21 +247,22 @@ async fn assert_valid_did_document_for_service(
|
||||
));
|
||||
}
|
||||
}
|
||||
} else if did.starts_with("did:web:") {
|
||||
} else if let Some(host_and_path) = did.strip_prefix("did:web:") {
|
||||
let client = reqwest::Client::new();
|
||||
let host_and_path = &did[8..];
|
||||
let decoded = host_and_path.replace("%3A", ":");
|
||||
let parts: Vec<&str> = decoded.split(':').collect();
|
||||
let (host, path_parts) = if parts.len() > 1 && parts[1].chars().all(|c| c.is_ascii_digit()) {
|
||||
let (host, path_parts) = if parts.len() > 1 && parts[1].chars().all(|c| c.is_ascii_digit())
|
||||
{
|
||||
(format!("{}:{}", parts[0], parts[1]), parts[2..].to_vec())
|
||||
} else {
|
||||
(parts[0].to_string(), parts[1..].to_vec())
|
||||
};
|
||||
let scheme = if host.starts_with("localhost") || host.starts_with("127.") || host.contains(':') {
|
||||
"http"
|
||||
} else {
|
||||
"https"
|
||||
};
|
||||
let scheme =
|
||||
if host.starts_with("localhost") || host.starts_with("127.") || host.contains(':') {
|
||||
"http"
|
||||
} else {
|
||||
"https"
|
||||
};
|
||||
let url = if path_parts.is_empty() {
|
||||
format!("{}://{}/.well-known/did.json", scheme, host)
|
||||
} else {
|
||||
@@ -323,11 +324,15 @@ pub async fn activate_account(
|
||||
State(state): State<AppState>,
|
||||
headers: axum::http::HeaderMap,
|
||||
) -> Response {
|
||||
info!("[MIGRATION] activateAccount called");
|
||||
let extracted = match crate::auth::extract_auth_token_from_header(
|
||||
headers.get("Authorization").and_then(|h| h.to_str().ok()),
|
||||
) {
|
||||
Some(t) => t,
|
||||
None => return ApiError::AuthenticationRequired.into_response(),
|
||||
None => {
|
||||
info!("[MIGRATION] activateAccount: No auth token");
|
||||
return ApiError::AuthenticationRequired.into_response();
|
||||
}
|
||||
};
|
||||
let dpop_proof = headers.get("DPoP").and_then(|h| h.to_str().ok());
|
||||
let http_uri = format!(
|
||||
@@ -346,8 +351,15 @@ pub async fn activate_account(
|
||||
.await
|
||||
{
|
||||
Ok(user) => user,
|
||||
Err(e) => return ApiError::from(e).into_response(),
|
||||
Err(e) => {
|
||||
info!("[MIGRATION] activateAccount: Auth failed: {:?}", e);
|
||||
return ApiError::from(e).into_response();
|
||||
}
|
||||
};
|
||||
info!(
|
||||
"[MIGRATION] activateAccount: Authenticated user did={}",
|
||||
auth_user.did
|
||||
);
|
||||
|
||||
if let Err(e) = crate::auth::scope_check::check_account_scope(
|
||||
auth_user.is_oauth,
|
||||
@@ -355,42 +367,80 @@ pub async fn activate_account(
|
||||
crate::oauth::scopes::AccountAttr::Repo,
|
||||
crate::oauth::scopes::AccountAction::Manage,
|
||||
) {
|
||||
info!("[MIGRATION] activateAccount: Scope check failed");
|
||||
return e;
|
||||
}
|
||||
|
||||
let did = auth_user.did;
|
||||
|
||||
info!(
|
||||
"[MIGRATION] activateAccount: Validating DID document for did={}",
|
||||
did
|
||||
);
|
||||
let did_validation_start = std::time::Instant::now();
|
||||
if let Err((status, json)) = assert_valid_did_document_for_service(&state.db, &did).await {
|
||||
info!(
|
||||
"activateAccount rejected for {}: DID document validation failed",
|
||||
did
|
||||
"[MIGRATION] activateAccount: DID document validation FAILED for {} (took {:?})",
|
||||
did,
|
||||
did_validation_start.elapsed()
|
||||
);
|
||||
return (status, json).into_response();
|
||||
}
|
||||
info!(
|
||||
"[MIGRATION] activateAccount: DID document validation SUCCESS for {} (took {:?})",
|
||||
did,
|
||||
did_validation_start.elapsed()
|
||||
);
|
||||
|
||||
let handle = sqlx::query_scalar!("SELECT handle FROM users WHERE did = $1", did)
|
||||
.fetch_optional(&state.db)
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
info!(
|
||||
"[MIGRATION] activateAccount: Activating account did={} handle={:?}",
|
||||
did, handle
|
||||
);
|
||||
let result = sqlx::query!("UPDATE users SET deactivated_at = NULL WHERE did = $1", did)
|
||||
.execute(&state.db)
|
||||
.await;
|
||||
match result {
|
||||
Ok(_) => {
|
||||
info!(
|
||||
"[MIGRATION] activateAccount: DB update success for did={}",
|
||||
did
|
||||
);
|
||||
if let Some(ref h) = handle {
|
||||
let _ = state.cache.delete(&format!("handle:{}", h)).await;
|
||||
}
|
||||
info!(
|
||||
"[MIGRATION] activateAccount: Sequencing account event (active=true) for did={}",
|
||||
did
|
||||
);
|
||||
if let Err(e) =
|
||||
crate::api::repo::record::sequence_account_event(&state, &did, true, None).await
|
||||
{
|
||||
warn!("Failed to sequence account activation event: {}", e);
|
||||
warn!(
|
||||
"[MIGRATION] activateAccount: Failed to sequence account activation event: {}",
|
||||
e
|
||||
);
|
||||
} else {
|
||||
info!("[MIGRATION] activateAccount: Account event sequenced successfully");
|
||||
}
|
||||
info!(
|
||||
"[MIGRATION] activateAccount: Sequencing identity event for did={} handle={:?}",
|
||||
did, handle
|
||||
);
|
||||
if let Err(e) =
|
||||
crate::api::repo::record::sequence_identity_event(&state, &did, handle.as_deref())
|
||||
.await
|
||||
{
|
||||
warn!("Failed to sequence identity event for activation: {}", e);
|
||||
warn!(
|
||||
"[MIGRATION] activateAccount: Failed to sequence identity event for activation: {}",
|
||||
e
|
||||
);
|
||||
} else {
|
||||
info!("[MIGRATION] activateAccount: Identity event sequenced successfully");
|
||||
}
|
||||
let repo_root = sqlx::query_scalar!(
|
||||
"SELECT r.repo_root_cid FROM repos r JOIN users u ON r.user_id = u.id WHERE u.did = $1",
|
||||
@@ -401,6 +451,10 @@ pub async fn activate_account(
|
||||
.ok()
|
||||
.flatten();
|
||||
if let Some(root_cid) = repo_root {
|
||||
info!(
|
||||
"[MIGRATION] activateAccount: Sequencing sync event for did={} root_cid={}",
|
||||
did, root_cid
|
||||
);
|
||||
let rev = if let Ok(cid) = Cid::from_str(&root_cid) {
|
||||
if let Ok(Some(block)) = state.block_store.get(&cid).await {
|
||||
Commit::from_cbor(&block).ok().map(|c| c.rev().to_string())
|
||||
@@ -410,16 +464,35 @@ pub async fn activate_account(
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Err(e) =
|
||||
crate::api::repo::record::sequence_sync_event(&state, &did, &root_cid, rev.as_deref()).await
|
||||
if let Err(e) = crate::api::repo::record::sequence_sync_event(
|
||||
&state,
|
||||
&did,
|
||||
&root_cid,
|
||||
rev.as_deref(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
warn!("Failed to sequence sync event for activation: {}", e);
|
||||
warn!(
|
||||
"[MIGRATION] activateAccount: Failed to sequence sync event for activation: {}",
|
||||
e
|
||||
);
|
||||
} else {
|
||||
info!("[MIGRATION] activateAccount: Sync event sequenced successfully");
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
"[MIGRATION] activateAccount: No repo root found for did={}",
|
||||
did
|
||||
);
|
||||
}
|
||||
info!("[MIGRATION] activateAccount: SUCCESS for did={}", did);
|
||||
(StatusCode::OK, Json(json!({}))).into_response()
|
||||
}
|
||||
Err(e) => {
|
||||
error!("DB error activating account: {:?}", e);
|
||||
error!(
|
||||
"[MIGRATION] activateAccount: DB error activating account: {:?}",
|
||||
e
|
||||
);
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({"error": "InternalError"})),
|
||||
|
||||
@@ -26,6 +26,9 @@ pub use email::{confirm_email, request_email_update, update_email};
|
||||
pub use invite::{create_invite_code, create_invite_codes, get_account_invite_codes};
|
||||
pub use logo::get_logo;
|
||||
pub use meta::{describe_server, health, robots_txt};
|
||||
pub use migration::{
|
||||
clear_migration_forwarding, get_migration_status, update_migration_forwarding,
|
||||
};
|
||||
pub use passkey_account::{
|
||||
complete_passkey_setup, create_passkey_account, recover_passkey_account,
|
||||
request_passkey_recovery, start_passkey_registration_for_setup,
|
||||
@@ -57,8 +60,5 @@ pub use trusted_devices::{
|
||||
extend_device_trust, is_device_trusted, list_trusted_devices, revoke_trusted_device,
|
||||
trust_device, update_trusted_device,
|
||||
};
|
||||
pub use migration::{
|
||||
clear_migration_forwarding, get_migration_status, update_migration_forwarding,
|
||||
};
|
||||
pub use verify_email::{resend_migration_verification, verify_migration_email};
|
||||
pub use verify_token::{VerifyTokenInput, VerifyTokenOutput, verify_token, verify_token_internal};
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
use axum::{
|
||||
Json,
|
||||
extract::State,
|
||||
http::StatusCode,
|
||||
};
|
||||
use axum::{Json, extract::State, http::StatusCode};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
+13
-16
@@ -229,24 +229,21 @@ async fn validate_bearer_token_with_options_internal(
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
match session_row {
|
||||
Some(row) => {
|
||||
if row.access_expires_at > chrono::Utc::now() {
|
||||
session_valid = true;
|
||||
if let Some(c) = cache {
|
||||
let _ = c
|
||||
.set(
|
||||
&session_cache_key,
|
||||
"1",
|
||||
Duration::from_secs(SESSION_CACHE_TTL_SECS),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
} else {
|
||||
return Err(TokenValidationError::TokenExpired);
|
||||
if let Some(row) = session_row {
|
||||
if row.access_expires_at > chrono::Utc::now() {
|
||||
session_valid = true;
|
||||
if let Some(c) = cache {
|
||||
let _ = c
|
||||
.set(
|
||||
&session_cache_key,
|
||||
"1",
|
||||
Duration::from_secs(SESSION_CACHE_TTL_SECS),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
} else {
|
||||
return Err(TokenValidationError::TokenExpired);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-3
@@ -1,5 +1,5 @@
|
||||
use async_trait::async_trait;
|
||||
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
|
||||
use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
|
||||
use reqwest::Client;
|
||||
use serde_json::json;
|
||||
use std::process::Stdio;
|
||||
@@ -103,8 +103,7 @@ impl EmailSender {
|
||||
}
|
||||
|
||||
pub fn format_email(&self, notification: &QueuedComms) -> String {
|
||||
let subject =
|
||||
mime_encode_header(notification.subject.as_deref().unwrap_or("Notification"));
|
||||
let subject = mime_encode_header(notification.subject.as_deref().unwrap_or("Notification"));
|
||||
let recipient = sanitize_header_value(¬ification.recipient);
|
||||
let from_header = if self.from_name.is_empty() {
|
||||
self.from_address.clone()
|
||||
|
||||
@@ -75,12 +75,10 @@ pub fn intersect_scopes(requested: &str, granted: &str) -> String {
|
||||
}
|
||||
|
||||
fn find_matching_scope<'a>(requested: &str, granted: &HashSet<&'a str>) -> Option<&'a str> {
|
||||
for granted_scope in granted {
|
||||
if scopes_compatible(granted_scope, requested) {
|
||||
return Some(granted_scope);
|
||||
}
|
||||
}
|
||||
None
|
||||
granted
|
||||
.iter()
|
||||
.find(|&granted_scope| scopes_compatible(granted_scope, requested))
|
||||
.map(|v| v as _)
|
||||
}
|
||||
|
||||
fn scopes_compatible(granted: &str, requested: &str) -> bool {
|
||||
@@ -97,11 +95,11 @@ fn scopes_compatible(granted: &str, requested: &str) -> bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
if granted_base.ends_with(".*") {
|
||||
let prefix = &granted_base[..granted_base.len() - 2];
|
||||
if requested_base.starts_with(prefix) && requested_base.len() > prefix.len() {
|
||||
return true;
|
||||
}
|
||||
if let Some(prefix) = granted_base.strip_suffix(".*")
|
||||
&& requested_base.starts_with(prefix)
|
||||
&& requested_base.len() > prefix.len()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
|
||||
+75
-66
@@ -1,16 +1,16 @@
|
||||
/*
|
||||
* CONTENT WARNING
|
||||
*
|
||||
* This file contains explicit slurs and hateful language. We're sorry you have to see them.
|
||||
*
|
||||
* These words exist here for one reason: to ensure our moderation system correctly blocks them.
|
||||
* We can't verify the filter catches the n-word without testing against the actual word.
|
||||
* Euphemisms wouldn't prove the protection works.
|
||||
*
|
||||
* If reading this file has caused you distress, please know:
|
||||
* - you are valued and welcome in this community
|
||||
* - these words do not reflect the views of this project or its contributors
|
||||
* - we maintain this code precisely because we believe everyone deserves an experience on the web that is free from this kinda language
|
||||
* CONTENT WARNING
|
||||
*
|
||||
* This file contains explicit slurs and hateful language. We're sorry you have to see them.
|
||||
*
|
||||
* These words exist here for one reason: to ensure our moderation system correctly blocks them.
|
||||
* We can't verify the filter catches the n-word without testing against the actual word.
|
||||
* Euphemisms wouldn't prove the protection works.
|
||||
*
|
||||
* If reading this file has caused you distress, please know:
|
||||
* - you are valued and welcome in this community
|
||||
* - these words do not reflect the views of this project or its contributors
|
||||
* - we maintain this code precisely because we believe everyone deserves an experience on the web that is free from this kinda language
|
||||
*/
|
||||
|
||||
use regex::Regex;
|
||||
@@ -70,7 +70,7 @@ pub fn has_explicit_slur(text: &str) -> bool {
|
||||
|
||||
fn has_explicit_slur_with_extra_words(text: &str, extra_words: &[String]) -> bool {
|
||||
let text_lower = text.to_lowercase();
|
||||
let normalized = text_lower.replace('.', "").replace('-', "").replace('_', "");
|
||||
let normalized = text_lower.replace(['.', '-', '_'], "");
|
||||
let stripped = strip_trailing_digits(&text_lower);
|
||||
let normalized_stripped = strip_trailing_digits(&normalized);
|
||||
|
||||
@@ -104,81 +104,87 @@ fn has_explicit_slur_with_extra_words(text: &str, extra_words: &[String]) -> boo
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use base64::Engine;
|
||||
|
||||
fn d(b64: &str) -> String {
|
||||
let bytes = base64::engine::general_purpose::STANDARD.decode(b64).unwrap();
|
||||
String::from_utf8(bytes).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chink_pattern() {
|
||||
assert!(has_explicit_slur("chink"));
|
||||
assert!(has_explicit_slur("chinks"));
|
||||
assert!(has_explicit_slur("CHINK"));
|
||||
assert!(has_explicit_slur("Chinks"));
|
||||
assert!(has_explicit_slur(&d("Y2hpbms=")));
|
||||
assert!(has_explicit_slur(&d("Y2hpbmtz")));
|
||||
assert!(has_explicit_slur(&d("Q0hJTks=")));
|
||||
assert!(has_explicit_slur(&d("Q2hpbmtz")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_coon_pattern() {
|
||||
assert!(has_explicit_slur("coon"));
|
||||
assert!(has_explicit_slur("coons"));
|
||||
assert!(has_explicit_slur("COON"));
|
||||
assert!(has_explicit_slur(&d("Y29vbg==")));
|
||||
assert!(has_explicit_slur(&d("Y29vbnM=")));
|
||||
assert!(has_explicit_slur(&d("Q09PTg==")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fag_pattern() {
|
||||
assert!(has_explicit_slur("fag"));
|
||||
assert!(has_explicit_slur("fags"));
|
||||
assert!(has_explicit_slur("faggot"));
|
||||
assert!(has_explicit_slur("faggots"));
|
||||
assert!(has_explicit_slur("faggotry"));
|
||||
assert!(has_explicit_slur(&d("ZmFn")));
|
||||
assert!(has_explicit_slur(&d("ZmFncw==")));
|
||||
assert!(has_explicit_slur(&d("ZmFnZ290")));
|
||||
assert!(has_explicit_slur(&d("ZmFnZ290cw==")));
|
||||
assert!(has_explicit_slur(&d("ZmFnZ290cnk=")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_kike_pattern() {
|
||||
assert!(has_explicit_slur("kike"));
|
||||
assert!(has_explicit_slur("kikes"));
|
||||
assert!(has_explicit_slur("KIKE"));
|
||||
assert!(has_explicit_slur("kikery"));
|
||||
assert!(has_explicit_slur(&d("a2lrZQ==")));
|
||||
assert!(has_explicit_slur(&d("a2lrZXM=")));
|
||||
assert!(has_explicit_slur(&d("S0lLRQ==")));
|
||||
assert!(has_explicit_slur(&d("a2lrZXJ5")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nigger_pattern() {
|
||||
assert!(has_explicit_slur("nigger"));
|
||||
assert!(has_explicit_slur("niggers"));
|
||||
assert!(has_explicit_slur("NIGGER"));
|
||||
assert!(has_explicit_slur("nigga"));
|
||||
assert!(has_explicit_slur("niggas"));
|
||||
assert!(has_explicit_slur(&d("bmlnZ2Vy")));
|
||||
assert!(has_explicit_slur(&d("bmlnZ2Vycw==")));
|
||||
assert!(has_explicit_slur(&d("TklHR0VS")));
|
||||
assert!(has_explicit_slur(&d("bmlnZ2E=")));
|
||||
assert!(has_explicit_slur(&d("bmlnZ2Fz")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tranny_pattern() {
|
||||
assert!(has_explicit_slur("tranny"));
|
||||
assert!(has_explicit_slur("trannies"));
|
||||
assert!(has_explicit_slur("TRANNY"));
|
||||
assert!(has_explicit_slur(&d("dHJhbm55")));
|
||||
assert!(has_explicit_slur(&d("dHJhbm5pZXM=")));
|
||||
assert!(has_explicit_slur(&d("VFJBTk5Z")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalization_bypass() {
|
||||
assert!(has_explicit_slur("n.i.g.g.e.r"));
|
||||
assert!(has_explicit_slur("n-i-g-g-e-r"));
|
||||
assert!(has_explicit_slur("n_i_g_g_e_r"));
|
||||
assert!(has_explicit_slur("f.a.g"));
|
||||
assert!(has_explicit_slur("f-a-g"));
|
||||
assert!(has_explicit_slur("c.h.i.n.k"));
|
||||
assert!(has_explicit_slur("k_i_k_e"));
|
||||
assert!(has_explicit_slur(&d("bi5pLmcuZy5lLnI=")));
|
||||
assert!(has_explicit_slur(&d("bi1pLWctZy1lLXI=")));
|
||||
assert!(has_explicit_slur(&d("bl9pX2dfZ19lX3I=")));
|
||||
assert!(has_explicit_slur(&d("Zi5hLmc=")));
|
||||
assert!(has_explicit_slur(&d("Zi1hLWc=")));
|
||||
assert!(has_explicit_slur(&d("Yy5oLmkubi5r")));
|
||||
assert!(has_explicit_slur(&d("a19pX2tfZQ==")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trailing_digits_bypass() {
|
||||
assert!(has_explicit_slur("faggot123"));
|
||||
assert!(has_explicit_slur("nigger69"));
|
||||
assert!(has_explicit_slur("chink420"));
|
||||
assert!(has_explicit_slur("fag1"));
|
||||
assert!(has_explicit_slur("kike2024"));
|
||||
assert!(has_explicit_slur("n_i_g_g_e_r123"));
|
||||
assert!(has_explicit_slur(&d("ZmFnZ290MTIz")));
|
||||
assert!(has_explicit_slur(&d("bmlnZ2VyNjk=")));
|
||||
assert!(has_explicit_slur(&d("Y2hpbms0MjA=")));
|
||||
assert!(has_explicit_slur(&d("ZmFnMQ==")));
|
||||
assert!(has_explicit_slur(&d("a2lrZTIwMjQ=")));
|
||||
assert!(has_explicit_slur(&d("bl9pX2dfZ19lX3IxMjM=")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_embedded_in_sentence() {
|
||||
assert!(has_explicit_slur("you are a faggot"));
|
||||
assert!(has_explicit_slur("stupid nigger"));
|
||||
assert!(has_explicit_slur("go away chink"));
|
||||
assert!(has_explicit_slur(&d("eW91IGFyZSBhIGZhZ2dvdA==")));
|
||||
assert!(has_explicit_slur(&d("c3R1cGlkIG5pZ2dlcg==")));
|
||||
assert!(has_explicit_slur(&d("Z28gYXdheSBjaGluaw==")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -210,22 +216,22 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_case_insensitive() {
|
||||
assert!(has_explicit_slur("NIGGER"));
|
||||
assert!(has_explicit_slur("Nigger"));
|
||||
assert!(has_explicit_slur("NiGgEr"));
|
||||
assert!(has_explicit_slur("FAGGOT"));
|
||||
assert!(has_explicit_slur("Faggot"));
|
||||
assert!(has_explicit_slur(&d("TklHR0VS")));
|
||||
assert!(has_explicit_slur(&d("TmlnZ2Vy")));
|
||||
assert!(has_explicit_slur(&d("TmlHZ0Vy")));
|
||||
assert!(has_explicit_slur(&d("RkFHR09U")));
|
||||
assert!(has_explicit_slur(&d("RmFnZ290")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_leetspeak_bypass() {
|
||||
assert!(has_explicit_slur("f4ggot"));
|
||||
assert!(has_explicit_slur("f4gg0t"));
|
||||
assert!(has_explicit_slur("n1gger"));
|
||||
assert!(has_explicit_slur("n1gg3r"));
|
||||
assert!(has_explicit_slur("k1ke"));
|
||||
assert!(has_explicit_slur("ch1nk"));
|
||||
assert!(has_explicit_slur("tr4nny"));
|
||||
assert!(has_explicit_slur(&d("ZjRnZ290")));
|
||||
assert!(has_explicit_slur(&d("ZjRnZzB0")));
|
||||
assert!(has_explicit_slur(&d("bjFnZ2Vy")));
|
||||
assert!(has_explicit_slur(&d("bjFnZzNy")));
|
||||
assert!(has_explicit_slur(&d("azFrZQ==")));
|
||||
assert!(has_explicit_slur(&d("Y2gxbms=")));
|
||||
assert!(has_explicit_slur(&d("dHI0bm55")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -253,7 +259,10 @@ mod tests {
|
||||
assert!(has_explicit_slur_with_extra_words("b4dw0rd", &extra));
|
||||
assert!(has_explicit_slur_with_extra_words("b4dw0rd789", &extra));
|
||||
assert!(has_explicit_slur_with_extra_words("b.4.d.w.0.r.d", &extra));
|
||||
assert!(has_explicit_slur_with_extra_words("this contains badword here", &extra));
|
||||
assert!(has_explicit_slur_with_extra_words(
|
||||
"this contains badword here",
|
||||
&extra
|
||||
));
|
||||
assert!(has_explicit_slur_with_extra_words("0ff3n$1v3", &extra));
|
||||
|
||||
assert!(!has_explicit_slur_with_extra_words("goodword", &extra));
|
||||
|
||||
@@ -88,7 +88,10 @@ pub async fn delegation_auth(
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(_) = db::set_request_did(&state.db, &form.request_uri, &delegated_did).await {
|
||||
if db::set_request_did(&state.db, &form.request_uri, &delegated_did)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
tracing::warn!("Failed to set delegated DID on authorization request");
|
||||
}
|
||||
|
||||
@@ -168,13 +171,11 @@ pub async fn delegation_auth(
|
||||
.into_response();
|
||||
}
|
||||
|
||||
let password_valid = match &controller.password_hash {
|
||||
Some(hash) => match bcrypt::verify(&form.password, hash) {
|
||||
Ok(valid) => valid,
|
||||
Err(_) => false,
|
||||
},
|
||||
None => false,
|
||||
};
|
||||
let password_valid = controller
|
||||
.password_hash
|
||||
.as_ref()
|
||||
.map(|hash| bcrypt::verify(&form.password, hash).unwrap_or_default())
|
||||
.unwrap_or_default();
|
||||
|
||||
if !password_valid {
|
||||
return Json(DelegationAuthResponse {
|
||||
@@ -186,7 +187,9 @@ pub async fn delegation_auth(
|
||||
.into_response();
|
||||
}
|
||||
|
||||
if let Err(_) = db::set_controller_did(&state.db, &form.request_uri, &form.controller_did).await
|
||||
if db::set_controller_did(&state.db, &form.request_uri, &form.controller_did)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
return Json(DelegationAuthResponse {
|
||||
success: false,
|
||||
|
||||
+27
-26
@@ -189,13 +189,16 @@ fn walk_mst_node(
|
||||
if let Some(Ipld::List(entries)) = obj.get("e") {
|
||||
for entry in entries {
|
||||
if let Ipld::Map(entry_obj) = entry {
|
||||
let prefix_len = entry_obj.get("p").and_then(|p| {
|
||||
if let Ipld::Integer(n) = p {
|
||||
Some(*n as usize)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}).unwrap_or(0);
|
||||
let prefix_len = entry_obj
|
||||
.get("p")
|
||||
.and_then(|p| {
|
||||
if let Ipld::Integer(n) = p {
|
||||
Some(*n as usize)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or(0);
|
||||
|
||||
let key_suffix = entry_obj.get("k").and_then(|k| {
|
||||
if let Ipld::Bytes(b) = k {
|
||||
@@ -222,25 +225,23 @@ fn walk_mst_node(
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(record_cid) = record_cid {
|
||||
if let Ok(full_key) = String::from_utf8(current_key.clone()) {
|
||||
if let Some(record_block) = blocks.get(&record_cid)
|
||||
&& let Ok(record_value) =
|
||||
serde_ipld_dagcbor::from_slice::<Ipld>(record_block)
|
||||
{
|
||||
let blob_refs = find_blob_refs_ipld(&record_value, 0);
|
||||
let parts: Vec<&str> = full_key.split('/').collect();
|
||||
if parts.len() >= 2 {
|
||||
let collection = parts[..parts.len() - 1].join("/");
|
||||
let rkey = parts[parts.len() - 1].to_string();
|
||||
records.push(ImportedRecord {
|
||||
collection,
|
||||
rkey,
|
||||
cid: record_cid,
|
||||
blob_refs,
|
||||
});
|
||||
}
|
||||
}
|
||||
if let Some(record_cid) = record_cid
|
||||
&& let Ok(full_key) = String::from_utf8(current_key.clone())
|
||||
&& let Some(record_block) = blocks.get(&record_cid)
|
||||
&& let Ok(record_value) =
|
||||
serde_ipld_dagcbor::from_slice::<Ipld>(record_block)
|
||||
{
|
||||
let blob_refs = find_blob_refs_ipld(&record_value, 0);
|
||||
let parts: Vec<&str> = full_key.split('/').collect();
|
||||
if parts.len() >= 2 {
|
||||
let collection = parts[..parts.len() - 1].join("/");
|
||||
let rkey = parts[parts.len() - 1].to_string();
|
||||
records.push(ImportedRecord {
|
||||
collection,
|
||||
rkey,
|
||||
cid: record_cid,
|
||||
blob_refs,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
-14
@@ -161,14 +161,13 @@ impl RecordValidator {
|
||||
.get("$type")
|
||||
.and_then(|v| v.as_str())
|
||||
.is_some_and(|t| t == "app.bsky.richtext.facet#tag");
|
||||
if is_tag {
|
||||
if let Some(tag) = feature.get("tag").and_then(|v| v.as_str()) {
|
||||
if crate::moderation::has_explicit_slur(tag) {
|
||||
return Err(ValidationError::BannedContent {
|
||||
path: format!("facets/{}/features/{}/tag", i, j),
|
||||
});
|
||||
}
|
||||
}
|
||||
if is_tag
|
||||
&& let Some(tag) = feature.get("tag").and_then(|v| v.as_str())
|
||||
&& crate::moderation::has_explicit_slur(tag)
|
||||
{
|
||||
return Err(ValidationError::BannedContent {
|
||||
path: format!("facets/{}/features/{}/tag", i, j),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -332,12 +331,12 @@ impl RecordValidator {
|
||||
if !obj.contains_key("createdAt") {
|
||||
return Err(ValidationError::MissingField("createdAt".to_string()));
|
||||
}
|
||||
if let Some(rkey) = rkey {
|
||||
if crate::moderation::has_explicit_slur(rkey) {
|
||||
return Err(ValidationError::BannedContent {
|
||||
path: "rkey".to_string(),
|
||||
});
|
||||
}
|
||||
if let Some(rkey) = rkey
|
||||
&& crate::moderation::has_explicit_slur(rkey)
|
||||
{
|
||||
return Err(ValidationError::BannedContent {
|
||||
path: "rkey".to_string(),
|
||||
});
|
||||
}
|
||||
if let Some(display_name) = obj.get("displayName").and_then(|v| v.as_str()) {
|
||||
if display_name.is_empty() || display_name.len() > 240 {
|
||||
|
||||
Reference in New Issue
Block a user