Skip to main content

reifydb_auth/
error.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use reifydb_value::{
5	error::{Diagnostic, Error, IntoDiagnostic},
6	fragment::Fragment,
7};
8
9pub type Result<T> = std::result::Result<T, Error>;
10
11#[derive(Debug, thiserror::Error)]
12pub enum AuthError {
13	#[error("password is required for password authentication")]
14	PasswordRequired,
15
16	#[error("stored authentication is missing hash")]
17	MissingHash,
18
19	#[error("stored authentication is missing salt")]
20	MissingSalt,
21
22	#[error("stored authentication is missing token")]
23	MissingToken,
24
25	#[error("unknown authentication method: {method}")]
26	UnknownMethod {
27		method: String,
28	},
29
30	#[error("failed to serialize authentication properties: {reason}")]
31	SerializeProperties {
32		reason: String,
33	},
34
35	#[error("password hashing failed: {reason}")]
36	HashingFailed {
37		reason: String,
38	},
39
40	#[error("stored hash is invalid or corrupted: {reason}")]
41	InvalidHash {
42		reason: String,
43	},
44
45	#[error("password verification failed: {reason}")]
46	VerificationFailed {
47		reason: String,
48	},
49}
50
51#[derive(Debug, thiserror::Error)]
52pub enum SolanaError {
53	#[error("public key is required for solana authentication")]
54	MissingPublicKey,
55
56	#[error("invalid public key: {reason}")]
57	InvalidPublicKey {
58		reason: String,
59	},
60
61	#[error("invalid signature: {reason}")]
62	InvalidSignature {
63		reason: String,
64	},
65
66	#[error("challenge payload is missing the sign-in message")]
67	MissingChallengeMessage,
68}
69
70#[derive(Debug, thiserror::Error)]
71pub enum GithubError {
72	#[error("user_id is required for github authentication")]
73	MissingUserId,
74
75	#[error("invalid github user_id: {reason}")]
76	InvalidUserId {
77		reason: String,
78	},
79
80	#[error("github oauth code exchange failed: {reason}")]
81	ExchangeFailed {
82		reason: String,
83	},
84
85	#[error("github api request failed: {reason}")]
86	ApiFailed {
87		reason: String,
88	},
89}
90
91impl IntoDiagnostic for AuthError {
92	fn into_diagnostic(self) -> Diagnostic {
93		match self {
94			AuthError::PasswordRequired => Diagnostic {
95				code: "AU_001".to_string(),
96				rql: None,
97				message: "password is required for password authentication".to_string(),
98				fragment: Fragment::None,
99				label: Some("missing password".to_string()),
100				help: Some("provide a password in the authentication configuration".to_string()),
101				column: None,
102				notes: vec![],
103				cause: None,
104				operator_chain: None,
105			},
106
107			AuthError::MissingHash => Diagnostic {
108				code: "AU_002".to_string(),
109				rql: None,
110				message: "stored authentication is missing hash".to_string(),
111				fragment: Fragment::None,
112				label: Some("missing hash".to_string()),
113				help: Some("the stored authentication record is corrupted or incomplete".to_string()),
114				column: None,
115				notes: vec![],
116				cause: None,
117				operator_chain: None,
118			},
119
120			AuthError::MissingSalt => Diagnostic {
121				code: "AU_003".to_string(),
122				rql: None,
123				message: "stored authentication is missing salt".to_string(),
124				fragment: Fragment::None,
125				label: Some("missing salt".to_string()),
126				help: Some("the stored authentication record is corrupted or incomplete".to_string()),
127				column: None,
128				notes: vec![],
129				cause: None,
130				operator_chain: None,
131			},
132
133			AuthError::MissingToken => Diagnostic {
134				code: "AU_004".to_string(),
135				rql: None,
136				message: "stored authentication is missing token".to_string(),
137				fragment: Fragment::None,
138				label: Some("missing token".to_string()),
139				help: Some("the stored authentication record is corrupted or incomplete".to_string()),
140				column: None,
141				notes: vec![],
142				cause: None,
143				operator_chain: None,
144			},
145
146			AuthError::SerializeProperties {
147				reason,
148			} => Diagnostic {
149				code: "AU_006".to_string(),
150				rql: None,
151				message: format!("failed to serialize authentication properties: {}", reason),
152				fragment: Fragment::None,
153				label: Some("serialization failed".to_string()),
154				help: Some("ensure authentication properties are valid".to_string()),
155				column: None,
156				notes: vec![],
157				cause: None,
158				operator_chain: None,
159			},
160
161			AuthError::UnknownMethod {
162				method,
163			} => Diagnostic {
164				code: "AU_005".to_string(),
165				rql: None,
166				message: format!("unknown authentication method: {}", method),
167				fragment: Fragment::None,
168				label: Some("unknown method".to_string()),
169				help: Some("supported authentication methods are: password, token, solana, github"
170					.to_string()),
171				column: None,
172				notes: vec![],
173				cause: None,
174				operator_chain: None,
175			},
176
177			AuthError::HashingFailed {
178				reason,
179			} => Diagnostic {
180				code: "AU_007".to_string(),
181				rql: None,
182				message: format!("password hashing failed: {}", reason),
183				fragment: Fragment::None,
184				label: Some("hashing failed".to_string()),
185				help: Some("an internal error occurred during password hashing".to_string()),
186				column: None,
187				notes: vec![],
188				cause: None,
189				operator_chain: None,
190			},
191
192			AuthError::InvalidHash {
193				reason,
194			} => Diagnostic {
195				code: "AU_008".to_string(),
196				rql: None,
197				message: format!("stored hash is invalid or corrupted: {}", reason),
198				fragment: Fragment::None,
199				label: Some("invalid hash".to_string()),
200				help: Some("the stored authentication record is corrupted or incomplete".to_string()),
201				column: None,
202				notes: vec![],
203				cause: None,
204				operator_chain: None,
205			},
206
207			AuthError::VerificationFailed {
208				reason,
209			} => Diagnostic {
210				code: "AU_009".to_string(),
211				rql: None,
212				message: format!("password verification failed: {}", reason),
213				fragment: Fragment::None,
214				label: Some("verification failed".to_string()),
215				help: Some("an internal error occurred during password verification".to_string()),
216				column: None,
217				notes: vec![],
218				cause: None,
219				operator_chain: None,
220			},
221		}
222	}
223}
224
225impl From<AuthError> for Error {
226	fn from(err: AuthError) -> Self {
227		Error(Box::new(err.into_diagnostic()))
228	}
229}
230
231impl IntoDiagnostic for SolanaError {
232	fn into_diagnostic(self) -> Diagnostic {
233		match self {
234			SolanaError::MissingPublicKey => Diagnostic {
235				code: "AU_010".to_string(),
236				rql: None,
237				message: "public key is required for solana authentication".to_string(),
238				fragment: Fragment::None,
239				label: Some("missing public key".to_string()),
240				help: Some("provide a public_key in the authentication configuration".to_string()),
241				column: None,
242				notes: vec![],
243				cause: None,
244				operator_chain: None,
245			},
246
247			SolanaError::InvalidPublicKey {
248				reason,
249			} => Diagnostic {
250				code: "AU_011".to_string(),
251				rql: None,
252				message: format!("invalid public key: {}", reason),
253				fragment: Fragment::None,
254				label: Some("invalid public key".to_string()),
255				help: Some("provide a valid base58-encoded 32-byte Solana public key".to_string()),
256				column: None,
257				notes: vec![],
258				cause: None,
259				operator_chain: None,
260			},
261
262			SolanaError::InvalidSignature {
263				reason,
264			} => Diagnostic {
265				code: "AU_012".to_string(),
266				rql: None,
267				message: format!("invalid signature: {}", reason),
268				fragment: Fragment::None,
269				label: Some("invalid signature".to_string()),
270				help: Some("provide a valid base58-encoded 64-byte ed25519 signature".to_string()),
271				column: None,
272				notes: vec![],
273				cause: None,
274				operator_chain: None,
275			},
276
277			SolanaError::MissingChallengeMessage => Diagnostic {
278				code: "AU_017".to_string(),
279				rql: None,
280				message: "challenge payload is missing the sign-in message".to_string(),
281				fragment: Fragment::None,
282				label: Some("missing challenge message".to_string()),
283				help: Some("the stored sign-in challenge is corrupted or incomplete".to_string()),
284				column: None,
285				notes: vec![],
286				cause: None,
287				operator_chain: None,
288			},
289		}
290	}
291}
292
293impl From<SolanaError> for Error {
294	fn from(err: SolanaError) -> Self {
295		Error(Box::new(err.into_diagnostic()))
296	}
297}
298
299impl IntoDiagnostic for GithubError {
300	fn into_diagnostic(self) -> Diagnostic {
301		match self {
302			GithubError::MissingUserId => Diagnostic {
303				code: "AU_013".to_string(),
304				rql: None,
305				message: "user_id is required for github authentication".to_string(),
306				fragment: Fragment::None,
307				label: Some("missing user_id".to_string()),
308				help: Some("provide a user_id in the authentication configuration".to_string()),
309				column: None,
310				notes: vec![],
311				cause: None,
312				operator_chain: None,
313			},
314
315			GithubError::InvalidUserId {
316				reason,
317			} => Diagnostic {
318				code: "AU_014".to_string(),
319				rql: None,
320				message: format!("invalid github user_id: {}", reason),
321				fragment: Fragment::None,
322				label: Some("invalid user_id".to_string()),
323				help: Some("provide the numeric github account id as user_id".to_string()),
324				column: None,
325				notes: vec![],
326				cause: None,
327				operator_chain: None,
328			},
329
330			GithubError::ExchangeFailed {
331				reason,
332			} => Diagnostic {
333				code: "AU_015".to_string(),
334				rql: None,
335				message: format!("github oauth code exchange failed: {}", reason),
336				fragment: Fragment::None,
337				label: Some("code exchange failed".to_string()),
338				help: Some("verify the github oauth client id, client secret, and redirect uri"
339					.to_string()),
340				column: None,
341				notes: vec![],
342				cause: None,
343				operator_chain: None,
344			},
345
346			GithubError::ApiFailed {
347				reason,
348			} => Diagnostic {
349				code: "AU_016".to_string(),
350				rql: None,
351				message: format!("github api request failed: {}", reason),
352				fragment: Fragment::None,
353				label: Some("github api failed".to_string()),
354				help: Some("github may be unreachable; retry the sign-in".to_string()),
355				column: None,
356				notes: vec![],
357				cause: None,
358				operator_chain: None,
359			},
360		}
361	}
362}
363
364impl From<GithubError> for Error {
365	fn from(err: GithubError) -> Self {
366		Error(Box::new(err.into_diagnostic()))
367	}
368}