Skip to content

Commit

Permalink
feat: add support for apiGateway.apiKeys (#1572)
Browse files Browse the repository at this point in the history
  • Loading branch information
dnalborczyk authored Sep 16, 2022
1 parent 18d4e8a commit 20f6e8b
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 1 deletion.
13 changes: 12 additions & 1 deletion src/events/http/HttpServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import logRoutes from '../../utils/logRoutes.js'
import {
detectEncoding,
generateHapiPath,
getApiKeysValues,
getHttpApiCorsConfig,
jsonPath,
splitHandlerPathAndName,
Expand All @@ -33,6 +34,8 @@ const { parse, stringify } = JSON
const { assign, entries, keys } = Object

export default class HttpServer {
#apiKeysValues = null

#lambda = null

#options = null
Expand All @@ -44,6 +47,10 @@ export default class HttpServer {
#terminalInfo = []

constructor(serverless, options, lambda) {
this.#apiKeysValues = getApiKeysValues(
serverless.service.provider.apiGateway?.apiKeys ?? [],
)

this.#lambda = lambda
this.#options = options
this.#serverless = serverless
Expand Down Expand Up @@ -428,7 +435,10 @@ export default class HttpServer {
const apiKey = request.headers['x-api-key']

if (apiKey) {
if (apiKey !== this.#options.apiKey) {
if (
apiKey !== this.#options.apiKey &&
!this.#apiKeysValues.has(apiKey)
) {
log.debug(
`Method ${method} of function ${functionKey} token ${apiKey} not valid`,
)
Expand Down Expand Up @@ -825,6 +835,7 @@ export default class HttpServer {
const parseCookies = (headerValue) => {
const cookieName = headerValue.slice(0, headerValue.indexOf('='))
const cookieValue = headerValue.slice(headerValue.indexOf('=') + 1)

h.state(cookieName, cookieValue, {
encoding: 'none',
strictHeader: false,
Expand Down
7 changes: 7 additions & 0 deletions src/utils/getApiKeysValues.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function getApiKeysValues(apiKeys) {
return new Set(
apiKeys
.filter((apiKey) => typeof apiKey === 'object' && apiKey.value != null)
.map(({ value }) => value),
)
}
1 change: 1 addition & 0 deletions src/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export { default as createUniqueId } from './createUniqueId.js'
export { default as detectExecutable } from './detectExecutable.js'
export { default as formatToClfTime } from './formatToClfTime.js'
export { default as generateHapiPath } from './generateHapiPath.js'
export { default as getApiKeysValues } from './getApiKeysValues.js'
export { default as getHttpApiCorsConfig } from './getHttpApiCorsConfig.js'
export { default as jsonPath } from './jsonPath.js'
export { default as lowerCaseKeys } from './lowerCaseKeys.js'
Expand Down
71 changes: 71 additions & 0 deletions tests/options/apiGateway/apiKeys/apiGateway-apiKeys.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import assert from 'node:assert'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { setup, teardown } from '../../../_testHelpers/index.js'
import { BASE_URL } from '../../../config.js'

const __dirname = dirname(fileURLToPath(import.meta.url))

describe('apiGateway apiKeys tests', function desc() {
beforeEach(() =>
setup({
servicePath: resolve(__dirname),
}),
)

afterEach(() => teardown())

//
;[
{
description: 'should ...',
expected: {
body: {
message: 'Forbidden',
},
statusCode: 403,
},
path: '/dev/foo',
},

{
description: 'should ...',
expected: {
body: {
foo: 'bar',
},
statusCode: 200,
},
headers: {
'x-api-key': 'fooValuefooValuefooValue',
},
path: '/dev/foo',
},

{
description: 'should ...',
expected: {
body: {
foo: 'bar',
},
statusCode: 200,
},
headers: {
'x-api-key': 'barValuebarValuebarValue',
},
path: '/dev/foo',
},
].forEach(({ description, expected, headers, path }) => {
it(description, async () => {
const url = new URL(path, BASE_URL)

const response = await fetch(url, {
headers,
})
assert.equal(response.status, expected.statusCode)

const json = await response.json()
assert.deepEqual(json, expected.body)
})
})
})
30 changes: 30 additions & 0 deletions tests/options/apiGateway/apiKeys/serverless.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
service: apiGateway-apiKeys-tests

configValidationMode: error
deprecationNotificationMode: error

plugins:
- ../../../../src/index.js

provider:
apiGateway:
apiKeys:
# - fooKeyName TODO, TEST MISSING for generated apiKey
- name: fooName
value: fooValuefooValuefooValue
- value: barValuebarValuebarValue
memorySize: 128
name: aws
region: us-east-1
runtime: nodejs16.x
stage: dev
versionFunctions: false

functions:
foo:
events:
- http:
method: get
path: /foo
private: true
handler: src/index.default
10 changes: 10 additions & 0 deletions tests/options/apiGateway/apiKeys/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const { stringify } = JSON

export default async function handler() {
return {
body: stringify({
foo: 'bar',
}),
statusCode: 200,
}
}
3 changes: 3 additions & 0 deletions tests/options/apiGateway/apiKeys/src/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"type": "module"
}

0 comments on commit 20f6e8b

Please sign in to comment.