diff --git a/README.md b/README.md index 78c2654..f048ce9 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ This package provides an implementation for the `@aws-appsync/utils` package tha ## Changelog: +- v0.1.8: `util.authType()` now reports `Lambda Authorization` for the identity of a Lambda-authorized Event API request, which carries what the authorizer returned as `handlerContext` rather than the `resolverContext` a GraphQL API uses - v0.1.7: complete the `@aws-appsync/utils/dynamodb` module with `query`, `sync`, the batch and transact helpers, the set helpers, `operations.remove` and `operations.updateListItem`, and the arguments the existing helpers dropped (`filter`, `projection`, `condition`, `consistentRead`, `_version` and the rest); add `util.transform.toSubscriptionFilter`; `util.dynamodb.fromS3ObjectJson` now parses instead of throwing - v0.1.6: add `util.authType()`, derived from the identity of the request; a host installs the request through the new `setResolverContext` export. `util.transform.toDynamoDBFilterExpression` and `toDynamoDBConditionExpression` now cover every operator, including `between`, `in`, `size` and `attributeType`, and nested `and`/`or`/`not`; `util.dynamodb.toDynamoDB` accepts `null` - v0.1.5: add `rds` `beginsWith`, `between` and `size` conditions, table aliases and `sql` template `where` clauses; invalid input now raises AWS's validation error instead of a raw `TypeError`, and the wildcard conditions require a string diff --git a/__tests__/authType.test.js b/__tests__/authType.test.js index beefd2a..4445101 100644 --- a/__tests__/authType.test.js +++ b/__tests__/authType.test.js @@ -24,6 +24,9 @@ const COGNITO_CLAIMS = { const LAMBDA_IDENTITY = { resolverContext: { userId: "alice", role: "admin" } }; +// the same authorizer output on an Event API, which names the key `handlerContext` +const EVENT_LAMBDA_IDENTITY = { handlerContext: { userId: "alice", invocation: "inv-1" } }; + const IAM_IDENTITY = { accountId: "123456789012", cognitoIdentityPoolId: null, @@ -134,6 +137,31 @@ describe("util.authType", () => { }); }); + // NOT compared against AWS by this suite, unlike the blocks above: `EvaluateCode` validates its + // mock context against the GraphQL identity keys and rejects one carrying `handlerContext` ("The + // following properties in your mock context object failed validation: ctx.identity"), so + // `checkResolverValid` cannot reach this shape even with `TEST_TARGET=AWS_CLOUD`. The expected + // values were instead read off responses recorded from a real Lambda-authorized Event API on + // AWS, where `ctx.identity` is `{handlerContext: {...}}` and `util.authType()` is `Lambda + // Authorization`. + describe("the identity of an Event API request", () => { + afterEach(() => setResolverContext(null)); + + const authTypeForEventIdentity = (identity) => { + setResolverContext({ identity }); + return util.authType(); + }; + + test("an identity carrying a handler context comes from a Lambda authorizer", () => { + expect(authTypeForEventIdentity(EVENT_LAMBDA_IDENTITY)).toBe("Lambda Authorization"); + }); + + // an authorizer that returns no context at all leaves the key holding an empty object + test("an identity whose handler context is empty comes from a Lambda authorizer", () => { + expect(authTypeForEventIdentity({ handlerContext: {} })).toBe("Lambda Authorization"); + }); + }); + // NOT compared against AWS: `setResolverContext` is the seam a host uses to hand the request to // the utils, so these pin our own contract with the runtime rather than any AWS behaviour. They // also show that the module a test imports is the module the resolver code imports. diff --git a/index.js b/index.js index 1b857fb..b1bd489 100644 --- a/index.js +++ b/index.js @@ -166,10 +166,12 @@ const IDENTITY_SCHEMAS = [ required: ["sub", "issuer", "claims"], }, { - // `resolverContext` is what the authorizer returned, and an authorizer may return nothing at - // all, so an identity with no keys whatsoever is a Lambda identity to AWS + // Both keys carry what the authorizer returned: a GraphQL API puts it under `resolverContext` + // and an Event API under `handlerContext`. An authorizer may return nothing at all, which + // leaves the key holding an empty object or the identity holding no keys whatsoever, so an + // empty identity is a Lambda identity to AWS rather than an absent one. authType: AUTH_TYPE_LAMBDA, - keys: ["resolverContext"], + keys: ["resolverContext", "handlerContext"], required: [], }, ]; @@ -181,6 +183,11 @@ const IDENTITY_SCHEMAS = [ // pool identity and not an OIDC one. The modes are mutually exclusive under those two rules, so // the order below only makes the outcome deterministic. Anything unmatched, an absent identity // included, is an API key request, which is the mode that populates no identity at all. +// +// The Event API identity, `{handlerContext}`, is the one shape not recorded that way: +// `EvaluateCode` validates its mock context against the GraphQL identity keys alone and rejects +// `handlerContext` before the resolver runs. It was read off the responses of a real +// Lambda-authorized Event API instead. function authTypeFromIdentity(identity) { if (identity === null || typeof identity !== "object" || Array.isArray(identity)) { return AUTH_TYPE_API_KEY; diff --git a/package.json b/package.json index 5fb5f71..a47b5bb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@localstack/appsync-utils", - "version": "0.1.7", + "version": "0.1.8", "description": "Implementation of the AppSync utils helpers", "type": "module", "main": "index.js",