* Resolves a human-friendly provider name to a providerId and returns a * browser-initiated authorize URL the user opens to connect the service. * * Steps: resolve provider → return the Sim `/api/auth/oauth2/authorize` URL. * That endpoint (not this server-side handler) creates the credential dr
( workspaceId: string | undefined, workflowId: string | undefined, chatId: string | undefined, providerName: string, baseUrl: string )
| 74 | * passes. |
| 75 | */ |
| 76 | async function generateOAuthLink( |
| 77 | workspaceId: string | undefined, |
| 78 | workflowId: string | undefined, |
| 79 | chatId: string | undefined, |
| 80 | providerName: string, |
| 81 | baseUrl: string |
| 82 | ): Promise<{ url: string; providerId: string; serviceName: string }> { |
| 83 | if (!workspaceId) { |
| 84 | throw new Error('workspaceId is required to generate an OAuth link') |
| 85 | } |
| 86 | |
| 87 | const allServices = getAllOAuthServices() |
| 88 | const normalizedInput = providerName.toLowerCase().trim() |
| 89 | |
| 90 | const matched = |
| 91 | allServices.find((s) => s.providerId === normalizedInput) || |
| 92 | allServices.find((s) => s.name.toLowerCase() === normalizedInput) || |
| 93 | allServices.find( |
| 94 | (s) => |
| 95 | s.name.toLowerCase().includes(normalizedInput) || |
| 96 | normalizedInput.includes(s.name.toLowerCase()) |
| 97 | ) || |
| 98 | allServices.find( |
| 99 | (s) => s.providerId.includes(normalizedInput) || normalizedInput.includes(s.providerId) |
| 100 | ) |
| 101 | |
| 102 | if (!matched) { |
| 103 | const available = allServices.map((s) => s.name).join(', ') |
| 104 | throw new Error(`Provider "${providerName}" not found. Available providers: ${available}`) |
| 105 | } |
| 106 | |
| 107 | const { providerId, name: serviceName } = matched |
| 108 | const callbackURL = |
| 109 | workflowId && workspaceId |
| 110 | ? `${baseUrl}/workspace/${workspaceId}/w/${workflowId}` |
| 111 | : chatId && workspaceId |
| 112 | ? `${baseUrl}/workspace/${workspaceId}/chat/${chatId}` |
| 113 | : `${baseUrl}/workspace/${workspaceId}` |
| 114 | |
| 115 | if (providerId === 'trello') { |
| 116 | return { url: `${baseUrl}/api/auth/trello/authorize`, providerId, serviceName } |
| 117 | } |
| 118 | if (providerId === 'shopify') { |
| 119 | const returnUrl = encodeURIComponent(callbackURL) |
| 120 | return { |
| 121 | url: `${baseUrl}/api/auth/shopify/authorize?returnUrl=${returnUrl}`, |
| 122 | providerId, |
| 123 | serviceName, |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | // Hand back a browser-initiated authorize URL rather than calling |
| 128 | // oAuth2LinkAccount here. Generating the link server-side would set Better |
| 129 | // Auth's signed `state` cookie on this server-to-server response instead of the |
| 130 | // user's browser, so the OAuth callback would fail with `state_mismatch`. The |
| 131 | // authorize endpoint runs the link inside the user's browser, planting the |
| 132 | // cookie correctly while keeping the callback's state check enabled. |
| 133 | // |
no test coverage detected