skyauth 0.1.0

High-assurance, formally verified OAuth 2.1 and RFC 9449 DPoP authentication engine for the AT Protocol (Bluesky)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
//! Strongly-typed error definitions for `atproto-oauth`.
//!
//! This module provides the central [`AtprotoOAuthError`] hierarchy along with
//! specialized error types for cryptography, DPoP (RFC 9449), PKCE (RFC 7636),
//! and session/token validation.

use thiserror::Error;

/// Root error type encompassing all failure modes across the `atproto-oauth` library.
#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum AtprotoOAuthError {
    /// Low-level cryptographic primitive failure.
    #[error("Cryptographic error: {0}")]
    Crypto(#[from] CryptoError),

    /// RFC 9449 Demonstrating Proof-of-Possession (DPoP) failure.
    #[error("DPoP error: {0}")]
    DPoP(#[from] DPoPError),

    /// RFC 7636 Proof Key for Code Exchange (PKCE) failure.
    #[error("PKCE error: {0}")]
    Pkce(#[from] PkceError),

    /// Token, session, or authentication scheme validation failure.
    #[error("Token error: {0}")]
    Token(#[from] TokenError),

    /// RFC 9126 Pushed Authorization Requests (PAR) failure.
    #[error("PAR error: {0}")]
    Par(#[from] ParError),

    /// Server-Side Request Forgery (SSRF) security filter violation.
    #[error("SSRF error: {0}")]
    Ssrf(#[from] SsrfError),

    /// Decentralized identity and handle resolution error.
    #[error("Identity error: {0}")]
    Identity(#[from] IdentityError),

    /// OAuth 2.0 / RFC 8414 / RFC 9728 discovery error.
    #[error("Discovery error: {0}")]
    Discovery(#[from] DiscoveryError),

    /// State storage and persistence error.
    #[error("Store error: {0}")]
    Store(#[from] StoreError),

    /// Framework integration and extractor error.
    #[error("Integration error: {0}")]
    Integration(#[from] IntegrationError),
}

/// Errors originating from cryptographic operations and primitive transformations.
#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum CryptoError {
    /// Failure during ECDSA P-256 signature generation.
    #[error("ECDSA P-256 signature generation error: {0}")]
    EcdsaSign(String),

    /// Failure during ECDSA P-256 signature verification.
    #[error("ECDSA P-256 signature verification failed: {0}")]
    EcdsaVerify(String),

    /// The provided key data or PEM/DER encoding is invalid.
    #[error("Invalid cryptographic key: {0}")]
    InvalidKey(String),

    /// The elliptic curve point coordinates are invalid or not on curve NIST P-256.
    #[error("Invalid elliptic curve point: {0}")]
    InvalidPoint(String),

    /// Base64 decoding failed due to malformed characters or padding.
    #[error("Base64 decode error: {0}")]
    Base64Decode(String),

    /// JSON serialization or deserialization failed.
    #[error("JSON serialization/deserialization error: {0}")]
    Json(String),

    /// The cryptographic random number generator failed.
    #[error("Random number generator error: {0}")]
    Rng(String),

    /// HMAC key initialization or digest computation failed.
    #[error("HMAC error: {0}")]
    Hmac(String),

    /// PEM certificate/key decoding error.
    #[error("PEM decoding error: {0}")]
    Pem(String),
}

/// Errors arising from RFC 9449 DPoP proof generation, serialization, or verification.
#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum DPoPError {
    /// Malformed compact JWT structure (must contain exactly three period-separated parts).
    #[error("Malformed JWT structure: {0}")]
    MalformedJwt(String),

    /// Invalid JOSE header `typ` parameter (RFC 9449 § 4.2 requires `dpop+jwt`).
    #[error("Invalid JOSE header typ parameter: expected 'dpop+jwt', got '{0}'")]
    InvalidHeaderTyp(String),

    /// Unsupported JOSE header `alg` algorithm (must be `ES256`).
    #[error("Unsupported JOSE algorithm: expected 'ES256', got '{0}'")]
    UnsupportedAlgorithm(String),

    /// The JOSE header is missing the mandatory public JWK parameter.
    #[error("Missing JWK in JOSE header")]
    MissingJwk,

    /// The JWK in the JOSE header is invalid or contains unexpected parameters.
    #[error("Invalid JWK parameter: {0}")]
    InvalidJwk(String),

    /// Security violation: The JWK contains private key coordinates (RFC 9449 § 4.3 item 7).
    #[error("Security violation: JWK must not contain private key parameters")]
    PrivateKeyInJwk,

    /// A required claim was absent from the DPoP payload.
    #[error("Missing required DPoP claim: {0}")]
    MissingClaim(&'static str),

    /// The HTTP method in the `htm` claim does not match the actual HTTP request method.
    #[error("HTTP method mismatch: expected '{expected}', got '{actual}'")]
    MethodMismatch {
        /// The expected HTTP method.
        expected: String,
        /// The actual HTTP method found in the claim.
        actual: String,
    },

    /// The HTTP target URI in the `htu` claim does not match the actual target URI.
    #[error("HTTP URI mismatch: expected '{expected}', got '{actual}'")]
    UriMismatch {
        /// The expected normalized URI.
        expected: String,
        /// The actual normalized URI found in the claim.
        actual: String,
    },

    /// The target URI cannot be parsed or normalized according to RFC 3986 / RFC 9449.
    #[error("Invalid URI: {0}")]
    InvalidUri(String),

    /// The DPoP proof has expired according to its `exp` claim.
    #[error("DPoP proof expired: exp {exp} < now {now}")]
    ExpiredProof {
        /// Expiration timestamp in seconds since epoch.
        exp: u64,
        /// Current timestamp in seconds since epoch.
        now: u64,
    },

    /// The DPoP proof `iat` claim is too far in the future (exceeds clock skew allowance).
    #[error("DPoP proof creation time in future: iat {iat} > now {now} (+{leeway}s leeway)")]
    FutureProof {
        /// Creation timestamp in seconds since epoch.
        iat: u64,
        /// Current timestamp in seconds since epoch.
        now: u64,
        /// Allowed clock skew leeway in seconds.
        leeway: u64,
    },

    /// The proof age exceeds the maximum allowed age limit.
    #[error("DPoP proof too old: iat {iat} older than maximum age {max_age_secs}s at now {now}")]
    ProofTooOld {
        /// Creation timestamp in seconds since epoch.
        iat: u64,
        /// Current timestamp in seconds since epoch.
        now: u64,
        /// Maximum permitted proof age in seconds.
        max_age_secs: u64,
    },

    /// The `nonce` claim does not match the server-issued challenge nonce.
    #[error("DPoP nonce mismatch: expected '{expected}', got '{actual}'")]
    NonceMismatch {
        /// The expected nonce issued by the server.
        expected: String,
        /// The actual nonce provided in the proof.
        actual: String,
    },

    /// The server requires a DPoP nonce, but none was supplied in the proof.
    #[error("Missing server-required DPoP nonce")]
    MissingNonce,

    /// The access token hash (`ath`) claim does not match the SHA-256 hash of the presented access token.
    #[error("Access token hash (ath) mismatch: expected '{expected}', got '{actual}'")]
    AthMismatch {
        /// The expected access token hash.
        expected: String,
        /// The actual access token hash in the proof.
        actual: String,
    },

    /// An access token was presented against a protected resource without an `ath` claim in the DPoP proof.
    #[error("Missing access token hash (ath) claim for protected resource access")]
    MissingAth,

    /// The cryptographic ECDSA P-256 signature on the DPoP proof is invalid.
    #[error("Cryptographic signature verification failed")]
    SignatureVerificationFailed,

    /// A duplicate `jti` token identifier was presented, indicating a potential replay attack.
    #[error("DPoP proof replay detected: jti '{jti}' already consumed")]
    ReplayDetected {
        /// The duplicated JWT unique identifier.
        jti: String,
    },

    /// Automatic nonce retry loop exceeded the maximum allowed attempts (1 retry).
    #[error("DPoP nonce retry limit exceeded")]
    NonceRetryLimitExceeded,

    /// JSON or byte serialization failed.
    #[error("Serialization error: {0}")]
    Serialization(String),

    /// System clock error or excessive clock skew.
    #[error("Clock skew error: {0}")]
    ClockSkew(String),

    /// Underlying cryptographic primitive error.
    #[error("Crypto error: {0}")]
    Crypto(#[from] CryptoError),
}

/// Errors originating from RFC 7636 PKCE code verifier or challenge generation/verification.
#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum PkceError {
    /// The code verifier length is out of the RFC 7636 range (43 to 128 characters).
    #[error("Invalid code_verifier length: {len} (must be between {min} and {max} characters)")]
    InvalidVerifierLength {
        /// Provided verifier character length.
        len: usize,
        /// Minimum permitted length (43).
        min: usize,
        /// Maximum permitted length (128).
        max: usize,
    },

    /// The code verifier contains an illegal character outside `[A-Za-z0-9-._~]`.
    #[error("Invalid character '{char}' at position {position} in code_verifier")]
    InvalidVerifierCharacter {
        /// The forbidden character encountered.
        char: char,
        /// Zero-based byte index where the character was found.
        position: usize,
    },

    /// The code challenge length is invalid (RFC 7636 S256 requires 43 characters).
    #[error("Invalid code_challenge length: {len} (must be 43 characters)")]
    InvalidChallengeLength {
        /// Provided challenge character length.
        len: usize,
    },

    /// An unsupported transformation method was requested (only `S256` is permitted).
    #[error("Unsupported code_challenge_method: expected 'S256', got '{0}'")]
    UnsupportedMethod(String),

    /// The code verifier does not match the expected code challenge.
    #[error("PKCE code challenge verification failed")]
    ChallengeMismatch,

    /// Underlying cryptographic primitive error.
    #[error("Crypto error: {0}")]
    Crypto(#[from] CryptoError),
}

/// Errors related to access tokens, refresh tokens, and session credentials.
#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum TokenError {
    /// The HTTP Authorization header is missing.
    #[error("Missing Authorization header")]
    MissingHeader,

    /// The HTTP Authorization scheme is invalid (must be `DPoP` or `Bearer`).
    #[error("Invalid authentication scheme: expected '{expected}', got '{actual}'")]
    InvalidScheme {
        /// The expected scheme.
        expected: String,
        /// The actual scheme provided.
        actual: String,
    },

    /// The token has expired.
    #[error("Token expired: exp {exp} < now {now}")]
    Expired {
        /// Expiration timestamp in seconds since epoch.
        exp: u64,
        /// Current timestamp in seconds since epoch.
        now: u64,
    },

    /// The token is not yet valid (`nbf` claim in future).
    #[error("Token not yet valid: nbf {nbf} > now {now}")]
    NotYetValid {
        /// Not-before timestamp in seconds since epoch.
        nbf: u64,
        /// Current timestamp in seconds since epoch.
        now: u64,
    },

    /// The token audience does not match the expected client ID or resource server.
    #[error("Audience mismatch: expected '{expected}', got '{actual}'")]
    AudienceMismatch {
        /// Expected audience string.
        expected: String,
        /// Actual audience found in token.
        actual: String,
    },

    /// The token issuer does not match the expected authorization server.
    #[error("Issuer mismatch: expected '{expected}', got '{actual}'")]
    IssuerMismatch {
        /// Expected issuer string.
        expected: String,
        /// Actual issuer found in token.
        actual: String,
    },

    /// The cryptographic signature on the token is invalid.
    #[error("Invalid token signature")]
    InvalidSignature,

    /// The token is missing the required Decentralized Identifier (`did`) subject.
    #[error("Missing or invalid subject/issuer DID")]
    MissingDid,

    /// The server challenged the client with a DPoP nonce requirement (`use_dpop_nonce`).
    #[error("Server requires DPoP nonce challenge (use_dpop_nonce)")]
    UseDPoPNonce {
        /// New nonce value provided by the server, if any.
        nonce: Option<String>,
    },

    /// The token format or claims payload is malformed.
    #[error("Malformed token: {0}")]
    MalformedToken(String),

    /// Invalid token_type (must be case-insensitively "DPoP").
    #[error("Invalid token_type: expected 'DPoP', got '{0}'")]
    InvalidTokenType(String),

    /// A required field was missing from the token response.
    #[error("Missing required token response field: {0}")]
    MissingField(&'static str),

    /// The subject DID does not match the expected DID.
    #[error("Subject DID mismatch: expected '{expected}', got '{actual}'")]
    SubMismatch {
        /// Expected DID subject.
        expected: String,
        /// Actual DID subject in token response.
        actual: String,
    },

    /// The token response is missing the mandatory `atproto` scope.
    #[error("Token scope '{0}' is missing mandatory 'atproto' scope")]
    MissingAtprotoScope(String),

    /// The token endpoint rejected the request.
    #[error("Token request failed with HTTP status {status}: {error} ({description:?})")]
    RequestFailed {
        /// HTTP status code.
        status: u16,
        /// Error code returned by server (e.g. `invalid_grant`).
        error: String,
        /// Optional descriptive explanation.
        description: Option<String>,
    },

    /// The session does not have a refresh token to perform rotation.
    #[error("Session is missing a refresh token for rotation")]
    MissingRefreshToken,

    /// The callback state parameter is invalid or missing.
    #[error("Invalid or missing OAuth state parameter: {0}")]
    InvalidState(String),

    /// The OAuth state entry has expired.
    #[error("OAuth state entry has expired")]
    StateExpired,

    /// HTTP error during token exchange or refresh.
    #[error("HTTP error during token operation: {0}")]
    Http(String),

    /// JSON error during token operation.
    #[error("JSON error during token operation: {0}")]
    Json(String),

    /// DPoP error during token operation.
    #[error("DPoP error during token operation: {0}")]
    DPoP(#[from] DPoPError),

    /// SSRF error during token operation.
    #[error("SSRF error during token operation: {0}")]
    Ssrf(#[from] SsrfError),

    /// Underlying cryptographic primitive error.
    #[error("Crypto error: {0}")]
    Crypto(#[from] CryptoError),
}

/// Errors originating from RFC 9126 Pushed Authorization Requests (PAR).
#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum ParError {
    /// The authorization server rejected the PAR request with an HTTP error.
    #[error("PAR request failed with HTTP status {status}: {error} ({description:?})")]
    RequestFailed {
        /// HTTP status code.
        status: u16,
        /// Error code returned by server (e.g. `invalid_request`).
        error: String,
        /// Optional descriptive error explanation.
        description: Option<String>,
    },

    /// The PAR response is missing a required parameter.
    #[error("Missing required PAR response field: {0}")]
    MissingField(&'static str),

    /// The returned `request_uri` is invalid or malformed.
    #[error("Invalid request_uri returned from PAR: '{0}'")]
    InvalidRequestUri(String),

    /// The PAR endpoint URL is invalid or malformed.
    #[error("Invalid PAR endpoint URL: {0}")]
    InvalidEndpoint(String),

    /// Low-level HTTP transport or connection error.
    #[error("HTTP error during PAR: {0}")]
    Http(String),

    /// JSON serialization or deserialization error.
    #[error("JSON error during PAR: {0}")]
    Json(String),

    /// RFC 9449 DPoP proof error during PAR.
    #[error("DPoP error during PAR: {0}")]
    DPoP(#[from] DPoPError),

    /// SSRF security filter violation during PAR.
    #[error("SSRF violation during PAR: {0}")]
    Ssrf(#[from] SsrfError),
}

/// Errors arising from Server-Side Request Forgery (SSRF) and IP boundary filtering.
#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum SsrfError {
    /// An outbound request targeted a forbidden/restricted IP address.
    #[error("SSRF violation: blocked attempt to connect to restricted IP {0}")]
    BlockedIp(String),

    /// An outbound request targeted a forbidden cloud metadata or internal hostname.
    #[error("SSRF violation: blocked attempt to connect to restricted host '{0}'")]
    BlockedHost(String),

    /// An insecure URL scheme was encountered (HTTPS is required in production).
    #[error("SSRF violation: insecure URL scheme in '{0}' (HTTPS required)")]
    InsecureScheme(String),

    /// DNS resolution failed for the target hostname.
    #[error("DNS resolution failed: {0}")]
    DnsResolutionFailed(String),

    /// The provided URL is malformed or invalid.
    #[error("Invalid URL: {0}")]
    InvalidUrl(String),

    /// Redirect chain exceeded the maximum allowed depth limit.
    #[error("Too many redirects: exceeded maximum permitted redirect limit")]
    TooManyRedirects,

    /// Response body exceeded the maximum permitted size limit.
    #[error(
        "Response body too large: max {max_bytes} bytes allowed, received {actual_bytes} bytes"
    )]
    ResponseTooLarge {
        /// Maximum permitted byte size.
        max_bytes: usize,
        /// Actual received byte size.
        actual_bytes: usize,
    },

    /// HTTP response returned an unsuccessful status code.
    #[error("HTTP status {0}: {1}")]
    HttpStatus(u16, String),

    /// Low-level HTTP transport or connection error.
    #[error("HTTP transport error: {0}")]
    Http(String),

    /// I/O or network socket error.
    #[error("I/O error: {0}")]
    Io(String),

    /// JSON serialization or deserialization error.
    #[error("JSON error: {0}")]
    Json(String),
}

/// Errors originating from decentralized identity (DID) and handle resolution.
#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum IdentityError {
    /// Handle does not conform to ATProto syntax requirements.
    #[error("Invalid handle syntax: {0}")]
    InvalidHandleSyntax(String),

    /// Handle uses a disallowed or restricted top-level domain (TLD).
    #[error("Disallowed handle TLD: {0}")]
    DisallowedHandleTld(String),

    /// Handle resolution failed via all configured mechanisms.
    #[error("Handle resolution failed for '{0}'")]
    HandleResolutionFailed(String),

    /// DNS TXT resolution returned multiple conflicting DID records.
    #[error("Ambiguous handle resolution: multiple conflicting DIDs found for '{0}'")]
    AmbiguousHandleResolution(String),

    /// Resolved DID document `alsoKnownAs` does not match the claimed handle.
    #[error(
        "Bidirectional verification failed: handle '{0}' does not match DID document alsoKnownAs"
    )]
    HandleDidMismatch(String),

    /// The provided DID string is malformed or has an invalid syntax.
    #[error("Invalid DID syntax: {0}")]
    InvalidDidSyntax(String),

    /// The DID method is unsupported (only `did:plc` and `did:web` are supported).
    #[error("Unsupported DID method: {0}")]
    UnsupportedDidMethod(String),

    /// The queried DID was not found in the directory or host.
    #[error("DID not found: {0}")]
    DidNotFound(String),

    /// The returned DID document JSON is malformed or missing mandatory fields.
    #[error("Malformed DID document: {0}")]
    MalformedDidDocument(String),

    /// The DID document `id` field does not match the queried DID.
    #[error("DID document ID mismatch: expected '{expected}', found '{actual}'")]
    DidDocumentIdMismatch {
        /// The expected DID identifier.
        expected: String,
        /// The actual DID identifier in the document.
        actual: String,
    },

    /// The DID document is missing the mandatory `#atproto_pds` service endpoint.
    #[error("DID document missing '#atproto_pds' service endpoint for DID '{0}'")]
    MissingPdsEndpoint(String),

    /// The `#atproto_pds` service endpoint URL is invalid or malformed.
    #[error("Invalid PDS endpoint URL: {0}")]
    InvalidPdsEndpoint(String),

    /// SSRF security violation during identity resolution.
    #[error("SSRF violation during identity resolution: {0}")]
    Ssrf(#[from] SsrfError),

    /// HTTP error during identity resolution.
    #[error("HTTP error during identity resolution: {0}")]
    Http(String),

    /// JSON error during identity resolution.
    #[error("JSON error during identity resolution: {0}")]
    Json(String),

    /// DNS resolution error during handle resolution.
    #[error("DNS error during handle resolution: {0}")]
    Dns(String),
}

/// Errors originating from RFC 8414 and RFC 9728 OAuth discovery.
#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum DiscoveryError {
    /// Protected Resource Metadata discovery failed (RFC 9728).
    #[error("Protected resource metadata discovery failed: {0}")]
    ProtectedResourceDiscoveryFailed(String),

    /// Protected Resource Metadata does not list any authorization servers.
    #[error("No authorization servers listed in protected resource metadata for PDS '{0}'")]
    MissingAuthorizationServers(String),

    /// Authorization Server Metadata discovery failed (RFC 8414).
    #[error("Authorization server metadata discovery failed: {0}")]
    AuthServerDiscoveryFailed(String),

    /// Authorization Server Metadata `issuer` does not match the expected origin.
    #[error("Authorization server issuer mismatch: expected '{expected}', found '{actual}'")]
    IssuerMismatch {
        /// The expected authorization server origin.
        expected: String,
        /// The actual issuer string declared in the metadata.
        actual: String,
    },

    /// Authorization server is missing required `ES256` DPoP signing algorithm support.
    #[error("Authorization server '{0}' is missing required ES256 DPoP algorithm support")]
    MissingDpopAlgorithm(String),

    /// Authorization server is missing required `S256` PKCE code challenge method support.
    #[error("Authorization server '{0}' is missing required S256 PKCE method support")]
    MissingPkceMethod(String),

    /// Authorization server metadata is missing the required PAR endpoint.
    #[error(
        "Authorization server '{0}' is missing required pushed_authorization_request_endpoint"
    )]
    MissingParEndpoint(String),

    /// Authorization server metadata is missing the required token endpoint.
    #[error("Authorization server '{0}' is missing required token_endpoint")]
    MissingTokenEndpoint(String),

    /// Authorization server metadata is missing the required authorization endpoint.
    #[error("Authorization server '{0}' is missing required authorization_endpoint")]
    MissingAuthorizationEndpoint(String),

    /// An endpoint URL in the metadata is invalid.
    #[error("Invalid endpoint URL in discovery metadata: {0}")]
    InvalidEndpointUrl(String),

    /// Identity resolution error during discovery.
    #[error("Identity resolution error during discovery: {0}")]
    Identity(#[from] IdentityError),

    /// SSRF security violation during discovery.
    #[error("SSRF violation during discovery: {0}")]
    Ssrf(#[from] SsrfError),

    /// HTTP error during discovery.
    #[error("HTTP error during discovery: {0}")]
    Http(String),

    /// JSON error during discovery.
    #[error("JSON error during discovery: {0}")]
    Json(String),
}

/// Errors originating from OAuth state and session storage backends.
#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum StoreError {
    /// The requested authorization state token has expired.
    #[error("State token has expired: '{0}'")]
    StateExpired(String),

    /// The requested authorization state token was not found (or already consumed).
    #[error("State token not found or already consumed: '{0}'")]
    StateNotFound(String),

    /// An error occurred in the underlying storage backend.
    #[error("Storage backend error: {0}")]
    Backend(String),

    /// State serialization or deserialization failed.
    #[error("Serialization error: {0}")]
    Serialization(String),

    /// A lock acquisition or concurrency invariant violation occurred.
    #[error("Lock acquisition or concurrency error: {0}")]
    Lock(String),
}

/// Errors originating from web framework integrations and extractors.
#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum IntegrationError {
    /// The callback request query is missing the mandatory `code` parameter.
    #[error("Missing OAuth code parameter in callback query")]
    MissingCode,

    /// The callback request query is missing the mandatory `state` parameter.
    #[error("Missing OAuth state parameter in callback query")]
    MissingState,

    /// An OAuth error code and description returned by the authorization server.
    #[error("OAuth authorization server error: {error} ({description})")]
    OAuthError {
        /// Standard OAuth error code (e.g. `access_denied`, `invalid_request`).
        error: String,
        /// Optional human-readable error description.
        description: String,
    },

    /// The request is missing the required `Authorization` header.
    #[error("Missing or malformed Authorization header")]
    MissingAuthHeader,

    /// The `Authorization` header scheme is invalid (expected `DPoP`).
    #[error("Invalid Authorization header scheme: expected 'DPoP', got '{0}'")]
    InvalidAuthScheme(String),

    /// The request is missing the required `DPoP` proof header.
    #[error("Missing DPoP proof header")]
    MissingDPoPProofHeader,

    /// The requested authenticated session was not found or has expired.
    #[error("Session not found or expired")]
    SessionNotFound,

    /// Inbound request authentication failed.
    #[error("Authentication failed: {0}")]
    AuthFailed(String),

    /// Internal framework integration or response generation failure.
    #[error("Internal framework error: {0}")]
    Internal(String),

    /// Underlying store error.
    #[error("Store error: {0}")]
    Store(#[from] StoreError),

    /// Underlying DPoP validation error.
    #[error("DPoP error: {0}")]
    DPoP(#[from] DPoPError),

    /// Underlying token error.
    #[error("Token error: {0}")]
    Token(#[from] TokenError),
}