+5
-32
src/api/actor/preferences.rs
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+1
src/api/repo/record/write.rs
+4
src/api/server/account_status.rs
+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
+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
+2
src/api/server/migration.rs
+5
-4
src/api/server/passkey_account.rs
+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
+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
+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
+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
+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
+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,
+5
-7
src/oauth/scopes/permission_set.rs
+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
+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
+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