/** Shape of the error object returned by the Better Auth client (`{ data, error }`). */ export interface AuthClientError { code?: string; message?: string; status?: number; statusText?: string; } export const GENERIC_ERROR = "Something went wrong on our side. Please try again in a moment."; /** Map Better Auth error codes to short, human, provider-agnostic messages. Never leaks raw server text. */ export function humanAuthError(error: AuthClientError | null | undefined, context: "login" | "signup" | "reset" | "forgot" | "verify" = "login"): string { if (!error) return GENERIC_ERROR; if (error.status === 429) return "Too many attempts. Please wait a minute and try again."; switch (error.code) { case "INVALID_EMAIL_OR_PASSWORD": case "INVALID_PASSWORD": case "USER_NOT_FOUND": return context === "login" ? "That email and password don't match." : "We couldn't find an account for that email."; case "INVALID_EMAIL": return "That doesn't look like a valid email address."; case "EMAIL_NOT_VERIFIED": return "Please verify your email before signing in."; case "USER_ALREADY_EXISTS": case "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL": return "An account with this email already exists. Try signing in instead."; case "PASSWORD_TOO_SHORT": return "Your password must be at least 10 characters."; case "PASSWORD_TOO_LONG": return "Your password must be 128 characters or fewer."; case "INVALID_TOKEN": return context === "verify" ? "This verification link is invalid or has already been used." : "This reset link is invalid or has already been used. Request a new one."; case "TOKEN_EXPIRED": return context === "verify" ? "This verification link has expired. Request a new one below." : "This reset link has expired. Request a new one."; case "FAILED_TO_CREATE_USER": case "FAILED_TO_CREATE_SESSION": case "FAILED_TO_UPDATE_USER": return GENERIC_ERROR; default: if (error.status && error.status >= 500) return GENERIC_ERROR; if (error.status === 0 || error.status === undefined) return "We couldn't reach the server. Check your connection and try again."; return GENERIC_ERROR; } } export function isValidEmail(email: string): boolean { return /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(email.trim()); } export const PASSWORD_MIN = 10; export const PASSWORD_MAX = 128; export function passwordLengthError(pw: string): string | null { if (pw.length < PASSWORD_MIN) return `Use at least ${PASSWORD_MIN} characters.`; if (pw.length > PASSWORD_MAX) return `Use at most ${PASSWORD_MAX} characters.`; return null; } /** Only allow same-origin relative paths for post-login redirects. */ export function safeNext(next: string | string[] | undefined): string { const v = Array.isArray(next) ? next[0] : next; if (!v || !v.startsWith("/") || v.startsWith("//") || v.startsWith("/\\")) return "/app/chat"; if (v.startsWith("/login") || v.startsWith("/signup")) return "/app/chat"; return v; }