Rename to tranquil PDS, sounds better than bullshit PDS

This commit is contained in:
lewis
2025-12-18 23:17:51 +02:00
parent 95958bb119
commit 80a3e04ec6
62 changed files with 744 additions and 590 deletions
+18 -2
View File
@@ -157,9 +157,20 @@ pub async fn activate_account(
.await;
match result {
Ok(_) => {
if let Some(h) = handle {
if let Some(ref h) = handle {
let _ = state.cache.delete(&format!("handle:{}", h)).await;
}
if let Err(e) =
crate::api::repo::record::sequence_account_event(&state, &did, true, None).await
{
warn!("Failed to sequence account activation event: {}", e);
}
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);
}
(StatusCode::OK, Json(json!({}))).into_response()
}
Err(e) => {
@@ -222,9 +233,14 @@ pub async fn deactivate_account(
.await;
match result {
Ok(_) => {
if let Some(h) = handle {
if let Some(ref h) = handle {
let _ = state.cache.delete(&format!("handle:{}", h)).await;
}
if let Err(e) =
crate::api::repo::record::sequence_account_event(&state, &did, false, Some("deactivated")).await
{
warn!("Failed to sequence account deactivation event: {}", e);
}
(StatusCode::OK, Json(json!({}))).into_response()
}
Err(e) => {
+102 -3
View File
@@ -10,6 +10,28 @@ use serde::{Deserialize, Serialize};
use serde_json::json;
use tracing::error;
const HOUR_SECS: i64 = 3600;
const MINUTE_SECS: i64 = 60;
const PROTECTED_METHODS: &[&str] = &[
"com.atproto.admin.sendEmail",
"com.atproto.identity.requestPlcOperationSignature",
"com.atproto.identity.signPlcOperation",
"com.atproto.identity.updateHandle",
"com.atproto.server.activateAccount",
"com.atproto.server.confirmEmail",
"com.atproto.server.createAppPassword",
"com.atproto.server.deactivateAccount",
"com.atproto.server.getAccountInviteCodes",
"com.atproto.server.getSession",
"com.atproto.server.listAppPasswords",
"com.atproto.server.requestAccountDelete",
"com.atproto.server.requestEmailConfirmation",
"com.atproto.server.requestEmailUpdate",
"com.atproto.server.revokeAppPassword",
"com.atproto.server.updateEmail",
];
#[derive(Deserialize)]
pub struct GetServiceAuthParams {
pub aud: String,
@@ -33,7 +55,7 @@ pub async fn get_service_auth(
Some(t) => t,
None => return ApiError::AuthenticationRequired.into_response(),
};
let auth_user = match crate::auth::validate_bearer_token(&state.db, &token).await {
let auth_user = match crate::auth::validate_bearer_token_for_service_auth(&state.db, &token).await {
Ok(user) => user,
Err(e) => return ApiError::from(e).into_response(),
};
@@ -46,9 +68,86 @@ pub async fn get_service_auth(
.into_response();
}
};
let lxm = params.lxm.as_deref().unwrap_or("*");
let lxm = params.lxm.as_deref();
let lxm_for_token = lxm.unwrap_or("*");
let user_status = sqlx::query!(
"SELECT takedown_ref FROM users WHERE did = $1",
auth_user.did
)
.fetch_optional(&state.db)
.await;
let is_takendown = match user_status {
Ok(Some(row)) => row.takedown_ref.is_some(),
_ => false,
};
if is_takendown && lxm != Some("com.atproto.server.createAccount") {
return (
StatusCode::BAD_REQUEST,
Json(json!({
"error": "InvalidToken",
"message": "Bad token scope"
})),
)
.into_response();
}
if let Some(method) = lxm {
if PROTECTED_METHODS.contains(&method) {
return (
StatusCode::BAD_REQUEST,
Json(json!({
"error": "InvalidRequest",
"message": format!("cannot request a service auth token for the following protected method: {}", method)
})),
)
.into_response();
}
}
if let Some(exp) = params.exp {
let now = chrono::Utc::now().timestamp();
let diff = exp - now;
if diff < 0 {
return (
StatusCode::BAD_REQUEST,
Json(json!({
"error": "BadExpiration",
"message": "expiration is in past"
})),
)
.into_response();
}
if diff > HOUR_SECS {
return (
StatusCode::BAD_REQUEST,
Json(json!({
"error": "BadExpiration",
"message": "cannot request a token with an expiration more than an hour in the future"
})),
)
.into_response();
}
if lxm.is_none() && diff > MINUTE_SECS {
return (
StatusCode::BAD_REQUEST,
Json(json!({
"error": "BadExpiration",
"message": "cannot request a method-less token with an expiration more than a minute in the future"
})),
)
.into_response();
}
}
let service_token =
match crate::auth::create_service_token(&auth_user.did, &params.aud, lxm, &key_bytes) {
match crate::auth::create_service_token(&auth_user.did, &params.aud, lxm_for_token, &key_bytes) {
Ok(t) => t,
Err(e) => {
error!("Failed to create service token: {:?}", e);
+13 -5
View File
@@ -59,14 +59,14 @@ pub async fn validate_bearer_token(
db: &PgPool,
token: &str,
) -> Result<AuthenticatedUser, TokenValidationError> {
validate_bearer_token_with_options_internal(db, None, token, false).await
validate_bearer_token_with_options_internal(db, None, token, false, false).await
}
pub async fn validate_bearer_token_allow_deactivated(
db: &PgPool,
token: &str,
) -> Result<AuthenticatedUser, TokenValidationError> {
validate_bearer_token_with_options_internal(db, None, token, true).await
validate_bearer_token_with_options_internal(db, None, token, true, false).await
}
pub async fn validate_bearer_token_cached(
@@ -74,7 +74,7 @@ pub async fn validate_bearer_token_cached(
cache: &Arc<dyn Cache>,
token: &str,
) -> Result<AuthenticatedUser, TokenValidationError> {
validate_bearer_token_with_options_internal(db, Some(cache), token, false).await
validate_bearer_token_with_options_internal(db, Some(cache), token, false, false).await
}
pub async fn validate_bearer_token_cached_allow_deactivated(
@@ -82,7 +82,14 @@ pub async fn validate_bearer_token_cached_allow_deactivated(
cache: &Arc<dyn Cache>,
token: &str,
) -> Result<AuthenticatedUser, TokenValidationError> {
validate_bearer_token_with_options_internal(db, Some(cache), token, true).await
validate_bearer_token_with_options_internal(db, Some(cache), token, true, false).await
}
pub async fn validate_bearer_token_for_service_auth(
db: &PgPool,
token: &str,
) -> Result<AuthenticatedUser, TokenValidationError> {
validate_bearer_token_with_options_internal(db, None, token, true, true).await
}
async fn validate_bearer_token_with_options_internal(
@@ -90,6 +97,7 @@ async fn validate_bearer_token_with_options_internal(
cache: Option<&Arc<dyn Cache>>,
token: &str,
allow_deactivated: bool,
allow_takendown: bool,
) -> Result<AuthenticatedUser, TokenValidationError> {
let did_from_token = get_did_from_token(token).ok();
@@ -155,7 +163,7 @@ async fn validate_bearer_token_with_options_internal(
return Err(TokenValidationError::AccountDeactivated);
}
if takedown_ref.is_some() {
if !allow_takendown && takedown_ref.is_some() {
return Err(TokenValidationError::AccountTakedown);
}
+2 -2
View File
@@ -87,7 +87,7 @@ impl EmailSender {
pub fn from_env() -> Option<Self> {
let from_address = std::env::var("MAIL_FROM_ADDRESS").ok()?;
let from_name = std::env::var("MAIL_FROM_NAME").unwrap_or_else(|_| "BSPDS".to_string());
let from_name = std::env::var("MAIL_FROM_NAME").unwrap_or_else(|_| "Tranquil PDS".to_string());
Some(Self::new(from_address, from_name))
}
@@ -168,7 +168,7 @@ impl CommsSender for DiscordSender {
let content = format!("**{}**\n\n{}", subject, notification.body);
let payload = json!({
"content": content,
"username": "BSPDS"
"username": "Tranquil PDS"
});
let mut last_error = None;
for attempt in 0..MAX_RETRIES {
+10 -10
View File
@@ -25,32 +25,32 @@ impl AuthConfig {
pub fn init() -> &'static Self {
CONFIG.get_or_init(|| {
let jwt_secret = std::env::var("JWT_SECRET").unwrap_or_else(|_| {
if cfg!(test) || std::env::var("BSPDS_ALLOW_INSECURE_SECRETS").is_ok() {
if cfg!(test) || std::env::var("TRANQUIL_PDS_ALLOW_INSECURE_SECRETS").is_ok() {
"test-jwt-secret-not-for-production".to_string()
} else {
panic!(
"JWT_SECRET environment variable must be set in production. \
Set BSPDS_ALLOW_INSECURE_SECRETS=1 for development/testing."
Set TRANQUIL_PDS_ALLOW_INSECURE_SECRETS=1 for development/testing."
);
}
});
let dpop_secret = std::env::var("DPOP_SECRET").unwrap_or_else(|_| {
if cfg!(test) || std::env::var("BSPDS_ALLOW_INSECURE_SECRETS").is_ok() {
if cfg!(test) || std::env::var("TRANQUIL_PDS_ALLOW_INSECURE_SECRETS").is_ok() {
"test-dpop-secret-not-for-production".to_string()
} else {
panic!(
"DPOP_SECRET environment variable must be set in production. \
Set BSPDS_ALLOW_INSECURE_SECRETS=1 for development/testing."
Set TRANQUIL_PDS_ALLOW_INSECURE_SECRETS=1 for development/testing."
);
}
});
if jwt_secret.len() < 32 && std::env::var("BSPDS_ALLOW_INSECURE_SECRETS").is_err() {
if jwt_secret.len() < 32 && std::env::var("TRANQUIL_PDS_ALLOW_INSECURE_SECRETS").is_err() {
panic!("JWT_SECRET must be at least 32 characters");
}
if dpop_secret.len() < 32 && std::env::var("BSPDS_ALLOW_INSECURE_SECRETS").is_err() {
if dpop_secret.len() < 32 && std::env::var("TRANQUIL_PDS_ALLOW_INSECURE_SECRETS").is_err() {
panic!("DPOP_SECRET must be at least 32 characters");
}
@@ -87,23 +87,23 @@ impl AuthConfig {
let signing_key_id = URL_SAFE_NO_PAD.encode(&kid_hash[..8]);
let master_key = std::env::var("MASTER_KEY").unwrap_or_else(|_| {
if cfg!(test) || std::env::var("BSPDS_ALLOW_INSECURE_SECRETS").is_ok() {
if cfg!(test) || std::env::var("TRANQUIL_PDS_ALLOW_INSECURE_SECRETS").is_ok() {
"test-master-key-not-for-production".to_string()
} else {
panic!(
"MASTER_KEY environment variable must be set in production. \
Set BSPDS_ALLOW_INSECURE_SECRETS=1 for development/testing."
Set TRANQUIL_PDS_ALLOW_INSECURE_SECRETS=1 for development/testing."
);
}
});
if master_key.len() < 32 && std::env::var("BSPDS_ALLOW_INSECURE_SECRETS").is_err() {
if master_key.len() < 32 && std::env::var("TRANQUIL_PDS_ALLOW_INSECURE_SECRETS").is_err() {
panic!("MASTER_KEY must be at least 32 characters");
}
let hk = Hkdf::<Sha256>::new(None, master_key.as_bytes());
let mut key_encryption_key = [0u8; 32];
hk.expand(b"bspds-user-key-encryption", &mut key_encryption_key)
hk.expand(b"tranquil-pds-user-key-encryption", &mut key_encryption_key)
.expect("HKDF expansion failed");
AuthConfig {
+8 -8
View File
@@ -52,11 +52,11 @@ pub fn app(state: AppState) -> Router {
get(api::server::get_session),
)
.route(
"/xrpc/com.bspds.account.listSessions",
"/xrpc/com.tranquil.account.listSessions",
get(api::server::list_sessions),
)
.route(
"/xrpc/com.bspds.account.revokeSession",
"/xrpc/com.tranquil.account.revokeSession",
post(api::server::revoke_session),
)
.route(
@@ -199,7 +199,7 @@ pub fn app(state: AppState) -> Router {
post(api::server::reset_password),
)
.route(
"/xrpc/com.bspds.account.changePassword",
"/xrpc/com.tranquil.account.changePassword",
post(api::server::change_password),
)
.route(
@@ -283,7 +283,7 @@ pub fn app(state: AppState) -> Router {
get(api::admin::get_invite_codes),
)
.route(
"/xrpc/com.bspds.admin.getServerStats",
"/xrpc/com.tranquil.admin.getServerStats",
get(api::admin::get_server_stats),
)
.route(
@@ -370,19 +370,19 @@ pub fn app(state: AppState) -> Router {
get(api::temp::check_signup_queue),
)
.route(
"/xrpc/com.bspds.account.getNotificationPrefs",
"/xrpc/com.tranquil.account.getNotificationPrefs",
get(api::notification_prefs::get_notification_prefs),
)
.route(
"/xrpc/com.bspds.account.updateNotificationPrefs",
"/xrpc/com.tranquil.account.updateNotificationPrefs",
post(api::notification_prefs::update_notification_prefs),
)
.route(
"/xrpc/com.bspds.account.getNotificationHistory",
"/xrpc/com.tranquil.account.getNotificationHistory",
get(api::notification_prefs::get_notification_history),
)
.route(
"/xrpc/com.bspds.account.confirmChannelVerification",
"/xrpc/com.tranquil.account.confirmChannelVerification",
post(api::verification::confirm_channel_verification),
)
.route("/xrpc/{*method}", any(api::proxy::proxy_handler))
+6 -6
View File
@@ -1,6 +1,6 @@
use bspds::comms::{CommsService, DiscordSender, EmailSender, SignalSender, TelegramSender};
use bspds::crawlers::{Crawlers, start_crawlers_service};
use bspds::state::AppState;
use tranquil_pds::comms::{CommsService, DiscordSender, EmailSender, SignalSender, TelegramSender};
use tranquil_pds::crawlers::{Crawlers, start_crawlers_service};
use tranquil_pds::state::AppState;
use std::net::SocketAddr;
use std::process::ExitCode;
use std::sync::Arc;
@@ -11,7 +11,7 @@ use tracing::{error, info, warn};
async fn main() -> ExitCode {
dotenvy::dotenv().ok();
tracing_subscriber::fmt::init();
bspds::metrics::init_metrics();
tranquil_pds::metrics::init_metrics();
match run().await {
Ok(()) => ExitCode::SUCCESS,
@@ -62,7 +62,7 @@ async fn run() -> Result<(), Box<dyn std::error::Error>> {
.map_err(|e| format!("Failed to run migrations: {}", e))?;
let state = AppState::new(pool.clone()).await;
bspds::sync::listener::start_sequencer_listener(state.clone()).await;
tranquil_pds::sync::listener::start_sequencer_listener(state.clone()).await;
let (shutdown_tx, shutdown_rx) = watch::channel(false);
@@ -108,7 +108,7 @@ async fn run() -> Result<(), Box<dyn std::error::Error>> {
None
};
let app = bspds::app(state);
let app = tranquil_pds::app(state);
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
info!("listening on {}", addr);
+25 -25
View File
@@ -24,46 +24,46 @@ pub fn init_metrics() -> PrometheusHandle {
}
fn describe_metrics() {
metrics::describe_counter!("bspds_http_requests_total", "Total number of HTTP requests");
metrics::describe_counter!("tranquil_pds_http_requests_total", "Total number of HTTP requests");
metrics::describe_histogram!(
"bspds_http_request_duration_seconds",
"tranquil_pds_http_request_duration_seconds",
"HTTP request duration in seconds"
);
metrics::describe_counter!(
"bspds_auth_cache_hits_total",
"tranquil_pds_auth_cache_hits_total",
"Total number of authentication cache hits"
);
metrics::describe_counter!(
"bspds_auth_cache_misses_total",
"tranquil_pds_auth_cache_misses_total",
"Total number of authentication cache misses"
);
metrics::describe_gauge!(
"bspds_firehose_subscribers",
"tranquil_pds_firehose_subscribers",
"Number of active firehose WebSocket subscribers"
);
metrics::describe_counter!(
"bspds_firehose_events_total",
"tranquil_pds_firehose_events_total",
"Total number of firehose events published"
);
metrics::describe_counter!(
"bspds_block_operations_total",
"tranquil_pds_block_operations_total",
"Total number of block store operations"
);
metrics::describe_counter!(
"bspds_s3_operations_total",
"tranquil_pds_s3_operations_total",
"Total number of S3/blob storage operations"
);
metrics::describe_gauge!(
"bspds_comms_queue_size",
"tranquil_pds_comms_queue_size",
"Current size of the comms queue"
);
metrics::describe_counter!(
"bspds_rate_limit_rejections_total",
"tranquil_pds_rate_limit_rejections_total",
"Total number of rate limit rejections"
);
metrics::describe_counter!("bspds_db_queries_total", "Total number of database queries");
metrics::describe_counter!("tranquil_pds_db_queries_total", "Total number of database queries");
metrics::describe_histogram!(
"bspds_db_query_duration_seconds",
"tranquil_pds_db_query_duration_seconds",
"Database query duration in seconds"
);
}
@@ -97,7 +97,7 @@ pub async fn metrics_middleware(request: Request<Body>, next: Next) -> Response
let status = response.status().as_u16().to_string();
counter!(
"bspds_http_requests_total",
"tranquil_pds_http_requests_total",
"method" => method.clone(),
"path" => path.clone(),
"status" => status.clone()
@@ -105,7 +105,7 @@ pub async fn metrics_middleware(request: Request<Body>, next: Next) -> Response
.increment(1);
histogram!(
"bspds_http_request_duration_seconds",
"tranquil_pds_http_request_duration_seconds",
"method" => method,
"path" => path
)
@@ -135,32 +135,32 @@ fn normalize_path(path: &str) -> String {
}
pub fn record_auth_cache_hit(cache_type: &str) {
counter!("bspds_auth_cache_hits_total", "cache_type" => cache_type.to_string()).increment(1);
counter!("tranquil_pds_auth_cache_hits_total", "cache_type" => cache_type.to_string()).increment(1);
}
pub fn record_auth_cache_miss(cache_type: &str) {
counter!("bspds_auth_cache_misses_total", "cache_type" => cache_type.to_string()).increment(1);
counter!("tranquil_pds_auth_cache_misses_total", "cache_type" => cache_type.to_string()).increment(1);
}
pub fn set_firehose_subscribers(count: usize) {
gauge!("bspds_firehose_subscribers").set(count as f64);
gauge!("tranquil_pds_firehose_subscribers").set(count as f64);
}
pub fn increment_firehose_subscribers() {
counter!("bspds_firehose_events_total").increment(1);
counter!("tranquil_pds_firehose_events_total").increment(1);
}
pub fn record_firehose_event() {
counter!("bspds_firehose_events_total").increment(1);
counter!("tranquil_pds_firehose_events_total").increment(1);
}
pub fn record_block_operation(op_type: &str) {
counter!("bspds_block_operations_total", "op_type" => op_type.to_string()).increment(1);
counter!("tranquil_pds_block_operations_total", "op_type" => op_type.to_string()).increment(1);
}
pub fn record_s3_operation(op_type: &str, status: &str) {
counter!(
"bspds_s3_operations_total",
"tranquil_pds_s3_operations_total",
"op_type" => op_type.to_string(),
"status" => status.to_string()
)
@@ -168,17 +168,17 @@ pub fn record_s3_operation(op_type: &str, status: &str) {
}
pub fn set_comms_queue_size(size: usize) {
gauge!("bspds_comms_queue_size").set(size as f64);
gauge!("tranquil_pds_comms_queue_size").set(size as f64);
}
pub fn record_rate_limit_rejection(limiter: &str) {
counter!("bspds_rate_limit_rejections_total", "limiter" => limiter.to_string()).increment(1);
counter!("tranquil_pds_rate_limit_rejections_total", "limiter" => limiter.to_string()).increment(1);
}
pub fn record_db_query(query_type: &str, duration_seconds: f64) {
counter!("bspds_db_queries_total", "query_type" => query_type.to_string()).increment(1);
counter!("tranquil_pds_db_queries_total", "query_type" => query_type.to_string()).increment(1);
histogram!(
"bspds_db_query_duration_seconds",
"tranquil_pds_db_query_duration_seconds",
"query_type" => query_type.to_string()
)
.record(duration_seconds);