import { OidcError } from "../OidcPluginOptions"; /** * Standard OIDC error codes */ export const OidcErrorCodes = { // Configuration errors INVALID_PROVIDER: "invalid_provider", PROVIDER_NOT_CONFIGURED: "provider_not_configured", DISCOVERY_FAILED: "discovery_failed", // Request validation errors MISSING_CODE: "missing_code", MISSING_STATE: "missing_state", INVALID_STATE: "invalid_state", INVALID_RESPONSE_TYPE: "invalid_response_type", // Session errors SESSION_NOT_FOUND: "session_not_found", SESSION_EXPIRED: "session_expired", // Token exchange errors TOKEN_EXCHANGE_FAILED: "token_exchange_failed", INVALID_TOKEN: "invalid_token", ID_TOKEN_VALIDATION_FAILED: "id_token_validation_failed", // User/Profile errors USERINFO_FAILED: "userinfo_failed", PROFILE_EXTRACTION_FAILED: "profile_extraction_failed", // JWT generation errors JWT_GENERATION_FAILED: "jwt_generation_failed", // IdP errors (from authorization endpoint) ACCESS_DENIED: "access_denied", UNAUTHORIZED_CLIENT: "unauthorized_client", INVALID_REQUEST: "invalid_request", UNSUPPORTED_RESPONSE_TYPE: "unsupported_response_type", INVALID_SCOPE: "invalid_scope", SERVER_ERROR: "server_error", TEMPORARILY_UNAVAILABLE: "temporarily_unavailable", } as const; /** * Create a standardized OIDC error object * * @param code - Error code from OidcErrorCodes * @param message - Human-readable error message * @param details - Additional error details for debugging * @returns OIDC error object */ export function createOidcError(code: string, message: string, details?: any): OidcError { const error: OidcError = { code, message, }; if (details) { error.details = details; } // Make it throwable const throwableError = new Error(message) as Error & OidcError; throwableError.code = code; throwableError.details = details; return throwableError as any; } /** * Validate provider name format * * Provider names must be alphanumeric with optional hyphens/underscores * to ensure they work correctly in URLs. * * @param provider - Provider name to validate * @throws OidcError if invalid */ export function validateProvider(provider: string): void { if (!provider) { throw createOidcError(OidcErrorCodes.INVALID_PROVIDER, "Provider name is required", { provider }); } // Allow alphanumeric, hyphens, underscores if (!/^[a-zA-Z0-9_-]+$/.test(provider)) { throw createOidcError(OidcErrorCodes.INVALID_PROVIDER, "Provider name must be alphanumeric (hyphens and underscores allowed)", { provider }); } } /** * Validate response type parameter * * @param responseType - Response type from query parameter * @throws OidcError if invalid */ export function validateResponseType(responseType?: string): void { if (responseType && responseType !== "json") { throw createOidcError(OidcErrorCodes.INVALID_RESPONSE_TYPE, 'Invalid response_type. Must be "json" or omitted for redirect', { responseType, }); } } /** * Map IdP error codes to user-friendly messages * * Maps OAuth 2.0 / OIDC error codes from the IdP to our standardized * error format with helpful messages. * * @param error - Error object or string from IdP * @returns Standardized OIDC error */ export function handleProviderError(error: any): OidcError { const errorCode = typeof error === "string" ? error : error.error || error.code; const errorDescription = error.error_description || error.message; // Map common OAuth/OIDC errors switch (errorCode) { case "access_denied": return createOidcError(OidcErrorCodes.ACCESS_DENIED, "User denied authorization", { originalError: errorCode, description: errorDescription, }); case "unauthorized_client": return createOidcError(OidcErrorCodes.UNAUTHORIZED_CLIENT, "Client not authorized for this request", { originalError: errorCode, description: errorDescription, }); case "invalid_request": return createOidcError(OidcErrorCodes.INVALID_REQUEST, "Invalid authorization request", { originalError: errorCode, description: errorDescription, }); case "unsupported_response_type": return createOidcError(OidcErrorCodes.UNSUPPORTED_RESPONSE_TYPE, "Response type not supported by IdP", { originalError: errorCode, description: errorDescription, }); case "invalid_scope": return createOidcError(OidcErrorCodes.INVALID_SCOPE, "Invalid or unsupported scope", { originalError: errorCode, description: errorDescription, }); case "server_error": return createOidcError(OidcErrorCodes.SERVER_ERROR, "IdP server error", { originalError: errorCode, description: errorDescription, }); case "temporarily_unavailable": return createOidcError(OidcErrorCodes.TEMPORARILY_UNAVAILABLE, "IdP temporarily unavailable", { originalError: errorCode, description: errorDescription, }); default: return createOidcError(OidcErrorCodes.SERVER_ERROR, errorDescription || "Unknown IdP error", { originalError: errorCode, description: errorDescription, }); } }