+4
-4
README.md
+4
-4
README.md
···
131
131
### XRPC Service
132
132
133
133
```rust
134
-
use atproto_xrpcs::authorization::ResolvingAuthorization;
134
+
use atproto_xrpcs::authorization::Authorization;
135
135
use axum::{Json, Router, extract::Query, routing::get};
136
136
use serde::Deserialize;
137
137
use serde_json::json;
···
143
143
144
144
async fn handle_hello(
145
145
params: Query<HelloParams>,
146
-
authorization: Option<ResolvingAuthorization>,
146
+
authorization: Option<Authorization>,
147
147
) -> Json<serde_json::Value> {
148
148
let subject = params.subject.as_deref().unwrap_or("World");
149
-
149
+
150
150
let message = if let Some(auth) = authorization {
151
151
format!("Hello, authenticated {}! (caller: {})", subject, auth.subject())
152
152
} else {
153
153
format!("Hello, {}!", subject)
154
154
};
155
-
155
+
156
156
Json(json!({ "message": message }))
157
157
}
158
158
+19
-1
crates/atproto-identity/src/model.rs
+19
-1
crates/atproto-identity/src/model.rs
···
70
70
/// The DID identifier (e.g., "did:plc:abc123").
71
71
pub id: String,
72
72
/// Alternative identifiers like handles and domains.
73
+
#[serde(default)]
73
74
pub also_known_as: Vec<String>,
74
75
/// Available services for this identity.
76
+
#[serde(default)]
75
77
pub service: Vec<Service>,
76
78
77
79
/// Cryptographic verification methods.
78
-
#[serde(alias = "verificationMethod")]
80
+
#[serde(alias = "verificationMethod", default)]
79
81
pub verification_method: Vec<VerificationMethod>,
80
82
81
83
/// Additional document properties not explicitly defined.
···
402
404
let document = document.unwrap();
403
405
assert_eq!(document.id, "did:plc:cbkjy5n7bk3ax2wplmtjofq2");
404
406
}
407
+
}
408
+
409
+
#[test]
410
+
fn test_deserialize_service_did_document() {
411
+
// DID document from api.bsky.app - a service DID without alsoKnownAs
412
+
let document = serde_json::from_str::<Document>(
413
+
r##"{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1"],"id":"did:web:api.bsky.app","verificationMethod":[{"id":"did:web:api.bsky.app#atproto","type":"Multikey","controller":"did:web:api.bsky.app","publicKeyMultibase":"zQ3shpRzb2NDriwCSSsce6EqGxG23kVktHZc57C3NEcuNy1jg"}],"service":[{"id":"#bsky_notif","type":"BskyNotificationService","serviceEndpoint":"https://api.bsky.app"},{"id":"#bsky_appview","type":"BskyAppView","serviceEndpoint":"https://api.bsky.app"}]}"##,
414
+
);
415
+
assert!(document.is_ok(), "Failed to parse: {:?}", document.err());
416
+
417
+
let document = document.unwrap();
418
+
assert_eq!(document.id, "did:web:api.bsky.app");
419
+
assert!(document.also_known_as.is_empty());
420
+
assert_eq!(document.service.len(), 2);
421
+
assert_eq!(document.service[0].id, "#bsky_notif");
422
+
assert_eq!(document.service[1].id, "#bsky_appview");
405
423
}
406
424
}
+283
crates/atproto-oauth/src/scopes.rs
+283
crates/atproto-oauth/src/scopes.rs
···
38
38
Atproto,
39
39
/// Transition scope for migration operations
40
40
Transition(TransitionScope),
41
+
/// Include scope for referencing permission sets by NSID
42
+
Include(IncludeScope),
41
43
/// OpenID Connect scope - required for OpenID Connect authentication
42
44
OpenId,
43
45
/// Profile scope - access to user profile information
···
91
93
Generic,
92
94
/// Email transition operations
93
95
Email,
96
+
}
97
+
98
+
/// Include scope for referencing permission sets by NSID
99
+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
100
+
pub struct IncludeScope {
101
+
/// The permission set NSID (e.g., "app.example.authFull")
102
+
pub nsid: String,
103
+
/// Optional audience DID for inherited RPC permissions
104
+
pub aud: Option<String>,
94
105
}
95
106
96
107
/// Blob scope with mime type constraints
···
310
321
"rpc",
311
322
"atproto",
312
323
"transition",
324
+
"include",
313
325
"openid",
314
326
"profile",
315
327
"email",
···
349
361
"rpc" => Self::parse_rpc(suffix),
350
362
"atproto" => Self::parse_atproto(suffix),
351
363
"transition" => Self::parse_transition(suffix),
364
+
"include" => Self::parse_include(suffix),
352
365
"openid" => Self::parse_openid(suffix),
353
366
"profile" => Self::parse_profile(suffix),
354
367
"email" => Self::parse_email(suffix),
···
573
586
Ok(Scope::Transition(scope))
574
587
}
575
588
589
+
fn parse_include(suffix: Option<&str>) -> Result<Self, ParseError> {
590
+
let (nsid, params) = match suffix {
591
+
Some(s) => {
592
+
if let Some(pos) = s.find('?') {
593
+
(&s[..pos], Some(&s[pos + 1..]))
594
+
} else {
595
+
(s, None)
596
+
}
597
+
}
598
+
None => return Err(ParseError::MissingResource),
599
+
};
600
+
601
+
if nsid.is_empty() {
602
+
return Err(ParseError::MissingResource);
603
+
}
604
+
605
+
let aud = if let Some(params) = params {
606
+
let parsed_params = parse_query_string(params);
607
+
parsed_params
608
+
.get("aud")
609
+
.and_then(|v| v.first())
610
+
.map(|s| url_decode(s))
611
+
} else {
612
+
None
613
+
};
614
+
615
+
Ok(Scope::Include(IncludeScope {
616
+
nsid: nsid.to_string(),
617
+
aud,
618
+
}))
619
+
}
620
+
576
621
fn parse_openid(suffix: Option<&str>) -> Result<Self, ParseError> {
577
622
if suffix.is_some() {
578
623
return Err(ParseError::InvalidResource(
···
730
775
TransitionScope::Generic => "transition:generic".to_string(),
731
776
TransitionScope::Email => "transition:email".to_string(),
732
777
},
778
+
Scope::Include(scope) => {
779
+
if let Some(ref aud) = scope.aud {
780
+
format!("include:{}?aud={}", scope.nsid, url_encode(aud))
781
+
} else {
782
+
format!("include:{}", scope.nsid)
783
+
}
784
+
}
733
785
Scope::OpenId => "openid".to_string(),
734
786
Scope::Profile => "profile".to_string(),
735
787
Scope::Email => "email".to_string(),
···
749
801
// Other scopes don't grant transition scopes
750
802
(_, Scope::Transition(_)) => false,
751
803
(Scope::Transition(_), _) => false,
804
+
// Include scopes only grant themselves (exact match including aud)
805
+
(Scope::Include(a), Scope::Include(b)) => a == b,
806
+
// Other scopes don't grant include scopes
807
+
(_, Scope::Include(_)) => false,
808
+
(Scope::Include(_), _) => false,
752
809
// OpenID Connect scopes only grant themselves
753
810
(Scope::OpenId, Scope::OpenId) => true,
754
811
(Scope::OpenId, _) => false,
···
888
945
}
889
946
890
947
params
948
+
}
949
+
950
+
/// Decode a percent-encoded string
951
+
fn url_decode(s: &str) -> String {
952
+
let mut result = String::with_capacity(s.len());
953
+
let mut chars = s.chars().peekable();
954
+
955
+
while let Some(c) = chars.next() {
956
+
if c == '%' {
957
+
let hex: String = chars.by_ref().take(2).collect();
958
+
if hex.len() == 2 {
959
+
if let Ok(byte) = u8::from_str_radix(&hex, 16) {
960
+
result.push(byte as char);
961
+
continue;
962
+
}
963
+
}
964
+
result.push('%');
965
+
result.push_str(&hex);
966
+
} else {
967
+
result.push(c);
968
+
}
969
+
}
970
+
971
+
result
972
+
}
973
+
974
+
/// Encode a string for use in a URL query parameter
975
+
fn url_encode(s: &str) -> String {
976
+
let mut result = String::with_capacity(s.len() * 3);
977
+
978
+
for c in s.chars() {
979
+
match c {
980
+
'A'..='Z' | 'a'..='z' | '0'..='9' | '-' | '_' | '.' | '~' | ':' => {
981
+
result.push(c);
982
+
}
983
+
_ => {
984
+
for byte in c.to_string().as_bytes() {
985
+
result.push_str(&format!("%{:02X}", byte));
986
+
}
987
+
}
988
+
}
989
+
}
990
+
991
+
result
891
992
}
892
993
893
994
/// Error type for scope parsing
···
1921
2022
let reduced = Scope::parse_multiple_reduced("repo:app.bsky.feed.* repo:app.bsky.graph.* repo:*").unwrap();
1922
2023
assert_eq!(reduced.len(), 1);
1923
2024
assert_eq!(reduced[0], repo_all);
2025
+
}
2026
+
2027
+
#[test]
2028
+
fn test_include_scope_parsing() {
2029
+
// Test basic include scope
2030
+
let scope = Scope::parse("include:app.example.authFull").unwrap();
2031
+
assert_eq!(
2032
+
scope,
2033
+
Scope::Include(IncludeScope {
2034
+
nsid: "app.example.authFull".to_string(),
2035
+
aud: None,
2036
+
})
2037
+
);
2038
+
2039
+
// Test include scope with audience
2040
+
let scope = Scope::parse("include:app.example.authFull?aud=did:web:api.example.com").unwrap();
2041
+
assert_eq!(
2042
+
scope,
2043
+
Scope::Include(IncludeScope {
2044
+
nsid: "app.example.authFull".to_string(),
2045
+
aud: Some("did:web:api.example.com".to_string()),
2046
+
})
2047
+
);
2048
+
2049
+
// Test include scope with URL-encoded audience (with fragment)
2050
+
let scope = Scope::parse("include:app.example.authFull?aud=did:web:api.example.com%23svc_chat").unwrap();
2051
+
assert_eq!(
2052
+
scope,
2053
+
Scope::Include(IncludeScope {
2054
+
nsid: "app.example.authFull".to_string(),
2055
+
aud: Some("did:web:api.example.com#svc_chat".to_string()),
2056
+
})
2057
+
);
2058
+
2059
+
// Test missing NSID
2060
+
assert!(matches!(
2061
+
Scope::parse("include"),
2062
+
Err(ParseError::MissingResource)
2063
+
));
2064
+
2065
+
// Test empty NSID with query params
2066
+
assert!(matches!(
2067
+
Scope::parse("include:?aud=did:example:123"),
2068
+
Err(ParseError::MissingResource)
2069
+
));
2070
+
}
2071
+
2072
+
#[test]
2073
+
fn test_include_scope_normalization() {
2074
+
// Test normalization without audience
2075
+
let scope = Scope::parse("include:com.example.authBasic").unwrap();
2076
+
assert_eq!(scope.to_string_normalized(), "include:com.example.authBasic");
2077
+
2078
+
// Test normalization with audience (no special chars)
2079
+
let scope = Scope::parse("include:com.example.authBasic?aud=did:plc:xyz123").unwrap();
2080
+
assert_eq!(
2081
+
scope.to_string_normalized(),
2082
+
"include:com.example.authBasic?aud=did:plc:xyz123"
2083
+
);
2084
+
2085
+
// Test normalization with URL encoding (fragment needs encoding)
2086
+
let scope = Scope::parse("include:app.example.authFull?aud=did:web:api.example.com%23svc_chat").unwrap();
2087
+
let normalized = scope.to_string_normalized();
2088
+
assert_eq!(
2089
+
normalized,
2090
+
"include:app.example.authFull?aud=did:web:api.example.com%23svc_chat"
2091
+
);
2092
+
}
2093
+
2094
+
#[test]
2095
+
fn test_include_scope_grants() {
2096
+
let include1 = Scope::parse("include:app.example.authFull").unwrap();
2097
+
let include2 = Scope::parse("include:app.example.authBasic").unwrap();
2098
+
let include1_with_aud = Scope::parse("include:app.example.authFull?aud=did:plc:xyz").unwrap();
2099
+
let account = Scope::parse("account:email").unwrap();
2100
+
2101
+
// Include scopes only grant themselves (exact match)
2102
+
assert!(include1.grants(&include1));
2103
+
assert!(!include1.grants(&include2));
2104
+
assert!(!include1.grants(&include1_with_aud)); // Different because aud differs
2105
+
assert!(include1_with_aud.grants(&include1_with_aud));
2106
+
2107
+
// Include scopes don't grant other scope types
2108
+
assert!(!include1.grants(&account));
2109
+
assert!(!account.grants(&include1));
2110
+
2111
+
// Include scopes don't grant atproto or transition
2112
+
let atproto = Scope::parse("atproto").unwrap();
2113
+
let transition = Scope::parse("transition:generic").unwrap();
2114
+
assert!(!include1.grants(&atproto));
2115
+
assert!(!include1.grants(&transition));
2116
+
assert!(!atproto.grants(&include1));
2117
+
assert!(!transition.grants(&include1));
2118
+
}
2119
+
2120
+
#[test]
2121
+
fn test_parse_multiple_with_include() {
2122
+
let scopes = Scope::parse_multiple("atproto include:app.example.auth repo:*").unwrap();
2123
+
assert_eq!(scopes.len(), 3);
2124
+
assert_eq!(scopes[0], Scope::Atproto);
2125
+
assert!(matches!(scopes[1], Scope::Include(_)));
2126
+
assert!(matches!(scopes[2], Scope::Repo(_)));
2127
+
2128
+
// Test with URL-encoded audience
2129
+
let scopes = Scope::parse_multiple(
2130
+
"include:app.example.auth?aud=did:web:api.example.com%23svc account:email"
2131
+
).unwrap();
2132
+
assert_eq!(scopes.len(), 2);
2133
+
if let Scope::Include(inc) = &scopes[0] {
2134
+
assert_eq!(inc.nsid, "app.example.auth");
2135
+
assert_eq!(inc.aud, Some("did:web:api.example.com#svc".to_string()));
2136
+
} else {
2137
+
panic!("Expected Include scope");
2138
+
}
2139
+
}
2140
+
2141
+
#[test]
2142
+
fn test_parse_multiple_reduced_with_include() {
2143
+
// Include scopes don't reduce each other (each is distinct)
2144
+
let scopes = Scope::parse_multiple_reduced(
2145
+
"include:app.example.auth include:app.example.other include:app.example.auth"
2146
+
).unwrap();
2147
+
assert_eq!(scopes.len(), 2); // Duplicates are removed
2148
+
assert!(scopes.contains(&Scope::Include(IncludeScope {
2149
+
nsid: "app.example.auth".to_string(),
2150
+
aud: None,
2151
+
})));
2152
+
assert!(scopes.contains(&Scope::Include(IncludeScope {
2153
+
nsid: "app.example.other".to_string(),
2154
+
aud: None,
2155
+
})));
2156
+
2157
+
// Include scopes with different audiences are not duplicates
2158
+
let scopes = Scope::parse_multiple_reduced(
2159
+
"include:app.example.auth include:app.example.auth?aud=did:plc:xyz"
2160
+
).unwrap();
2161
+
assert_eq!(scopes.len(), 2);
2162
+
}
2163
+
2164
+
#[test]
2165
+
fn test_serialize_multiple_with_include() {
2166
+
let scopes = vec![
2167
+
Scope::parse("repo:*").unwrap(),
2168
+
Scope::parse("include:app.example.authFull").unwrap(),
2169
+
Scope::Atproto,
2170
+
];
2171
+
let result = Scope::serialize_multiple(&scopes);
2172
+
assert_eq!(result, "atproto include:app.example.authFull repo:*");
2173
+
2174
+
// Test with URL-encoded audience
2175
+
let scopes = vec![
2176
+
Scope::Include(IncludeScope {
2177
+
nsid: "app.example.auth".to_string(),
2178
+
aud: Some("did:web:api.example.com#svc".to_string()),
2179
+
}),
2180
+
];
2181
+
let result = Scope::serialize_multiple(&scopes);
2182
+
assert_eq!(result, "include:app.example.auth?aud=did:web:api.example.com%23svc");
2183
+
}
2184
+
2185
+
#[test]
2186
+
fn test_remove_scope_with_include() {
2187
+
let scopes = vec![
2188
+
Scope::Atproto,
2189
+
Scope::parse("include:app.example.auth").unwrap(),
2190
+
Scope::parse("account:email").unwrap(),
2191
+
];
2192
+
let to_remove = Scope::parse("include:app.example.auth").unwrap();
2193
+
let result = Scope::remove_scope(&scopes, &to_remove);
2194
+
assert_eq!(result.len(), 2);
2195
+
assert!(!result.contains(&to_remove));
2196
+
assert!(result.contains(&Scope::Atproto));
2197
+
}
2198
+
2199
+
#[test]
2200
+
fn test_include_scope_roundtrip() {
2201
+
// Test that parse and serialize are inverses
2202
+
let original = "include:com.example.authBasicFeatures?aud=did:web:api.example.com%23svc_appview";
2203
+
let scope = Scope::parse(original).unwrap();
2204
+
let serialized = scope.to_string_normalized();
2205
+
let reparsed = Scope::parse(&serialized).unwrap();
2206
+
assert_eq!(scope, reparsed);
1924
2207
}
1925
2208
}
+3
-13
crates/atproto-xrpcs-helloworld/src/main.rs
+3
-13
crates/atproto-xrpcs-helloworld/src/main.rs
···
7
7
config::{CertificateBundles, DnsNameservers, default_env, optional_env, require_env, version},
8
8
key::{KeyData, KeyResolver, identify_key, to_public},
9
9
resolve::{HickoryDnsResolver, IdentityResolver, InnerIdentityResolver},
10
-
storage_lru::LruDidDocumentStorage,
11
-
traits::DidDocumentStorage,
12
10
};
13
-
use atproto_xrpcs::authorization::ResolvingAuthorization;
11
+
use atproto_xrpcs::authorization::Authorization;
14
12
use axum::{
15
13
Json, Router,
16
14
extract::{FromRef, Query, State},
···
21
19
use http::{HeaderMap, StatusCode};
22
20
use serde::Deserialize;
23
21
use serde_json::json;
24
-
use std::{collections::HashMap, num::NonZeroUsize, ops::Deref, sync::Arc};
22
+
use std::{collections::HashMap, ops::Deref, sync::Arc};
25
23
26
24
#[derive(Clone)]
27
25
pub struct SimpleKeyResolver {
···
61
59
62
60
pub struct InnerWebContext {
63
61
pub http_client: reqwest::Client,
64
-
pub document_storage: Arc<dyn DidDocumentStorage>,
65
62
pub key_resolver: Arc<dyn KeyResolver>,
66
63
pub service_document: ServiceDocument,
67
64
pub service_did: ServiceDID,
···
97
94
}
98
95
}
99
96
100
-
impl FromRef<WebContext> for Arc<dyn DidDocumentStorage> {
101
-
fn from_ref(context: &WebContext) -> Self {
102
-
context.0.document_storage.clone()
103
-
}
104
-
}
105
-
106
97
impl FromRef<WebContext> for Arc<dyn KeyResolver> {
107
98
fn from_ref(context: &WebContext) -> Self {
108
99
context.0.key_resolver.clone()
···
216
207
217
208
let web_context = WebContext(Arc::new(InnerWebContext {
218
209
http_client: http_client.clone(),
219
-
document_storage: Arc::new(LruDidDocumentStorage::new(NonZeroUsize::new(255).unwrap())),
220
210
key_resolver: Arc::new(SimpleKeyResolver {
221
211
keys: signing_key_storage,
222
212
}),
···
284
274
async fn handle_xrpc_hello_world(
285
275
parameters: Query<HelloParameters>,
286
276
headers: HeaderMap,
287
-
authorization: Option<ResolvingAuthorization>,
277
+
authorization: Option<Authorization>,
288
278
) -> Json<serde_json::Value> {
289
279
println!("headers {headers:?}");
290
280
let subject = parameters.subject.as_deref().unwrap_or("World");
+13
-13
crates/atproto-xrpcs/README.md
+13
-13
crates/atproto-xrpcs/README.md
···
23
23
### Basic XRPC Service
24
24
25
25
```rust
26
-
use atproto_xrpcs::authorization::ResolvingAuthorization;
26
+
use atproto_xrpcs::authorization::Authorization;
27
27
use axum::{Json, Router, extract::Query, routing::get};
28
28
use serde::Deserialize;
29
29
use serde_json::json;
···
35
35
36
36
async fn handle_hello(
37
37
params: Query<HelloParams>,
38
-
authorization: Option<ResolvingAuthorization>,
38
+
authorization: Option<Authorization>,
39
39
) -> Json<serde_json::Value> {
40
40
let name = params.name.as_deref().unwrap_or("World");
41
-
41
+
42
42
let message = if authorization.is_some() {
43
43
format!("Hello, authenticated {}!", name)
44
44
} else {
45
45
format!("Hello, {}!", name)
46
46
};
47
-
47
+
48
48
Json(json!({ "message": message }))
49
49
}
50
50
···
56
56
### JWT Authorization
57
57
58
58
```rust
59
-
use atproto_xrpcs::authorization::ResolvingAuthorization;
59
+
use atproto_xrpcs::authorization::Authorization;
60
60
61
61
async fn handle_secure_endpoint(
62
-
authorization: ResolvingAuthorization, // Required authorization
62
+
authorization: Authorization, // Required authorization
63
63
) -> Json<serde_json::Value> {
64
-
// The ResolvingAuthorization extractor automatically:
64
+
// The Authorization extractor automatically:
65
65
// 1. Validates the JWT token
66
-
// 2. Resolves the caller's DID document
66
+
// 2. Resolves the caller's DID document
67
67
// 3. Verifies the signature against the DID document
68
68
// 4. Provides access to caller identity information
69
-
69
+
70
70
let caller_did = authorization.subject();
71
71
Json(json!({"caller": caller_did, "status": "authenticated"}))
72
72
}
···
79
79
use axum::{response::IntoResponse, http::StatusCode};
80
80
81
81
async fn protected_handler(
82
-
authorization: Result<ResolvingAuthorization, AuthorizationError>,
82
+
authorization: Result<Authorization, AuthorizationError>,
83
83
) -> impl IntoResponse {
84
84
match authorization {
85
85
Ok(auth) => (StatusCode::OK, "Access granted").into_response(),
86
-
Err(AuthorizationError::InvalidJWTToken { .. }) => {
86
+
Err(AuthorizationError::InvalidJWTFormat) => {
87
87
(StatusCode::UNAUTHORIZED, "Invalid token").into_response()
88
88
}
89
-
Err(AuthorizationError::DIDDocumentResolutionFailed { .. }) => {
89
+
Err(AuthorizationError::SubjectResolutionFailed { .. }) => {
90
90
(StatusCode::FORBIDDEN, "Identity verification failed").into_response()
91
91
}
92
92
Err(_) => {
···
98
98
99
99
## Authorization Flow
100
100
101
-
The `ResolvingAuthorization` extractor implements:
101
+
The `Authorization` extractor implements:
102
102
103
103
1. JWT extraction from HTTP Authorization headers
104
104
2. Token validation (signature and claims structure)
+5
-49
crates/atproto-xrpcs/src/errors.rs
+5
-49
crates/atproto-xrpcs/src/errors.rs
···
42
42
#[error("error-atproto-xrpcs-authorization-4 No issuer found in JWT claims")]
43
43
NoIssuerInClaims,
44
44
45
-
/// Occurs when DID document is not found for the issuer
46
-
#[error("error-atproto-xrpcs-authorization-5 DID document not found for issuer: {issuer}")]
47
-
DIDDocumentNotFound {
48
-
/// The issuer DID that was not found
49
-
issuer: String,
50
-
},
51
-
52
45
/// Occurs when no verification keys are found in DID document
53
-
#[error("error-atproto-xrpcs-authorization-6 No verification keys found in DID document")]
46
+
#[error("error-atproto-xrpcs-authorization-5 No verification keys found in DID document")]
54
47
NoVerificationKeys,
55
48
56
49
/// Occurs when JWT header cannot be base64 decoded
57
-
#[error("error-atproto-xrpcs-authorization-7 Failed to decode JWT header: {error}")]
50
+
#[error("error-atproto-xrpcs-authorization-6 Failed to decode JWT header: {error}")]
58
51
HeaderDecodeError {
59
52
/// The underlying base64 decode error
60
53
error: base64::DecodeError,
61
54
},
62
55
63
56
/// Occurs when JWT header cannot be parsed as JSON
64
-
#[error("error-atproto-xrpcs-authorization-8 Failed to parse JWT header: {error}")]
57
+
#[error("error-atproto-xrpcs-authorization-7 Failed to parse JWT header: {error}")]
65
58
HeaderParseError {
66
59
/// The underlying JSON parse error
67
60
error: serde_json::Error,
68
61
},
69
62
70
63
/// Occurs when JWT validation fails with all available keys
71
-
#[error("error-atproto-xrpcs-authorization-9 JWT validation failed with all available keys")]
64
+
#[error("error-atproto-xrpcs-authorization-8 JWT validation failed with all available keys")]
72
65
ValidationFailedAllKeys,
73
66
74
67
/// Occurs when subject resolution fails during DID document lookup
75
-
#[error("error-atproto-xrpcs-authorization-10 Subject resolution failed: {issuer} {error}")]
68
+
#[error("error-atproto-xrpcs-authorization-9 Subject resolution failed: {issuer} {error}")]
76
69
SubjectResolutionFailed {
77
70
/// The issuer that failed to resolve
78
71
issuer: String,
79
72
/// The underlying resolution error
80
-
error: anyhow::Error,
81
-
},
82
-
83
-
/// Occurs when DID document lookup fails after successful resolution
84
-
#[error(
85
-
"error-atproto-xrpcs-authorization-11 DID document not found for resolved issuer: {resolved_did}"
86
-
)]
87
-
ResolvedDIDDocumentNotFound {
88
-
/// The resolved DID that was not found in storage
89
-
resolved_did: String,
90
-
},
91
-
92
-
/// Occurs when PLC directory query fails
93
-
#[error("error-atproto-xrpcs-authorization-12 PLC directory query failed: {error}")]
94
-
PLCQueryFailed {
95
-
/// The underlying PLC query error
96
-
error: anyhow::Error,
97
-
},
98
-
99
-
/// Occurs when web DID query fails
100
-
#[error("error-atproto-xrpcs-authorization-13 Web DID query failed: {error}")]
101
-
WebDIDQueryFailed {
102
-
/// The underlying web DID query error
103
-
error: anyhow::Error,
104
-
},
105
-
106
-
/// Occurs when DID document storage operation fails
107
-
#[error("error-atproto-xrpcs-authorization-14 DID document storage failed: {error}")]
108
-
DocumentStorageFailed {
109
-
/// The underlying storage error
110
-
error: anyhow::Error,
111
-
},
112
-
113
-
/// Occurs when input parsing fails for resolved DID
114
-
#[error("error-atproto-xrpcs-authorization-15 Input parsing failed for resolved DID: {error}")]
115
-
InputParsingFailed {
116
-
/// The underlying parsing error
117
73
error: anyhow::Error,
118
74
},
119
75
}