tangled
alpha
login
or
join now
tranquil.farm
/
tranquil-pds
149
fork
atom
Our Personal Data Server from scratch!
tranquil.farm
oauth
atproto
pds
rust
postgresql
objectstorage
fun
149
fork
atom
overview
issues
19
pulls
2
pipelines
fix: match ref pds permission-levels for some endpoints
lewis.moe
1 month ago
7542b3f8
cc4769d4
+128
-26
10 changed files
expand all
collapse all
unified
split
crates
tranquil-pds
src
api
actor
preferences.rs
identity
plc
request.rs
sign.rs
submit.rs
server
account_status.rs
app_password.rs
email.rs
invite.rs
session.rs
tests
actor.rs
+3
-3
crates/tranquil-pds/src/api/actor/preferences.rs
···
1
1
use crate::api::error::ApiError;
2
2
-
use crate::auth::{Active, Auth};
2
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
35
-
pub async fn get_preferences(State(state): State<AppState>, auth: Auth<Active>) -> Response {
35
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
92
-
auth: Auth<Active>,
92
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
3
-
use crate::auth::{Auth, NotTakendown};
3
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
18
-
auth: Auth<NotTakendown>,
18
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
2
-
use crate::auth::{Auth, NotTakendown};
2
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
43
-
auth: Auth<NotTakendown>,
43
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
2
-
use crate::auth::{Auth, NotTakendown};
2
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
23
-
auth: Auth<NotTakendown>,
23
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
3
-
use crate::auth::{Active, Auth, NotTakendown};
3
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
44
-
auth: Auth<NotTakendown>,
44
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
309
-
auth: Auth<NotTakendown>,
309
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
473
-
auth: Auth<Active>,
473
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
3
-
use crate::auth::{Active, Auth, generate_app_password};
3
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
36
-
auth: Auth<Active>,
36
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
93
-
auth: Auth<Active>,
93
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
230
-
auth: Auth<Active>,
230
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
3
-
use crate::auth::{Active, Auth};
3
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
48
-
auth: Auth<Active>,
48
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
143
-
auth: Auth<Active>,
143
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
236
-
auth: Auth<Active>,
236
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
503
-
auth: Auth<Active>,
503
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
2
-
use crate::auth::{Active, Admin, Auth};
2
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
196
-
auth: Auth<Active>,
196
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
3
-
use crate::auth::{Active, Auth, NotTakendown};
3
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
282
-
auth: Auth<NotTakendown>,
282
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
439
+
440
440
+
#[tokio::test]
441
441
+
async fn test_deactivated_account_can_get_preferences() {
442
442
+
let client = client();
443
443
+
let base = base_url().await;
444
444
+
let (token, _did) = create_account_and_login(&client).await;
445
445
+
446
446
+
let prefs = json!({
447
447
+
"preferences": [
448
448
+
{
449
449
+
"$type": "app.bsky.actor.defs#adultContentPref",
450
450
+
"enabled": true
451
451
+
}
452
452
+
]
453
453
+
});
454
454
+
let put_resp = client
455
455
+
.post(format!("{}/xrpc/app.bsky.actor.putPreferences", base))
456
456
+
.header("Authorization", format!("Bearer {}", token))
457
457
+
.json(&prefs)
458
458
+
.send()
459
459
+
.await
460
460
+
.unwrap();
461
461
+
assert_eq!(put_resp.status(), 200);
462
462
+
463
463
+
let deactivate = client
464
464
+
.post(format!(
465
465
+
"{}/xrpc/com.atproto.server.deactivateAccount",
466
466
+
base
467
467
+
))
468
468
+
.header("Authorization", format!("Bearer {}", token))
469
469
+
.json(&json!({}))
470
470
+
.send()
471
471
+
.await
472
472
+
.unwrap();
473
473
+
assert_eq!(deactivate.status(), 200);
474
474
+
475
475
+
let get_resp = client
476
476
+
.get(format!("{}/xrpc/app.bsky.actor.getPreferences", base))
477
477
+
.header("Authorization", format!("Bearer {}", token))
478
478
+
.send()
479
479
+
.await
480
480
+
.unwrap();
481
481
+
assert_eq!(
482
482
+
get_resp.status(),
483
483
+
200,
484
484
+
"Deactivated account should still be able to get preferences"
485
485
+
);
486
486
+
let body: Value = get_resp.json().await.unwrap();
487
487
+
let prefs_arr = body["preferences"].as_array().unwrap();
488
488
+
assert_eq!(prefs_arr.len(), 1);
489
489
+
}
490
490
+
491
491
+
#[tokio::test]
492
492
+
async fn test_deactivated_account_can_put_preferences() {
493
493
+
let client = client();
494
494
+
let base = base_url().await;
495
495
+
let (token, _did) = create_account_and_login(&client).await;
496
496
+
497
497
+
let deactivate = client
498
498
+
.post(format!(
499
499
+
"{}/xrpc/com.atproto.server.deactivateAccount",
500
500
+
base
501
501
+
))
502
502
+
.header("Authorization", format!("Bearer {}", token))
503
503
+
.json(&json!({}))
504
504
+
.send()
505
505
+
.await
506
506
+
.unwrap();
507
507
+
assert_eq!(deactivate.status(), 200);
508
508
+
509
509
+
let prefs = json!({
510
510
+
"preferences": [
511
511
+
{
512
512
+
"$type": "app.bsky.actor.defs#adultContentPref",
513
513
+
"enabled": true
514
514
+
}
515
515
+
]
516
516
+
});
517
517
+
let put_resp = client
518
518
+
.post(format!("{}/xrpc/app.bsky.actor.putPreferences", base))
519
519
+
.header("Authorization", format!("Bearer {}", token))
520
520
+
.json(&prefs)
521
521
+
.send()
522
522
+
.await
523
523
+
.unwrap();
524
524
+
assert_eq!(
525
525
+
put_resp.status(),
526
526
+
200,
527
527
+
"Deactivated account should still be able to put preferences"
528
528
+
);
529
529
+
530
530
+
let get_resp = client
531
531
+
.get(format!("{}/xrpc/app.bsky.actor.getPreferences", base))
532
532
+
.header("Authorization", format!("Bearer {}", token))
533
533
+
.send()
534
534
+
.await
535
535
+
.unwrap();
536
536
+
assert_eq!(get_resp.status(), 200);
537
537
+
let body: Value = get_resp.json().await.unwrap();
538
538
+
let prefs_arr = body["preferences"].as_array().unwrap();
539
539
+
assert_eq!(prefs_arr.len(), 1);
540
540
+
}