A better Rust ATProto crate
1use jacquard_common::session::SessionStoreError;
2use miette::Diagnostic;
3
4use crate::request::RequestError;
5use crate::resolver::ResolverError;
6
7/// High-level errors emitted by OAuth helpers.
8#[derive(Debug, thiserror::Error, Diagnostic)]
9#[non_exhaustive]
10pub enum OAuthError {
11 /// An error occurred during identity or metadata resolution.
12 #[error(transparent)]
13 #[diagnostic(code(jacquard_oauth::resolver))]
14 Resolver(#[from] ResolverError),
15
16 /// An error occurred while making an OAuth HTTP request.
17 #[error(transparent)]
18 #[diagnostic(code(jacquard_oauth::request))]
19 Request(#[from] RequestError),
20
21 /// An error occurred reading or writing session state.
22 #[error(transparent)]
23 #[diagnostic(code(jacquard_oauth::storage))]
24 Storage(#[from] SessionStoreError),
25
26 /// An error occurred during DPoP proof generation or validation.
27 #[error(transparent)]
28 #[diagnostic(code(jacquard_oauth::dpop))]
29 Dpop(#[from] crate::dpop::DpopError),
30
31 /// An error occurred with the client's key set.
32 #[error(transparent)]
33 #[diagnostic(code(jacquard_oauth::keyset))]
34 Keyset(#[from] crate::keyset::Error),
35
36 /// An ATProto-specific OAuth error (e.g. scope validation, client ID).
37 #[error(transparent)]
38 #[diagnostic(code(jacquard_oauth::atproto))]
39 Atproto(#[from] crate::atproto::Error),
40
41 /// An error occurred managing or refreshing an OAuth session.
42 #[error(transparent)]
43 #[diagnostic(code(jacquard_oauth::session))]
44 Session(#[from] crate::session::Error),
45
46 /// A JSON serialization or deserialization error.
47 #[error(transparent)]
48 #[diagnostic(code(jacquard_oauth::serde_json))]
49 SerdeJson(#[from] serde_json::Error),
50
51 /// A URI parse error.
52 #[error(transparent)]
53 #[diagnostic(code(jacquard_oauth::url))]
54 Url(#[from] jacquard_common::deps::fluent_uri::ParseError),
55
56 /// A form (URL-encoded) serialization error.
57 #[error(transparent)]
58 #[diagnostic(code(jacquard_oauth::form))]
59 Form(#[from] serde_html_form::ser::Error),
60
61 /// An error validating an authorization callback.
62 #[error(transparent)]
63 #[diagnostic(code(jacquard_oauth::callback))]
64 Callback(#[from] CallbackError),
65}
66
67/// Typed callback validation errors (redirect handling).
68#[derive(Debug, thiserror::Error, Diagnostic)]
69#[non_exhaustive]
70pub enum CallbackError {
71 /// The `state` parameter was absent from the authorization callback.
72 ///
73 /// State is required to prevent CSRF attacks per RFC 6749 §10.12.
74 #[error("missing state parameter in callback")]
75 #[diagnostic(code(jacquard_oauth::callback::missing_state))]
76 MissingState,
77 /// The `iss` (issuer) parameter was absent from the authorization callback.
78 ///
79 /// RFC 9207 requires `iss` to be present so that clients can reject
80 /// mix-up attacks from malicious authorization servers.
81 #[error("missing `iss` parameter")]
82 #[diagnostic(code(jacquard_oauth::callback::missing_iss))]
83 MissingIssuer,
84 /// The issuer in the callback did not match the expected authorization server.
85 #[error("issuer mismatch: expected {expected}, got {got}")]
86 #[diagnostic(code(jacquard_oauth::callback::issuer_mismatch))]
87 IssuerMismatch {
88 /// The issuer that was expected.
89 expected: String,
90 /// The issuer that was actually present in the callback.
91 got: String,
92 },
93 /// The authorization request timed out before a callback was received.
94 #[error("timeout")]
95 #[diagnostic(code(jacquard_oauth::callback::timeout))]
96 Timeout,
97}
98
99/// Convenience alias for `Result<T, OAuthError>`.
100pub type Result<T> = core::result::Result<T, OAuthError>;