this repo has no description

Better auth extractor use

lewis 8b61506c 08bec9b4

+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