@djangocfg/api 2.1.567 → 2.1.570

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/auth.d.cts CHANGED
@@ -1212,14 +1212,84 @@ declare const useDeleteAccount: () => UseDeleteAccountReturn;
1212
1212
  declare const validateEmail: (email: string) => boolean;
1213
1213
 
1214
1214
  /**
1215
- * Format authentication error for display.
1215
+ * Translation keys for the copy this module can choose.
1216
+ *
1217
+ * Every one already exists, translated, in all 17 locales of `@djangocfg/i18n`
1218
+ * — `api.errors.*` for transport conditions and `ui.errors.*` for the ones a
1219
+ * person can act on. Nothing is added here: a second key saying "Server is
1220
+ * unavailable" in a namespace of its own would be a second owner of the same
1221
+ * sentence, and the one that loses is whichever the caller did not use.
1222
+ *
1223
+ * A 404 maps to `serverUnavailable`, not to `ui.errors.notFound` ("Not found"):
1224
+ * on a sign-in POST the missing thing is the ENDPOINT, which tells the visitor
1225
+ * nothing about a page and everything about the service being down.
1226
+ */
1227
+ declare const AUTH_ERROR_KEYS: {
1228
+ readonly invalidCredentials: "ui.errors.unauthorized";
1229
+ readonly rateLimited: "api.errors.rateLimited";
1230
+ readonly serverUnavailable: "api.errors.serverUnavailable";
1231
+ readonly networkError: "api.errors.networkError";
1232
+ readonly generic: "ui.errors.generic";
1233
+ };
1234
+ type AuthErrorKey = (typeof AUTH_ERROR_KEYS)[keyof typeof AUTH_ERROR_KEYS];
1235
+ /** What the auth layer decided to show, and why. */
1236
+ interface AuthErrorCopy {
1237
+ /** Ready-to-render text: server prose, or this module's English fallback. */
1238
+ message: string;
1239
+ /**
1240
+ * Set only when `message` came from `FALLBACK_COPY`. A caller holding a
1241
+ * translator renders `t(key)` instead; server prose carries no key because
1242
+ * the backend already chose the words and the locale.
1243
+ */
1244
+ key?: AuthErrorKey;
1245
+ }
1246
+ /**
1247
+ * Decide what a failed auth call should say.
1248
+ *
1249
+ * Order: text the SERVER wrote, then copy chosen from the status code, then a
1250
+ * generic sentence. Server prose wins because the backend knows the specific
1251
+ * reason ("This code has expired") that a status code cannot express.
1216
1252
  *
1217
1253
  * Each candidate is checked to be a string before it is returned. Under the
1218
1254
  * previous `any` the check was only that the property was truthy, so a DRF
1219
1255
  * field-error object (`{detail: {email: [...]}}`) reached the UI as
1220
1256
  * "[object Object]" — an error message that tells the user nothing.
1257
+ *
1258
+ * `HTTP <code>:` strings are rejected on the way through. They are the
1259
+ * transport restating its own status line, not prose anyone wrote to be read,
1260
+ * and treating them as a message is what put a bare `HTTP 404:` under a
1261
+ * sign-in field. The code itself stays useful — `logAuthFailure` puts it in
1262
+ * the console, where diagnosis belongs.
1263
+ */
1264
+ declare const resolveAuthError: (error: unknown, fallback?: string) => AuthErrorCopy;
1265
+ /**
1266
+ * `resolveAuthError` for callers that only want the string.
1267
+ *
1268
+ * Kept because most call sites store a plain `error` string in form state and
1269
+ * have no translator to hand.
1270
+ */
1271
+ declare const formatAuthError: (error: unknown, fallback?: string) => string;
1272
+ /**
1273
+ * Log a failed auth call at the level its cause deserves.
1274
+ *
1275
+ * A 4xx on an auth endpoint is the USER getting it wrong — a mistyped OTP, an
1276
+ * expired code, a bad password. It is the endpoint working. Logged through
1277
+ * `console.error` it became a production alert: `@djangocfg/devtools` captures
1278
+ * `console.error` as an `ERROR` event, so every typo raised "❌ Error" in
1279
+ * Telegram, complete with consola's `%c` CSS in the message body. Real faults
1280
+ * drown in that.
1281
+ *
1282
+ * 5xx, a network failure and a broken response contract stay at `error`:
1283
+ * nobody can fix those by retyping, and they are what the alert is for.
1284
+ *
1285
+ * Both `statusCode` (what `APIError` declares) and `status` are read, because
1286
+ * a Response-shaped rejection can reach here from code that does not go
1287
+ * through the interceptor.
1221
1288
  */
1222
- declare const formatAuthError: (error: unknown) => string;
1289
+ declare const logAuthFailure: (log: {
1290
+ warn: (...args: unknown[]) => void;
1291
+ error: (...args: unknown[]) => void;
1292
+ }, message: string, error: unknown) => void;
1223
1293
 
1224
1294
  /**
1225
1295
  * Pure auth-guard state resolution.
@@ -1370,4 +1440,4 @@ declare const Analytics: {
1370
1440
  setUser(userId: string | null): void;
1371
1441
  };
1372
1442
 
1373
- export { AUTH_CONSTANTS, type AccountsContextValue, AccountsProvider, Analytics, AnalyticsCategory, type AnalyticsCategoryType, AnalyticsEvent, type AnalyticsEventType, type AnalyticsSink, type AuthConfig, AuthContext, type AuthContextType, type AuthFormAutoSubmit, type AuthFormReturn, type AuthFormState, type AuthFormStateHandlers, type AuthFormSubmitHandlers, type AuthFormValidation, AuthProvider, type AuthProviderProps, type AuthStep, type DeleteAccountResult, type GuardInput, type OTPRequestConsent, type OTPRequestResult, type PasswordLoginOptions, type PasswordLoginResult, type PatchedCfgUserUpdateRequest, PatchedCfgUserUpdateRequestSchema, type ProfileCacheOptions, type SessionSnapshot, type SessionStatus, type TwoFactorDevice, type TwoFactorSetupData, type UseAuthFormOptions, type UseAuthFormStateReturn, type UseAutoAuthOptions, type UseDeleteAccountReturn, type UseGithubAuthOptions, type UseGithubAuthReturn, type UseTwoFactorOptions, type UseTwoFactorReturn, type UseTwoFactorSetupOptions, type UseTwoFactorSetupReturn, type UseTwoFactorStatusReturn, type UseTwoFactorVerifyOptions, type UseTwoFactorVerifyReturn, type UserProfile, type WebmailLink, authLogger, clearProfileCache, consumeSavedRedirect, decodeBase64, encodeBase64, formatAuthError, getCacheMetadata, getCachedProfile, hasValidCache, isAllowedAuthPath, isForbidden, logger, normalizePath, peekSavedRedirect, resolveGuardIsAuthenticated, resolveGuardIsLoading, setAnalyticsSink, setCachedProfile, shouldRedirectToAuth, useAccountsContext, useAuth, useAuthForm, useAuthFormState, useAuthRedirectManager, useAuthValidation, useAutoAuth, useBase64, useCfgRouter, useDeleteAccount, useGithubAuth, useLocalStorage, useQueryParams, useSession, useSessionStorage, useTwoFactor, useTwoFactorSetup, useTwoFactorStatus, useTwoFactorVerify, validateEmail, validateIdentifier };
1443
+ export { AUTH_CONSTANTS, AUTH_ERROR_KEYS, type AccountsContextValue, AccountsProvider, Analytics, AnalyticsCategory, type AnalyticsCategoryType, AnalyticsEvent, type AnalyticsEventType, type AnalyticsSink, type AuthConfig, AuthContext, type AuthContextType, type AuthErrorCopy, type AuthErrorKey, type AuthFormAutoSubmit, type AuthFormReturn, type AuthFormState, type AuthFormStateHandlers, type AuthFormSubmitHandlers, type AuthFormValidation, AuthProvider, type AuthProviderProps, type AuthStep, type DeleteAccountResult, type GuardInput, type OTPRequestConsent, type OTPRequestResult, type PasswordLoginOptions, type PasswordLoginResult, type PatchedCfgUserUpdateRequest, PatchedCfgUserUpdateRequestSchema, type ProfileCacheOptions, type SessionSnapshot, type SessionStatus, type TwoFactorDevice, type TwoFactorSetupData, type UseAuthFormOptions, type UseAuthFormStateReturn, type UseAutoAuthOptions, type UseDeleteAccountReturn, type UseGithubAuthOptions, type UseGithubAuthReturn, type UseTwoFactorOptions, type UseTwoFactorReturn, type UseTwoFactorSetupOptions, type UseTwoFactorSetupReturn, type UseTwoFactorStatusReturn, type UseTwoFactorVerifyOptions, type UseTwoFactorVerifyReturn, type UserProfile, type WebmailLink, authLogger, clearProfileCache, consumeSavedRedirect, decodeBase64, encodeBase64, formatAuthError, getCacheMetadata, getCachedProfile, hasValidCache, isAllowedAuthPath, isForbidden, logAuthFailure, logger, normalizePath, peekSavedRedirect, resolveAuthError, resolveGuardIsAuthenticated, resolveGuardIsLoading, setAnalyticsSink, setCachedProfile, shouldRedirectToAuth, useAccountsContext, useAuth, useAuthForm, useAuthFormState, useAuthRedirectManager, useAuthValidation, useAutoAuth, useBase64, useCfgRouter, useDeleteAccount, useGithubAuth, useLocalStorage, useQueryParams, useSession, useSessionStorage, useTwoFactor, useTwoFactorSetup, useTwoFactorStatus, useTwoFactorVerify, validateEmail, validateIdentifier };
package/dist/auth.d.ts CHANGED
@@ -1212,14 +1212,84 @@ declare const useDeleteAccount: () => UseDeleteAccountReturn;
1212
1212
  declare const validateEmail: (email: string) => boolean;
1213
1213
 
1214
1214
  /**
1215
- * Format authentication error for display.
1215
+ * Translation keys for the copy this module can choose.
1216
+ *
1217
+ * Every one already exists, translated, in all 17 locales of `@djangocfg/i18n`
1218
+ * — `api.errors.*` for transport conditions and `ui.errors.*` for the ones a
1219
+ * person can act on. Nothing is added here: a second key saying "Server is
1220
+ * unavailable" in a namespace of its own would be a second owner of the same
1221
+ * sentence, and the one that loses is whichever the caller did not use.
1222
+ *
1223
+ * A 404 maps to `serverUnavailable`, not to `ui.errors.notFound` ("Not found"):
1224
+ * on a sign-in POST the missing thing is the ENDPOINT, which tells the visitor
1225
+ * nothing about a page and everything about the service being down.
1226
+ */
1227
+ declare const AUTH_ERROR_KEYS: {
1228
+ readonly invalidCredentials: "ui.errors.unauthorized";
1229
+ readonly rateLimited: "api.errors.rateLimited";
1230
+ readonly serverUnavailable: "api.errors.serverUnavailable";
1231
+ readonly networkError: "api.errors.networkError";
1232
+ readonly generic: "ui.errors.generic";
1233
+ };
1234
+ type AuthErrorKey = (typeof AUTH_ERROR_KEYS)[keyof typeof AUTH_ERROR_KEYS];
1235
+ /** What the auth layer decided to show, and why. */
1236
+ interface AuthErrorCopy {
1237
+ /** Ready-to-render text: server prose, or this module's English fallback. */
1238
+ message: string;
1239
+ /**
1240
+ * Set only when `message` came from `FALLBACK_COPY`. A caller holding a
1241
+ * translator renders `t(key)` instead; server prose carries no key because
1242
+ * the backend already chose the words and the locale.
1243
+ */
1244
+ key?: AuthErrorKey;
1245
+ }
1246
+ /**
1247
+ * Decide what a failed auth call should say.
1248
+ *
1249
+ * Order: text the SERVER wrote, then copy chosen from the status code, then a
1250
+ * generic sentence. Server prose wins because the backend knows the specific
1251
+ * reason ("This code has expired") that a status code cannot express.
1216
1252
  *
1217
1253
  * Each candidate is checked to be a string before it is returned. Under the
1218
1254
  * previous `any` the check was only that the property was truthy, so a DRF
1219
1255
  * field-error object (`{detail: {email: [...]}}`) reached the UI as
1220
1256
  * "[object Object]" — an error message that tells the user nothing.
1257
+ *
1258
+ * `HTTP <code>:` strings are rejected on the way through. They are the
1259
+ * transport restating its own status line, not prose anyone wrote to be read,
1260
+ * and treating them as a message is what put a bare `HTTP 404:` under a
1261
+ * sign-in field. The code itself stays useful — `logAuthFailure` puts it in
1262
+ * the console, where diagnosis belongs.
1263
+ */
1264
+ declare const resolveAuthError: (error: unknown, fallback?: string) => AuthErrorCopy;
1265
+ /**
1266
+ * `resolveAuthError` for callers that only want the string.
1267
+ *
1268
+ * Kept because most call sites store a plain `error` string in form state and
1269
+ * have no translator to hand.
1270
+ */
1271
+ declare const formatAuthError: (error: unknown, fallback?: string) => string;
1272
+ /**
1273
+ * Log a failed auth call at the level its cause deserves.
1274
+ *
1275
+ * A 4xx on an auth endpoint is the USER getting it wrong — a mistyped OTP, an
1276
+ * expired code, a bad password. It is the endpoint working. Logged through
1277
+ * `console.error` it became a production alert: `@djangocfg/devtools` captures
1278
+ * `console.error` as an `ERROR` event, so every typo raised "❌ Error" in
1279
+ * Telegram, complete with consola's `%c` CSS in the message body. Real faults
1280
+ * drown in that.
1281
+ *
1282
+ * 5xx, a network failure and a broken response contract stay at `error`:
1283
+ * nobody can fix those by retyping, and they are what the alert is for.
1284
+ *
1285
+ * Both `statusCode` (what `APIError` declares) and `status` are read, because
1286
+ * a Response-shaped rejection can reach here from code that does not go
1287
+ * through the interceptor.
1221
1288
  */
1222
- declare const formatAuthError: (error: unknown) => string;
1289
+ declare const logAuthFailure: (log: {
1290
+ warn: (...args: unknown[]) => void;
1291
+ error: (...args: unknown[]) => void;
1292
+ }, message: string, error: unknown) => void;
1223
1293
 
1224
1294
  /**
1225
1295
  * Pure auth-guard state resolution.
@@ -1370,4 +1440,4 @@ declare const Analytics: {
1370
1440
  setUser(userId: string | null): void;
1371
1441
  };
1372
1442
 
1373
- export { AUTH_CONSTANTS, type AccountsContextValue, AccountsProvider, Analytics, AnalyticsCategory, type AnalyticsCategoryType, AnalyticsEvent, type AnalyticsEventType, type AnalyticsSink, type AuthConfig, AuthContext, type AuthContextType, type AuthFormAutoSubmit, type AuthFormReturn, type AuthFormState, type AuthFormStateHandlers, type AuthFormSubmitHandlers, type AuthFormValidation, AuthProvider, type AuthProviderProps, type AuthStep, type DeleteAccountResult, type GuardInput, type OTPRequestConsent, type OTPRequestResult, type PasswordLoginOptions, type PasswordLoginResult, type PatchedCfgUserUpdateRequest, PatchedCfgUserUpdateRequestSchema, type ProfileCacheOptions, type SessionSnapshot, type SessionStatus, type TwoFactorDevice, type TwoFactorSetupData, type UseAuthFormOptions, type UseAuthFormStateReturn, type UseAutoAuthOptions, type UseDeleteAccountReturn, type UseGithubAuthOptions, type UseGithubAuthReturn, type UseTwoFactorOptions, type UseTwoFactorReturn, type UseTwoFactorSetupOptions, type UseTwoFactorSetupReturn, type UseTwoFactorStatusReturn, type UseTwoFactorVerifyOptions, type UseTwoFactorVerifyReturn, type UserProfile, type WebmailLink, authLogger, clearProfileCache, consumeSavedRedirect, decodeBase64, encodeBase64, formatAuthError, getCacheMetadata, getCachedProfile, hasValidCache, isAllowedAuthPath, isForbidden, logger, normalizePath, peekSavedRedirect, resolveGuardIsAuthenticated, resolveGuardIsLoading, setAnalyticsSink, setCachedProfile, shouldRedirectToAuth, useAccountsContext, useAuth, useAuthForm, useAuthFormState, useAuthRedirectManager, useAuthValidation, useAutoAuth, useBase64, useCfgRouter, useDeleteAccount, useGithubAuth, useLocalStorage, useQueryParams, useSession, useSessionStorage, useTwoFactor, useTwoFactorSetup, useTwoFactorStatus, useTwoFactorVerify, validateEmail, validateIdentifier };
1443
+ export { AUTH_CONSTANTS, AUTH_ERROR_KEYS, type AccountsContextValue, AccountsProvider, Analytics, AnalyticsCategory, type AnalyticsCategoryType, AnalyticsEvent, type AnalyticsEventType, type AnalyticsSink, type AuthConfig, AuthContext, type AuthContextType, type AuthErrorCopy, type AuthErrorKey, type AuthFormAutoSubmit, type AuthFormReturn, type AuthFormState, type AuthFormStateHandlers, type AuthFormSubmitHandlers, type AuthFormValidation, AuthProvider, type AuthProviderProps, type AuthStep, type DeleteAccountResult, type GuardInput, type OTPRequestConsent, type OTPRequestResult, type PasswordLoginOptions, type PasswordLoginResult, type PatchedCfgUserUpdateRequest, PatchedCfgUserUpdateRequestSchema, type ProfileCacheOptions, type SessionSnapshot, type SessionStatus, type TwoFactorDevice, type TwoFactorSetupData, type UseAuthFormOptions, type UseAuthFormStateReturn, type UseAutoAuthOptions, type UseDeleteAccountReturn, type UseGithubAuthOptions, type UseGithubAuthReturn, type UseTwoFactorOptions, type UseTwoFactorReturn, type UseTwoFactorSetupOptions, type UseTwoFactorSetupReturn, type UseTwoFactorStatusReturn, type UseTwoFactorVerifyOptions, type UseTwoFactorVerifyReturn, type UserProfile, type WebmailLink, authLogger, clearProfileCache, consumeSavedRedirect, decodeBase64, encodeBase64, formatAuthError, getCacheMetadata, getCachedProfile, hasValidCache, isAllowedAuthPath, isForbidden, logAuthFailure, logger, normalizePath, peekSavedRedirect, resolveAuthError, resolveGuardIsAuthenticated, resolveGuardIsLoading, setAnalyticsSink, setCachedProfile, shouldRedirectToAuth, useAccountsContext, useAuth, useAuthForm, useAuthFormState, useAuthRedirectManager, useAuthValidation, useAutoAuth, useBase64, useCfgRouter, useDeleteAccount, useGithubAuth, useLocalStorage, useQueryParams, useSession, useSessionStorage, useTwoFactor, useTwoFactorSetup, useTwoFactorStatus, useTwoFactorVerify, validateEmail, validateIdentifier };
package/dist/auth.mjs CHANGED
@@ -2496,6 +2496,67 @@ var Analytics = {
2496
2496
  }
2497
2497
  };
2498
2498
 
2499
+ // src/auth/utils/errors.ts
2500
+ var prop = /* @__PURE__ */ __name((value, key) => typeof value === "object" && value !== null ? value[key] : void 0, "prop");
2501
+ var TRANSPORT_FALLBACK = /^HTTP \d{3}:\s*/;
2502
+ var AUTH_ERROR_KEYS = {
2503
+ invalidCredentials: "ui.errors.unauthorized",
2504
+ rateLimited: "api.errors.rateLimited",
2505
+ serverUnavailable: "api.errors.serverUnavailable",
2506
+ networkError: "api.errors.networkError",
2507
+ generic: "ui.errors.generic"
2508
+ };
2509
+ var FALLBACK_COPY = {
2510
+ [AUTH_ERROR_KEYS.invalidCredentials]: "Please sign in to continue",
2511
+ [AUTH_ERROR_KEYS.rateLimited]: "Too many requests. Please wait.",
2512
+ [AUTH_ERROR_KEYS.serverUnavailable]: "Server is unavailable",
2513
+ [AUTH_ERROR_KEYS.networkError]: "Network error",
2514
+ [AUTH_ERROR_KEYS.generic]: "Something went wrong"
2515
+ };
2516
+ var keyForStatus = /* @__PURE__ */ __name((status) => {
2517
+ if (status === void 0) return AUTH_ERROR_KEYS.generic;
2518
+ if (status === 401 || status === 403) return AUTH_ERROR_KEYS.invalidCredentials;
2519
+ if (status === 404) return AUTH_ERROR_KEYS.serverUnavailable;
2520
+ if (status === 429) return AUTH_ERROR_KEYS.rateLimited;
2521
+ if (status >= 500) return AUTH_ERROR_KEYS.serverUnavailable;
2522
+ return AUTH_ERROR_KEYS.generic;
2523
+ }, "keyForStatus");
2524
+ var resolveAuthError = /* @__PURE__ */ __name((error, fallback) => {
2525
+ if (typeof error === "string" && error && !TRANSPORT_FALLBACK.test(error)) {
2526
+ return { message: error };
2527
+ }
2528
+ const candidates = [
2529
+ prop(error, "message"),
2530
+ prop(error, "detail"),
2531
+ prop(prop(error, "response"), "error"),
2532
+ prop(prop(error, "response"), "detail"),
2533
+ prop(prop(error, "response"), "message"),
2534
+ prop(prop(prop(error, "response"), "data"), "detail")
2535
+ ];
2536
+ for (const candidate of candidates) {
2537
+ if (typeof candidate === "string" && candidate && !TRANSPORT_FALLBACK.test(candidate)) {
2538
+ return { message: candidate };
2539
+ }
2540
+ }
2541
+ const rawStatus = prop(error, "statusCode") ?? prop(error, "status");
2542
+ const status = typeof rawStatus === "number" ? rawStatus : void 0;
2543
+ if (status === void 0 && fallback) {
2544
+ return { message: fallback };
2545
+ }
2546
+ const key = keyForStatus(status);
2547
+ return { message: FALLBACK_COPY[key], key };
2548
+ }, "resolveAuthError");
2549
+ var formatAuthError = /* @__PURE__ */ __name((error, fallback) => resolveAuthError(error, fallback).message, "formatAuthError");
2550
+ var logAuthFailure = /* @__PURE__ */ __name((log, message, error) => {
2551
+ const code = prop(error, "statusCode") ?? prop(error, "status");
2552
+ const isClientError = typeof code === "number" && code >= 400 && code < 500;
2553
+ if (isClientError) {
2554
+ log.warn(message, error);
2555
+ return;
2556
+ }
2557
+ log.error(message, error);
2558
+ }, "logAuthFailure");
2559
+
2499
2560
  // src/auth/hooks/useSessionStorage.ts
2500
2561
  import { useState as useState3 } from "react";
2501
2562
 
@@ -2716,18 +2777,6 @@ var useAuthRedirectManager = /* @__PURE__ */ __name((options = {}) => {
2716
2777
 
2717
2778
  // src/auth/hooks/twoFactor.shared.ts
2718
2779
  var TOTP_CODE_LENGTH = 6;
2719
- function extractTwoFactorError(err, fallback) {
2720
- if (err instanceof APIError) {
2721
- const body = err.response ?? null;
2722
- if (typeof body?.error === "string") return body.error;
2723
- if (typeof body?.detail === "string") return body.detail;
2724
- if (typeof body?.message === "string") return body.message;
2725
- return err.errorMessage ?? err.message;
2726
- }
2727
- if (err instanceof Error) return err.message;
2728
- return fallback;
2729
- }
2730
- __name(extractTwoFactorError, "extractTwoFactorError");
2731
2780
  function extractAttemptsRemaining(err) {
2732
2781
  if (err instanceof APIError) {
2733
2782
  const body = err.response ?? null;
@@ -2803,8 +2852,8 @@ var useTwoFactorVerify = /* @__PURE__ */ __name((options = {}) => {
2803
2852
  handleSuccess(response);
2804
2853
  return true;
2805
2854
  } catch (err) {
2806
- authLogger.error("2FA TOTP verification error:", err);
2807
- const errorMessage = extractTwoFactorError(err, "Invalid verification code");
2855
+ logAuthFailure(authLogger, "2FA TOTP verification failed:", err);
2856
+ const errorMessage = formatAuthError(err, "Invalid verification code");
2808
2857
  const attempts = extractAttemptsRemaining(err);
2809
2858
  if (attempts !== null) setAttemptsRemaining(attempts);
2810
2859
  setError(errorMessage);
@@ -2849,8 +2898,8 @@ var useTwoFactorVerify = /* @__PURE__ */ __name((options = {}) => {
2849
2898
  handleSuccess(response);
2850
2899
  return true;
2851
2900
  } catch (err) {
2852
- authLogger.error("2FA backup code verification error:", err);
2853
- const errorMessage = extractTwoFactorError(err, "Invalid backup code");
2901
+ logAuthFailure(authLogger, "2FA backup code verification failed:", err);
2902
+ const errorMessage = formatAuthError(err, "Invalid backup code");
2854
2903
  const attempts = extractAttemptsRemaining(err);
2855
2904
  if (attempts !== null) setAttemptsRemaining(attempts);
2856
2905
  setError(errorMessage);
@@ -3273,7 +3322,7 @@ var useGithubAuth = /* @__PURE__ */ __name((options = {}) => {
3273
3322
  }
3274
3323
  window.location.href = response.authorization_url;
3275
3324
  } catch (err) {
3276
- const errorMessage = err instanceof Error ? err.message : "Failed to start GitHub authentication";
3325
+ const errorMessage = formatAuthError(err, "Failed to start GitHub authentication");
3277
3326
  authLogger.error("GitHub OAuth start error:", err);
3278
3327
  setError(errorMessage);
3279
3328
  onError?.(errorMessage);
@@ -3336,7 +3385,7 @@ var useGithubAuth = /* @__PURE__ */ __name((options = {}) => {
3336
3385
  }
3337
3386
  return;
3338
3387
  } catch (err) {
3339
- const errorMessage = err instanceof Error ? err.message : "GitHub authentication failed";
3388
+ const errorMessage = formatAuthError(err, "GitHub authentication failed");
3340
3389
  authLogger.error("GitHub OAuth callback error:", err);
3341
3390
  setError(errorMessage);
3342
3391
  onError?.(errorMessage);
@@ -3398,8 +3447,8 @@ var useTwoFactorSetup = /* @__PURE__ */ __name((options = {}) => {
3398
3447
  authLogger.info("2FA setup initiated, expires in:", data.expiresIn, "seconds");
3399
3448
  return data;
3400
3449
  } catch (err) {
3401
- const errorMessage = extractTwoFactorError(err, "Failed to start 2FA setup");
3402
- authLogger.error("2FA setup error:", err);
3450
+ const errorMessage = formatAuthError(err, "Failed to start 2FA setup");
3451
+ logAuthFailure(authLogger, "2FA setup failed:", err);
3403
3452
  setError(errorMessage);
3404
3453
  setSetupStep("idle");
3405
3454
  onError?.(errorMessage);
@@ -3439,8 +3488,8 @@ var useTwoFactorSetup = /* @__PURE__ */ __name((options = {}) => {
3439
3488
  onComplete?.(codes);
3440
3489
  return codes;
3441
3490
  } catch (err) {
3442
- const errorMessage = extractTwoFactorError(err, "Invalid code. Please try again.");
3443
- authLogger.error("2FA setup confirmation error:", err);
3491
+ const errorMessage = formatAuthError(err, "Invalid code. Please try again.");
3492
+ logAuthFailure(authLogger, "2FA setup confirmation failed:", err);
3444
3493
  setError(errorMessage);
3445
3494
  setSetupStep("scanning");
3446
3495
  onError?.(errorMessage);
@@ -3491,8 +3540,8 @@ var useTwoFactorStatus = /* @__PURE__ */ __name(() => {
3491
3540
  setHas2FAEnabled(response.has_2fa_enabled);
3492
3541
  authLogger.info("2FA status:", response.has_2fa_enabled ? "enabled" : "disabled");
3493
3542
  } catch (err) {
3494
- const errorMessage = extractTwoFactorError(err, "Failed to fetch 2FA status");
3495
- authLogger.error("Failed to fetch 2FA status:", err);
3543
+ const errorMessage = formatAuthError(err, "Failed to fetch 2FA status");
3544
+ logAuthFailure(authLogger, "Failed to fetch 2FA status:", err);
3496
3545
  setError(errorMessage);
3497
3546
  } finally {
3498
3547
  setIsLoading(false);
@@ -3513,8 +3562,8 @@ var useTwoFactorStatus = /* @__PURE__ */ __name(() => {
3513
3562
  authLogger.info("2FA disabled successfully");
3514
3563
  return true;
3515
3564
  } catch (err) {
3516
- const errorMessage = extractTwoFactorError(err, "Invalid verification code");
3517
- authLogger.error("Failed to disable 2FA:", err);
3565
+ const errorMessage = formatAuthError(err, "Invalid verification code");
3566
+ logAuthFailure(authLogger, "Failed to disable 2FA:", err);
3518
3567
  setError(errorMessage);
3519
3568
  return false;
3520
3569
  } finally {
@@ -4700,10 +4749,6 @@ var resolveAuthRoutes = /* @__PURE__ */ __name((routes) => ({
4700
4749
  }), "resolveAuthRoutes");
4701
4750
  var EMAIL_STORAGE_KEY = "auth_email";
4702
4751
  var AuthContext = createContext2(void 0);
4703
- var apiErrorMessage = /* @__PURE__ */ __name((error) => {
4704
- const body = error.response;
4705
- return body?.error || body?.detail || body?.message || error.errorMessage;
4706
- }, "apiErrorMessage");
4707
4752
  var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
4708
4753
  const accounts = useAccountsContext();
4709
4754
  const session = useSession();
@@ -4831,18 +4876,18 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
4831
4876
  webmail: result.webmail ?? null
4832
4877
  };
4833
4878
  } catch (error) {
4834
- authLogger.error("Request OTP error:", error);
4879
+ logAuthFailure(authLogger, "Request OTP failed:", error);
4835
4880
  if (error instanceof APIError) {
4836
4881
  const body = error.response;
4837
4882
  const retryAfter = body?.retry_after ?? body?.retryAfter;
4838
4883
  return {
4839
4884
  success: false,
4840
4885
  statusCode: error.statusCode,
4841
- message: apiErrorMessage(error),
4886
+ message: formatAuthError(error),
4842
4887
  retryAfter: typeof retryAfter === "number" ? retryAfter : void 0
4843
4888
  };
4844
4889
  }
4845
- return { success: false, message: "Failed to send OTP" };
4890
+ return { success: false, message: formatAuthError(error, "Failed to send OTP") };
4846
4891
  }
4847
4892
  },
4848
4893
  []
@@ -4895,15 +4940,15 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
4895
4940
  should_prompt_2fa: result.should_prompt_2fa
4896
4941
  };
4897
4942
  } catch (error) {
4898
- authLogger.error("Verify OTP error:", error);
4943
+ logAuthFailure(authLogger, "Verify OTP failed:", error);
4899
4944
  Analytics.event("auth_otp_verify_fail" /* AUTH_OTP_VERIFY_FAIL */, {
4900
4945
  category: "auth" /* AUTH */,
4901
4946
  label: "email"
4902
4947
  });
4903
4948
  if (error instanceof APIError) {
4904
- return { success: false, message: apiErrorMessage(error) };
4949
+ return { success: false, message: formatAuthError(error) };
4905
4950
  }
4906
- return { success: false, message: "Failed to verify OTP" };
4951
+ return { success: false, message: formatAuthError(error, "Failed to verify OTP") };
4907
4952
  }
4908
4953
  },
4909
4954
  [setStoredEmail, redirectManager, router]
@@ -4954,7 +4999,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
4954
4999
  should_prompt_2fa: result.should_prompt_2fa
4955
5000
  };
4956
5001
  } catch (error) {
4957
- authLogger.error("Password login error:", error);
5002
+ logAuthFailure(authLogger, "Password login failed:", error);
4958
5003
  Analytics.event("auth_password_login_fail" /* AUTH_PASSWORD_LOGIN_FAIL */, {
4959
5004
  category: "auth" /* AUTH */,
4960
5005
  label: "password"
@@ -4964,12 +5009,12 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
4964
5009
  return {
4965
5010
  success: false,
4966
5011
  statusCode: error.statusCode,
4967
- message: apiErrorMessage(error),
5012
+ message: formatAuthError(error),
4968
5013
  errorCode: body?.error_code,
4969
5014
  retryAfter: typeof body?.retry_after === "number" ? body.retry_after : void 0
4970
5015
  };
4971
5016
  }
4972
- return { success: false, message: "Failed to sign in" };
5017
+ return { success: false, message: formatAuthError(error, "Failed to sign in") };
4973
5018
  }
4974
5019
  },
4975
5020
  [setStoredEmail, redirectManager, router]
@@ -5139,25 +5184,6 @@ var validateEmail = /* @__PURE__ */ __name((email) => {
5139
5184
  return emailRegex.test(email);
5140
5185
  }, "validateEmail");
5141
5186
 
5142
- // src/auth/utils/errors.ts
5143
- var prop = /* @__PURE__ */ __name((value, key) => typeof value === "object" && value !== null ? value[key] : void 0, "prop");
5144
- var formatAuthError = /* @__PURE__ */ __name((error) => {
5145
- if (typeof error === "string") {
5146
- return error;
5147
- }
5148
- const candidates = [
5149
- prop(error, "message"),
5150
- prop(error, "detail"),
5151
- prop(prop(prop(error, "response"), "data"), "detail")
5152
- ];
5153
- for (const candidate of candidates) {
5154
- if (typeof candidate === "string" && candidate) {
5155
- return candidate;
5156
- }
5157
- }
5158
- return "An unexpected error occurred";
5159
- }, "formatAuthError");
5160
-
5161
5187
  // src/auth/utils/guard.ts
5162
5188
  function isForbidden(s) {
5163
5189
  return Boolean(
@@ -5182,6 +5208,7 @@ function resolveGuardIsAuthenticated(s) {
5182
5208
  __name(resolveGuardIsAuthenticated, "resolveGuardIsAuthenticated");
5183
5209
  export {
5184
5210
  AUTH_CONSTANTS,
5211
+ AUTH_ERROR_KEYS,
5185
5212
  AccountsProvider,
5186
5213
  Analytics,
5187
5214
  AnalyticsCategory,
@@ -5200,9 +5227,11 @@ export {
5200
5227
  hasValidCache,
5201
5228
  isAllowedAuthPath,
5202
5229
  isForbidden,
5230
+ logAuthFailure,
5203
5231
  logger,
5204
5232
  normalizePath,
5205
5233
  peekSavedRedirect,
5234
+ resolveAuthError,
5206
5235
  resolveGuardIsAuthenticated,
5207
5236
  resolveGuardIsLoading,
5208
5237
  setAnalyticsSink,