tangled
alpha
login
or
join now
tranquil.farm
/
tranquil-pds
156
fork
atom
Our Personal Data Server from scratch!
tranquil.farm
oauth
atproto
pds
rust
postgresql
objectstorage
fun
156
fork
atom
overview
issues
24
pulls
2
pipelines
fix: match ref pds permission-levels for some endpoints
lewis.moe
1 month ago
2317df45
cc4769d4
+216
-110
14 changed files
expand all
collapse all
unified
split
crates
tranquil-pds
src
api
actor
preferences.rs
error.rs
identity
plc
request.rs
sign.rs
submit.rs
server
account_status.rs
app_password.rs
email.rs
invite.rs
session.rs
auth
extractor.rs
mod.rs
tests
actor.rs
auth_extractor.rs
+3
-3
crates/tranquil-pds/src/api/actor/preferences.rs
···
1
use crate::api::error::ApiError;
2
-
use crate::auth::{Active, Auth};
3
use crate::state::AppState;
4
use axum::{
5
Json,
···
32
pub struct GetPreferencesOutput {
33
pub preferences: Vec<Value>,
34
}
35
-
pub async fn get_preferences(State(state): State<AppState>, auth: Auth<Active>) -> Response {
36
let has_full_access = auth.permissions().has_full_access();
37
let user_id: uuid::Uuid = match state.user_repo.get_id_by_did(&auth.did).await {
38
Ok(Some(id)) => id,
···
89
}
90
pub async fn put_preferences(
91
State(state): State<AppState>,
92
-
auth: Auth<Active>,
93
Json(input): Json<PutPreferencesInput>,
94
) -> Response {
95
let has_full_access = auth.permissions().has_full_access();
···
1
use crate::api::error::ApiError;
2
+
use crate::auth::{Auth, NotTakendown, Permissive};
3
use crate::state::AppState;
4
use axum::{
5
Json,
···
32
pub struct GetPreferencesOutput {
33
pub preferences: Vec<Value>,
34
}
35
+
pub async fn get_preferences(State(state): State<AppState>, auth: Auth<Permissive>) -> Response {
36
let has_full_access = auth.permissions().has_full_access();
37
let user_id: uuid::Uuid = match state.user_repo.get_id_by_did(&auth.did).await {
38
Ok(Some(id)) => id,
···
89
}
90
pub async fn put_preferences(
91
State(state): State<AppState>,
92
+
auth: Auth<NotTakendown>,
93
Json(input): Json<PutPreferencesInput>,
94
) -> Response {
95
let has_full_access = auth.permissions().has_full_access();
-1
crates/tranquil-pds/src/api/error.rs
···
546
crate::auth::extractor::AuthError::ServiceAuthNotAllowed => Self::AuthenticationFailed(
547
Some("Service authentication not allowed for this endpoint".to_string()),
548
),
549
-
crate::auth::extractor::AuthError::SigningKeyRequired => Self::InvalidSigningKey,
550
crate::auth::extractor::AuthError::InsufficientScope(msg) => {
551
Self::InsufficientScope(Some(msg))
552
}
···
546
crate::auth::extractor::AuthError::ServiceAuthNotAllowed => Self::AuthenticationFailed(
547
Some("Service authentication not allowed for this endpoint".to_string()),
548
),
0
549
crate::auth::extractor::AuthError::InsufficientScope(msg) => {
550
Self::InsufficientScope(Some(msg))
551
}
+2
-2
crates/tranquil-pds/src/api/identity/plc/request.rs
···
1
use crate::api::EmptyResponse;
2
use crate::api::error::ApiError;
3
-
use crate::auth::{Auth, NotTakendown};
4
use crate::state::AppState;
5
use axum::{
6
extract::State,
···
15
16
pub async fn request_plc_operation_signature(
17
State(state): State<AppState>,
18
-
auth: Auth<NotTakendown>,
19
) -> Result<Response, ApiError> {
20
if let Err(e) = crate::auth::scope_check::check_identity_scope(
21
auth.is_oauth(),
···
1
use crate::api::EmptyResponse;
2
use crate::api::error::ApiError;
3
+
use crate::auth::{Auth, Permissive};
4
use crate::state::AppState;
5
use axum::{
6
extract::State,
···
15
16
pub async fn request_plc_operation_signature(
17
State(state): State<AppState>,
18
+
auth: Auth<Permissive>,
19
) -> Result<Response, ApiError> {
20
if let Err(e) = crate::auth::scope_check::check_identity_scope(
21
auth.is_oauth(),
+2
-2
crates/tranquil-pds/src/api/identity/plc/sign.rs
···
1
use crate::api::ApiError;
2
-
use crate::auth::{Auth, NotTakendown};
3
use crate::circuit_breaker::with_circuit_breaker;
4
use crate::plc::{PlcClient, PlcError, PlcService, create_update_op, sign_operation};
5
use crate::state::AppState;
···
40
41
pub async fn sign_plc_operation(
42
State(state): State<AppState>,
43
-
auth: Auth<NotTakendown>,
44
Json(input): Json<SignPlcOperationInput>,
45
) -> Result<Response, ApiError> {
46
if let Err(e) = crate::auth::scope_check::check_identity_scope(
···
1
use crate::api::ApiError;
2
+
use crate::auth::{Auth, Permissive};
3
use crate::circuit_breaker::with_circuit_breaker;
4
use crate::plc::{PlcClient, PlcError, PlcService, create_update_op, sign_operation};
5
use crate::state::AppState;
···
40
41
pub async fn sign_plc_operation(
42
State(state): State<AppState>,
43
+
auth: Auth<Permissive>,
44
Json(input): Json<SignPlcOperationInput>,
45
) -> Result<Response, ApiError> {
46
if let Err(e) = crate::auth::scope_check::check_identity_scope(
+2
-2
crates/tranquil-pds/src/api/identity/plc/submit.rs
···
1
use crate::api::{ApiError, EmptyResponse};
2
-
use crate::auth::{Auth, NotTakendown};
3
use crate::circuit_breaker::with_circuit_breaker;
4
use crate::plc::{PlcClient, signing_key_to_did_key, validate_plc_operation};
5
use crate::state::AppState;
···
20
21
pub async fn submit_plc_operation(
22
State(state): State<AppState>,
23
-
auth: Auth<NotTakendown>,
24
Json(input): Json<SubmitPlcOperationInput>,
25
) -> Result<Response, ApiError> {
26
if let Err(e) = crate::auth::scope_check::check_identity_scope(
···
1
use crate::api::{ApiError, EmptyResponse};
2
+
use crate::auth::{Auth, Permissive};
3
use crate::circuit_breaker::with_circuit_breaker;
4
use crate::plc::{PlcClient, signing_key_to_did_key, validate_plc_operation};
5
use crate::state::AppState;
···
20
21
pub async fn submit_plc_operation(
22
State(state): State<AppState>,
23
+
auth: Auth<Permissive>,
24
Json(input): Json<SubmitPlcOperationInput>,
25
) -> Result<Response, ApiError> {
26
if let Err(e) = crate::auth::scope_check::check_identity_scope(
+4
-4
crates/tranquil-pds/src/api/server/account_status.rs
···
1
use crate::api::EmptyResponse;
2
use crate::api::error::ApiError;
3
-
use crate::auth::{Active, Auth, NotTakendown};
4
use crate::cache::Cache;
5
use crate::plc::PlcClient;
6
use crate::state::AppState;
···
41
42
pub async fn check_account_status(
43
State(state): State<AppState>,
44
-
auth: Auth<NotTakendown>,
45
) -> Result<Response, ApiError> {
46
let did = &auth.did;
47
let user_id = state
···
306
307
pub async fn activate_account(
308
State(state): State<AppState>,
309
-
auth: Auth<NotTakendown>,
310
) -> Result<Response, ApiError> {
311
info!("[MIGRATION] activateAccount called");
312
info!(
···
470
471
pub async fn deactivate_account(
472
State(state): State<AppState>,
473
-
auth: Auth<Active>,
474
Json(input): Json<DeactivateAccountInput>,
475
) -> Result<Response, ApiError> {
476
if let Err(e) = crate::auth::scope_check::check_account_scope(
···
1
use crate::api::EmptyResponse;
2
use crate::api::error::ApiError;
3
+
use crate::auth::{Auth, NotTakendown, Permissive};
4
use crate::cache::Cache;
5
use crate::plc::PlcClient;
6
use crate::state::AppState;
···
41
42
pub async fn check_account_status(
43
State(state): State<AppState>,
44
+
auth: Auth<Permissive>,
45
) -> Result<Response, ApiError> {
46
let did = &auth.did;
47
let user_id = state
···
306
307
pub async fn activate_account(
308
State(state): State<AppState>,
309
+
auth: Auth<Permissive>,
310
) -> Result<Response, ApiError> {
311
info!("[MIGRATION] activateAccount called");
312
info!(
···
470
471
pub async fn deactivate_account(
472
State(state): State<AppState>,
473
+
auth: Auth<Permissive>,
474
Json(input): Json<DeactivateAccountInput>,
475
) -> Result<Response, ApiError> {
476
if let Err(e) = crate::auth::scope_check::check_account_scope(
+4
-4
crates/tranquil-pds/src/api/server/app_password.rs
···
1
use crate::api::EmptyResponse;
2
use crate::api::error::ApiError;
3
-
use crate::auth::{Active, Auth, generate_app_password};
4
use crate::delegation::{DelegationActionType, intersect_scopes};
5
use crate::state::{AppState, RateLimitKind};
6
use axum::{
···
33
34
pub async fn list_app_passwords(
35
State(state): State<AppState>,
36
-
auth: Auth<Active>,
37
) -> Result<Response, ApiError> {
38
let user = state
39
.user_repo
···
90
pub async fn create_app_password(
91
State(state): State<AppState>,
92
headers: HeaderMap,
93
-
auth: Auth<Active>,
94
Json(input): Json<CreateAppPasswordInput>,
95
) -> Result<Response, ApiError> {
96
let client_ip = crate::rate_limit::extract_client_ip(&headers, None);
···
227
228
pub async fn revoke_app_password(
229
State(state): State<AppState>,
230
-
auth: Auth<Active>,
231
Json(input): Json<RevokeAppPasswordInput>,
232
) -> Result<Response, ApiError> {
233
let user = state
···
1
use crate::api::EmptyResponse;
2
use crate::api::error::ApiError;
3
+
use crate::auth::{Auth, NotTakendown, Permissive, generate_app_password};
4
use crate::delegation::{DelegationActionType, intersect_scopes};
5
use crate::state::{AppState, RateLimitKind};
6
use axum::{
···
33
34
pub async fn list_app_passwords(
35
State(state): State<AppState>,
36
+
auth: Auth<Permissive>,
37
) -> Result<Response, ApiError> {
38
let user = state
39
.user_repo
···
90
pub async fn create_app_password(
91
State(state): State<AppState>,
92
headers: HeaderMap,
93
+
auth: Auth<NotTakendown>,
94
Json(input): Json<CreateAppPasswordInput>,
95
) -> Result<Response, ApiError> {
96
let client_ip = crate::rate_limit::extract_client_ip(&headers, None);
···
227
228
pub async fn revoke_app_password(
229
State(state): State<AppState>,
230
+
auth: Auth<Permissive>,
231
Json(input): Json<RevokeAppPasswordInput>,
232
) -> Result<Response, ApiError> {
233
let user = state
+5
-5
crates/tranquil-pds/src/api/server/email.rs
···
1
use crate::api::error::ApiError;
2
use crate::api::{EmptyResponse, TokenRequiredResponse, VerifiedResponse};
3
-
use crate::auth::{Active, Auth};
4
use crate::state::{AppState, RateLimitKind};
5
use axum::{
6
Json,
···
45
pub async fn request_email_update(
46
State(state): State<AppState>,
47
headers: axum::http::HeaderMap,
48
-
auth: Auth<Active>,
49
input: Option<Json<RequestEmailUpdateInput>>,
50
) -> Result<Response, ApiError> {
51
let client_ip = crate::rate_limit::extract_client_ip(&headers, None);
···
140
pub async fn confirm_email(
141
State(state): State<AppState>,
142
headers: axum::http::HeaderMap,
143
-
auth: Auth<Active>,
144
Json(input): Json<ConfirmEmailInput>,
145
) -> Result<Response, ApiError> {
146
let client_ip = crate::rate_limit::extract_client_ip(&headers, None);
···
233
234
pub async fn update_email(
235
State(state): State<AppState>,
236
-
auth: Auth<Active>,
237
Json(input): Json<UpdateEmailInput>,
238
) -> Result<Response, ApiError> {
239
if let Err(e) = crate::auth::scope_check::check_account_scope(
···
500
pub async fn check_email_update_status(
501
State(state): State<AppState>,
502
headers: axum::http::HeaderMap,
503
-
auth: Auth<Active>,
504
) -> Result<Response, ApiError> {
505
let client_ip = crate::rate_limit::extract_client_ip(&headers, None);
506
if !state
···
1
use crate::api::error::ApiError;
2
use crate::api::{EmptyResponse, TokenRequiredResponse, VerifiedResponse};
3
+
use crate::auth::{Auth, NotTakendown};
4
use crate::state::{AppState, RateLimitKind};
5
use axum::{
6
Json,
···
45
pub async fn request_email_update(
46
State(state): State<AppState>,
47
headers: axum::http::HeaderMap,
48
+
auth: Auth<NotTakendown>,
49
input: Option<Json<RequestEmailUpdateInput>>,
50
) -> Result<Response, ApiError> {
51
let client_ip = crate::rate_limit::extract_client_ip(&headers, None);
···
140
pub async fn confirm_email(
141
State(state): State<AppState>,
142
headers: axum::http::HeaderMap,
143
+
auth: Auth<NotTakendown>,
144
Json(input): Json<ConfirmEmailInput>,
145
) -> Result<Response, ApiError> {
146
let client_ip = crate::rate_limit::extract_client_ip(&headers, None);
···
233
234
pub async fn update_email(
235
State(state): State<AppState>,
236
+
auth: Auth<NotTakendown>,
237
Json(input): Json<UpdateEmailInput>,
238
) -> Result<Response, ApiError> {
239
if let Err(e) = crate::auth::scope_check::check_account_scope(
···
500
pub async fn check_email_update_status(
501
State(state): State<AppState>,
502
headers: axum::http::HeaderMap,
503
+
auth: Auth<NotTakendown>,
504
) -> Result<Response, ApiError> {
505
let client_ip = crate::rate_limit::extract_client_ip(&headers, None);
506
if !state
+2
-2
crates/tranquil-pds/src/api/server/invite.rs
···
1
use crate::api::ApiError;
2
-
use crate::auth::{Active, Admin, Auth};
3
use crate::state::AppState;
4
use crate::types::Did;
5
use axum::{
···
193
194
pub async fn get_account_invite_codes(
195
State(state): State<AppState>,
196
-
auth: Auth<Active>,
197
axum::extract::Query(params): axum::extract::Query<GetAccountInviteCodesParams>,
198
) -> Result<Response, ApiError> {
199
let include_used = params.include_used.unwrap_or(true);
···
1
use crate::api::ApiError;
2
+
use crate::auth::{Admin, Auth, NotTakendown};
3
use crate::state::AppState;
4
use crate::types::Did;
5
use axum::{
···
193
194
pub async fn get_account_invite_codes(
195
State(state): State<AppState>,
196
+
auth: Auth<NotTakendown>,
197
axum::extract::Query(params): axum::extract::Query<GetAccountInviteCodesParams>,
198
) -> Result<Response, ApiError> {
199
let include_used = params.include_used.unwrap_or(true);
+2
-2
crates/tranquil-pds/src/api/server/session.rs
···
1
use crate::api::error::ApiError;
2
use crate::api::{EmptyResponse, SuccessResponse};
3
-
use crate::auth::{Active, Auth, NotTakendown};
4
use crate::state::{AppState, RateLimitKind};
5
use crate::types::{AccountState, Did, Handle, PlainPassword};
6
use axum::{
···
279
280
pub async fn get_session(
281
State(state): State<AppState>,
282
-
auth: Auth<NotTakendown>,
283
) -> Result<Response, ApiError> {
284
let permissions = auth.permissions();
285
let can_read_email = permissions.allows_email_read();
···
1
use crate::api::error::ApiError;
2
use crate::api::{EmptyResponse, SuccessResponse};
3
+
use crate::auth::{Active, Auth, Permissive};
4
use crate::state::{AppState, RateLimitKind};
5
use crate::types::{AccountState, Did, Handle, PlainPassword};
6
use axum::{
···
279
280
pub async fn get_session(
281
State(state): State<AppState>,
282
+
auth: Auth<Permissive>,
283
) -> Result<Response, ApiError> {
284
let permissions = auth.permissions();
285
let can_read_email = permissions.allows_email_read();
+22
-81
crates/tranquil-pds/src/auth/extractor.rs
···
27
AccountTakedown,
28
AdminRequired,
29
ServiceAuthNotAllowed,
30
-
SigningKeyRequired,
31
InsufficientScope(String),
32
OAuthExpiredToken(String),
33
UseDpopNonce(String),
···
430
}
431
}
432
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
433
pub enum AuthAny<P: AuthPolicy = Active> {
434
User(Auth<P>),
435
Service(ServiceAuth),
···
514
Err(AuthError::MissingToken) => Ok(None),
515
Err(e) => Err(e),
516
}
517
-
}
518
-
}
519
-
520
-
pub struct SigningAuth<P: AuthPolicy = Active> {
521
-
pub did: Did,
522
-
pub key_bytes: Vec<u8>,
523
-
pub is_admin: bool,
524
-
pub status: AccountStatus,
525
-
pub scope: Option<String>,
526
-
pub controller_did: Option<Did>,
527
-
is_oauth: bool,
528
-
_policy: PhantomData<P>,
529
-
}
530
-
531
-
impl<P: AuthPolicy> SigningAuth<P> {
532
-
pub fn needs_scope_check(&self) -> bool {
533
-
self.is_oauth
534
-
}
535
-
536
-
pub fn permissions(&self) -> ScopePermissions {
537
-
if let Some(ref scope) = self.scope
538
-
&& scope != super::SCOPE_ACCESS
539
-
{
540
-
return ScopePermissions::from_scope_string(Some(scope));
541
-
}
542
-
if !self.is_oauth {
543
-
return ScopePermissions::from_scope_string(Some("atproto"));
544
-
}
545
-
ScopePermissions::from_scope_string(self.scope.as_deref())
546
-
}
547
-
548
-
#[allow(clippy::result_large_err)]
549
-
pub fn check_repo_scope(&self, action: RepoAction, collection: &str) -> Result<(), Response> {
550
-
if !self.needs_scope_check() {
551
-
return Ok(());
552
-
}
553
-
self.permissions()
554
-
.assert_repo(action, collection)
555
-
.map_err(|e| ApiError::InsufficientScope(Some(e.to_string())).into_response())
556
-
}
557
-
}
558
-
559
-
impl<P: AuthPolicy> FromRequestParts<AppState> for SigningAuth<P> {
560
-
type Rejection = AuthError;
561
-
562
-
async fn from_request_parts(
563
-
parts: &mut Parts,
564
-
state: &AppState,
565
-
) -> Result<Self, Self::Rejection> {
566
-
let user = extract_user_auth_internal(parts, state).await?;
567
-
P::validate(&user)?;
568
-
569
-
let key_bytes = match user.key_bytes {
570
-
Some(kb) => kb,
571
-
None => {
572
-
let user_with_key = state
573
-
.user_repo
574
-
.get_with_key_by_did(&user.did)
575
-
.await
576
-
.ok()
577
-
.flatten()
578
-
.ok_or(AuthError::SigningKeyRequired)?;
579
-
crate::config::decrypt_key(
580
-
&user_with_key.key_bytes,
581
-
user_with_key.encryption_version,
582
-
)
583
-
.map_err(|_| AuthError::SigningKeyRequired)?
584
-
}
585
-
};
586
-
587
-
Ok(SigningAuth {
588
-
did: user.did,
589
-
key_bytes,
590
-
is_admin: user.is_admin,
591
-
status: user.status,
592
-
scope: user.scope,
593
-
controller_did: user.controller_did,
594
-
is_oauth: user.auth_source.is_oauth(),
595
-
_policy: PhantomData,
596
-
})
597
}
598
}
599
···
27
AccountTakedown,
28
AdminRequired,
29
ServiceAuthNotAllowed,
0
30
InsufficientScope(String),
31
OAuthExpiredToken(String),
32
UseDpopNonce(String),
···
429
}
430
}
431
432
+
impl OptionalFromRequestParts<AppState> for ServiceAuth {
433
+
type Rejection = AuthError;
434
+
435
+
async fn from_request_parts(
436
+
parts: &mut Parts,
437
+
state: &AppState,
438
+
) -> Result<Option<Self>, Self::Rejection> {
439
+
match extract_auth_internal(parts, state).await {
440
+
Ok(ExtractedAuth::Service(claims)) => {
441
+
let did: Did = claims
442
+
.iss
443
+
.parse()
444
+
.map_err(|_| AuthError::AuthenticationFailed)?;
445
+
Ok(Some(ServiceAuth { did, claims }))
446
+
}
447
+
Ok(ExtractedAuth::User(_)) => Err(AuthError::AuthenticationFailed),
448
+
Err(AuthError::MissingToken) => Ok(None),
449
+
Err(e) => Err(e),
450
+
}
451
+
}
452
+
}
453
+
454
pub enum AuthAny<P: AuthPolicy = Active> {
455
User(Auth<P>),
456
Service(ServiceAuth),
···
535
Err(AuthError::MissingToken) => Ok(None),
536
Err(e) => Err(e),
537
}
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
538
}
539
}
540
+1
-2
crates/tranquil-pds/src/auth/mod.rs
···
18
19
pub use extractor::{
20
Active, Admin, AnyUser, Auth, AuthAny, AuthError, AuthPolicy, ExtractedToken, NotTakendown,
21
-
Permissive, ServiceAuth, SigningAuth, extract_auth_token_from_header,
22
-
extract_bearer_token_from_header,
23
};
24
pub use service::{ServiceTokenClaims, ServiceTokenVerifier, is_service_token};
25
···
18
19
pub use extractor::{
20
Active, Admin, AnyUser, Auth, AuthAny, AuthError, AuthPolicy, ExtractedToken, NotTakendown,
21
+
Permissive, ServiceAuth, extract_auth_token_from_header, extract_bearer_token_from_header,
0
22
};
23
pub use service::{ServiceTokenClaims, ServiceTokenVerifier, is_service_token};
24
+102
crates/tranquil-pds/tests/actor.rs
···
436
assert_eq!(declared_age["isOverAge16"], false);
437
assert_eq!(declared_age["isOverAge18"], false);
438
}
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
436
assert_eq!(declared_age["isOverAge16"], false);
437
assert_eq!(declared_age["isOverAge18"], false);
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
+
}
+65
crates/tranquil-pds/tests/auth_extractor.rs
···
581
let proof = format!("{}.{}", signing_input, sig_b64);
582
(jwk, proof)
583
}
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
581
let proof = format!("{}.{}", signing_input, sig_b64);
582
(jwk, proof)
583
}
584
+
585
+
#[tokio::test]
586
+
async fn test_optional_service_auth_extractor_behavior() {
587
+
let url = base_url().await;
588
+
let http_client = client();
589
+
let (access_jwt, did) = create_account_and_login(&http_client).await;
590
+
591
+
let service_auth_res = http_client
592
+
.get(format!("{}/xrpc/com.atproto.server.getServiceAuth", url))
593
+
.bearer_auth(&access_jwt)
594
+
.query(&[("aud", "did:web:test.example")])
595
+
.send()
596
+
.await
597
+
.unwrap();
598
+
assert_eq!(service_auth_res.status(), StatusCode::OK);
599
+
let service_body: Value = service_auth_res.json().await.unwrap();
600
+
let service_token = service_body["token"].as_str().unwrap();
601
+
602
+
let no_auth_res = http_client
603
+
.get(format!(
604
+
"{}/xrpc/com.atproto.sync.getBlob?did={}&cid=bafyreifakecidfornowfakecidfornow1234567",
605
+
url, did
606
+
))
607
+
.send()
608
+
.await
609
+
.unwrap();
610
+
assert!(
611
+
no_auth_res.status() == StatusCode::NOT_FOUND
612
+
|| no_auth_res.status() == StatusCode::BAD_REQUEST,
613
+
"getBlob with no auth should reach handler (AuthAny optional path) - got {}",
614
+
no_auth_res.status()
615
+
);
616
+
617
+
let service_auth_blob_res = http_client
618
+
.get(format!(
619
+
"{}/xrpc/com.atproto.sync.getBlob?did={}&cid=bafyreifakecidfornowfakecidfornow1234567",
620
+
url, did
621
+
))
622
+
.bearer_auth(service_token)
623
+
.send()
624
+
.await
625
+
.unwrap();
626
+
assert!(
627
+
service_auth_blob_res.status() == StatusCode::NOT_FOUND
628
+
|| service_auth_blob_res.status() == StatusCode::BAD_REQUEST,
629
+
"getBlob with service auth should reach handler (AuthAny service path) - got {}",
630
+
service_auth_blob_res.status()
631
+
);
632
+
633
+
let user_auth_blob_res = http_client
634
+
.get(format!(
635
+
"{}/xrpc/com.atproto.sync.getBlob?did={}&cid=bafyreifakecidfornowfakecidfornow1234567",
636
+
url, did
637
+
))
638
+
.bearer_auth(&access_jwt)
639
+
.send()
640
+
.await
641
+
.unwrap();
642
+
assert!(
643
+
user_auth_blob_res.status() == StatusCode::NOT_FOUND
644
+
|| user_auth_blob_res.status() == StatusCode::BAD_REQUEST,
645
+
"getBlob with user auth should reach handler (AuthAny user path) - got {}",
646
+
user_auth_blob_res.status()
647
+
);
648
+
}