mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 09:36:44 +00:00
fix(core): Add support for .cn Amazon regions (#19363)
This commit is contained in:
@@ -11,7 +11,17 @@ import type {
|
||||
} from 'n8n-workflow';
|
||||
import { isObjectEmpty } from 'n8n-workflow';
|
||||
|
||||
export const regions = [
|
||||
type RegionData = {
|
||||
name: string;
|
||||
displayName: string;
|
||||
location: string;
|
||||
domain?: string;
|
||||
};
|
||||
|
||||
const chinaDomain = 'amazonaws.com.cn';
|
||||
const globalDomain = 'amazonaws.com';
|
||||
|
||||
export const regions: RegionData[] = [
|
||||
{
|
||||
name: 'af-south-1',
|
||||
displayName: 'Africa',
|
||||
@@ -91,11 +101,13 @@ export const regions = [
|
||||
name: 'cn-north-1',
|
||||
displayName: 'China',
|
||||
location: 'Beijing',
|
||||
domain: chinaDomain,
|
||||
},
|
||||
{
|
||||
name: 'cn-northwest-1',
|
||||
displayName: 'China',
|
||||
location: 'Ningxia',
|
||||
domain: chinaDomain,
|
||||
},
|
||||
{
|
||||
name: 'eu-central-1',
|
||||
@@ -211,12 +223,18 @@ export type AwsCredentialsType = {
|
||||
ssmEndpoint?: string;
|
||||
};
|
||||
|
||||
function getAwsDomain(region: AWSRegion): string {
|
||||
return regions.find((r) => r.name === region)?.domain ?? globalDomain;
|
||||
}
|
||||
|
||||
// Some AWS services are global and don't have a region
|
||||
// https://docs.aws.amazon.com/general/latest/gr/rande.html#global-endpoints
|
||||
// Example: iam.amazonaws.com (global), s3.us-east-1.amazonaws.com (regional)
|
||||
function parseAwsUrl(url: URL): { region: AWSRegion | null; service: string } {
|
||||
const [service, region] = url.hostname.replace('amazonaws.com', '').split('.');
|
||||
return { service, region: region as AWSRegion };
|
||||
const hostname = url.hostname;
|
||||
// Handle both .amazonaws.com and .amazonaws.com.cn domains
|
||||
const [service, region] = hostname.replace(/\.amazonaws\.com.*$/, '').split('.');
|
||||
return { service, region };
|
||||
}
|
||||
|
||||
export class Aws implements ICredentialType {
|
||||
@@ -436,10 +454,11 @@ export class Aws implements ICredentialType {
|
||||
endpointString = credentials.sesEndpoint;
|
||||
} else if (service === 'rekognition' && credentials.rekognitionEndpoint) {
|
||||
endpointString = credentials.rekognitionEndpoint;
|
||||
} else if (service) {
|
||||
endpointString = `https://${service}.${region}.amazonaws.com`;
|
||||
} else if (service === 'ssm' && credentials.ssmEndpoint) {
|
||||
endpointString = credentials.ssmEndpoint;
|
||||
} else if (service) {
|
||||
const domain = getAwsDomain(region);
|
||||
endpointString = `https://${service}.${region}.${domain}`;
|
||||
}
|
||||
endpoint = new URL(endpointString!.replace('{region}', region) + path);
|
||||
} else {
|
||||
@@ -486,7 +505,6 @@ export class Aws implements ICredentialType {
|
||||
bodyContent = params.toString();
|
||||
contentTypeHeader = 'application/x-www-form-urlencoded';
|
||||
}
|
||||
|
||||
const signOpts = {
|
||||
...requestOptions,
|
||||
headers: {
|
||||
@@ -526,7 +544,9 @@ export class Aws implements ICredentialType {
|
||||
|
||||
test: ICredentialTestRequest = {
|
||||
request: {
|
||||
baseURL: '=https://sts.{{$credentials.region}}.amazonaws.com',
|
||||
baseURL:
|
||||
// eslint-disable-next-line n8n-local-rules/no-interpolation-in-regular-string
|
||||
'={{$credentials.region.startsWith("cn-") ? `https://sts.${$credentials.region}.amazonaws.com.cn` : `https://sts.${$credentials.region}.amazonaws.com`}}',
|
||||
url: '?Action=GetCallerIdentity&Version=2011-06-15',
|
||||
method: 'POST',
|
||||
},
|
||||
|
||||
@@ -25,7 +25,10 @@ describe('Aws Credential', () => {
|
||||
expect(aws.documentationUrl).toBe('aws');
|
||||
expect(aws.icon).toEqual({ light: 'file:icons/AWS.svg', dark: 'file:icons/AWS.dark.svg' });
|
||||
expect(aws.properties.length).toBeGreaterThan(0);
|
||||
expect(aws.test.request.baseURL).toBe('=https://sts.{{$credentials.region}}.amazonaws.com');
|
||||
expect(aws.test.request.baseURL).toBe(
|
||||
// eslint-disable-next-line n8n-local-rules/no-interpolation-in-regular-string
|
||||
'={{$credentials.region.startsWith("cn-") ? `https://sts.${$credentials.region}.amazonaws.com.cn` : `https://sts.${$credentials.region}.amazonaws.com`}}',
|
||||
);
|
||||
expect(aws.test.request.url).toBe('?Action=GetCallerIdentity&Version=2011-06-15');
|
||||
expect(aws.test.request.method).toBe('POST');
|
||||
});
|
||||
@@ -178,5 +181,103 @@ describe('Aws Credential', () => {
|
||||
expect(result.method).toBe('POST');
|
||||
expect(result.url).toBe('https://iam.amazonaws.com/');
|
||||
});
|
||||
|
||||
describe('China regions', () => {
|
||||
const chinaCredentials: AwsCredentialsType = {
|
||||
region: 'cn-north-1',
|
||||
accessKeyId: 'hakuna',
|
||||
secretAccessKey: 'matata',
|
||||
customEndpoints: false,
|
||||
temporaryCredentials: false,
|
||||
};
|
||||
|
||||
it('should use amazonaws.com.cn domain for cn-north-1 region', async () => {
|
||||
const result = await aws.authenticate(chinaCredentials, {
|
||||
...requestOptions,
|
||||
url: '',
|
||||
baseURL: '',
|
||||
qs: { service: 's3' },
|
||||
});
|
||||
|
||||
expect(mockSign).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
host: 's3.cn-north-1.amazonaws.com.cn',
|
||||
region: 'cn-north-1',
|
||||
}),
|
||||
{
|
||||
accessKeyId: 'hakuna',
|
||||
secretAccessKey: 'matata',
|
||||
sessionToken: undefined,
|
||||
},
|
||||
);
|
||||
expect(result.url).toBe('https://s3.cn-north-1.amazonaws.com.cn/');
|
||||
});
|
||||
|
||||
it('should handle custom endpoints for China regions', async () => {
|
||||
const customEndpoint = 'https://custom.china.endpoint.com.cn';
|
||||
const result = await aws.authenticate(
|
||||
{ ...chinaCredentials, customEndpoints: true, s3Endpoint: customEndpoint },
|
||||
{ ...requestOptions, url: '', baseURL: '', qs: { service: 's3' } },
|
||||
);
|
||||
|
||||
expect(mockSign).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
host: 'custom.china.endpoint.com.cn',
|
||||
}),
|
||||
{
|
||||
accessKeyId: 'hakuna',
|
||||
secretAccessKey: 'matata',
|
||||
sessionToken: undefined,
|
||||
},
|
||||
);
|
||||
expect(result.url).toBe(`${customEndpoint}/`);
|
||||
});
|
||||
|
||||
it('should parse China region URLs correctly', async () => {
|
||||
const result = await aws.authenticate(chinaCredentials, {
|
||||
...requestOptions,
|
||||
url: 'https://s3.cn-north-1.amazonaws.com.cn/bucket/key',
|
||||
baseURL: '',
|
||||
});
|
||||
|
||||
expect(mockSign).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
host: 's3.cn-north-1.amazonaws.com.cn',
|
||||
region: 'cn-north-1',
|
||||
path: '/bucket/key',
|
||||
}),
|
||||
{
|
||||
accessKeyId: 'hakuna',
|
||||
secretAccessKey: 'matata',
|
||||
sessionToken: undefined,
|
||||
},
|
||||
);
|
||||
expect(result.url).toBe('https://s3.cn-north-1.amazonaws.com.cn/bucket/key');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Regular regions (non-China)', () => {
|
||||
it('should use amazonaws.com domain for regular regions', async () => {
|
||||
const result = await aws.authenticate(credentials, {
|
||||
...requestOptions,
|
||||
url: '',
|
||||
baseURL: '',
|
||||
qs: { service: 's3' },
|
||||
});
|
||||
|
||||
expect(mockSign).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
host: 's3.eu-central-1.amazonaws.com',
|
||||
region: 'eu-central-1',
|
||||
}),
|
||||
{
|
||||
accessKeyId: 'hakuna',
|
||||
secretAccessKey: 'matata',
|
||||
sessionToken: undefined,
|
||||
},
|
||||
);
|
||||
expect(result.url).toBe('https://s3.eu-central-1.amazonaws.com/');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user