File size: 3,088 Bytes
c09f67c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/**
 * 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()}`;
}