/** * Shared OAuth utilities for app integrations */ import type { OAuthErrorCode } from "@midday/app-store/oauth-errors"; export type { OAuthErrorCode }; /** * Map OAuth errors from any provider to standardized error codes * * Handles errors from: * - Standard OAuth (access_denied, consent_required, etc.) * - Fortnox (error_missing_license, error_missing_system_admin_right, etc.) * - Google/Microsoft (scope errors) * - Xero/QuickBooks (various provider errors) */ export function mapOAuthError(error: string | undefined): OAuthErrorCode { if (!error) return "unknown_error"; const errorLower = error.toLowerCase(); // Access denied / User cancelled if ( errorLower.includes("access_denied") || errorLower.includes("user_denied") || errorLower.includes("consent") || errorLower.includes("cancelled") || errorLower.includes("canceled") ) { return "access_denied"; } // License errors (Fortnox) if ( errorLower.includes("missing_license") || errorLower.includes("missing_app_license") || errorLower.includes("license_required") || errorLower.includes("no_license") ) { return "missing_license"; } // Permission/Admin errors (Fortnox, general) if ( errorLower.includes("missing_system_admin") || errorLower.includes("admin_right") || errorLower.includes("insufficient_permission") || errorLower.includes("invalid_scope") || errorLower.includes("insufficient_scope") || errorLower.includes("scope") ) { return "missing_permissions"; } // State errors if ( errorLower.includes("invalid_state") || errorLower.includes("state_mismatch") || errorLower.includes("state_expired") ) { return "invalid_state"; } return "unknown_error"; } /** * Build success redirect URL for OAuth callback */ export function buildSuccessRedirect( dashboardUrl: string, provider: string, source?: string, fallbackPath = "/settings/apps", ): string { // For apps flow (popup), redirect to oauth-callback if (source === "apps") { return `${dashboardUrl}/oauth-callback?status=success`; } // For direct navigation flow, redirect to fallback path with success params const params = new URLSearchParams({ connected: "true", provider, }); return `${dashboardUrl}${fallbackPath}?${params.toString()}`; } /** * Build error redirect URL for OAuth callback */ export function buildErrorRedirect( dashboardUrl: string, errorCode: string, provider: string, source?: string, fallbackPath = "/settings/apps", ): string { // For apps flow (popup), redirect to oauth-callback to show error if (source === "apps") { const params = new URLSearchParams({ status: "error", error: errorCode, }); return `${dashboardUrl}/oauth-callback?${params.toString()}`; } // For direct navigation flow, redirect to fallback path with error params const params = new URLSearchParams({ connected: "false", error: errorCode, provider, }); return `${dashboardUrl}${fallbackPath}?${params.toString()}`; }