diff --git a/src/openapi-lambda-adapters.test.ts b/src/openapi-lambda-adapters.test.ts index e6af289..c2ff475 100644 --- a/src/openapi-lambda-adapters.test.ts +++ b/src/openapi-lambda-adapters.test.ts @@ -1,4 +1,4 @@ -import { APIGatewayProxyStructuredResultV2 } from 'aws-lambda' +import { APIGatewayProxyStructuredResultV2, Context } from 'aws-lambda' import { AxiosRequestConfig } from 'axios' import { AxiosError, HttpMethod, Operation } from 'openapi-client-axios' import { convertAxiosToApiGw, convertApiGwToAxios } from './openapi-lambda-adapters' @@ -26,7 +26,8 @@ describe('Adapt axios request/response to AWS Lambda Proxy Event/Response', () = // then const event = convertAxiosToApiGw(axiosConfig, operation) expect(event.rawPath).toEqual('/v1/users') - expect(Object.keys(event.headers).length).toEqual(2) + expect(Object.keys(event.headers)).toContain('Accept') + expect(Object.keys(event.headers)).toContain('authorization') expect(event.pathParameters).toEqual({}) expect(event.queryStringParameters).toEqual({}) expect(event.rawQueryString).toEqual('') @@ -204,7 +205,52 @@ describe('Adapt axios request/response to AWS Lambda Proxy Event/Response', () = expect(event.headers['x-null']).toBeUndefined() expect(event.headers['x-undefined']).toBeUndefined() }) - + + it('defaults to lambda arn the user agent when passed in the context', () => { + // given + const axiosConfig: AxiosRequestConfig = { + method: 'get', + url: '/v1/users', + } + const operation: Operation = { + path: '/v1/users', + method: HttpMethod.Get, + responses: {} + } + + // then + const event = convertAxiosToApiGw(axiosConfig, operation, { + invokedFunctionArn: 'arn:aws:lambda:us-east-1:123456789012:function:my-function' + } as Context) + + expect(event.requestContext.authorizer.lambda) + expect(event.requestContext.http.userAgent).toBe('lambda-invoke-arn:aws:lambda:us-east-1:123456789012:function:my-function') + expect(event.headers['User-Agent']).toBe('lambda-invoke-arn:aws:lambda:us-east-1:123456789012:function:my-function') + }) + + it('includes the user agent when passed in the request config', () => { + // given + const axiosConfig: AxiosRequestConfig = { + method: 'get', + url: '/v1/users', + headers: { + 'User-Agent': 'daniel-api', + } + } + const operation: Operation = { + path: '/v1/users', + method: HttpMethod.Get, + responses: {} + } + + // then + const event = convertAxiosToApiGw(axiosConfig, operation, { + invokedFunctionArn: 'arn:aws:lambda:us-east-1:123456789012:function:my-function' + } as Context) + + expect(event.requestContext.http.userAgent).toBe('daniel-api') + expect(event.headers['User-Agent']).toBe('daniel-api') + }) }) describe('Api GW Proxy Response to Axios Response', () => { @@ -431,4 +477,4 @@ describe('Adapt axios request/response to AWS Lambda Proxy Event/Response', () = }) -}) \ No newline at end of file +}) diff --git a/src/openapi-lambda-adapters.ts b/src/openapi-lambda-adapters.ts index 7620da5..a6a0cde 100644 --- a/src/openapi-lambda-adapters.ts +++ b/src/openapi-lambda-adapters.ts @@ -1,6 +1,7 @@ -import { +import type { APIGatewayProxyEventV2, APIGatewayProxyStructuredResultV2, APIGatewayProxyEventQueryStringParameters, APIGatewayProxyEventPathParameters, + APIGatewayProxyEventV2WithLambdaAuthorizer, Context } from 'aws-lambda' @@ -30,7 +31,9 @@ const lambdaRunner = async (axiosConfig: AxiosRequestConfig, operation: Operatio .then((resp) => convertApiGwToAxios(resp, axiosConfig)) } -export const convertAxiosToApiGw = (config: AxiosRequestConfig, operation: Operation, crtLambdaContext?: Context): APIGatewayProxyEventV2 => { +export interface LambdaRunnerAuthContext { 'lambda-invoke': true, callerIdentity: string } + +export const convertAxiosToApiGw = (config: AxiosRequestConfig, operation: Operation, crtLambdaContext?: Context): APIGatewayProxyEventV2WithLambdaAuthorizer => { // extract path params // eg: for path template /v1/users/{id} & path url /v1/users/1108 -> will extract {'id': '1108'} const template = operation.path @@ -61,7 +64,15 @@ export const convertAxiosToApiGw = (config: AxiosRequestConfig, operation: Opera headers[key] = val.toString() } - const lambdaPayload = { + // identify caller lambda + const sourceIdentity = ['lambda-invoke', crtLambdaContext?.invokedFunctionArn].filter(Boolean).join('-') + + // default to lambda-invoke user-agent + if (!headers['User-Agent'] && !headers['user-agent']) { + headers['User-Agent'] = sourceIdentity + } + + const lambdaPayload: APIGatewayProxyEventV2WithLambdaAuthorizer = { version: '2.0', routeKey: '$default', rawPath: config.url, @@ -75,7 +86,7 @@ export const convertAxiosToApiGw = (config: AxiosRequestConfig, operation: Opera authorizer: { lambda: { 'lambda-invoke': true, - callerIdentity: crtLambdaContext?.invokedFunctionArn ?? 'lambda-invoke-not-specified' + callerIdentity: sourceIdentity } }, domainName: 'lambda-invoke', @@ -85,7 +96,7 @@ export const convertAxiosToApiGw = (config: AxiosRequestConfig, operation: Opera sourceIp: '', path: config.url, protocol: 'HTTP/1.1', - userAgent: 'lambda-invoke' + userAgent: headers['user-agent'] ?? headers['User-Agent'] }, requestId: crtLambdaContext?.awsRequestId ?? `lambda-invoke-${uuidv4()}`, routeKey: '$default', @@ -94,9 +105,11 @@ export const convertAxiosToApiGw = (config: AxiosRequestConfig, operation: Opera timeEpoch: Date.now() }, body: config.data ? JSON.stringify(config.data) : '', - isBase64Encoded: false, - httpMethod: config.method - } as APIGatewayProxyEventV2 + isBase64Encoded: false + } + + // for backwards compat with older event format + Object.assign(lambdaPayload, { httpMethod: config.method }) debug('lambdaRequest %o', lambdaPayload)