Our Personal Data Server from scratch! tranquil.farm
oauth atproto pds rust postgresql objectstorage fun

fix: match ref pds permission-levels for some endpoints

+128 -26
+3 -3
crates/tranquil-pds/src/api/actor/preferences.rs
··· 1 1 use crate::api::error::ApiError; 2 - use crate::auth::{Active, Auth}; 2 + use crate::auth::{Auth, NotTakendown, Permissive}; 3 3 use crate::state::AppState; 4 4 use axum::{ 5 5 Json, ··· 32 32 pub struct GetPreferencesOutput { 33 33 pub preferences: Vec<Value>, 34 34 } 35 - pub async fn get_preferences(State(state): State<AppState>, auth: Auth<Active>) -> Response { 35 + pub async fn get_preferences(State(state): State<AppState>, auth: Auth<Permissive>) -> Response { 36 36 let has_full_access = auth.permissions().has_full_access(); 37 37 let user_id: uuid::Uuid = match state.user_repo.get_id_by_did(&auth.did).await { 38 38 Ok(Some(id)) => id, ··· 89 89 } 90 90 pub async fn put_preferences( 91 91 State(state): State<AppState>, 92 - auth: Auth<Active>, 92 + auth: Auth<NotTakendown>, 93 93 Json(input): Json<PutPreferencesInput>, 94 94 ) -> Response { 95 95 let has_full_access = auth.permissions().has_full_access();
+2 -2
crates/tranquil-pds/src/api/identity/plc/request.rs
··· 1 1 use crate::api::EmptyResponse; 2 2 use crate::api::error::ApiError; 3 - use crate::auth::{Auth, NotTakendown}; 3 + use crate::auth::{Auth, Permissive}; 4 4 use crate::state::AppState; 5 5 use axum::{ 6 6 extract::State, ··· 15 15 16 16 pub async fn request_plc_operation_signature( 17 17 State(state): State<AppState>, 18 - auth: Auth<NotTakendown>, 18 + auth: Auth<Permissive>, 19 19 ) -> Result<Response, ApiError> { 20 20 if let Err(e) = crate::auth::scope_check::check_identity_scope( 21 21 auth.is_oauth(),
+2 -2
crates/tranquil-pds/src/api/identity/plc/sign.rs
··· 1 1 use crate::api::ApiError; 2 - use crate::auth::{Auth, NotTakendown}; 2 + use crate::auth::{Auth, Permissive}; 3 3 use crate::circuit_breaker::with_circuit_breaker; 4 4 use crate::plc::{PlcClient, PlcError, PlcService, create_update_op, sign_operation}; 5 5 use crate::state::AppState; ··· 40 40 41 41 pub async fn sign_plc_operation( 42 42 State(state): State<AppState>, 43 - auth: Auth<NotTakendown>, 43 + auth: Auth<Permissive>, 44 44 Json(input): Json<SignPlcOperationInput>, 45 45 ) -> Result<Response, ApiError> { 46 46 if let Err(e) = crate::auth::scope_check::check_identity_scope(
+2 -2
crates/tranquil-pds/src/api/identity/plc/submit.rs
··· 1 1 use crate::api::{ApiError, EmptyResponse}; 2 - use crate::auth::{Auth, NotTakendown}; 2 + use crate::auth::{Auth, Permissive}; 3 3 use crate::circuit_breaker::with_circuit_breaker; 4 4 use crate::plc::{PlcClient, signing_key_to_did_key, validate_plc_operation}; 5 5 use crate::state::AppState; ··· 20 20 21 21 pub async fn submit_plc_operation( 22 22 State(state): State<AppState>, 23 - auth: Auth<NotTakendown>, 23 + auth: Auth<Permissive>, 24 24 Json(input): Json<SubmitPlcOperationInput>, 25 25 ) -> Result<Response, ApiError> { 26 26 if let Err(e) = crate::auth::scope_check::check_identity_scope(
+4 -4
crates/tranquil-pds/src/api/server/account_status.rs
··· 1 1 use crate::api::EmptyResponse; 2 2 use crate::api::error::ApiError; 3 - use crate::auth::{Active, Auth, NotTakendown}; 3 + use crate::auth::{Auth, NotTakendown, Permissive}; 4 4 use crate::cache::Cache; 5 5 use crate::plc::PlcClient; 6 6 use crate::state::AppState; ··· 41 41 42 42 pub async fn check_account_status( 43 43 State(state): State<AppState>, 44 - auth: Auth<NotTakendown>, 44 + auth: Auth<Permissive>, 45 45 ) -> Result<Response, ApiError> { 46 46 let did = &auth.did; 47 47 let user_id = state ··· 306 306 307 307 pub async fn activate_account( 308 308 State(state): State<AppState>, 309 - auth: Auth<NotTakendown>, 309 + auth: Auth<Permissive>, 310 310 ) -> Result<Response, ApiError> { 311 311 info!("[MIGRATION] activateAccount called"); 312 312 info!( ··· 470 470 471 471 pub async fn deactivate_account( 472 472 State(state): State<AppState>, 473 - auth: Auth<Active>, 473 + auth: Auth<Permissive>, 474 474 Json(input): Json<DeactivateAccountInput>, 475 475 ) -> Result<Response, ApiError> { 476 476 if let Err(e) = crate::auth::scope_check::check_account_scope(
+4 -4
crates/tranquil-pds/src/api/server/app_password.rs
··· 1 1 use crate::api::EmptyResponse; 2 2 use crate::api::error::ApiError; 3 - use crate::auth::{Active, Auth, generate_app_password}; 3 + use crate::auth::{Auth, NotTakendown, Permissive, generate_app_password}; 4 4 use crate::delegation::{DelegationActionType, intersect_scopes}; 5 5 use crate::state::{AppState, RateLimitKind}; 6 6 use axum::{ ··· 33 33 34 34 pub async fn list_app_passwords( 35 35 State(state): State<AppState>, 36 - auth: Auth<Active>, 36 + auth: Auth<Permissive>, 37 37 ) -> Result<Response, ApiError> { 38 38 let user = state 39 39 .user_repo ··· 90 90 pub async fn create_app_password( 91 91 State(state): State<AppState>, 92 92 headers: HeaderMap, 93 - auth: Auth<Active>, 93 + auth: Auth<NotTakendown>, 94 94 Json(input): Json<CreateAppPasswordInput>, 95 95 ) -> Result<Response, ApiError> { 96 96 let client_ip = crate::rate_limit::extract_client_ip(&headers, None); ··· 227 227 228 228 pub async fn revoke_app_password( 229 229 State(state): State<AppState>, 230 - auth: Auth<Active>, 230 + auth: Auth<Permissive>, 231 231 Json(input): Json<RevokeAppPasswordInput>, 232 232 ) -> Result<Response, ApiError> { 233 233 let user = state
+5 -5
crates/tranquil-pds/src/api/server/email.rs
··· 1 1 use crate::api::error::ApiError; 2 2 use crate::api::{EmptyResponse, TokenRequiredResponse, VerifiedResponse}; 3 - use crate::auth::{Active, Auth}; 3 + use crate::auth::{Auth, NotTakendown}; 4 4 use crate::state::{AppState, RateLimitKind}; 5 5 use axum::{ 6 6 Json, ··· 45 45 pub async fn request_email_update( 46 46 State(state): State<AppState>, 47 47 headers: axum::http::HeaderMap, 48 - auth: Auth<Active>, 48 + auth: Auth<NotTakendown>, 49 49 input: Option<Json<RequestEmailUpdateInput>>, 50 50 ) -> Result<Response, ApiError> { 51 51 let client_ip = crate::rate_limit::extract_client_ip(&headers, None); ··· 140 140 pub async fn confirm_email( 141 141 State(state): State<AppState>, 142 142 headers: axum::http::HeaderMap, 143 - auth: Auth<Active>, 143 + auth: Auth<NotTakendown>, 144 144 Json(input): Json<ConfirmEmailInput>, 145 145 ) -> Result<Response, ApiError> { 146 146 let client_ip = crate::rate_limit::extract_client_ip(&headers, None); ··· 233 233 234 234 pub async fn update_email( 235 235 State(state): State<AppState>, 236 - auth: Auth<Active>, 236 + auth: Auth<NotTakendown>, 237 237 Json(input): Json<UpdateEmailInput>, 238 238 ) -> Result<Response, ApiError> { 239 239 if let Err(e) = crate::auth::scope_check::check_account_scope( ··· 500 500 pub async fn check_email_update_status( 501 501 State(state): State<AppState>, 502 502 headers: axum::http::HeaderMap, 503 - auth: Auth<Active>, 503 + auth: Auth<NotTakendown>, 504 504 ) -> Result<Response, ApiError> { 505 505 let client_ip = crate::rate_limit::extract_client_ip(&headers, None); 506 506 if !state
+2 -2
crates/tranquil-pds/src/api/server/invite.rs
··· 1 1 use crate::api::ApiError; 2 - use crate::auth::{Active, Admin, Auth}; 2 + use crate::auth::{Admin, Auth, NotTakendown}; 3 3 use crate::state::AppState; 4 4 use crate::types::Did; 5 5 use axum::{ ··· 193 193 194 194 pub async fn get_account_invite_codes( 195 195 State(state): State<AppState>, 196 - auth: Auth<Active>, 196 + auth: Auth<NotTakendown>, 197 197 axum::extract::Query(params): axum::extract::Query<GetAccountInviteCodesParams>, 198 198 ) -> Result<Response, ApiError> { 199 199 let include_used = params.include_used.unwrap_or(true);
+2 -2
crates/tranquil-pds/src/api/server/session.rs
··· 1 1 use crate::api::error::ApiError; 2 2 use crate::api::{EmptyResponse, SuccessResponse}; 3 - use crate::auth::{Active, Auth, NotTakendown}; 3 + use crate::auth::{Active, Auth, Permissive}; 4 4 use crate::state::{AppState, RateLimitKind}; 5 5 use crate::types::{AccountState, Did, Handle, PlainPassword}; 6 6 use axum::{ ··· 279 279 280 280 pub async fn get_session( 281 281 State(state): State<AppState>, 282 - auth: Auth<NotTakendown>, 282 + auth: Auth<Permissive>, 283 283 ) -> Result<Response, ApiError> { 284 284 let permissions = auth.permissions(); 285 285 let can_read_email = permissions.allows_email_read();
+102
crates/tranquil-pds/tests/actor.rs
··· 436 436 assert_eq!(declared_age["isOverAge16"], false); 437 437 assert_eq!(declared_age["isOverAge18"], false); 438 438 } 439 + 440 + #[tokio::test] 441 + async fn test_deactivated_account_can_get_preferences() { 442 + let client = client(); 443 + let base = base_url().await; 444 + let (token, _did) = create_account_and_login(&client).await; 445 + 446 + let prefs = json!({ 447 + "preferences": [ 448 + { 449 + "$type": "app.bsky.actor.defs#adultContentPref", 450 + "enabled": true 451 + } 452 + ] 453 + }); 454 + let put_resp = client 455 + .post(format!("{}/xrpc/app.bsky.actor.putPreferences", base)) 456 + .header("Authorization", format!("Bearer {}", token)) 457 + .json(&prefs) 458 + .send() 459 + .await 460 + .unwrap(); 461 + assert_eq!(put_resp.status(), 200); 462 + 463 + let deactivate = client 464 + .post(format!( 465 + "{}/xrpc/com.atproto.server.deactivateAccount", 466 + base 467 + )) 468 + .header("Authorization", format!("Bearer {}", token)) 469 + .json(&json!({})) 470 + .send() 471 + .await 472 + .unwrap(); 473 + assert_eq!(deactivate.status(), 200); 474 + 475 + let get_resp = client 476 + .get(format!("{}/xrpc/app.bsky.actor.getPreferences", base)) 477 + .header("Authorization", format!("Bearer {}", token)) 478 + .send() 479 + .await 480 + .unwrap(); 481 + assert_eq!( 482 + get_resp.status(), 483 + 200, 484 + "Deactivated account should still be able to get preferences" 485 + ); 486 + let body: Value = get_resp.json().await.unwrap(); 487 + let prefs_arr = body["preferences"].as_array().unwrap(); 488 + assert_eq!(prefs_arr.len(), 1); 489 + } 490 + 491 + #[tokio::test] 492 + async fn test_deactivated_account_can_put_preferences() { 493 + let client = client(); 494 + let base = base_url().await; 495 + let (token, _did) = create_account_and_login(&client).await; 496 + 497 + let deactivate = client 498 + .post(format!( 499 + "{}/xrpc/com.atproto.server.deactivateAccount", 500 + base 501 + )) 502 + .header("Authorization", format!("Bearer {}", token)) 503 + .json(&json!({})) 504 + .send() 505 + .await 506 + .unwrap(); 507 + assert_eq!(deactivate.status(), 200); 508 + 509 + let prefs = json!({ 510 + "preferences": [ 511 + { 512 + "$type": "app.bsky.actor.defs#adultContentPref", 513 + "enabled": true 514 + } 515 + ] 516 + }); 517 + let put_resp = client 518 + .post(format!("{}/xrpc/app.bsky.actor.putPreferences", base)) 519 + .header("Authorization", format!("Bearer {}", token)) 520 + .json(&prefs) 521 + .send() 522 + .await 523 + .unwrap(); 524 + assert_eq!( 525 + put_resp.status(), 526 + 200, 527 + "Deactivated account should still be able to put preferences" 528 + ); 529 + 530 + let get_resp = client 531 + .get(format!("{}/xrpc/app.bsky.actor.getPreferences", base)) 532 + .header("Authorization", format!("Bearer {}", token)) 533 + .send() 534 + .await 535 + .unwrap(); 536 + assert_eq!(get_resp.status(), 200); 537 + let body: Value = get_resp.json().await.unwrap(); 538 + let prefs_arr = body["preferences"].as_array().unwrap(); 539 + assert_eq!(prefs_arr.len(), 1); 540 + }