Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: support authorizer with no identity source specified #1639

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
}