systemprompt_security/
error.rs1use systemprompt_identifiers::PluginId;
16use thiserror::Error;
17
18#[derive(Debug, Error)]
19pub enum AuthError {
20 #[error("missing authorization header")]
21 MissingAuthorization,
22
23 #[error("invalid JWT token: {0}")]
24 InvalidToken(#[source] jsonwebtoken::errors::Error),
25
26 #[error("missing session_id in token")]
27 MissingSessionId,
28
29 #[error("hook token: missing or non-`hook` audience")]
30 HookAudienceMissing,
31
32 #[error("hook token: required scope `{0}` not present")]
33 HookScopeMissing(&'static str),
34
35 #[error("hook token: missing `plugin_id` claim")]
36 HookPluginIdMissing,
37
38 #[error(
39 "hook token: plugin_id `{actual}` in claim does not match request plugin_id `{expected}`"
40 )]
41 HookPluginIdMismatch {
42 expected: PluginId,
43 actual: PluginId,
44 },
45
46 #[error("token has unsupported algorithm `{got}`; only RS256 is accepted")]
47 UnsupportedAlgorithm { got: String },
48
49 #[error("audience policy is empty; token decoding requires at least one expected audience")]
50 EmptyAudiencePolicy,
51
52 #[error("token is missing `kid` header")]
53 MissingKid,
54
55 #[error("token `kid` `{0}` does not match any known signing key")]
56 UnknownKid(String),
57
58 #[error("signing key lookup failed: {0}")]
59 KeyLookup(#[source] crate::keys::authority::TokenAuthorityError),
60
61 #[error("issuer `{0}` is not trusted")]
62 UntrustedIssuer(String),
63
64 #[error("JWKS fetch failed for issuer `{issuer}`: {source}")]
65 JwksFetch {
66 issuer: String,
67 #[source]
68 source: crate::keys::JwksClientError,
69 },
70
71 #[error("token `act` delegation chain exceeds maximum depth of {max} (got {depth})")]
72 ActChainTooDeep { depth: usize, max: usize },
73
74 #[error("token is missing the `scope` claim")]
75 MissingScope,
76
77 #[error("token `sub` is not a valid user id: {0}")]
78 InvalidSubject(#[source] systemprompt_identifiers::error::IdValidationError),
79
80 #[error("token `user_type` claim `{claimed}` does not match permissions (derived `{derived}`)")]
81 UserTypeMismatch {
82 claimed: systemprompt_models::auth::UserType,
83 derived: systemprompt_models::auth::UserType,
84 },
85}
86
87impl AuthError {
88 #[must_use]
89 pub fn is_issuer_mismatch(&self) -> bool {
90 matches!(
91 self,
92 Self::InvalidToken(inner)
93 if matches!(inner.kind(), jsonwebtoken::errors::ErrorKind::InvalidIssuer)
94 )
95 }
96}
97
98#[derive(Debug, Error)]
99pub enum JwtError {
100 #[error("jwt encoding failed: {0}")]
101 Encoding(#[from] jsonwebtoken::errors::Error),
102
103 #[error("jwt signing key unavailable: {0}")]
104 Signing(#[source] crate::keys::authority::TokenAuthorityError),
105}
106
107#[derive(Debug, Error)]
108pub enum ManifestSigningError {
109 #[error("manifest signing seed unavailable: {0}")]
110 SeedUnavailable(#[source] systemprompt_config::SecretsBootstrapError),
111
112 #[error("jcs canonicalize: {0}")]
113 Canonicalize(#[source] serde_json::Error),
114
115 #[error("signing key missing after initialization")]
116 KeyMissing,
117
118 #[error("invalid base64 in {field}: {source}")]
119 InvalidBase64 {
120 field: &'static str,
121 #[source]
122 source: base64::DecodeError,
123 },
124
125 #[error("invalid ed25519 public key: {0}")]
126 InvalidPublicKey(#[source] ed25519_dalek::SignatureError),
127
128 #[error("{field} decoded to {actual} bytes, expected {expected}")]
129 InvalidKeyLength {
130 field: &'static str,
131 expected: usize,
132 actual: usize,
133 },
134
135 #[error("ed25519 signature verification failed")]
136 SignatureInvalid,
137}
138
139pub type AuthResult<T> = Result<T, AuthError>;
140
141pub type JwtResult<T> = Result<T, JwtError>;
142
143pub type ManifestSigningResult<T> = Result<T, ManifestSigningError>;