1use serde::Deserialize;
2use std::fmt;
3use supabase_client_core::SupabaseError;
4
5#[derive(Debug, Clone, Deserialize)]
9pub struct GoTrueErrorResponse {
10 #[serde(default)]
11 pub error: Option<String>,
12 #[serde(default)]
13 pub error_description: Option<String>,
14 #[serde(default)]
15 pub msg: Option<String>,
16 #[serde(default)]
17 pub message: Option<String>,
18 #[serde(default)]
19 pub code: Option<i32>,
20 #[serde(default)]
21 pub error_code: Option<String>,
22}
23
24impl GoTrueErrorResponse {
25 pub fn error_message(&self) -> String {
27 self.msg
28 .as_deref()
29 .or(self.message.as_deref())
30 .or(self.error_description.as_deref())
31 .or(self.error.as_deref())
32 .unwrap_or("Unknown error")
33 .to_string()
34 }
35}
36
37#[derive(Debug, thiserror::Error)]
39pub enum AuthError {
40 #[error("HTTP error: {0}")]
42 Http(#[from] reqwest::Error),
43
44 #[error("Auth API error ({status}): {message}")]
46 Api {
47 status: u16,
48 message: String,
49 #[source]
50 error_code: Option<AuthErrorCode>,
51 },
52
53 #[error("Invalid auth configuration: {0}")]
55 InvalidConfig(String),
56
57 #[error("Session expired")]
59 SessionExpired,
60
61 #[error("No active session")]
63 NoSession,
64
65 #[error("Serialization error: {0}")]
67 Serialization(#[from] serde_json::Error),
68
69 #[error("URL parse error: {0}")]
71 UrlParse(#[from] url::ParseError),
72
73 #[error("Invalid token: {0}")]
75 InvalidToken(String),
76}
77
78#[derive(Debug, Clone, PartialEq, Eq)]
80pub enum AuthErrorCode {
81 InvalidCredentials,
82 UserNotFound,
83 UserAlreadyExists,
84 EmailNotConfirmed,
85 PhoneNotConfirmed,
86 SessionNotFound,
87 RefreshTokenNotFound,
88 OtpExpired,
89 OtpDisabled,
90 WeakPassword,
91 SamePassword,
92 ValidationFailed,
93 OverRequestRateLimit,
94 MfaFactorNameConflict,
96 MfaFactorNotFound,
97 MfaChallengeExpired,
98 MfaVerificationFailed,
99 MfaVerificationRejected,
100 MfaIpAddressMismatch,
101 MfaEnrollNotEnabled,
102 MfaVerifyNotEnabled,
103 SsoProviderNotFound,
105 SsoDomainAlreadyExists,
106 IdentityAlreadyExists,
108 IdentityNotFound,
109 ManualLinkingDisabled,
110 SingleIdentityNotDeletable,
111 OAuthClientNotFound,
113 OAuthClientAlreadyExists,
114 OAuthInvalidGrant,
115 Unknown(String),
116}
117
118impl fmt::Display for AuthErrorCode {
119 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120 match self {
121 Self::InvalidCredentials => write!(f, "invalid_credentials"),
122 Self::UserNotFound => write!(f, "user_not_found"),
123 Self::UserAlreadyExists => write!(f, "user_already_exists"),
124 Self::EmailNotConfirmed => write!(f, "email_not_confirmed"),
125 Self::PhoneNotConfirmed => write!(f, "phone_not_confirmed"),
126 Self::SessionNotFound => write!(f, "session_not_found"),
127 Self::RefreshTokenNotFound => write!(f, "refresh_token_not_found"),
128 Self::OtpExpired => write!(f, "otp_expired"),
129 Self::OtpDisabled => write!(f, "otp_disabled"),
130 Self::WeakPassword => write!(f, "weak_password"),
131 Self::SamePassword => write!(f, "same_password"),
132 Self::ValidationFailed => write!(f, "validation_failed"),
133 Self::OverRequestRateLimit => write!(f, "over_request_rate_limit"),
134 Self::MfaFactorNameConflict => write!(f, "mfa_factor_name_conflict"),
135 Self::MfaFactorNotFound => write!(f, "mfa_factor_not_found"),
136 Self::MfaChallengeExpired => write!(f, "mfa_challenge_expired"),
137 Self::MfaVerificationFailed => write!(f, "mfa_verification_failed"),
138 Self::MfaVerificationRejected => write!(f, "mfa_verification_rejected"),
139 Self::MfaIpAddressMismatch => write!(f, "mfa_ip_address_mismatch"),
140 Self::MfaEnrollNotEnabled => write!(f, "mfa_enroll_not_enabled"),
141 Self::MfaVerifyNotEnabled => write!(f, "mfa_verify_not_enabled"),
142 Self::SsoProviderNotFound => write!(f, "sso_provider_not_found"),
143 Self::SsoDomainAlreadyExists => write!(f, "sso_domain_already_exists"),
144 Self::IdentityAlreadyExists => write!(f, "identity_already_exists"),
145 Self::IdentityNotFound => write!(f, "identity_not_found"),
146 Self::ManualLinkingDisabled => write!(f, "manual_linking_disabled"),
147 Self::SingleIdentityNotDeletable => write!(f, "single_identity_not_deletable"),
148 Self::OAuthClientNotFound => write!(f, "oauth_client_not_found"),
149 Self::OAuthClientAlreadyExists => write!(f, "oauth_client_already_exists"),
150 Self::OAuthInvalidGrant => write!(f, "oauth_invalid_grant"),
151 Self::Unknown(code) => write!(f, "{}", code),
152 }
153 }
154}
155
156impl std::error::Error for AuthErrorCode {}
157
158impl From<&str> for AuthErrorCode {
159 fn from(s: &str) -> Self {
160 match s {
161 "invalid_credentials" => Self::InvalidCredentials,
162 "user_not_found" => Self::UserNotFound,
163 "user_already_exists" => Self::UserAlreadyExists,
164 "email_not_confirmed" => Self::EmailNotConfirmed,
165 "phone_not_confirmed" => Self::PhoneNotConfirmed,
166 "session_not_found" => Self::SessionNotFound,
167 "refresh_token_not_found" => Self::RefreshTokenNotFound,
168 "otp_expired" => Self::OtpExpired,
169 "otp_disabled" => Self::OtpDisabled,
170 "weak_password" => Self::WeakPassword,
171 "same_password" => Self::SamePassword,
172 "validation_failed" => Self::ValidationFailed,
173 "over_request_rate_limit" => Self::OverRequestRateLimit,
174 "mfa_factor_name_conflict" => Self::MfaFactorNameConflict,
175 "mfa_factor_not_found" => Self::MfaFactorNotFound,
176 "mfa_challenge_expired" => Self::MfaChallengeExpired,
177 "mfa_verification_failed" => Self::MfaVerificationFailed,
178 "mfa_verification_rejected" => Self::MfaVerificationRejected,
179 "mfa_ip_address_mismatch" => Self::MfaIpAddressMismatch,
180 "mfa_enroll_not_enabled" => Self::MfaEnrollNotEnabled,
181 "mfa_verify_not_enabled" => Self::MfaVerifyNotEnabled,
182 "sso_provider_not_found" => Self::SsoProviderNotFound,
183 "sso_domain_already_exists" => Self::SsoDomainAlreadyExists,
184 "identity_already_exists" => Self::IdentityAlreadyExists,
185 "identity_not_found" => Self::IdentityNotFound,
186 "manual_linking_disabled" => Self::ManualLinkingDisabled,
187 "single_identity_not_deletable" => Self::SingleIdentityNotDeletable,
188 "oauth_client_not_found" => Self::OAuthClientNotFound,
189 "oauth_client_already_exists" => Self::OAuthClientAlreadyExists,
190 "oauth_invalid_grant" => Self::OAuthInvalidGrant,
191 other => Self::Unknown(other.to_string()),
192 }
193 }
194}
195
196impl From<AuthError> for SupabaseError {
197 fn from(err: AuthError) -> Self {
198 SupabaseError::Auth(err.to_string())
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 #[test]
207 fn mfa_error_codes_roundtrip() {
208 let codes = [
209 ("mfa_factor_name_conflict", AuthErrorCode::MfaFactorNameConflict),
210 ("mfa_factor_not_found", AuthErrorCode::MfaFactorNotFound),
211 ("mfa_challenge_expired", AuthErrorCode::MfaChallengeExpired),
212 ("mfa_verification_failed", AuthErrorCode::MfaVerificationFailed),
213 ("mfa_verification_rejected", AuthErrorCode::MfaVerificationRejected),
214 ("mfa_ip_address_mismatch", AuthErrorCode::MfaIpAddressMismatch),
215 ("mfa_enroll_not_enabled", AuthErrorCode::MfaEnrollNotEnabled),
216 ("mfa_verify_not_enabled", AuthErrorCode::MfaVerifyNotEnabled),
217 ];
218 for (s, expected) in &codes {
219 let parsed: AuthErrorCode = (*s).into();
220 assert_eq!(parsed, *expected);
221 assert_eq!(parsed.to_string(), *s);
222 }
223 }
224
225 #[test]
226 fn sso_error_codes_roundtrip() {
227 let parsed: AuthErrorCode = "sso_provider_not_found".into();
228 assert_eq!(parsed, AuthErrorCode::SsoProviderNotFound);
229 assert_eq!(parsed.to_string(), "sso_provider_not_found");
230
231 let parsed: AuthErrorCode = "sso_domain_already_exists".into();
232 assert_eq!(parsed, AuthErrorCode::SsoDomainAlreadyExists);
233 }
234
235 #[test]
236 fn identity_error_codes_roundtrip() {
237 let codes = [
238 ("identity_already_exists", AuthErrorCode::IdentityAlreadyExists),
239 ("identity_not_found", AuthErrorCode::IdentityNotFound),
240 ("manual_linking_disabled", AuthErrorCode::ManualLinkingDisabled),
241 ("single_identity_not_deletable", AuthErrorCode::SingleIdentityNotDeletable),
242 ];
243 for (s, expected) in &codes {
244 let parsed: AuthErrorCode = (*s).into();
245 assert_eq!(parsed, *expected);
246 assert_eq!(parsed.to_string(), *s);
247 }
248 }
249
250 #[test]
251 fn oauth_error_codes_roundtrip() {
252 let codes = [
253 ("oauth_client_not_found", AuthErrorCode::OAuthClientNotFound),
254 ("oauth_client_already_exists", AuthErrorCode::OAuthClientAlreadyExists),
255 ("oauth_invalid_grant", AuthErrorCode::OAuthInvalidGrant),
256 ];
257 for (s, expected) in &codes {
258 let parsed: AuthErrorCode = (*s).into();
259 assert_eq!(parsed, *expected);
260 assert_eq!(parsed.to_string(), *s);
261 }
262 }
263}