diff --git a/packages/nodes-base/credentials/Aws.credentials.ts b/packages/nodes-base/credentials/Aws.credentials.ts index 78e05d7653..747a346e73 100644 --- a/packages/nodes-base/credentials/Aws.credentials.ts +++ b/packages/nodes-base/credentials/Aws.credentials.ts @@ -476,13 +476,29 @@ export class Aws implements ICredentialType { path = endpoint.pathname + endpoint.search; + // ! aws4.sign *must* have the body to sign, but we might have .form instead of .body + const requestWithForm = requestOptions as unknown as { form?: Record }; + let bodyContent = body !== '' ? body : undefined; + let contentTypeHeader: string | undefined = undefined; + if (requestWithForm.form) { + const params = new URLSearchParams(); + for (const key in requestWithForm.form) { + params.append(key, requestWithForm.form[key]); + } + bodyContent = params.toString(); + contentTypeHeader = 'application/x-www-form-urlencoded'; + } + const signOpts = { ...requestOptions, - headers: requestOptions.headers ?? {}, + headers: { + ...(requestOptions.headers ?? {}), + ...(contentTypeHeader && { 'content-type': contentTypeHeader }), + }, host: endpoint.host, method, path, - body: body !== '' ? body : undefined, + body: bodyContent, region, } as Request; diff --git a/packages/nodes-base/credentials/test/Aws.credentials.test.ts b/packages/nodes-base/credentials/test/Aws.credentials.test.ts index 1874758ecd..1e936da269 100644 --- a/packages/nodes-base/credentials/test/Aws.credentials.test.ts +++ b/packages/nodes-base/credentials/test/Aws.credentials.test.ts @@ -139,5 +139,44 @@ describe('Aws Credential', () => { expect(result.method).toBe('POST'); expect(result.url).toBe('https://iam.amazonaws.com/'); }); + + it('should handle an IRequestOptions object with form instead of body', async () => { + const result = await aws.authenticate({ ...credentials }, { + ...requestOptions, + body: undefined, + form: { + Action: 'ListUsers', + Version: '2010-05-08', + }, + baseURL: '', + url: 'https://iam.amazonaws.com', + host: 'iam.amazonaws.com', + path: '/', + } as IHttpRequestOptions); + + expect(mockSign).toHaveBeenCalledWith( + { + ...signOpts, + form: { + Action: 'ListUsers', + Version: '2010-05-08', + }, + body: 'Action=ListUsers&Version=2010-05-08', + host: 'iam.amazonaws.com', + url: 'https://iam.amazonaws.com', + baseURL: '', + path: '/', + headers: { + 'content-type': 'application/x-www-form-urlencoded', + }, + // PR #14037 introduces region normalization for global endpoints + // This test works with or without the normalization + region: expect.stringMatching(/[a-z]{2}-[a-z]+-[0-9]+/), + }, + securityHeaders, + ); + expect(result.method).toBe('POST'); + expect(result.url).toBe('https://iam.amazonaws.com/'); + }); }); });