From b1fadd7ec7a4a4b651bb136aa9345c9adcca2a01 Mon Sep 17 00:00:00 2001 From: Samuel Bodin <1637651+bodinsamuel@users.noreply.github.com> Date: Tue, 26 Nov 2024 20:07:20 +0100 Subject: [PATCH 1/3] fix(ui): add missing page titles (#3066) # Changes Fixes https://linear.app/nango/issue/NAN-2254/add-missing-title-for-all-pages - Add missing page titles - Consolidate error handling --- .../webapp/src/components/ErrorComponent.tsx | 4 + .../src/pages/Account/ForgotPassword.tsx | 4 + .../src/pages/Account/ResetPassword.tsx | 4 + packages/webapp/src/pages/Account/Signin.tsx | 174 +++++++++--------- packages/webapp/src/pages/Account/Signup.tsx | 4 + .../webapp/src/pages/Connection/Create.tsx | 4 + packages/webapp/src/pages/Connection/List.tsx | 22 +-- packages/webapp/src/pages/Connection/Show.tsx | 8 +- .../webapp/src/pages/Environment/Settings.tsx | 4 + packages/webapp/src/pages/Homepage/Show.tsx | 7 + .../webapp/src/pages/Integrations/Create.tsx | 4 + .../webapp/src/pages/Integrations/List.tsx | 16 +- .../Integrations/providerConfigKey/Show.tsx | 27 +-- packages/webapp/src/pages/Logs/Search.tsx | 37 ++-- packages/webapp/src/pages/Team/Settings.tsx | 23 +-- packages/webapp/src/pages/User/Settings.tsx | 23 +-- 16 files changed, 195 insertions(+), 170 deletions(-) diff --git a/packages/webapp/src/components/ErrorComponent.tsx b/packages/webapp/src/components/ErrorComponent.tsx index d0536b728ce..362c0e659ed 100644 --- a/packages/webapp/src/components/ErrorComponent.tsx +++ b/packages/webapp/src/components/ErrorComponent.tsx @@ -3,6 +3,7 @@ import PageNotFound from '../pages/PageNotFound'; import DashboardLayout from '../layout/DashboardLayout'; import { Info } from './Info'; import type { LeftNavBarItems } from './LeftNavBar'; +import { Helmet } from 'react-helmet'; export const ErrorPageComponent: React.FC<{ title: string; error: ApiError; page: LeftNavBarItems }> = ({ title, error, page }) => { if (error.error.code === 'not_found') { @@ -11,6 +12,9 @@ export const ErrorPageComponent: React.FC<{ title: string; error: ApiError + + Error - Nango +

{title}

An error occurred, refresh your page or reach out to the support.{' '} diff --git a/packages/webapp/src/pages/Account/ForgotPassword.tsx b/packages/webapp/src/pages/Account/ForgotPassword.tsx index 27afc47e8dc..4729f19575f 100644 --- a/packages/webapp/src/pages/Account/ForgotPassword.tsx +++ b/packages/webapp/src/pages/Account/ForgotPassword.tsx @@ -5,6 +5,7 @@ import { useRequestPasswordResetAPI } from '../../utils/api'; import DefaultLayout from '../../layout/DefaultLayout'; import { Input } from '../../components/ui/input/Input'; import Button from '../../components/ui/button/Button'; +import { Helmet } from 'react-helmet'; export default function Signin() { const requestPasswordResetAPI = useRequestPasswordResetAPI(); @@ -34,6 +35,9 @@ export default function Signin() { return ( + + Forgot Password - Nango +

Request password reset

diff --git a/packages/webapp/src/pages/Account/ResetPassword.tsx b/packages/webapp/src/pages/Account/ResetPassword.tsx index 78eb954002e..77a9101ba6c 100644 --- a/packages/webapp/src/pages/Account/ResetPassword.tsx +++ b/packages/webapp/src/pages/Account/ResetPassword.tsx @@ -6,6 +6,7 @@ import { useResetPasswordAPI } from '../../utils/api'; import DefaultLayout from '../../layout/DefaultLayout'; import { Password } from './components/Password'; import Button from '../../components/ui/button/Button'; +import { Helmet } from 'react-helmet'; export default function ResetPassword() { const resetPasswordAPI = useResetPasswordAPI(); @@ -40,6 +41,9 @@ export default function ResetPassword() { return ( + + Reset Password - Nango +

Reset password

diff --git a/packages/webapp/src/pages/Account/Signin.tsx b/packages/webapp/src/pages/Account/Signin.tsx index 7e17ab07de4..63157db5d43 100644 --- a/packages/webapp/src/pages/Account/Signin.tsx +++ b/packages/webapp/src/pages/Account/Signin.tsx @@ -8,6 +8,7 @@ import DefaultLayout from '../../layout/DefaultLayout'; import GoogleButton from '../../components/ui/button/Auth/Google'; import Button from '../../components/ui/button/Button'; import { globalEnv } from '../../utils/env'; +import { Helmet } from 'react-helmet'; export default function Signin() { const [serverErrorMessage, setServerErrorMessage] = useState(''); @@ -67,104 +68,105 @@ export default function Signin() { }; return ( - <> - -
-
-

Log in to Nango

-
-
-
- -
+ + + Login - Nango + +
+
+

Log in to Nango

+ +
+
+
+
-
- -
- +
+
+
+ + Forgot your password? +
- -
- - {serverErrorMessage && ( - <> -

{serverErrorMessage}

- {showResendEmail && ( - - )} - - )} +
+
+
- {globalEnv.features.managedAuth && ( +
+ + {serverErrorMessage && ( <> -
-
- or continue with -
-
- - +

{serverErrorMessage}

+ {showResendEmail && ( + + )} )} - -
-
-
-

Don't have an account?

- - Sign up. -
+ + {globalEnv.features.managedAuth && ( + <> +
+
+ or continue with +
+
+ + + + )} + +
+
+
+

Don't have an account?

+ + Sign up. +
-
-
-

- By signing in, you agree to our - - Terms of Service - - and - - Privacy Policy - - . -

-
+
+
+
+

+ By signing in, you agree to our + + Terms of Service + + and + + Privacy Policy + + . +

- - +
+ ); } diff --git a/packages/webapp/src/pages/Account/Signup.tsx b/packages/webapp/src/pages/Account/Signup.tsx index 8a08077a561..913e724e224 100644 --- a/packages/webapp/src/pages/Account/Signup.tsx +++ b/packages/webapp/src/pages/Account/Signup.tsx @@ -1,9 +1,13 @@ +import { Helmet } from 'react-helmet'; import DefaultLayout from '../../layout/DefaultLayout'; import { SignupForm } from './components/SignupForm'; export const Signup: React.FC = () => { return ( + + Signup - Nango +
diff --git a/packages/webapp/src/pages/Connection/Create.tsx b/packages/webapp/src/pages/Connection/Create.tsx index a08fcf4cb83..5e555089bf1 100644 --- a/packages/webapp/src/pages/Connection/Create.tsx +++ b/packages/webapp/src/pages/Connection/Create.tsx @@ -20,6 +20,7 @@ import SecretTextArea from '../../components/ui/input/SecretTextArea'; import { useStore } from '../../store'; import type { AuthModeType } from '@nangohq/types'; import { useEnvironment } from '../../hooks/useEnvironment'; +import { Helmet } from 'react-helmet'; export default function IntegrationCreate() { const { mutate } = useSWRConfig(); @@ -625,6 +626,9 @@ nango.${integration?.authMode === 'NONE' ? 'create' : 'auth'}('${integration?.un return ( + + Create Connection - Nango + {integrations && !!integrations.length && publicKey && hostUrl && (

Add New Connection

diff --git a/packages/webapp/src/pages/Connection/List.tsx b/packages/webapp/src/pages/Connection/List.tsx index 3ad2a4ddf98..89f1697c436 100644 --- a/packages/webapp/src/pages/Connection/List.tsx +++ b/packages/webapp/src/pages/Connection/List.tsx @@ -20,7 +20,6 @@ import { useDebounce, useUnmount } from 'react-use'; import { globalEnv } from '../../utils/env'; import { apiConnectSessions } from '../../hooks/useConnect'; import { useListIntegration } from '../../hooks/useIntegration'; -import { Info } from '../../components/Info'; import { Skeleton } from '../../components/ui/Skeleton'; import type { ColumnDef } from '@tanstack/react-table'; import { flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table'; @@ -34,6 +33,8 @@ import { useToast } from '../../hooks/useToast'; import type { ApiConnectionSimple } from '@nangohq/types'; import { CopyText } from '../../components/CopyText'; import { SimpleTooltip } from '../../components/SimpleTooltip'; +import { Helmet } from 'react-helmet'; +import { ErrorPageComponent } from '../../components/ErrorComponent'; const defaultFilter = ['all']; const filterErrors = [ @@ -253,23 +254,15 @@ export const ConnectionList: React.FC = () => { const hasFiltered = debouncedSearch || selectedIntegration[0] !== 'all' || filterWithError !== 'all'; if (error) { - return ( - - - An error occurred, refresh your page or reach out to the support.{' '} - {error.error.code === 'generic_error_support' && ( - <> - (id: {error.error.payload}) - - )} - - - ); + return ; } if (!connections || !readyToDisplay) { return ( + + Connections - Nango +

Connections

@@ -283,6 +276,9 @@ export const ConnectionList: React.FC = () => { return ( + + Connections - Nango +

Connections

diff --git a/packages/webapp/src/pages/Connection/Show.tsx b/packages/webapp/src/pages/Connection/Show.tsx index a7280760637..d9bb1e39fea 100644 --- a/packages/webapp/src/pages/Connection/Show.tsx +++ b/packages/webapp/src/pages/Connection/Show.tsx @@ -124,6 +124,9 @@ export const ConnectionShow: React.FC = () => { if (loading || loadingSyncs) { return ( + + Connection - Nango +
@@ -146,7 +149,7 @@ export const ConnectionShow: React.FC = () => { } if (error) { - return ; + return ; } if (!connection || !syncs) { @@ -155,6 +158,9 @@ export const ConnectionShow: React.FC = () => { return ( + + {connection.endUser?.email || connection.connection.connection_id} - Connection - Nango +
diff --git a/packages/webapp/src/pages/Environment/Settings.tsx b/packages/webapp/src/pages/Environment/Settings.tsx index 8ab9b0efbcf..beb5a2b1160 100644 --- a/packages/webapp/src/pages/Environment/Settings.tsx +++ b/packages/webapp/src/pages/Environment/Settings.tsx @@ -30,6 +30,7 @@ import { globalEnv } from '../../utils/env'; import { useLocalStorage } from 'react-use'; import { Info } from '../../components/Info'; import { Link } from 'react-router-dom'; +import { Helmet } from 'react-helmet'; export const EnvironmentSettings: React.FC = () => { const env = useStore((state) => state.env); @@ -460,6 +461,9 @@ export const EnvironmentSettings: React.FC = () => { return ( + + Environment Settings - Nango +
diff --git a/packages/webapp/src/pages/Homepage/Show.tsx b/packages/webapp/src/pages/Homepage/Show.tsx index ac5923687ad..14e5f1199ae 100644 --- a/packages/webapp/src/pages/Homepage/Show.tsx +++ b/packages/webapp/src/pages/Homepage/Show.tsx @@ -5,6 +5,7 @@ import DashboardLayout from '../../layout/DashboardLayout'; import { InsightChart } from './components/InsightChart'; import { useMeta } from '../../hooks/useMeta'; import { globalEnv } from '../../utils/env'; +import { Helmet } from 'react-helmet'; export const Homepage: React.FC = () => { const { meta } = useMeta(); @@ -17,6 +18,9 @@ export const Homepage: React.FC = () => { if (!globalEnv.features.logs) { return ( + + Homepage - Nango +

Hello, {me.name}!

@@ -39,6 +43,9 @@ export const Homepage: React.FC = () => { return ( + + Homepage - Nango +

Hello, {me.name}!

diff --git a/packages/webapp/src/pages/Integrations/Create.tsx b/packages/webapp/src/pages/Integrations/Create.tsx index 99fdb891a7c..bbe14dbcba0 100644 --- a/packages/webapp/src/pages/Integrations/Create.tsx +++ b/packages/webapp/src/pages/Integrations/Create.tsx @@ -12,6 +12,7 @@ import { useSWRConfig } from 'swr'; import type { AuthModeType } from '@nangohq/types'; import { apiPostIntegration } from '../../hooks/useIntegration'; import { useToast } from '../../hooks/useToast'; +import { Helmet } from 'react-helmet'; interface Provider { name: string; @@ -96,6 +97,9 @@ export default function Create() { return ( + + Integrations Create - Nango + {providers && (

Create Integration

diff --git a/packages/webapp/src/pages/Integrations/List.tsx b/packages/webapp/src/pages/Integrations/List.tsx index 549b88f4d60..ed07f192263 100644 --- a/packages/webapp/src/pages/Integrations/List.tsx +++ b/packages/webapp/src/pages/Integrations/List.tsx @@ -5,10 +5,11 @@ import { PlusIcon } from '@heroicons/react/24/outline'; import DashboardLayout from '../../layout/DashboardLayout'; import { LeftNavBarItems } from '../../components/LeftNavBar'; import IntegrationLogo from '../../components/ui/IntegrationLogo'; -import { requestErrorToast } from '../../utils/api'; import { useStore } from '../../store'; import { useListIntegration } from '../../hooks/useIntegration'; +import { Helmet } from 'react-helmet'; +import { ErrorPageComponent } from '../../components/ErrorComponent'; export default function IntegrationList() { const navigate = useNavigate(); @@ -18,17 +19,15 @@ export default function IntegrationList() { const { list: data, error } = useListIntegration(env); if (error) { - requestErrorToast(); - return ( - - - - ); + return ; } if (!data) { return ( + + Integrations - Nango + ); @@ -38,6 +37,9 @@ export default function IntegrationList() { return ( + + Integrations - Nango +

Integrations

{integrations.length > 0 && ( diff --git a/packages/webapp/src/pages/Integrations/providerConfigKey/Show.tsx b/packages/webapp/src/pages/Integrations/providerConfigKey/Show.tsx index 5ad8474691f..0548ed7fd0d 100644 --- a/packages/webapp/src/pages/Integrations/providerConfigKey/Show.tsx +++ b/packages/webapp/src/pages/Integrations/providerConfigKey/Show.tsx @@ -8,7 +8,6 @@ import { useStore } from '../../../store'; import { PlusIcon } from '@radix-ui/react-icons'; import { useGetIntegration } from '../../../hooks/useIntegration'; import { Skeleton } from '../../../components/ui/Skeleton'; -import { Info } from '../../../components/Info'; import PageNotFound from '../../PageNotFound'; import { useCallback, useEffect, useRef, useState } from 'react'; import { EndpointsShow } from './Endpoints/Show'; @@ -22,6 +21,8 @@ import { baseUrl } from '../../../utils/utils'; import { globalEnv } from '../../../utils/env'; import { apiConnectSessions } from '../../../hooks/useConnect'; import { useToast } from '../../../hooks/useToast'; +import { Helmet } from 'react-helmet'; +import { ErrorPageComponent } from '../../../components/ErrorComponent'; export const ShowIntegration: React.FC = () => { const { providerConfigKey } = useParams(); @@ -100,6 +101,9 @@ export const ShowIntegration: React.FC = () => { if (loading) { return ( + + Integration - Nango +
@@ -122,23 +126,7 @@ export const ShowIntegration: React.FC = () => { } if (error) { - if (error.error.code === 'not_found') { - return ; - } - - return ( - -

Integration

- - An error occurred, refresh your page or reach out to the support.{' '} - {error.error.code === 'generic_error_support' && ( - <> - (id: {error.error.payload}) - - )} - -
- ); + return ; } if (!data) { @@ -147,6 +135,9 @@ export const ShowIntegration: React.FC = () => { return ( + + {data.integration.unique_key} - Integration - Nango +
diff --git a/packages/webapp/src/pages/Logs/Search.tsx b/packages/webapp/src/pages/Logs/Search.tsx index 28571a26c87..b77ee25c13d 100644 --- a/packages/webapp/src/pages/Logs/Search.tsx +++ b/packages/webapp/src/pages/Logs/Search.tsx @@ -1,7 +1,6 @@ import { LeftNavBarItems } from '../../components/LeftNavBar'; import DashboardLayout from '../../layout/DashboardLayout'; import { useStore } from '../../store'; -import { Info } from '../../components/Info'; import { useSearchOperations } from '../../hooks/useLogs'; import * as Table from '../../components/ui/Table'; import { getCoreRowModel, useReactTable, flexRender } from '@tanstack/react-table'; @@ -41,6 +40,8 @@ import { OperationDrawer } from './components/OperationDrawer'; import { OperationRow } from './components/OperationRow'; import type { DateRange } from 'react-day-picker'; import { getPresetRange, matchPresetFromRange, slidePeriod } from '../../utils/logs'; +import { ErrorPageComponent } from '../../components/ErrorComponent'; +import { Helmet } from 'react-helmet'; const limit = 20; @@ -311,10 +312,13 @@ export const LogsSearch: React.FC = () => { }, [period]); if (error) { - return ( - -

Logs

- {error.error.code === 'feature_disabled' ? ( + if (error.error.code === 'feature_disabled') { + return ( + + + Logs - Nango + +

Logs

Logs not configured

@@ -325,23 +329,19 @@ export const LogsSearch: React.FC = () => { to configure logs.
- ) : ( - - An error occurred, refresh your page or reach out to the support.{' '} - {error.error.code === 'generic_error_support' && ( - <> - (id: {error.error.payload}) - - )} - - )} -
- ); +
+ ); + } + + return ; } if (!synced) { return ( + + Logs - Nango +

Logs

@@ -355,6 +355,9 @@ export const LogsSearch: React.FC = () => { return ( + + Logs - Nango +

Logs {loading && }

diff --git a/packages/webapp/src/pages/Team/Settings.tsx b/packages/webapp/src/pages/Team/Settings.tsx index ea350c8ebcb..26b5b2888f6 100644 --- a/packages/webapp/src/pages/Team/Settings.tsx +++ b/packages/webapp/src/pages/Team/Settings.tsx @@ -1,5 +1,4 @@ import { LeftNavBarItems } from '../../components/LeftNavBar'; -import { Info } from '../../components/Info'; import { Skeleton } from '../../components/ui/Skeleton'; import { useTeam } from '../../hooks/useTeam'; import DashboardLayout from '../../layout/DashboardLayout'; @@ -8,6 +7,8 @@ import { TeamInfo } from './components/Info'; import { TeamUsers } from './components/Users'; import { AddTeamMember } from './components/AddTeamMember'; import { Admin } from './components/Admin'; +import { Helmet } from 'react-helmet'; +import { ErrorPageComponent } from '../../components/ErrorComponent'; export const TeamSettings: React.FC = () => { const env = useStore((state) => state.env); @@ -17,6 +18,9 @@ export const TeamSettings: React.FC = () => { if (loading) { return ( + + Team Settings - Nango +

Team Settings

@@ -27,23 +31,14 @@ export const TeamSettings: React.FC = () => { } if (error) { - return ( - -

Team Settings

- - An error occurred, refresh your page or reach out to the support.{' '} - {error.error.code === 'generic_error_support' && ( - <> - (id: {error.error.payload}) - - )} - -
- ); + return ; } return ( + + Team Settings - Nango +

Team Settings

diff --git a/packages/webapp/src/pages/User/Settings.tsx b/packages/webapp/src/pages/User/Settings.tsx index 027c6e86459..4117dd190cb 100644 --- a/packages/webapp/src/pages/User/Settings.tsx +++ b/packages/webapp/src/pages/User/Settings.tsx @@ -1,6 +1,5 @@ import { useRef, useState } from 'react'; import { LeftNavBarItems } from '../../components/LeftNavBar'; -import { Info } from '../../components/Info'; import { Skeleton } from '../../components/ui/Skeleton'; import { Input } from '../../components/ui/input/Input'; import { apiPatchUser, useUser } from '../../hooks/useUser'; @@ -9,6 +8,8 @@ import { Pencil1Icon } from '@radix-ui/react-icons'; import { useToast } from '../../hooks/useToast'; import { Tooltip, TooltipContent, TooltipTrigger } from '../../components/ui/Tooltip'; import Button from '../../components/ui/button/Button'; +import { Helmet } from 'react-helmet'; +import { ErrorPageComponent } from '../../components/ErrorComponent'; export const UserSettings: React.FC = () => { const { toast } = useToast(); @@ -33,6 +34,9 @@ export const UserSettings: React.FC = () => { if (loading) { return ( + + Profile Settings - Nango +

Profile Settings

@@ -43,19 +47,7 @@ export const UserSettings: React.FC = () => { } if (error) { - return ( - -

Profile Settings

- - An error occurred, refresh your page or reach out to the support.{' '} - {error.error.code === 'generic_error_support' && ( - <> - (id: {error.error.payload}) - - )} - -
- ); + return ; } if (!user) { @@ -64,6 +56,9 @@ export const UserSettings: React.FC = () => { return ( + + Profile Settings - Nango +

Profile Settings

From 7160cdfbeb90a149a324f7703ae4ee3c14091917 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Tue, 26 Nov 2024 19:25:53 +0000 Subject: [PATCH 2/3] chore(integration-templates): Automated commit updating flows.yaml based on changes in https://github.com/NangoHQ/integration-templates/commit/0b4c7696fc1a009c3a3f921639efb80af00bd09f by Khaliq. Commit message: fix(aliases): Fix upload alias (#125) --- packages/shared/flows.yaml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/shared/flows.yaml b/packages/shared/flows.yaml index 951e0b4d54e..acf3482be0c 100644 --- a/packages/shared/flows.yaml +++ b/packages/shared/flows.yaml @@ -7534,6 +7534,7 @@ integrations: endpoint: method: GET path: /users + group: Users sync_type: incremental scopes: - okta.users.read @@ -7543,10 +7544,12 @@ integrations: output: User endpoint: method: POST - path: /user + path: /users + group: Users input: OktaCreateUser scopes: - okta.users.manage + version: 1.0.0 add-group: description: Adds a new group with the OKTA_GROUP type to your org output: Group @@ -7561,7 +7564,8 @@ integrations: output: SuccessResponse endpoint: method: PUT - path: /user-group + path: /user-groups + group: User Groups input: OktaAssignRemoveUserGroup scopes: - okta.groups.manage @@ -7570,7 +7574,8 @@ integrations: output: SuccessResponse endpoint: method: DELETE - path: /user-group + path: /user-groups + group: User Groups input: OktaAssignRemoveUserGroup scopes: - okta.groups.manage From 57bac9a3806b2a12bb12096652f9ba637ca98a50 Mon Sep 17 00:00:00 2001 From: Hassan_Wari <85742599+hassan254-prog@users.noreply.github.com> Date: Wed, 27 Nov 2024 08:57:48 +0300 Subject: [PATCH 3/3] fix(authorization): authorization url encoding (#3062) ### Describe the problem and your solution Turns out some of the providers don't like it when we encode some of the url parts, i.e `scopes`, `base_url` when we are interpolating with `connectionConfigs`. To help with this, the PR introduces a way we can handle this within the various parts. Currently we can skip authorization_url encoding within the `scopes`, or when interpolating the `base_url` with `connectionConfig`. ### Testing instructions When creating a new connection for `Pennylane`, the scopes aren't encoded `customer_invoices+supplier_invoices+ledger+accounting` --------- Co-authored-by: Hassan Wari --- .../server/lib/controllers/oauth.controller.ts | 17 ++++++++++++++++- packages/shared/lib/clients/oauth2.client.ts | 8 ++++---- packages/shared/providers.yaml | 5 ++++- packages/types/lib/providers/provider.ts | 4 ++-- scripts/validation/providers/schema.json | 14 ++++++++++---- 5 files changed, 36 insertions(+), 12 deletions(-) diff --git a/packages/server/lib/controllers/oauth.controller.ts b/packages/server/lib/controllers/oauth.controller.ts index dc0a6bb3809..b5e20845eea 100644 --- a/packages/server/lib/controllers/oauth.controller.ts +++ b/packages/server/lib/controllers/oauth.controller.ts @@ -533,13 +533,28 @@ class OAuthController { oauth2Client.getSimpleOAuth2ClientConfig(providerConfig, provider, connectionConfig) ); + const scopeSeparator = provider.scope_separator || ' '; + const scopes = providerConfig.oauth_scopes ? providerConfig.oauth_scopes.split(',').join(scopeSeparator) : ''; + let authorizationUri = simpleOAuthClient.authorizeURL({ redirect_uri: callbackUrl, - scope: providerConfig.oauth_scopes ? providerConfig.oauth_scopes.split(',').join(provider.scope_separator || ' ') : '', + scope: scopes, state: session.id, ...allAuthParams }); + if (provider?.authorization_url_skip_encode?.includes('scopes')) { + const url = new URL(authorizationUri); + const queryParams = new URLSearchParams(url.search); + queryParams.delete('scope'); + let newQuery = queryParams.toString(); + if (scopes) { + newQuery = newQuery ? `${newQuery}&scope=${scopes}` : `scope=${scopes}`; + } + url.search = newQuery; + authorizationUri = url.toString(); + } + if (provider.authorization_url_fragment) { const urlObj = new URL(authorizationUri); const { search } = urlObj; diff --git a/packages/shared/lib/clients/oauth2.client.ts b/packages/shared/lib/clients/oauth2.client.ts index 65fb2e984ed..d7400df1fb3 100644 --- a/packages/shared/lib/clients/oauth2.client.ts +++ b/packages/shared/lib/clients/oauth2.client.ts @@ -18,8 +18,8 @@ export function getSimpleOAuth2ClientConfig( connectionConfig: Record ): Merge { const templateTokenUrl = typeof provider.token_url === 'string' ? provider.token_url : (provider.token_url!['OAUTH2'] as string); - const tokenUrl = makeUrl(templateTokenUrl, connectionConfig, provider.token_url_encode); - const authorizeUrl = makeUrl(provider.authorization_url!, connectionConfig, provider.authorization_url_encode); + const tokenUrl = makeUrl(templateTokenUrl, connectionConfig, provider.token_url_skip_encode); + const authorizeUrl = makeUrl(provider.authorization_url!, connectionConfig, provider.authorization_url_skip_encode); const headers = { 'User-Agent': 'Nango' }; @@ -157,9 +157,9 @@ export async function getFreshOAuth2Credentials( } } -function makeUrl(template: string, config: Record, encodeAllParams = true): URL { +function makeUrl(template: string, config: Record, skipEncodeKeys: string[] = []): URL { const cleanTemplate = template.replace(/connectionConfig\./g, ''); - const encodedParams = encodeAllParams ? encodeParameters(config) : config; + const encodedParams = skipEncodeKeys.includes('base_url') ? config : encodeParameters(config); const interpolatedUrl = interpolateString(cleanTemplate, encodedParams); return new URL(interpolatedUrl); } diff --git a/packages/shared/providers.yaml b/packages/shared/providers.yaml index b0177d24b10..6430766081b 100644 --- a/packages/shared/providers.yaml +++ b/packages/shared/providers.yaml @@ -2591,7 +2591,8 @@ github-app-oauth: alias: github auth_mode: CUSTOM authorization_url: ${connectionConfig.appPublicLink}/installations/new - authorization_url_encode: false + authorization_url_skip_encode: + - base_url token_url: OAUTH2: https://github.com/login/oauth/access_token APP: https://api.github.com/app/installations/${connectionConfig.installation_id}/access_tokens @@ -4469,6 +4470,8 @@ pennylane: grant_type: authorization_code refresh_params: grant_type: refresh_token + authorization_url_skip_encode: + - scopes docs: https://docs.nango.dev/integrations/all/pennylane peopledatalabs: diff --git a/packages/types/lib/providers/provider.ts b/packages/types/lib/providers/provider.ts index c500e8368bf..877eb87b7a9 100644 --- a/packages/types/lib/providers/provider.ts +++ b/packages/types/lib/providers/provider.ts @@ -59,13 +59,13 @@ export interface BaseProvider { }; }; authorization_url?: string; - authorization_url_encode?: boolean; + authorization_url_skip_encode?: string[]; access_token_url?: string; authorization_params?: Record; scope_separator?: string; default_scopes?: string[]; token_url?: string | TokenUrlObject; - token_url_encode?: boolean; + token_url_skip_encode?: string[]; token_params?: Record; authorization_url_replacements?: Record; redirect_uri_metadata?: string[]; diff --git a/scripts/validation/providers/schema.json b/scripts/validation/providers/schema.json index 754e8a3989f..592cc9bb9d3 100644 --- a/scripts/validation/providers/schema.json +++ b/scripts/validation/providers/schema.json @@ -88,8 +88,11 @@ "authorization_url": { "type": "string" }, - "authorization_url_encode": { - "type": "boolean" + "authorization_url_skip_encode": { + "type": "array", + "items": { + "type": "string" + } }, "authorization_url_replacements": { "type": "object", @@ -397,8 +400,11 @@ } ] }, - "token_url_encode": { - "type": "boolean" + "token_url_skip_encode": { + "type": "array", + "items": { + "type": "string" + } }, "webhook_user_defined_secret": { "type": "boolean"