mirror of
https://tangled.org/tranquil.farm/tranquil-pds
synced 2026-08-19 07:46:05 +00:00
Fix regression with appview proxying
This commit is contained in:
@@ -53,19 +53,6 @@ Modern passwordless authentication using WebAuthn/FIDO2, plus TOTP for defense i
|
||||
- [ ] Rate limit 2FA attempts
|
||||
- [ ] Re-auth for sensitive actions (email change, adding new auth methods)
|
||||
|
||||
### Private/encrypted data
|
||||
Records that only authorized parties can see and decrypt. Requires key federation between PDSes.
|
||||
|
||||
- [ ] Survey current ATProto discourse on private data
|
||||
- [ ] Document Bluesky team's likely approach
|
||||
- [ ] Design key management strategy
|
||||
- [ ] Per-user encryption keys (separate from signing keys)
|
||||
- [ ] Key derivation for per-record or per-collection encryption
|
||||
- [ ] Encrypted record storage format
|
||||
- [ ] Transparent encryption/decryption in repo operations
|
||||
- [ ] Protocol for sharing decryption keys between PDSes
|
||||
- [ ] Handle key rotation and revocation
|
||||
|
||||
### Plugin system
|
||||
Extensible architecture allowing third-party plugins to add functionality, like minecraft mods or browser extensions.
|
||||
|
||||
@@ -82,6 +69,19 @@ Extensible architecture allowing third-party plugins to add functionality, like
|
||||
- [ ] Example plugins: custom feed algorithm, content filter, S3 backup
|
||||
- [ ] Plugin registry with signature verification and version compatibility
|
||||
|
||||
### Plugin: Private/encrypted data
|
||||
Records that only authorized parties can see and decrypt. Requires key federation between PDSes. Implemented as a plugin using the plugin system above.
|
||||
|
||||
- [ ] Survey current ATProto discourse on private data
|
||||
- [ ] Document Bluesky team's likely approach
|
||||
- [ ] Design key management strategy
|
||||
- [ ] Per-user encryption keys (separate from signing keys)
|
||||
- [ ] Key derivation for per-record or per-collection encryption
|
||||
- [ ] Encrypted record storage format
|
||||
- [ ] Transparent encryption/decryption in repo operations
|
||||
- [ ] Protocol for sharing decryption keys between PDSes
|
||||
- [ ] Handle key rotation and revocation
|
||||
|
||||
---
|
||||
|
||||
## Completed
|
||||
|
||||
@@ -21,7 +21,6 @@ services:
|
||||
JWT_SECRET: "${JWT_SECRET:?JWT_SECRET is required (min 32 chars)}"
|
||||
DPOP_SECRET: "${DPOP_SECRET:?DPOP_SECRET is required (min 32 chars)}"
|
||||
MASTER_KEY: "${MASTER_KEY:?MASTER_KEY is required (min 32 chars)}"
|
||||
APPVIEW_URL: "${APPVIEW_URL:-https://api.bsky.app}"
|
||||
CRAWLERS: "${CRAWLERS:-https://bsky.network}"
|
||||
FRONTEND_DIR: "/app/frontend/dist"
|
||||
depends_on:
|
||||
|
||||
@@ -141,7 +141,7 @@ start_pre() {
|
||||
. /etc/bspds/bspds.env
|
||||
export SERVER_HOST SERVER_PORT PDS_HOSTNAME DATABASE_URL
|
||||
export S3_ENDPOINT AWS_REGION S3_BUCKET AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY
|
||||
export VALKEY_URL JWT_SECRET DPOP_SECRET MASTER_KEY APPVIEW_URL CRAWLERS
|
||||
export VALKEY_URL JWT_SECRET DPOP_SECRET MASTER_KEY CRAWLERS
|
||||
}
|
||||
EOF
|
||||
chmod +x /etc/init.d/bspds
|
||||
|
||||
@@ -15,7 +15,6 @@ The container image expects:
|
||||
- `VALKEY_URL` - redis:// connection string
|
||||
- `PDS_HOSTNAME` - your PDS hostname (without protocol)
|
||||
- `JWT_SECRET`, `DPOP_SECRET`, `MASTER_KEY` - generate with `openssl rand -base64 48`
|
||||
- `APPVIEW_URL` - typically `https://api.bsky.app`
|
||||
- `CRAWLERS` - typically `https://bsky.network`
|
||||
and more, check the .env.example.
|
||||
|
||||
|
||||
@@ -389,7 +389,6 @@ JWT_SECRET=${JWT_SECRET}
|
||||
DPOP_SECRET=${DPOP_SECRET}
|
||||
MASTER_KEY=${MASTER_KEY}
|
||||
PLC_DIRECTORY_URL=https://plc.directory
|
||||
APPVIEW_URL=https://api.bsky.app
|
||||
CRAWLERS=https://bsky.network
|
||||
AVAILABLE_USER_DOMAINS=${PDS_DOMAIN}
|
||||
MAIL_FROM_ADDRESS=noreply@${PDS_DOMAIN}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use crate::api::proxy_client::proxy_client;
|
||||
use crate::state::AppState;
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Query, State},
|
||||
http::StatusCode,
|
||||
http::{HeaderMap, StatusCode},
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use cid::Cid;
|
||||
@@ -11,7 +12,7 @@ use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
use tracing::error;
|
||||
use tracing::{error, info};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct GetRecordInput {
|
||||
@@ -23,6 +24,7 @@ pub struct GetRecordInput {
|
||||
|
||||
pub async fn get_record(
|
||||
State(state): State<AppState>,
|
||||
headers: HeaderMap,
|
||||
Query(input): Query<GetRecordInput>,
|
||||
) -> Response {
|
||||
let hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string());
|
||||
@@ -46,6 +48,63 @@ pub async fn get_record(
|
||||
let user_id: uuid::Uuid = match user_id_opt {
|
||||
Ok(Some(id)) => id,
|
||||
Ok(None) => {
|
||||
if let Some(proxy_header) = headers
|
||||
.get("atproto-proxy")
|
||||
.and_then(|h| h.to_str().ok())
|
||||
{
|
||||
let did = proxy_header.split('#').next().unwrap_or(proxy_header);
|
||||
if let Some(resolved) = state.did_resolver.resolve_did(did).await {
|
||||
let mut url = format!(
|
||||
"{}/xrpc/com.atproto.repo.getRecord?repo={}&collection={}&rkey={}",
|
||||
resolved.url.trim_end_matches('/'),
|
||||
urlencoding::encode(&input.repo),
|
||||
urlencoding::encode(&input.collection),
|
||||
urlencoding::encode(&input.rkey)
|
||||
);
|
||||
if let Some(cid) = &input.cid {
|
||||
url.push_str(&format!("&cid={}", urlencoding::encode(cid)));
|
||||
}
|
||||
info!("Proxying getRecord to {}: {}", did, url);
|
||||
match proxy_client().get(&url).send().await {
|
||||
Ok(resp) => {
|
||||
let status = resp.status();
|
||||
let body = match resp.bytes().await {
|
||||
Ok(b) => b,
|
||||
Err(e) => {
|
||||
error!("Error reading proxy response: {:?}", e);
|
||||
return (
|
||||
StatusCode::BAD_GATEWAY,
|
||||
Json(json!({"error": "UpstreamFailure", "message": "Error reading upstream response"})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
};
|
||||
return Response::builder()
|
||||
.status(status)
|
||||
.header("content-type", "application/json")
|
||||
.body(axum::body::Body::from(body))
|
||||
.unwrap_or_else(|_| {
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, "Internal error").into_response()
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error proxying request: {:?}", e);
|
||||
return (
|
||||
StatusCode::BAD_GATEWAY,
|
||||
Json(json!({"error": "UpstreamFailure", "message": "Failed to reach upstream service"})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error!("Could not resolve DID from atproto-proxy header: {}", did);
|
||||
return (
|
||||
StatusCode::BAD_GATEWAY,
|
||||
Json(json!({"error": "UpstreamFailure", "message": "Could not resolve proxy DID"})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
}
|
||||
return (
|
||||
StatusCode::NOT_FOUND,
|
||||
Json(json!({"error": "RepoNotFound", "message": "Repo not found"})),
|
||||
|
||||
Reference in New Issue
Block a user