Skip to content

Commit

Permalink
fix: connectionConfigParams parsing (#1659)
Browse files Browse the repository at this point in the history
connectionConfigParams in authorization_url or token_url should not be
ignored even if they are part of redirect_uri_metadata or
token_response_metadata
  • Loading branch information
TBonnin authored Feb 12, 2024
1 parent 10b6bb4 commit a4ef026
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 39 deletions.
5 changes: 1 addition & 4 deletions packages/server/lib/controllers/config.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,7 @@ class ConfigController {
creationDate: config.created_at
};
if (template && template.auth_mode !== AuthModes.App && template.auth_mode !== AuthModes.Custom) {
integration['connectionConfigParams'] = parseConnectionConfigParamsFromTemplate(template!).filter(
// we ignore connection config params that are in the token response metadata or redirect url metadata
(element) => [...(template.token_response_metadata || []), ...(template.redirect_uri_metadata || [])].indexOf(element) == -1
);
integration['connectionConfigParams'] = parseConnectionConfigParamsFromTemplate(template!);
}
return integration;
});
Expand Down
17 changes: 10 additions & 7 deletions packages/server/lib/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,17 +145,20 @@ export function parseJsonDateAware(input: string) {

export function parseConnectionConfigParamsFromTemplate(template: ProviderTemplate): string[] {
if (template.token_url || template.authorization_url || template.proxy?.base_url || template.proxy?.headers) {
const tokenUrlMatches = typeof template.token_url === 'string' ? template.token_url?.match(/\${connectionConfig\.([^{}]*)}/g) : null;
const authorizationUrlMatches = template.authorization_url?.match(/\${connectionConfig\.([^{}]*)}/g);
const proxyBaseUrlMatches = template.proxy?.base_url?.match(/\${connectionConfig\.([^{}]*)}/g);
const cleanParamName = (param: string) => param.replace('${connectionConfig.', '').replace('}', '');
const tokenUrlMatches = typeof template.token_url === 'string' ? template.token_url?.match(/\${connectionConfig\.([^{}]*)}/g) || [] : [];
const authorizationUrlMatches = template.authorization_url?.match(/\${connectionConfig\.([^{}]*)}/g) || [];
const proxyBaseUrlMatches = template.proxy?.base_url?.match(/\${connectionConfig\.([^{}]*)}/g) || [];
const proxyHeaderMatches = template.proxy?.headers
? Array.from(new Set(Object.values(template.proxy.headers).flatMap((header) => header.match(/\${connectionConfig\.([^{}]*)}/g) || [])))
: [];

const params = [...(tokenUrlMatches || []), ...(authorizationUrlMatches || []), ...(proxyBaseUrlMatches || []), ...proxyHeaderMatches].filter(
(value, index, array) => array.indexOf(value) === index
const proxyMatches = [...proxyBaseUrlMatches, ...proxyHeaderMatches].filter(
// we ignore config params in proxy attributes that are also in the token response metadata or redirect url metadata
(param) => [...(template.token_response_metadata || []), ...(template.redirect_uri_metadata || [])].indexOf(cleanParamName(param)) == -1
);
return params.map((param) => param.replace('${connectionConfig.', '').replace('}', '')); // Remove the ${connectionConfig.'} and return only the param name.
return [...tokenUrlMatches, ...authorizationUrlMatches, ...proxyMatches]
.map(cleanParamName)
.filter((value, index, array) => array.indexOf(value) === index); // remove duplicates
}

return [];
Expand Down
136 changes: 108 additions & 28 deletions packages/server/lib/utils/utils.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,120 @@
import { expect, describe, it } from 'vitest';
import { getConnectionMetadataFromTokenResponse, parseConnectionConfigParamsFromTemplate, getAdditionalAuthorizationParams } from './utils.js';
import type { Template as ProviderTemplate } from '@nangohq/shared';
import { AuthModes } from '@nangohq/shared';

describe('Utils unit tests', () => {
it('Should parse template connection config params', () => {
// parsing connectionConfigParams from authorization_url
const authTemplate = {
name: 'braintree',
provider: 'braintree',
authorization_url: 'https://api.${connectionConfig.auth}.com/oauth/authorize'
};

const connectionConfigAuth = parseConnectionConfigParamsFromTemplate(authTemplate as unknown as ProviderTemplate);
expect(connectionConfigAuth).toEqual(['auth']);

// parsing connectionConfigParams from token_url
const tokenTemplate = {
name: 'braintree',
provider: 'braintree',
token_url: 'https://api.${connectionConfig.token}.com/oauth/access_token'
};

const connectionConfigToken = parseConnectionConfigParamsFromTemplate(tokenTemplate as unknown as ProviderTemplate);
expect(connectionConfigToken).toEqual(['token']);
it('Should parse config params in authorization_url', () => {
const params = parseConnectionConfigParamsFromTemplate({
auth_mode: AuthModes.OAuth2,
authorization_url: 'https://api.${connectionConfig.auth}.com/oauth/authorize',
token_url: 'n/a',
proxy: {
base_url: 'https://api.domain.com'
}
});
expect(params).toEqual(['auth']);
});
it('Should parse config params in token_url', () => {
const params = parseConnectionConfigParamsFromTemplate({
auth_mode: AuthModes.OAuth2,
authorization_url: 'n/a',
token_url: 'https://api.${connectionConfig.token}.com/oauth/access_token',
proxy: {
base_url: 'https://api.domain.com'
}
});
expect(params).toEqual(['token']);
});

// parsing connectionConfigParams from proxy.base_url
const proxyBaseUrlTemplate = {
name: 'freshdesk',
provider: 'freshdesk',
it('Should parse config params in proxy_url', () => {
const params = parseConnectionConfigParamsFromTemplate({
auth_mode: AuthModes.OAuth2,
authorization_url: 'n/a',
token_url: 'n/a',
proxy: {
base_url: 'https://${connectionConfig.subdomain}.freshdesk.com'
}
};

const connectionConfigProxyBaseUrl = parseConnectionConfigParamsFromTemplate(proxyBaseUrlTemplate as unknown as ProviderTemplate);
expect(connectionConfigProxyBaseUrl).toEqual(['subdomain']);
});
expect(params).toEqual(['subdomain']);
});
it('Should ignore config param in proxy.base_url if in redirect_uri_metadata', () => {
const params = parseConnectionConfigParamsFromTemplate({
auth_mode: AuthModes.OAuth2,
authorization_url: 'n/a',
token_url: 'n/a',
redirect_uri_metadata: ['instance_url'],
proxy: {
base_url: '${connectionConfig.instance_url}'
}
});
expect(params).toEqual([]);
});
it('Should ignore config param in proxy.base_url if in token_response_metadata', () => {
const params = parseConnectionConfigParamsFromTemplate({
auth_mode: AuthModes.OAuth2,
authorization_url: 'n/a',
token_url: 'n/a',
token_response_metadata: ['api_domain'],
proxy: {
base_url: 'https://${connectionConfig.api_domain}'
}
});
expect(params).toEqual([]);
});
it('Should ignore config param in proxy.headers if in redirect_uri_metadata', () => {
const params = parseConnectionConfigParamsFromTemplate({
auth_mode: AuthModes.OAuth2,
authorization_url: 'n/a',
token_url: 'n/a',
redirect_uri_metadata: ['some_header'],
proxy: {
headers: {
'X-Some-Header': '${connectionConfig.some_header}'
},
base_url: 'n/a'
}
});
expect(params).toEqual([]);
});
it('Should ignore config param in proxy.headers if in token_response_metadata', () => {
const params = parseConnectionConfigParamsFromTemplate({
auth_mode: AuthModes.OAuth2,
authorization_url: 'n/a',
token_url: 'n/a',
token_response_metadata: ['another_header'],
proxy: {
headers: {
'X-Another-Header': '${connectionConfig.another_header}'
},
base_url: 'n/a'
}
});
expect(params).toEqual([]);
});
it('Should not ignore param in token_response_metadata if also in authorization_url', () => {
const params = parseConnectionConfigParamsFromTemplate({
auth_mode: AuthModes.OAuth2,
authorization_url: 'https://${connectionConfig.provider_domain}.com/oauth/authorize',
token_url: 'n/a',
token_response_metadata: ['provider_domain'],
proxy: {
base_url: 'https://${connectionConfig.provider_domain}'
}
});
expect(params).toEqual(['provider_domain']);
});
it('Should not ignore param in token_response_metadata if also in token_url', () => {
const params = parseConnectionConfigParamsFromTemplate({
auth_mode: AuthModes.OAuth2,
authorization_url: 'https://provider.com/oauth/authorize',
token_url: 'https://${connectionConfig.some_domain}.com/oauth/access_token',
token_response_metadata: ['some_domain'],
proxy: {
base_url: 'https://${connectionConfig.some_domain}'
}
});
expect(params).toEqual(['some_domain']);
});

it('Should extract metadata from token response based on template', () => {
Expand Down

0 comments on commit a4ef026

Please sign in to comment.