| export function decodeAuthMessage(raw: string | null): string | null { |
| if (!raw) return null; |
| try { |
| return decodeURIComponent(String(raw).replace(/\+/g, " ")); |
| } catch { |
| return raw; |
| } |
| } |
|
|
| function redactSensitiveAuthText(input: string): string { |
| let text = String(input || ""); |
| |
| text = text.replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9._-]+\.[A-Za-z0-9._-]+\b/g, "[redacted-jwt]"); |
| text = text.replace(/\bsk-[A-Za-z0-9_-]{20,}\b/g, "[redacted-key]"); |
| text = text.replace(/\b(Bearer)\s+[A-Za-z0-9\-._~+/=]+\b/gi, "$1 [redacted]"); |
| text = text.replace( |
| /\b(code|state|access_token|id_token|refresh_token)=([^&\s]+)/gi, |
| (_m, key) => `${String(key)}=[redacted]` |
| ); |
| return text; |
| } |
|
|
| function clampAuthText(input: string, maxLen: number = 360): string { |
| const text = String(input || ""); |
| if (text.length <= maxLen) return text; |
| return `${text.slice(0, maxLen).trimEnd()}...`; |
| } |
|
|
| export function toUserFacingAuthError(message: string): string { |
| const raw = String(message || "").replace(/\s+/g, " ").trim(); |
| if (!raw) return "Authentication error."; |
| if (/service not found:\s*https:\/\/masters-toolkit-api\/?/i.test(raw)) { |
| return "Auth0 was asked for legacy audience `https://masters-toolkit-api`, which this app no longer uses. Remove `VITE_AUTH0_AUDIENCE`/`AUTH0_AUDIENCE` unless you have a real Auth0 API Identifier configured."; |
| } |
| if (/invalid state/i.test(raw)) { |
| return "Invalid state. Your login session expired or became stale. Retry login to start a fresh session."; |
| } |
| if (/^access_denied$/i.test(raw)) { |
| return "Access denied by Auth0 policy. Confirm allowed email domains and API/application access, then retry login."; |
| } |
| if (/^invalid_request$/i.test(raw)) { |
| return "Authentication request is invalid. Check callback URL and Auth0 application settings. If this deployment does not use a custom API, leave `VITE_AUTH0_AUDIENCE` unset."; |
| } |
| if (/^unauthorized_client$/i.test(raw)) { |
| return "Auth0 client is not authorized for this request. Verify callback URLs and API Application Access settings."; |
| } |
| return clampAuthText(redactSensitiveAuthText(raw)); |
| } |
|
|
| function parseAuthCallbackParams(urlValue: string): { err: string | null; desc: string | null } { |
| const url = new URL(String(urlValue || "")); |
| const errRaw = url.searchParams.get("error"); |
| const descRaw = url.searchParams.get("error_description"); |
| if (errRaw || descRaw) { |
| return { err: decodeAuthMessage(errRaw), desc: decodeAuthMessage(descRaw) }; |
| } |
|
|
| const hash = String(url.hash || "").replace(/^#/, "").trim(); |
| if (!hash || !hash.includes("=")) { |
| return { err: null, desc: null }; |
| } |
| const hp = new URLSearchParams(hash); |
| const errHash = hp.get("error"); |
| const descHash = hp.get("error_description"); |
| return { err: decodeAuthMessage(errHash), desc: decodeAuthMessage(descHash) }; |
| } |
|
|
| export function getCallbackErrorFromUrl(urlValue: string): string | null { |
| try { |
| const { err, desc } = parseAuthCallbackParams(urlValue); |
| if (!err && !desc) return null; |
| const combined = toUserFacingAuthError(String(desc || err || "")); |
| return combined || null; |
| } catch { |
| return null; |
| } |
| } |
|
|
| export function getAuthErrorMessage(error: unknown): string { |
| if (!error) return ""; |
| if (typeof error === "string") return clampAuthText(redactSensitiveAuthText(error)); |
| const obj = error as Record<string, unknown>; |
|
|
| const candidates: unknown[] = [ |
| obj.error_description, |
| obj.description, |
| obj.message, |
| obj.error, |
| ]; |
| for (const c of candidates) { |
| const text = String(c || "").trim(); |
| if (text) return clampAuthText(redactSensitiveAuthText(text)); |
| } |
|
|
| |
| const cause = obj.cause as Record<string, unknown> | undefined; |
| if (cause && typeof cause === "object") { |
| for (const c of [cause.error_description, cause.description, cause.message, cause.error]) { |
| const text = String(c || "").trim(); |
| if (text) return clampAuthText(redactSensitiveAuthText(text)); |
| } |
| } |
| return ""; |
| } |
|
|