@djangocfg/api 2.1.566 → 2.1.569
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.cjs +98 -69
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +73 -3
- package/dist/auth.d.ts +73 -3
- package/dist/auth.mjs +98 -69
- package/dist/auth.mjs.map +1 -1
- package/package.json +2 -2
- package/src/_api/generated/_cfg_accounts/schemas/OAuthAuthorizeRequestRequest.ts +2 -2
- package/src/_api/generated/_cfg_accounts/schemas/OAuthAuthorizeResponse.ts +1 -1
- package/src/_api/generated/_cfg_accounts/schemas/OAuthCallbackRequestRequest.ts +1 -1
- package/src/_api/generated/_cfg_accounts/schemas/OAuthConnection.ts +2 -2
- package/src/_api/generated/_cfg_accounts/schemas/OTPRequestRequest.ts +1 -1
- package/src/_api/generated/_cfg_accounts/schemas/OTPVerifyRequest.ts +1 -1
- package/src/_api/generated/_cfg_accounts/schemas/User.ts +2 -2
- package/src/_api/generated/_cfg_accounts/schemas/WebmailLink.ts +1 -1
- package/src/_api/generated/_cfg_totp/schemas/TotpVerifyUser.ts +2 -2
- package/src/auth/__tests__/formatAuthError.test.ts +70 -6
- package/src/auth/__tests__/logAuthFailure.test.ts +89 -0
- package/src/auth/context/AuthContext.tsx +10 -17
- package/src/auth/hooks/twoFactor.shared.ts +5 -20
- package/src/auth/hooks/useGithubAuth.ts +3 -2
- package/src/auth/hooks/useTwoFactorSetup.ts +6 -5
- package/src/auth/hooks/useTwoFactorStatus.ts +6 -5
- package/src/auth/hooks/useTwoFactorVerify.ts +6 -5
- package/src/auth/utils/errors.ts +141 -7
- package/src/auth/utils/index.ts +8 -1
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
|
2807
|
-
const errorMessage =
|
|
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
|
|
2853
|
-
const errorMessage =
|
|
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
|
|
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
|
|
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 =
|
|
3402
|
-
authLogger
|
|
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 =
|
|
3443
|
-
authLogger
|
|
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 =
|
|
3495
|
-
authLogger
|
|
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 =
|
|
3517
|
-
authLogger
|
|
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 {
|
|
@@ -3933,8 +3982,8 @@ var OAuthConnectionSchema = z6.object({
|
|
|
3933
3982
|
provider: OAuthProviderEnumSchema,
|
|
3934
3983
|
provider_display: z6.string(),
|
|
3935
3984
|
provider_username: z6.string(),
|
|
3936
|
-
provider_email: z6.email(),
|
|
3937
|
-
provider_avatar_url: z6.url(),
|
|
3985
|
+
provider_email: z6.email().or(z6.literal("")),
|
|
3986
|
+
provider_avatar_url: z6.url().or(z6.literal("")),
|
|
3938
3987
|
connected_at: z6.string().datetime({ offset: true }),
|
|
3939
3988
|
last_login_at: z6.string().datetime({ offset: true })
|
|
3940
3989
|
});
|
|
@@ -3957,7 +4006,7 @@ import useSWRMutation5 from "swr/mutation";
|
|
|
3957
4006
|
// src/_api/generated/_cfg_accounts/schemas/OAuthAuthorizeResponse.ts
|
|
3958
4007
|
import { z as z9 } from "zod";
|
|
3959
4008
|
var OAuthAuthorizeResponseSchema = z9.object({
|
|
3960
|
-
authorization_url: z9.url(),
|
|
4009
|
+
authorization_url: z9.url().or(z9.literal("")),
|
|
3961
4010
|
state: z9.string()
|
|
3962
4011
|
});
|
|
3963
4012
|
|
|
@@ -4020,7 +4069,7 @@ var WebmailLinkProviderEnumSchema = z14.enum(["gmail", "outlook", "yahoo", "iclo
|
|
|
4020
4069
|
var WebmailLinkSchema = z15.object({
|
|
4021
4070
|
provider: WebmailLinkProviderEnumSchema,
|
|
4022
4071
|
provider_name: z15.string(),
|
|
4023
|
-
url: z15.url(),
|
|
4072
|
+
url: z15.url().or(z15.literal("")),
|
|
4024
4073
|
is_search: z15.boolean()
|
|
4025
4074
|
});
|
|
4026
4075
|
|
|
@@ -4087,7 +4136,7 @@ import { z as z18 } from "zod";
|
|
|
4087
4136
|
import { z as z17 } from "zod";
|
|
4088
4137
|
var UserSchema = z17.object({
|
|
4089
4138
|
id: z17.number().int(),
|
|
4090
|
-
email: z17.email(),
|
|
4139
|
+
email: z17.email().or(z17.literal("")),
|
|
4091
4140
|
first_name: z17.string().max(50).nullable().optional(),
|
|
4092
4141
|
last_name: z17.string().max(50).nullable().optional(),
|
|
4093
4142
|
full_name: z17.string(),
|
|
@@ -4098,7 +4147,7 @@ var UserSchema = z17.object({
|
|
|
4098
4147
|
position: z17.string().max(100).nullable().optional(),
|
|
4099
4148
|
language: z17.string().max(10).nullable().optional(),
|
|
4100
4149
|
timezone: z17.string().max(64).nullable().optional(),
|
|
4101
|
-
avatar: z17.url().nullable(),
|
|
4150
|
+
avatar: z17.url().or(z17.literal("")).nullable(),
|
|
4102
4151
|
is_staff: z17.boolean(),
|
|
4103
4152
|
is_superuser: z17.boolean(),
|
|
4104
4153
|
date_joined: z17.string().datetime({ offset: true }),
|
|
@@ -4432,8 +4481,8 @@ var CfgUserUpdateRequestSchema = z24.object({
|
|
|
4432
4481
|
// src/_api/generated/_cfg_accounts/schemas/OAuthAuthorizeRequestRequest.ts
|
|
4433
4482
|
import { z as z25 } from "zod";
|
|
4434
4483
|
var OAuthAuthorizeRequestRequestSchema = z25.object({
|
|
4435
|
-
redirect_uri: z25.url().optional(),
|
|
4436
|
-
source_url: z25.url().optional()
|
|
4484
|
+
redirect_uri: z25.url().or(z25.literal("")).optional(),
|
|
4485
|
+
source_url: z25.url().or(z25.literal("")).optional()
|
|
4437
4486
|
});
|
|
4438
4487
|
|
|
4439
4488
|
// src/_api/generated/_cfg_accounts/schemas/OAuthCallbackRequestRequest.ts
|
|
@@ -4441,7 +4490,7 @@ import { z as z26 } from "zod";
|
|
|
4441
4490
|
var OAuthCallbackRequestRequestSchema = z26.object({
|
|
4442
4491
|
code: z26.string().min(10).max(500),
|
|
4443
4492
|
state: z26.string().min(20).max(100),
|
|
4444
|
-
redirect_uri: z26.url().optional(),
|
|
4493
|
+
redirect_uri: z26.url().or(z26.literal("")).optional(),
|
|
4445
4494
|
remember_me: z26.boolean().default(false).optional()
|
|
4446
4495
|
});
|
|
4447
4496
|
|
|
@@ -4470,7 +4519,7 @@ var OTPErrorResponseSchema = z29.object({
|
|
|
4470
4519
|
import { z as z30 } from "zod";
|
|
4471
4520
|
var OTPRequestRequestSchema = z30.object({
|
|
4472
4521
|
identifier: z30.string().min(1),
|
|
4473
|
-
source_url: z30.url().optional(),
|
|
4522
|
+
source_url: z30.url().or(z30.literal("")).optional(),
|
|
4474
4523
|
marketing_consent: z30.boolean().nullable().optional(),
|
|
4475
4524
|
consent_disclosure_version: z30.string().max(64).optional()
|
|
4476
4525
|
});
|
|
@@ -4480,7 +4529,7 @@ import { z as z31 } from "zod";
|
|
|
4480
4529
|
var OTPVerifyRequestSchema = z31.object({
|
|
4481
4530
|
identifier: z31.string().min(1),
|
|
4482
4531
|
otp: z31.string().min(4).max(4),
|
|
4483
|
-
source_url: z31.url().optional(),
|
|
4532
|
+
source_url: z31.url().or(z31.literal("")).optional(),
|
|
4484
4533
|
remember_me: z31.boolean().default(false).optional()
|
|
4485
4534
|
});
|
|
4486
4535
|
|
|
@@ -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
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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,
|