Our Personal Data Server from scratch! tranquil.farm
pds rust database fun oauth atproto
237
fork

Configure Feed

Select the types of activity you want to include in your feed.

Better auth extractor use

+245 -277
+5 -32
src/api/actor/preferences.rs
··· 1 1 use crate::api::error::ApiError; 2 + use crate::auth::BearerAuthAllowDeactivated; 2 3 use crate::state::AppState; 3 4 use axum::{ 4 5 Json, ··· 33 34 } 34 35 pub async fn get_preferences( 35 36 State(state): State<AppState>, 36 - headers: axum::http::HeaderMap, 37 + auth: BearerAuthAllowDeactivated, 37 38 ) -> Response { 38 - let token = match crate::auth::extract_bearer_token_from_header( 39 - headers.get("Authorization").and_then(|h| h.to_str().ok()), 40 - ) { 41 - Some(t) => t, 42 - None => { 43 - return ApiError::AuthenticationRequired.into_response(); 44 - } 45 - }; 46 - let auth_user = 47 - match crate::auth::validate_bearer_token_allow_deactivated(&state.db, &token).await { 48 - Ok(user) => user, 49 - Err(_) => { 50 - return ApiError::AuthenticationFailed(None).into_response(); 51 - } 52 - }; 39 + let auth_user = auth.0; 53 40 let has_full_access = auth_user.permissions().has_full_access(); 54 41 let user_id: uuid::Uuid = 55 42 match sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", &*auth_user.did) ··· 117 104 } 118 105 pub async fn put_preferences( 119 106 State(state): State<AppState>, 120 - headers: axum::http::HeaderMap, 107 + auth: BearerAuthAllowDeactivated, 121 108 Json(input): Json<PutPreferencesInput>, 122 109 ) -> Response { 123 - let token = match crate::auth::extract_bearer_token_from_header( 124 - headers.get("Authorization").and_then(|h| h.to_str().ok()), 125 - ) { 126 - Some(t) => t, 127 - None => { 128 - return ApiError::AuthenticationRequired.into_response(); 129 - } 130 - }; 131 - let auth_user = 132 - match crate::auth::validate_bearer_token_allow_deactivated(&state.db, &token).await { 133 - Ok(user) => user, 134 - Err(_) => { 135 - return ApiError::AuthenticationFailed(None).into_response(); 136 - } 137 - }; 110 + let auth_user = auth.0; 138 111 let has_full_access = auth_user.permissions().has_full_access(); 139 112 let user_id: uuid::Uuid = 140 113 match sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", &*auth_user.did)
+17 -3
src/api/age_assurance.rs
··· 1 - use crate::auth::{extract_bearer_token_from_header, validate_bearer_token}; 1 + use crate::auth::{extract_auth_token_from_header, validate_token_with_dpop}; 2 2 use crate::state::AppState; 3 3 use axum::{ 4 4 Json, ··· 36 36 let auth_header = headers.get("Authorization").and_then(|h| h.to_str().ok()); 37 37 tracing::debug!(?auth_header, "age assurance: extracting token"); 38 38 39 - let token = extract_bearer_token_from_header(auth_header)?; 39 + let extracted = extract_auth_token_from_header(auth_header)?; 40 40 tracing::debug!("age assurance: got token, validating"); 41 41 42 - let auth_user = match validate_bearer_token(&state.db, &token).await { 42 + let dpop_proof = headers.get("DPoP").and_then(|h| h.to_str().ok()); 43 + let http_uri = "/"; 44 + 45 + let auth_user = match validate_token_with_dpop( 46 + &state.db, 47 + &extracted.token, 48 + extracted.is_dpop, 49 + dpop_proof, 50 + "GET", 51 + http_uri, 52 + false, 53 + false, 54 + ) 55 + .await 56 + { 43 57 Ok(user) => { 44 58 tracing::debug!(did = %user.did, "age assurance: validated user"); 45 59 user
+5 -4
src/api/identity/account.rs
··· 1 1 use super::did::verify_did_web; 2 2 use crate::api::error::ApiError; 3 3 use crate::api::repo::record::utils::create_signed_commit; 4 - use crate::auth::{ServiceTokenVerifier, extract_bearer_token_from_header, is_service_token}; 4 + use crate::auth::{ServiceTokenVerifier, is_service_token}; 5 5 use crate::plc::{PlcClient, create_genesis_operation, signing_key_to_did_key}; 6 6 use crate::state::{AppState, RateLimitKind}; 7 7 use crate::types::{Did, Handle, PlainPassword}; ··· 96 96 .into_response(); 97 97 } 98 98 99 - let migration_auth = if let Some(token) = 100 - extract_bearer_token_from_header(headers.get("Authorization").and_then(|h| h.to_str().ok())) 101 - { 99 + let migration_auth = if let Some(extracted) = crate::auth::extract_auth_token_from_header( 100 + headers.get("Authorization").and_then(|h| h.to_str().ok()), 101 + ) { 102 + let token = extracted.token; 102 103 if is_service_token(&token) { 103 104 let verifier = ServiceTokenVerifier::new(); 104 105 match verifier
+5 -26
src/api/identity/did.rs
··· 1 1 use crate::api::{ApiError, DidResponse, EmptyResponse}; 2 + use crate::auth::BearerAuthAllowDeactivated; 2 3 use crate::plc::signing_key_to_did_key; 3 4 use crate::state::AppState; 4 5 use axum::{ ··· 522 523 523 524 pub async fn get_recommended_did_credentials( 524 525 State(state): State<AppState>, 525 - headers: axum::http::HeaderMap, 526 + auth: BearerAuthAllowDeactivated, 526 527 ) -> Response { 527 - let token = match crate::auth::extract_bearer_token_from_header( 528 - headers.get("Authorization").and_then(|h| h.to_str().ok()), 529 - ) { 530 - Some(t) => t, 531 - None => { 532 - return ApiError::AuthenticationRequired.into_response(); 533 - } 534 - }; 535 - let auth_user = 536 - match crate::auth::validate_bearer_token_allow_deactivated(&state.db, &token).await { 537 - Ok(user) => user, 538 - Err(e) => return ApiError::from(e).into_response(), 539 - }; 528 + let auth_user = auth.0; 540 529 let user = match sqlx::query!( 541 530 "SELECT handle FROM users u JOIN user_keys k ON u.id = k.user_id WHERE u.did = $1", 542 531 &auth_user.did ··· 601 590 602 591 pub async fn update_handle( 603 592 State(state): State<AppState>, 604 - headers: axum::http::HeaderMap, 593 + auth: BearerAuthAllowDeactivated, 605 594 Json(input): Json<UpdateHandleInput>, 606 595 ) -> Response { 607 - let token = match crate::auth::extract_bearer_token_from_header( 608 - headers.get("Authorization").and_then(|h| h.to_str().ok()), 609 - ) { 610 - Some(t) => t, 611 - None => return ApiError::AuthenticationRequired.into_response(), 612 - }; 613 - let auth_user = 614 - match crate::auth::validate_bearer_token_allow_deactivated(&state.db, &token).await { 615 - Ok(user) => user, 616 - Err(e) => return ApiError::from(e).into_response(), 617 - }; 596 + let auth_user = auth.0; 618 597 if let Err(e) = crate::auth::scope_check::check_identity_scope( 619 598 auth_user.is_oauth, 620 599 auth_user.scope.as_deref(),
+3 -12
src/api/identity/plc/request.rs
··· 1 1 use crate::api::EmptyResponse; 2 2 use crate::api::error::ApiError; 3 + use crate::auth::BearerAuthAllowDeactivated; 3 4 use crate::state::AppState; 4 5 use axum::{ 5 6 extract::State, ··· 14 15 15 16 pub async fn request_plc_operation_signature( 16 17 State(state): State<AppState>, 17 - headers: axum::http::HeaderMap, 18 + auth: BearerAuthAllowDeactivated, 18 19 ) -> Response { 19 - let token = match crate::auth::extract_bearer_token_from_header( 20 - headers.get("Authorization").and_then(|h| h.to_str().ok()), 21 - ) { 22 - Some(t) => t, 23 - None => return ApiError::AuthenticationRequired.into_response(), 24 - }; 25 - let auth_user = 26 - match crate::auth::validate_bearer_token_allow_deactivated(&state.db, &token).await { 27 - Ok(user) => user, 28 - Err(e) => return ApiError::from(e).into_response(), 29 - }; 20 + let auth_user = auth.0; 30 21 if let Err(e) = crate::auth::scope_check::check_identity_scope( 31 22 auth_user.is_oauth, 32 23 auth_user.scope.as_deref(),
+3 -12
src/api/identity/plc/sign.rs
··· 1 1 use crate::api::ApiError; 2 + use crate::auth::BearerAuthAllowDeactivated; 2 3 use crate::circuit_breaker::with_circuit_breaker; 3 4 use crate::plc::{PlcClient, PlcError, PlcService, create_update_op, sign_operation}; 4 5 use crate::state::AppState; ··· 39 40 40 41 pub async fn sign_plc_operation( 41 42 State(state): State<AppState>, 42 - headers: axum::http::HeaderMap, 43 + auth: BearerAuthAllowDeactivated, 43 44 Json(input): Json<SignPlcOperationInput>, 44 45 ) -> Response { 45 - let bearer = match crate::auth::extract_bearer_token_from_header( 46 - headers.get("Authorization").and_then(|h| h.to_str().ok()), 47 - ) { 48 - Some(t) => t, 49 - None => return ApiError::AuthenticationRequired.into_response(), 50 - }; 51 - let auth_user = 52 - match crate::auth::validate_bearer_token_allow_deactivated(&state.db, &bearer).await { 53 - Ok(user) => user, 54 - Err(e) => return ApiError::from(e).into_response(), 55 - }; 46 + let auth_user = auth.0; 56 47 if let Err(e) = crate::auth::scope_check::check_identity_scope( 57 48 auth_user.is_oauth, 58 49 auth_user.scope.as_deref(),
+3 -16
src/api/identity/plc/submit.rs
··· 1 1 use crate::api::{ApiError, EmptyResponse}; 2 + use crate::auth::BearerAuthAllowDeactivated; 2 3 use crate::circuit_breaker::with_circuit_breaker; 3 4 use crate::plc::{PlcClient, signing_key_to_did_key, validate_plc_operation}; 4 5 use crate::state::AppState; ··· 19 20 20 21 pub async fn submit_plc_operation( 21 22 State(state): State<AppState>, 22 - headers: axum::http::HeaderMap, 23 + auth: BearerAuthAllowDeactivated, 23 24 Json(input): Json<SubmitPlcOperationInput>, 24 25 ) -> Response { 25 - let bearer = match crate::auth::extract_bearer_token_from_header( 26 - headers.get("Authorization").and_then(|h| h.to_str().ok()), 27 - ) { 28 - Some(t) => t, 29 - None => { 30 - return ApiError::AuthenticationRequired.into_response(); 31 - } 32 - }; 33 - let auth_user = 34 - match crate::auth::validate_bearer_token_allow_deactivated(&state.db, &bearer).await { 35 - Ok(user) => user, 36 - Err(e) => { 37 - return ApiError::from(e).into_response(); 38 - } 39 - }; 26 + let auth_user = auth.0; 40 27 if let Err(e) = crate::auth::scope_check::check_identity_scope( 41 28 auth_user.is_oauth, 42 29 auth_user.scope.as_deref(),
+3 -14
src/api/moderation/mod.rs
··· 1 1 use crate::api::ApiError; 2 2 use crate::api::proxy_client::{is_ssrf_safe, proxy_client}; 3 + use crate::auth::extractor::BearerAuthAllowTakendown; 3 4 use crate::state::AppState; 4 5 use axum::{ 5 6 Json, ··· 41 42 42 43 pub async fn create_report( 43 44 State(state): State<AppState>, 44 - headers: axum::http::HeaderMap, 45 + auth: BearerAuthAllowTakendown, 45 46 Json(input): Json<CreateReportInput>, 46 47 ) -> Response { 47 - let token = match crate::auth::extract_bearer_token_from_header( 48 - headers.get("Authorization").and_then(|h| h.to_str().ok()), 49 - ) { 50 - Some(t) => t, 51 - None => return ApiError::AuthenticationRequired.into_response(), 52 - }; 53 - 54 - let auth_user = 55 - match crate::auth::validate_bearer_token_allow_takendown(&state.db, &token).await { 56 - Ok(user) => user, 57 - Err(e) => return ApiError::from(e).into_response(), 58 - }; 59 - 48 + let auth_user = auth.0; 60 49 let did = &auth_user.did; 61 50 62 51 if let Some((service_url, service_did)) = get_report_service_config() {
+7 -44
src/api/notification_prefs.rs
··· 1 1 use crate::api::error::ApiError; 2 - use crate::auth::validate_bearer_token; 2 + use crate::auth::BearerAuth; 3 3 use crate::state::AppState; 4 4 use axum::{ 5 5 Json, 6 6 extract::State, 7 - http::HeaderMap, 8 7 response::{IntoResponse, Response}, 9 8 }; 10 9 use serde::{Deserialize, Serialize}; ··· 25 24 pub signal_verified: bool, 26 25 } 27 26 28 - pub async fn get_notification_prefs(State(state): State<AppState>, headers: HeaderMap) -> Response { 29 - let token = match crate::auth::extract_bearer_token_from_header( 30 - headers.get("Authorization").and_then(|h| h.to_str().ok()), 31 - ) { 32 - Some(t) => t, 33 - None => return ApiError::AuthenticationRequired.into_response(), 34 - }; 35 - let user = match validate_bearer_token(&state.db, &token).await { 36 - Ok(u) => u, 37 - Err(_) => { 38 - return ApiError::AuthenticationFailed(None).into_response(); 39 - } 40 - }; 27 + pub async fn get_notification_prefs(State(state): State<AppState>, auth: BearerAuth) -> Response { 28 + let user = auth.0; 41 29 let row = match sqlx::query( 42 30 r#" 43 31 SELECT ··· 100 88 pub notifications: Vec<NotificationHistoryEntry>, 101 89 } 102 90 103 - pub async fn get_notification_history( 104 - State(state): State<AppState>, 105 - headers: HeaderMap, 106 - ) -> Response { 107 - let token = match crate::auth::extract_bearer_token_from_header( 108 - headers.get("Authorization").and_then(|h| h.to_str().ok()), 109 - ) { 110 - Some(t) => t, 111 - None => return ApiError::AuthenticationRequired.into_response(), 112 - }; 113 - let user = match validate_bearer_token(&state.db, &token).await { 114 - Ok(u) => u, 115 - Err(_) => { 116 - return ApiError::AuthenticationFailed(None).into_response(); 117 - } 118 - }; 91 + pub async fn get_notification_history(State(state): State<AppState>, auth: BearerAuth) -> Response { 92 + let user = auth.0; 119 93 120 94 let user_id: uuid::Uuid = 121 95 match sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", &user.did) ··· 253 227 254 228 pub async fn update_notification_prefs( 255 229 State(state): State<AppState>, 256 - headers: HeaderMap, 230 + auth: BearerAuth, 257 231 Json(input): Json<UpdateNotificationPrefsInput>, 258 232 ) -> Response { 259 - let token = match crate::auth::extract_bearer_token_from_header( 260 - headers.get("Authorization").and_then(|h| h.to_str().ok()), 261 - ) { 262 - Some(t) => t, 263 - None => return ApiError::AuthenticationRequired.into_response(), 264 - }; 265 - let user = match validate_bearer_token(&state.db, &token).await { 266 - Ok(u) => u, 267 - Err(_) => { 268 - return ApiError::AuthenticationFailed(None).into_response(); 269 - } 270 - }; 233 + let user = auth.0; 271 234 272 235 let user_row = match sqlx::query!( 273 236 "SELECT id, handle, email FROM users WHERE did = $1",
+19 -11
src/api/proxy.rs
··· 214 214 info!("Proxying {} request to {}", method_verb, target_url); 215 215 216 216 let client = proxy_client(); 217 - let mut request_builder = client.request(method_verb, &target_url); 217 + let mut request_builder = client.request(method_verb.clone(), &target_url); 218 218 219 219 let mut auth_header_val = headers.get("Authorization").cloned(); 220 - if let Some(token) = crate::auth::extract_bearer_token_from_header( 220 + if let Some(extracted) = crate::auth::extract_auth_token_from_header( 221 221 headers.get("Authorization").and_then(|h| h.to_str().ok()), 222 222 ) { 223 - match crate::auth::validate_bearer_token(&state.db, &token).await { 223 + let token = extracted.token; 224 + let dpop_proof = headers.get("DPoP").and_then(|h| h.to_str().ok()); 225 + let http_uri = uri.to_string(); 226 + 227 + match crate::auth::validate_token_with_dpop( 228 + &state.db, 229 + &token, 230 + extracted.is_dpop, 231 + dpop_proof, 232 + method_verb.as_str(), 233 + &http_uri, 234 + false, 235 + false, 236 + ) 237 + .await 238 + { 224 239 Ok(auth_user) => { 225 240 if let Err(e) = crate::auth::scope_check::check_rpc_scope( 226 241 auth_user.is_oauth, ··· 254 269 Err(e) => { 255 270 warn!("Token validation failed: {:?}", e); 256 271 if matches!(e, crate::auth::TokenValidationError::TokenExpired) { 257 - let auth_header_str = headers 258 - .get("Authorization") 259 - .and_then(|h| h.to_str().ok()) 260 - .unwrap_or(""); 261 - let is_dpop = auth_header_str 262 - .trim() 263 - .get(..5) 264 - .is_some_and(|s| s.eq_ignore_ascii_case("dpop ")); 272 + let is_dpop = extracted.is_dpop; 265 273 let scheme = if is_dpop { "DPoP" } else { "Bearer" }; 266 274 let www_auth = format!( 267 275 "{} error=\"invalid_token\", error_description=\"Token has expired\"",
+25 -18
src/api/repo/blob.rs
··· 1 1 use crate::api::error::ApiError; 2 - use crate::auth::{ServiceTokenVerifier, is_service_token}; 2 + use crate::auth::{BearerAuthAllowDeactivated, ServiceTokenVerifier, is_service_token}; 3 3 use crate::delegation::{self, DelegationActionType}; 4 4 use crate::state::AppState; 5 5 use crate::util::get_max_blob_size; ··· 45 45 headers: axum::http::HeaderMap, 46 46 body: Body, 47 47 ) -> Response { 48 - let Some(token) = crate::auth::extract_bearer_token_from_header( 48 + let extracted = match crate::auth::extract_auth_token_from_header( 49 49 headers.get("Authorization").and_then(|h| h.to_str().ok()), 50 - ) else { 51 - return ApiError::AuthenticationRequired.into_response(); 50 + ) { 51 + Some(t) => t, 52 + None => return ApiError::AuthenticationRequired.into_response(), 52 53 }; 54 + let token = extracted.token; 53 55 54 56 let is_service_auth = is_service_token(&token); 55 57 ··· 74 76 } 75 77 } 76 78 } else { 77 - match crate::auth::validate_bearer_token_allow_deactivated(&state.db, &token).await { 79 + let dpop_proof = headers.get("DPoP").and_then(|h| h.to_str().ok()); 80 + let http_uri = format!( 81 + "https://{}/xrpc/com.atproto.repo.uploadBlob", 82 + std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string()) 83 + ); 84 + match crate::auth::validate_token_with_dpop( 85 + &state.db, 86 + &token, 87 + extracted.is_dpop, 88 + dpop_proof, 89 + "POST", 90 + &http_uri, 91 + true, 92 + false, 93 + ) 94 + .await 95 + { 78 96 Ok(user) => { 79 97 let mime_type_for_check = headers 80 98 .get("content-type") ··· 283 301 284 302 pub async fn list_missing_blobs( 285 303 State(state): State<AppState>, 286 - headers: axum::http::HeaderMap, 304 + auth: BearerAuthAllowDeactivated, 287 305 Query(params): Query<ListMissingBlobsParams>, 288 306 ) -> Response { 289 - let Some(token) = crate::auth::extract_bearer_token_from_header( 290 - headers.get("Authorization").and_then(|h| h.to_str().ok()), 291 - ) else { 292 - return ApiError::AuthenticationRequired.into_response(); 293 - }; 294 - let auth_user = 295 - match crate::auth::validate_bearer_token_allow_deactivated(&state.db, &token).await { 296 - Ok(user) => user, 297 - Err(_) => { 298 - return ApiError::AuthenticationFailed(None).into_response(); 299 - } 300 - }; 307 + let auth_user = auth.0; 301 308 let did = auth_user.did; 302 309 let user_query = sqlx::query!("SELECT id FROM users WHERE did = $1", did.as_str()) 303 310 .fetch_optional(&state.db)
+3 -12
src/api/repo/import.rs
··· 1 1 use crate::api::EmptyResponse; 2 2 use crate::api::error::ApiError; 3 3 use crate::api::repo::record::create_signed_commit; 4 + use crate::auth::BearerAuthAllowDeactivated; 4 5 use crate::state::AppState; 5 6 use crate::sync::import::{ImportError, apply_import, parse_car}; 6 7 use crate::sync::verify::CarVerifier; ··· 20 21 21 22 pub async fn import_repo( 22 23 State(state): State<AppState>, 23 - headers: axum::http::HeaderMap, 24 + auth: BearerAuthAllowDeactivated, 24 25 body: Bytes, 25 26 ) -> Response { 26 27 let accepting_imports = std::env::var("ACCEPTING_REPO_IMPORTS") ··· 41 42 )) 42 43 .into_response(); 43 44 } 44 - let token = match crate::auth::extract_bearer_token_from_header( 45 - headers.get("Authorization").and_then(|h| h.to_str().ok()), 46 - ) { 47 - Some(t) => t, 48 - None => return ApiError::AuthenticationRequired.into_response(), 49 - }; 50 - let auth_user = 51 - match crate::auth::validate_bearer_token_allow_deactivated(&state.db, &token).await { 52 - Ok(user) => user, 53 - Err(e) => return ApiError::from(e).into_response(), 54 - }; 45 + let auth_user = auth.0; 55 46 let did = &auth_user.did; 56 47 let user = match sqlx::query!( 57 48 "SELECT id, handle, deactivated_at, takedown_ref FROM users WHERE did = $1",
+3 -10
src/api/repo/record/batch.rs
··· 2 2 use super::write::has_verified_comms_channel; 3 3 use crate::api::error::ApiError; 4 4 use crate::api::repo::record::utils::{CommitParams, RecordOp, commit_and_log, extract_blob_cids}; 5 + use crate::auth::BearerAuth; 5 6 use crate::delegation::{self, DelegationActionType}; 6 7 use crate::repo::tracking::TrackingBlockStore; 7 8 use crate::state::AppState; ··· 85 86 86 87 pub async fn apply_writes( 87 88 State(state): State<AppState>, 88 - headers: axum::http::HeaderMap, 89 + auth: BearerAuth, 89 90 Json(input): Json<ApplyWritesInput>, 90 91 ) -> Response { 91 92 info!( ··· 93 94 input.repo, 94 95 input.writes.len() 95 96 ); 96 - let Some(token) = crate::auth::extract_bearer_token_from_header( 97 - headers.get("Authorization").and_then(|h| h.to_str().ok()), 98 - ) else { 99 - return ApiError::AuthenticationRequired.into_response(); 100 - }; 101 - let auth_user = match crate::auth::validate_bearer_token(&state.db, &token).await { 102 - Ok(user) => user, 103 - Err(_) => return ApiError::AuthenticationFailed(None).into_response(), 104 - }; 97 + let auth_user = auth.0; 105 98 let did = auth_user.did.clone(); 106 99 let is_oauth = auth_user.is_oauth; 107 100 let scope = auth_user.scope;
+1
src/api/repo/record/write.rs
··· 77 77 http_method, 78 78 http_uri, 79 79 false, 80 + false, 80 81 ) 81 82 .await 82 83 .map_err(|e| {
+4
src/api/server/account_status.rs
··· 59 59 "GET", 60 60 &http_uri, 61 61 true, 62 + false, 62 63 ) 63 64 .await 64 65 { ··· 370 371 "POST", 371 372 &http_uri, 372 373 true, 374 + false, 373 375 ) 374 376 .await 375 377 { ··· 561 563 "POST", 562 564 &http_uri, 563 565 false, 566 + false, 564 567 ) 565 568 .await 566 569 { ··· 646 649 "POST", 647 650 &http_uri, 648 651 true, 652 + false, 649 653 ) 650 654 .await 651 655 {
+2 -12
src/api/server/email.rs
··· 193 193 194 194 pub async fn update_email( 195 195 State(state): State<AppState>, 196 - headers: axum::http::HeaderMap, 196 + auth: BearerAuth, 197 197 Json(input): Json<UpdateEmailInput>, 198 198 ) -> Response { 199 - let Some(bearer_token) = crate::auth::extract_bearer_token_from_header( 200 - headers.get("Authorization").and_then(|h| h.to_str().ok()), 201 - ) else { 202 - return ApiError::AuthenticationRequired.into_response(); 203 - }; 204 - 205 - let auth_result = crate::auth::validate_bearer_token(&state.db, &bearer_token).await; 206 - let auth_user = match auth_result { 207 - Ok(user) => user, 208 - Err(e) => return ApiError::from(e).into_response(), 209 - }; 199 + let auth_user = auth.0; 210 200 211 201 if let Err(e) = crate::auth::scope_check::check_account_scope( 212 202 auth_user.is_oauth,
+2
src/api/server/migration.rs
··· 58 58 "POST", 59 59 &http_uri, 60 60 true, 61 + false, 61 62 ) 62 63 .await 63 64 { ··· 224 225 "GET", 225 226 &http_uri, 226 227 true, 228 + false, 227 229 ) 228 230 .await 229 231 {
+5 -4
src/api/server/passkey_account.rs
··· 18 18 use uuid::Uuid; 19 19 20 20 use crate::api::repo::record::utils::create_signed_commit; 21 - use crate::auth::{ServiceTokenVerifier, extract_bearer_token_from_header, is_service_token}; 21 + use crate::auth::{ServiceTokenVerifier, is_service_token}; 22 22 use crate::state::{AppState, RateLimitKind}; 23 23 use crate::types::{Did, Handle, PlainPassword}; 24 24 use crate::validation::validate_password; ··· 108 108 .into_response(); 109 109 } 110 110 111 - let byod_auth = if let Some(token) = 112 - extract_bearer_token_from_header(headers.get("Authorization").and_then(|h| h.to_str().ok())) 113 - { 111 + let byod_auth = if let Some(extracted) = crate::auth::extract_auth_token_from_header( 112 + headers.get("Authorization").and_then(|h| h.to_str().ok()), 113 + ) { 114 + let token = extracted.token; 114 115 if is_service_token(&token) { 115 116 let verifier = ServiceTokenVerifier::new(); 116 117 match verifier
+10 -9
src/api/server/session.rs
··· 365 365 pub async fn delete_session( 366 366 State(state): State<AppState>, 367 367 headers: axum::http::HeaderMap, 368 + _auth: BearerAuth, 368 369 ) -> Response { 369 - let token = match crate::auth::extract_bearer_token_from_header( 370 + let extracted = match crate::auth::extract_auth_token_from_header( 370 371 headers.get("Authorization").and_then(|h| h.to_str().ok()), 371 372 ) { 372 373 Some(t) => t, 373 374 None => return ApiError::AuthenticationRequired.into_response(), 374 375 }; 375 - let jti = match crate::auth::get_jti_from_token(&token) { 376 + let jti = match crate::auth::get_jti_from_token(&extracted.token) { 376 377 Ok(jti) => jti, 377 378 Err(_) => return ApiError::AuthenticationFailed(None).into_response(), 378 379 }; 379 - let did = crate::auth::get_did_from_token(&token).ok(); 380 + let did = crate::auth::get_did_from_token(&extracted.token).ok(); 380 381 match sqlx::query!("DELETE FROM session_tokens WHERE access_jti = $1", jti) 381 382 .execute(&state.db) 382 383 .await ··· 408 409 tracing::warn!(ip = %client_ip, "Refresh session rate limit exceeded"); 409 410 return ApiError::RateLimitExceeded(None).into_response(); 410 411 } 411 - let refresh_token = match crate::auth::extract_bearer_token_from_header( 412 + let extracted = match crate::auth::extract_auth_token_from_header( 412 413 headers.get("Authorization").and_then(|h| h.to_str().ok()), 413 414 ) { 414 415 Some(t) => t, 415 416 None => return ApiError::AuthenticationRequired.into_response(), 416 417 }; 418 + let refresh_token = extracted.token; 417 419 let refresh_jti = match crate::auth::get_jti_from_token(&refresh_token) { 418 420 Ok(jti) => jti, 419 421 Err(_) => { ··· 1048 1050 headers: HeaderMap, 1049 1051 auth: BearerAuth, 1050 1052 ) -> Response { 1051 - let current_jti = headers 1052 - .get("authorization") 1053 - .and_then(|v| v.to_str().ok()) 1054 - .and_then(|v| v.strip_prefix("Bearer ")) 1055 - .and_then(|token| crate::auth::get_jti_from_token(token).ok()); 1053 + let current_jti = crate::auth::extract_auth_token_from_header( 1054 + headers.get("authorization").and_then(|v| v.to_str().ok()), 1055 + ) 1056 + .and_then(|extracted| crate::auth::get_jti_from_token(&extracted.token).ok()); 1056 1057 1057 1058 let Some(ref jti) = current_jti else { 1058 1059 return ApiError::InvalidToken(None).into_response();
+21 -16
src/api/temp.rs
··· 1 1 use crate::api::error::ApiError; 2 - use crate::auth::{extract_bearer_token_from_header, validate_bearer_token}; 2 + use crate::auth::{BearerAuth, extract_auth_token_from_header, validate_token_with_dpop}; 3 3 use crate::state::AppState; 4 4 use axum::{ 5 5 Json, ··· 23 23 } 24 24 25 25 pub async fn check_signup_queue(State(state): State<AppState>, headers: HeaderMap) -> Response { 26 - if let Some(token) = 27 - extract_bearer_token_from_header(headers.get("Authorization").and_then(|h| h.to_str().ok())) 28 - && let Ok(user) = validate_bearer_token(&state.db, &token).await 29 - && user.is_oauth 26 + if let Some(extracted) = 27 + extract_auth_token_from_header(headers.get("Authorization").and_then(|h| h.to_str().ok())) 30 28 { 31 - return ApiError::Forbidden.into_response(); 29 + let dpop_proof = headers.get("DPoP").and_then(|h| h.to_str().ok()); 30 + if let Ok(user) = validate_token_with_dpop( 31 + &state.db, 32 + &extracted.token, 33 + extracted.is_dpop, 34 + dpop_proof, 35 + "GET", 36 + "/", 37 + false, 38 + false, 39 + ) 40 + .await 41 + && user.is_oauth 42 + { 43 + return ApiError::Forbidden.into_response(); 44 + } 32 45 } 33 46 Json(CheckSignupQueueOutput { 34 47 activated: true, ··· 52 65 53 66 pub async fn dereference_scope( 54 67 State(state): State<AppState>, 55 - headers: HeaderMap, 68 + auth: BearerAuth, 56 69 Json(input): Json<DereferenceScopeInput>, 57 70 ) -> Response { 58 - let Some(token) = extract_bearer_token_from_header( 59 - headers.get("Authorization").and_then(|h| h.to_str().ok()), 60 - ) else { 61 - return ApiError::AuthenticationRequired.into_response(); 62 - }; 63 - 64 - if validate_bearer_token(&state.db, &token).await.is_err() { 65 - return ApiError::AuthenticationFailed(None).into_response(); 66 - } 71 + let _ = auth; 67 72 68 73 let scope_parts: Vec<&str> = input.scope.split_whitespace().collect(); 69 74 let mut resolved_scopes: Vec<String> = Vec::new();
+58 -2
src/auth/extractor.rs
··· 5 5 }; 6 6 7 7 use super::{ 8 - AuthenticatedUser, TokenValidationError, validate_bearer_token_cached, 9 - validate_bearer_token_cached_allow_deactivated, validate_token_with_dpop, 8 + AuthenticatedUser, TokenValidationError, validate_bearer_token_allow_takendown, 9 + validate_bearer_token_cached, validate_bearer_token_cached_allow_deactivated, 10 + validate_token_with_dpop, 10 11 }; 11 12 use crate::api::error::ApiError; 12 13 use crate::state::AppState; ··· 136 137 method, 137 138 &uri, 138 139 false, 140 + false, 139 141 ) 140 142 .await 141 143 { ··· 191 193 method, 192 194 &uri, 193 195 true, 196 + false, 194 197 ) 195 198 .await 196 199 { ··· 216 219 } 217 220 } 218 221 222 + pub struct BearerAuthAllowTakendown(pub AuthenticatedUser); 223 + 224 + impl FromRequestParts<AppState> for BearerAuthAllowTakendown { 225 + type Rejection = AuthError; 226 + 227 + async fn from_request_parts( 228 + parts: &mut Parts, 229 + state: &AppState, 230 + ) -> Result<Self, Self::Rejection> { 231 + let auth_header = parts 232 + .headers 233 + .get(AUTHORIZATION) 234 + .ok_or(AuthError::MissingToken)? 235 + .to_str() 236 + .map_err(|_| AuthError::InvalidFormat)?; 237 + 238 + let extracted = 239 + extract_auth_token_from_header(Some(auth_header)).ok_or(AuthError::InvalidFormat)?; 240 + 241 + if extracted.is_dpop { 242 + let dpop_proof = parts.headers.get("dpop").and_then(|h| h.to_str().ok()); 243 + let method = parts.method.as_str(); 244 + let uri = build_full_url(&parts.uri.to_string()); 245 + 246 + match validate_token_with_dpop( 247 + &state.db, 248 + &extracted.token, 249 + true, 250 + dpop_proof, 251 + method, 252 + &uri, 253 + false, 254 + true, 255 + ) 256 + .await 257 + { 258 + Ok(user) => Ok(BearerAuthAllowTakendown(user)), 259 + Err(TokenValidationError::AccountDeactivated) => Err(AuthError::AccountDeactivated), 260 + Err(TokenValidationError::TokenExpired) => Err(AuthError::TokenExpired), 261 + Err(_) => Err(AuthError::AuthenticationFailed), 262 + } 263 + } else { 264 + match validate_bearer_token_allow_takendown(&state.db, &extracted.token).await { 265 + Ok(user) => Ok(BearerAuthAllowTakendown(user)), 266 + Err(TokenValidationError::AccountDeactivated) => Err(AuthError::AccountDeactivated), 267 + Err(TokenValidationError::TokenExpired) => Err(AuthError::TokenExpired), 268 + Err(_) => Err(AuthError::AuthenticationFailed), 269 + } 270 + } 271 + } 272 + } 273 + 219 274 pub struct BearerAuthAdmin(pub AuthenticatedUser); 220 275 221 276 impl FromRequestParts<AppState> for BearerAuthAdmin { ··· 247 302 dpop_proof, 248 303 method, 249 304 &uri, 305 + false, 250 306 false, 251 307 ) 252 308 .await
+6 -2
src/auth/mod.rs
··· 416 416 let _ = cache.delete(&status_cache_key).await; 417 417 } 418 418 419 + #[allow(clippy::too_many_arguments)] 419 420 pub async fn validate_token_with_dpop( 420 421 db: &PgPool, 421 422 token: &str, ··· 424 425 http_method: &str, 425 426 http_uri: &str, 426 427 allow_deactivated: bool, 428 + allow_takendown: bool, 427 429 ) -> Result<AuthenticatedUser, TokenValidationError> { 428 430 if !is_dpop_token { 429 - if allow_deactivated { 431 + if allow_takendown { 432 + return validate_bearer_token_allow_takendown(db, token).await; 433 + } else if allow_deactivated { 430 434 return validate_bearer_token_allow_deactivated(db, token).await; 431 435 } else { 432 436 return validate_bearer_token(db, token).await; ··· 464 468 if !allow_deactivated && status.is_deactivated() { 465 469 return Err(TokenValidationError::AccountDeactivated); 466 470 } 467 - if status.is_takendown() { 471 + if !allow_takendown && status.is_takendown() { 468 472 return Err(TokenValidationError::AccountTakedown); 469 473 } 470 474 let key_bytes = if let (Some(kb), Some(ev)) =
+3 -1
src/oauth/client.rs
··· 82 82 .connect_timeout(std::time::Duration::from_secs(10)) 83 83 .pool_max_idle_per_host(10) 84 84 .pool_idle_timeout(std::time::Duration::from_secs(90)) 85 - .user_agent("Tranquil-PDS/1.0 (ATProto; +https://tangled.org/lewis.moe/bspds-sandbox)") 85 + .user_agent( 86 + "Tranquil-PDS/1.0 (ATProto; +https://tangled.org/lewis.moe/bspds-sandbox)", 87 + ) 86 88 .build() 87 89 .unwrap_or_else(|_| Client::new()), 88 90 cache_ttl_secs,
+8 -3
src/oauth/endpoints/authorize.rs
··· 56 56 } 57 57 58 58 fn is_granular_scope(s: &str) -> bool { 59 - s.starts_with("repo:") || s.starts_with("repo?") || s == "repo" 60 - || s.starts_with("blob:") || s.starts_with("blob?") || s == "blob" 61 - || s.starts_with("rpc:") || s.starts_with("rpc?") 59 + s.starts_with("repo:") 60 + || s.starts_with("repo?") 61 + || s == "repo" 62 + || s.starts_with("blob:") 63 + || s.starts_with("blob?") 64 + || s == "blob" 65 + || s.starts_with("rpc:") 66 + || s.starts_with("rpc?") 62 67 || s.starts_with("account:") 63 68 || s.starts_with("identity:") 64 69 }
+5 -7
src/oauth/scopes/permission_set.rs
··· 57 57 async fn expand_permission_set(nsid: &str) -> Result<String, String> { 58 58 { 59 59 let cache = LEXICON_CACHE.read().await; 60 - if let Some(cached) = cache.get(nsid) { 61 - if cached.cached_at.elapsed().as_secs() < CACHE_TTL_SECS { 62 - debug!(nsid, "Using cached permission set expansion"); 63 - return Ok(cached.expanded_scope.clone()); 64 - } 60 + if let Some(cached) = cache.get(nsid) 61 + && cached.cached_at.elapsed().as_secs() < CACHE_TTL_SECS 62 + { 63 + debug!(nsid, "Using cached permission set expansion"); 64 + return Ok(cached.expanded_scope.clone()); 65 65 } 66 66 } 67 67 ··· 156 156 157 157 #[cfg(test)] 158 158 mod tests { 159 - use super::*; 160 - 161 159 #[test] 162 160 fn test_nsid_to_url() { 163 161 let nsid = "io.atcr.authFullApp";
+15 -3
src/sync/deprecated.rs
··· 1 1 use crate::api::error::ApiError; 2 - use crate::auth::{extract_bearer_token_from_header, validate_bearer_token_allow_takendown}; 3 2 use crate::state::AppState; 4 3 use crate::sync::car::encode_car_header; 5 4 use crate::sync::util::assert_repo_availability; ··· 19 18 const MAX_REPO_BLOCKS_TRAVERSAL: usize = 20_000; 20 19 21 20 async fn check_admin_or_self(state: &AppState, headers: &HeaderMap, did: &str) -> bool { 22 - let token = match extract_bearer_token_from_header( 21 + let extracted = match crate::auth::extract_auth_token_from_header( 23 22 headers.get("Authorization").and_then(|h| h.to_str().ok()), 24 23 ) { 25 24 Some(t) => t, 26 25 None => return false, 27 26 }; 28 - match validate_bearer_token_allow_takendown(&state.db, &token).await { 27 + let dpop_proof = headers.get("DPoP").and_then(|h| h.to_str().ok()); 28 + let http_uri = "/"; 29 + match crate::auth::validate_token_with_dpop( 30 + &state.db, 31 + &extracted.token, 32 + extracted.is_dpop, 33 + dpop_proof, 34 + "GET", 35 + http_uri, 36 + false, 37 + true, 38 + ) 39 + .await 40 + { 29 41 Ok(auth_user) => auth_user.is_admin || auth_user.did == did, 30 42 Err(_) => false, 31 43 }
+4 -4
tests/dpop_unit.rs
··· 191 191 let verifier = DPoPVerifier::new(b"test-secret-32-bytes-long!!!!!!!"); 192 192 let url = "https://pds.example/xrpc/foo"; 193 193 194 - let (proof_301s_future, _) = create_dpop_proof("GET", url, 301, "ES256", None, None); 194 + let (proof_301s_future, _) = create_dpop_proof("GET", url, 310, "ES256", None, None); 195 195 let result = verifier.verify_proof(&proof_301s_future, "GET", url, None); 196 196 assert!( 197 197 result.is_err(), 198 - "301s in future should exceed clock skew tolerance" 198 + "310s in future should exceed clock skew tolerance" 199 199 ); 200 200 201 - let (proof_301s_past, _) = create_dpop_proof("GET", url, -301, "ES256", None, None); 201 + let (proof_301s_past, _) = create_dpop_proof("GET", url, -310, "ES256", None, None); 202 202 let result = verifier.verify_proof(&proof_301s_past, "GET", url, None); 203 203 assert!( 204 204 result.is_err(), 205 - "301s in past should exceed clock skew tolerance" 205 + "310s in past should exceed clock skew tolerance" 206 206 ); 207 207 } 208 208