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: some small bugs
lewis.moe
1 month ago
14c93541
a9a7159d
+83
-29
2 changed files
expand all
collapse all
unified
split
crates
tranquil-pds
src
api
proxy.rs
tranquil-scopes
src
parser.rs
+52
-26
crates/tranquil-pds/src/api/proxy.rs
···
218
218
) {
219
219
let token = extracted.token;
220
220
let dpop_proof = crate::util::get_header_str(&headers, "DPoP");
221
221
-
let http_uri = crate::util::build_full_url(&uri.to_string());
221
221
+
let http_uri = crate::util::build_full_url(&format!("/xrpc{}", uri));
222
222
223
223
match crate::auth::validate_token_with_dpop(
224
224
state.user_repo.as_ref(),
···
243
243
return e;
244
244
}
245
245
246
246
-
if let Some(key_bytes) = auth_user.key_bytes {
247
247
-
match crate::auth::create_service_token(
248
248
-
&auth_user.did,
249
249
-
&resolved.did,
250
250
-
method,
251
251
-
&key_bytes,
252
252
-
) {
253
253
-
Ok(new_token) => {
254
254
-
if let Ok(val) =
255
255
-
axum::http::HeaderValue::from_str(&format!("Bearer {}", new_token))
256
256
-
{
257
257
-
auth_header_val = Some(val);
246
246
+
let key_bytes = match auth_user.key_bytes {
247
247
+
Some(kb) => kb,
248
248
+
None => {
249
249
+
match state.user_repo.get_user_info_by_did(&auth_user.did).await {
250
250
+
Ok(Some(info)) => match info.key_bytes {
251
251
+
Some(key_bytes_enc) => {
252
252
+
match crate::config::decrypt_key(
253
253
+
&key_bytes_enc,
254
254
+
info.encryption_version,
255
255
+
) {
256
256
+
Ok(key) => key,
257
257
+
Err(e) => {
258
258
+
error!(error = ?e, "Failed to decrypt user key for proxy");
259
259
+
return ApiError::UpstreamFailure.into_response();
260
260
+
}
261
261
+
}
262
262
+
}
263
263
+
None => {
264
264
+
warn!(did = %auth_user.did, "User has no signing key for proxy");
265
265
+
return ApiError::UpstreamFailure.into_response();
266
266
+
}
267
267
+
},
268
268
+
Ok(None) => {
269
269
+
warn!(did = %auth_user.did, "User not found for proxy service auth");
270
270
+
return ApiError::UpstreamFailure.into_response();
271
271
+
}
272
272
+
Err(e) => {
273
273
+
error!(error = ?e, "DB error fetching user key for proxy");
274
274
+
return ApiError::UpstreamFailure.into_response();
258
275
}
259
276
}
260
260
-
Err(e) => {
261
261
-
warn!("Failed to create service token: {:?}", e);
277
277
+
}
278
278
+
};
279
279
+
280
280
+
match crate::auth::create_service_token(
281
281
+
&auth_user.did,
282
282
+
&resolved.did,
283
283
+
method,
284
284
+
&key_bytes,
285
285
+
) {
286
286
+
Ok(new_token) => {
287
287
+
if let Ok(val) =
288
288
+
axum::http::HeaderValue::from_str(&format!("Bearer {}", new_token))
289
289
+
{
290
290
+
auth_header_val = Some(val);
262
291
}
263
292
}
293
293
+
Err(e) => {
294
294
+
error!("Failed to create service token: {:?}", e);
295
295
+
return ApiError::UpstreamFailure.into_response();
296
296
+
}
264
297
}
265
298
}
266
299
Err(e) => {
267
300
info!(error = ?e, "Proxy token validation failed, returning error to client");
268
268
-
if matches!(
269
269
-
e,
270
270
-
crate::auth::TokenValidationError::OAuthTokenExpired
271
271
-
| crate::auth::TokenValidationError::TokenExpired
272
272
-
) {
273
273
-
let mut response = ApiError::from(e).into_response();
274
274
-
let nonce = crate::oauth::verify::generate_dpop_nonce();
275
275
-
if let Ok(nonce_val) = nonce.parse() {
276
276
-
response.headers_mut().insert("DPoP-Nonce", nonce_val);
277
277
-
}
278
278
-
return response;
301
301
+
let mut response = ApiError::from(e).into_response();
302
302
+
if let Ok(nonce_val) = crate::oauth::verify::generate_dpop_nonce().parse() {
303
303
+
response.headers_mut().insert("DPoP-Nonce", nonce_val);
279
304
}
305
305
+
return response;
280
306
}
281
307
}
282
308
}
+31
-3
crates/tranquil-scopes/src/parser.rs
···
142
142
.split('&')
143
143
.filter_map(|part| part.split_once('='))
144
144
.fold(HashMap::new(), |mut acc, (key, value)| {
145
145
-
acc.entry(key.to_string())
146
146
-
.or_default()
147
147
-
.push(value.to_string());
145
145
+
let decoded = urlencoding::decode(value)
146
146
+
.map(|s| s.into_owned())
147
147
+
.unwrap_or_else(|_| value.to_string());
148
148
+
acc.entry(key.to_string()).or_default().push(decoded);
148
149
acc
149
150
})
150
151
}
···
479
480
480
481
let scope4 = parse_scope("rpc:*?aud=did:web:api.bsky.app");
481
482
assert!(matches!(scope4, ParsedScope::Rpc(_)));
483
483
+
}
484
484
+
485
485
+
#[test]
486
486
+
fn test_url_encoded_aud_with_fragment() {
487
487
+
let scope =
488
488
+
parse_scope("include:app.bsky.authFullApp?aud=did:web:api.bsky.app%23bsky_appview");
489
489
+
match scope {
490
490
+
ParsedScope::Include(i) => {
491
491
+
assert_eq!(i.nsid, "app.bsky.authFullApp");
492
492
+
assert_eq!(i.aud, Some("did:web:api.bsky.app#bsky_appview".to_string()));
493
493
+
}
494
494
+
_ => panic!("Expected Include scope"),
495
495
+
}
496
496
+
497
497
+
let scope2 = parse_scope(
498
498
+
"rpc:com.atproto.moderation.createReport?aud=did:web:api.bsky.app%23bsky_appview",
499
499
+
);
500
500
+
match scope2 {
501
501
+
ParsedScope::Rpc(r) => {
502
502
+
assert_eq!(
503
503
+
r.lxm,
504
504
+
Some("com.atproto.moderation.createReport".to_string())
505
505
+
);
506
506
+
assert_eq!(r.aud, Some("did:web:api.bsky.app#bsky_appview".to_string()));
507
507
+
}
508
508
+
_ => panic!("Expected Rpc scope"),
509
509
+
}
482
510
}
483
511
}