Skip to content

Commit

Permalink
fix: support authorizer with no identity source specified (#1639)
Browse files Browse the repository at this point in the history
  • Loading branch information
frozenbonito committed Sep 20, 2023
1 parent 81035ee commit b7dfae6
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 29 deletions.
14 changes: 11 additions & 3 deletions src/events/http/HttpServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -323,9 +323,7 @@ export default class HttpServer {
(endpoint.isHttpApi &&
serverlessAuthorizerOptions?.enableSimpleResponses) ||
false,
identitySource:
serverlessAuthorizerOptions?.identitySource ||
'method.request.header.Authorization',
identitySource: serverlessAuthorizerOptions?.identitySource,
identityValidationExpression:
serverlessAuthorizerOptions?.identityValidationExpression || '(.*)',
payloadVersion: endpoint.isHttpApi
Expand All @@ -351,6 +349,16 @@ export default class HttpServer {
assign(authorizerOptions, endpoint.authorizer)
}

if (
!authorizerOptions.identitySource &&
!(
authorizerOptions.type === 'request' &&
authorizerOptions.resultTtlInSeconds === 0
)
) {
authorizerOptions.identitySource = 'method.request.header.Authorization'
}

// Create a unique scheme per endpoint
// This allows the methodArn on the event property to be set appropriately
const authKey = `${functionKey}-${authFunctionName}-${method}-${path}`
Expand Down
70 changes: 44 additions & 26 deletions src/events/http/createAuthScheme.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {

const IDENTITY_SOURCE_TYPE_HEADER = 'header'
const IDENTITY_SOURCE_TYPE_QUERYSTRING = 'querystring'
const IDENTITY_SOURCE_TYPE_NONE = 'none'

export default function createAuthScheme(authorizerOptions, provider, lambda) {
const authFunName = authorizerOptions.name
Expand Down Expand Up @@ -65,38 +66,50 @@ export default function createAuthScheme(authorizerOptions, provider, lambda) {
const methodArn = `arn:aws:execute-api:${provider.region}:${accountId}:${apiId}/${provider.stage}/${httpMethod}${resourcePath}`

let authorization
if (identitySourceType === IDENTITY_SOURCE_TYPE_HEADER) {
const headers = request.raw.req.headers ?? {}
authorization = headers[identitySourceField]
} else if (identitySourceType === IDENTITY_SOURCE_TYPE_QUERYSTRING) {
const queryStringParameters = parseQueryStringParameters(url) ?? {}
authorization = queryStringParameters[identitySourceField]
} else {
throw new Error(
`No Authorization source has been specified. This should never happen. (λ: ${authFunName})`,
)
switch (identitySourceType) {
case IDENTITY_SOURCE_TYPE_HEADER: {
const headers = request.raw.req.headers ?? {}
authorization = headers[identitySourceField]
break
}
case IDENTITY_SOURCE_TYPE_QUERYSTRING: {
const queryStringParameters = parseQueryStringParameters(url) ?? {}
authorization = queryStringParameters[identitySourceField]
break
}
case IDENTITY_SOURCE_TYPE_NONE: {
break
}
default: {
throw new Error(
`No Authorization source has been specified. This should never happen. (λ: ${authFunName})`,
)
}
}

if (authorization === undefined) {
log.error(
`Identity Source is null for ${identitySourceType} ${identitySourceField} (λ: ${authFunName})`,
let finalAuthorization
if (identitySourceType !== IDENTITY_SOURCE_TYPE_NONE) {
if (authorization === undefined) {
log.error(
`Identity Source is null for ${identitySourceType} ${identitySourceField} (λ: ${authFunName})`,
)
return Boom.unauthorized(
'User is not authorized to access this resource',
)
}

const identityValidationExpression = new RegExp(
authorizerOptions.identityValidationExpression,
)
return Boom.unauthorized(
'User is not authorized to access this resource',
const matchedAuthorization =
identityValidationExpression.test(authorization)
finalAuthorization = matchedAuthorization ? authorization : ''

log.debug(
`Retrieved ${identitySourceField} ${identitySourceType} "${finalAuthorization}"`,
)
}

const identityValidationExpression = new RegExp(
authorizerOptions.identityValidationExpression,
)
const matchedAuthorization =
identityValidationExpression.test(authorization)
const finalAuthorization = matchedAuthorization ? authorization : ''

log.debug(
`Retrieved ${identitySourceField} ${identitySourceType} "${finalAuthorization}"`,
)

if (authorizerOptions.payloadVersion === '1.0') {
event = {
...event,
Expand Down Expand Up @@ -296,5 +309,10 @@ export default function createAuthScheme(authorizerOptions, provider, lambda) {
)
}

if (authorizerOptions.resultTtlInSeconds === 0) {
identitySourceType = IDENTITY_SOURCE_TYPE_NONE
return finalizeAuthScheme()
}

return finalizeAuthScheme()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import assert from 'node:assert'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { BASE_URL } from '../../config.js'
import { setup, teardown } from '../../_testHelpers/index.js'

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

describe('no identity source authorizer tests', function desc() {
beforeEach(() =>
setup({
servicePath: resolve(__dirname),
}),
)

afterEach(() => teardown())

//
;[
{
description: 'should respond with 200',
expected: {
message: 'hello',
},
options: {
headers: {
Authorization: 'Bearer 4674cc54-bd05-11e7-abc4-cec278b6b50a',
},
},
path: '/dev/hello',
status: 200,
},

{
description:
'should respond with 200 if request has no authorization header',
expected: {
message: 'hello',
},
path: '/dev/hello',
status: 200,
},
].forEach(({ description, expected, options, path, status }) => {
it(description, async () => {
const url = new URL(path, BASE_URL)

const response = await fetch(url, options)
assert.equal(response.status, status)

const json = await response.json()
assert.deepEqual(json, expected)
})
})
})
32 changes: 32 additions & 0 deletions tests/integration/no-identity-source-authorizer/serverless.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
service: no-identity-source-authorizer

configValidationMode: error
deprecationNotificationMode: error

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

provider:
architecture: arm64
deploymentMethod: direct
memorySize: 1024
name: aws
region: us-east-1
runtime: nodejs18.x
stage: dev
versionFunctions: false

functions:
hello:
events:
- http:
authorizer:
name: authorizer
resultTtlInSeconds: 0
type: request
method: get
path: hello
handler: src/handler.hello

authorizer:
handler: src/authorizer.authorizer
15 changes: 15 additions & 0 deletions tests/integration/no-identity-source-authorizer/src/authorizer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export async function authorizer(event) {
return {
policyDocument: {
Statement: [
{
Action: 'execute-api:Invoke',
Effect: 'Allow',
Resource: event.methodArn,
},
],
Version: '2012-10-17',
},
principalId: 'user',
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const { stringify } = JSON

export async function hello() {
return {
body: stringify({ message: 'hello' }),
statusCode: 200,
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"type": "module"
}

0 comments on commit b7dfae6

Please sign in to comment.