diff --git a/types/apex.js/index.d.ts b/types/apex.js/index.d.ts index edc136a678..3af0de9d72 100644 --- a/types/apex.js/index.d.ts +++ b/types/apex.js/index.d.ts @@ -2,7 +2,7 @@ // Project: https://github.com/apex/node-apex // Definitions by: Yoriki Yamaguchi // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped -// TypeScript Version: 2.8 +// TypeScript Version: 3.0 /// diff --git a/types/aws-lambda/common/api-gateway.d.ts b/types/aws-lambda/common/api-gateway.d.ts index 49e4b95b8d..15f6e921e6 100644 --- a/types/aws-lambda/common/api-gateway.d.ts +++ b/types/aws-lambda/common/api-gateway.d.ts @@ -1,8 +1,33 @@ -// Used by both APIGatewayProxyEvent and APIGatewayAuthorizerEvent -export interface APIGatewayEventRequestContext { +// Types shared between trigger/api-gateway-authorizer.d.ts and api-gateway-proxy.d.ts + +// Poorly documented, but API Gateway will just fail internally if +// the context type does not match this. +// Note that although non-string types will be accepted, they will be +// coerced to strings on the other side. +export interface APIGatewayAuthorizerResultContext { + [name: string]: string | number | boolean | null | undefined; +} + +// Default authorizer type, prefer using a specific type with the "...WithAuthorizer..." variant types. +// Note that this doesn't have to be a context from a custom lambda outhorizer, AWS also has a cognito +// authorizer type and could add more, so the property won't always be a string. +export type APIGatewayEventDefaultAuthorizerContext = undefined | null | { + [name: string]: any; +}; + +export type APIGatewayEventRequestContext = + APIGatewayEventRequestContextWithAuthorizer; + +// The requestContext property of both request authorizer and proxy integration events. +export interface APIGatewayEventRequestContextWithAuthorizer { accountId: string; apiId: string; - authorizer?: AuthResponseContext | null; + // This one is a bit confusing: it is not actually present in authorizer calls + // and proxy calls without an authorizer. We model this by allowing undefined in the type, + // since it ends up the same and avoids breaking users that are testing the property. + // This lets us allow parameterizing the authorizer for proxy events that know what authorizer + // context values they have. + authorizer: TAuthorizerContext; connectedAt?: number; connectionId?: string; domainName?: string; @@ -40,10 +65,3 @@ export interface APIGatewayEventIdentity { userAgent: string | null; userArn: string | null; } - -/** - * http://docs.aws.amazon.com/apigateway/latest/developerguide/use-custom-authorizer.html#api-gateway-custom-authorizer-output - */ -export interface AuthResponseContext { - [name: string]: any; -} diff --git a/types/aws-lambda/index.d.ts b/types/aws-lambda/index.d.ts index 53d1b819f3..d5a38f51f4 100644 --- a/types/aws-lambda/index.d.ts +++ b/types/aws-lambda/index.d.ts @@ -34,7 +34,7 @@ // Marian Zange // Alexander Pepper // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped -// TypeScript Version: 2.8 +// TypeScript Version: 3.0 export * from "./handler"; export * from "./common/api-gateway"; diff --git a/types/aws-lambda/test/api-gateway-tests.ts b/types/aws-lambda/test/api-gateway-tests.ts index a91d338883..540e9aee88 100644 --- a/types/aws-lambda/test/api-gateway-tests.ts +++ b/types/aws-lambda/test/api-gateway-tests.ts @@ -1,18 +1,70 @@ import { + APIGatewayAuthorizerHandler, + APIGatewayAuthorizerResult, + APIGatewayAuthorizerResultContext, + APIGatewayAuthorizerWithContextHandler, + APIGatewayAuthorizerWithContextResult, APIGatewayEvent, + APIGatewayEventDefaultAuthorizerContext, + APIGatewayEventLambdaAuthorizerContext, APIGatewayEventRequestContext, + APIGatewayEventRequestContextWithAuthorizer, + APIGatewayProxyEvent, APIGatewayProxyHandler, APIGatewayProxyResult, + APIGatewayProxyWithLambdaAuthorizerEventRequestContext, + APIGatewayProxyWithLambdaAuthorizerHandler, + APIGatewayRequestAuthorizerHandler, + APIGatewayRequestAuthorizerWithContextHandler, + APIGatewayTokenAuthorizerHandler, + APIGatewayTokenAuthorizerWithContextHandler, AuthResponseContext, Context, CustomAuthorizerHandler, CustomAuthorizerResult, PolicyDocument, - ProxyHandler, ProxyCallback, + ProxyHandler, Statement, } from "aws-lambda"; +interface CustomAuthorizerContext extends APIGatewayAuthorizerResultContext { + valid: string | number | boolean | null | undefined; + str: string; + num: number; + bool: boolean; + numOrNull: number | null; + numOrUndefined: number | undefined; + und: undefined; +} + +// Can't serialize objects in the response from an authorizer +interface CustomAuthorizerInvalidResponseContext extends APIGatewayAuthorizerResultContext { + valid: string | number | boolean | null | undefined; + // $ExpectError + invalid: { + id: number; + }; +} + +// Enforce custom response contexts extend APIGatewayAuthorizerResultContext for use in authorizer, +// $ExpectError +type InvalidCustomAuthorizerHandler = APIGatewayAuthorizerWithContextHandler<{ + valid: string | number | boolean | null | undefined; + invalid: { + id: number; + }; +}>; + +// but don't care about in proxy, since it's overkill to force extending an interface if +// its not defined in the same codebase +type ProbablyInvalidCustomProxyHandler = APIGatewayProxyWithLambdaAuthorizerHandler<{ + valid: string | number | boolean | null | undefined; + invalid: { + id: number; + }; +}>; + let proxyHandler: APIGatewayProxyHandler = async (event, context, callback) => { strOrNull = event.body; str = event.headers['example']; @@ -26,12 +78,23 @@ let proxyHandler: APIGatewayProxyHandler = async (event, context, callback) => { str = event.stageVariables!['example']; let requestContext: APIGatewayEventRequestContext; requestContext = event.requestContext; + let requestContextWithCustomAuthorizer: APIGatewayEventRequestContextWithAuthorizer; + // $ExpectError + requestContextWithCustomAuthorizer = event.requestContext; str = event.resource; str = requestContext.protocol; str = requestContext.accountId; str = requestContext.apiId; - const authContext: AuthResponseContext | null | undefined = requestContext.authorizer; + const authContext: APIGatewayEventDefaultAuthorizerContext = requestContext.authorizer; + if (authContext) { + // Anything goes by default + str = authContext.claims[str]; + str = authContext.principalId; + num = authContext.integrationLatency; + // Even probable mistakes: lambda contexts properties are converted to string + num = authContext.num; + } numOrUndefined = requestContext.connectedAt; strOrUndefined = requestContext.connectionId; strOrUndefined = requestContext.domainName; @@ -62,6 +125,55 @@ let proxyHandler: APIGatewayProxyHandler = async (event, context, callback) => { str = requestContext.resourcePath; strOrUndefined = requestContext.routeKey; + const result = createProxyResult(); + + callback(new Error()); + callback(null, result); + return result; +}; + +const proxyHandlerWithCustomAuthorizer: APIGatewayProxyWithLambdaAuthorizerHandler = async (event, context, callback) => { + // standard fields... + strOrNull = event.body; + str = event.headers['example']; + str = event.multiValueHeaders['example'][0]; + + // It seems like it would be easy to make this mistake, but it's still a useful type. + let requestContextWithAuthorizerDirectly: APIGatewayEventRequestContextWithAuthorizer; + // $ExpectError + requestContextWithAuthorizerDirectly = event.requestContext; + + // Check assignable to named types + let requestContext: APIGatewayProxyWithLambdaAuthorizerEventRequestContext; + requestContext = event.requestContext; + + let authorizer: APIGatewayEventLambdaAuthorizerContext; + authorizer = requestContext.authorizer; + + // And it can be converted down to the basic type + const basicEvent: APIGatewayProxyEvent = event; + const basicRequestContext: APIGatewayEventRequestContext = event.requestContext; + + // All non-null or undefined types are converted to string. + str = authorizer.valid; + str = authorizer.str; + str = authorizer.num; + str = authorizer.bool; + strOrNull = authorizer.numOrNull; + strOrUndefined = authorizer.numOrUndefined; + // And these extra properties are added + str = authorizer.principalId; + num = authorizer.integrationLatency; + + const result = createProxyResult(); + + callback(new Error()); + callback(null, result); + + return result; +}; + +function createProxyResult(): APIGatewayProxyResult { let result: APIGatewayProxyResult = { statusCode: num, body: str, @@ -79,13 +191,131 @@ let proxyHandler: APIGatewayProxyHandler = async (event, context, callback) => { isBase64Encoded: true, body: str, }; + return result; +} + +const authorizer: APIGatewayAuthorizerHandler = async (event, context, callback) => { + if (event.type === "TOKEN") { + str = event.methodArn; + str = event.authorizationToken; + str = event.resource; // $ExpectError + } else { + event.type; // $ExpectType "REQUEST" + str = event.methodArn; // $ExpectError + str = event.resource; + } + + let result: APIGatewayAuthorizerResult = createAuthorizerResult(); + // Can convert down to existing type + result = createAuthorizerResultWithCustomContext(); callback(new Error()); callback(null, result); return result; }; -const authorizerHandler: CustomAuthorizerHandler = async (event, context, callback) => { +const authorizerWithCustomContext: APIGatewayAuthorizerWithContextHandler = async (event, context, callback) => { + if (event.type === "TOKEN") { + str = event.methodArn; + str = event.authorizationToken; + str = event.resource; // $ExpectError + } else { + event.type; // $ExpectType "REQUEST" + str = event.methodArn; // $ExpectError + str = event.resource; + } + + let result: APIGatewayAuthorizerWithContextResult; + result = createAuthorizerResultWithCustomContext(); + + // Can't convert up from existing type + // $ExpectError + result = createAuthorizerResult(); + + callback(new Error()); + callback(null, result); + return result; +}; + +const tokenAuthorizer: APIGatewayTokenAuthorizerHandler = async (event, context, callback) => { + event.type; // $ExpectType "TOKEN" + + str = event.type; + str = event.methodArn; + str = event.authorizationToken; + strOrUndefined = event.resource; // $ExpectError + // etc... + + const result = createAuthorizerResult(); + + callback(new Error()); + callback(null, result); + return result; +}; + +const tokenAuthorizerWithCustomContext: APIGatewayTokenAuthorizerWithContextHandler = async (event, context, callback) => { + event.type; // $ExpectType "TOKEN" + + str = event.type; + str = event.methodArn; + str = event.authorizationToken; + strOrUndefined = event.resource; // $ExpectError + // etc... + + const result = createAuthorizerResultWithCustomContext(); + + callback(new Error()); + callback(null, result); + return result; +}; + +const requestAuthorizer: APIGatewayRequestAuthorizerHandler = async (event, context, callback) => { + event.type; // $ExpectType "REQUEST" + + str = event.type; + str = event.methodArn; // $ExpectError + str = event.authorizationToken; // $ExpectError + str = event.resource; + str = event.path; + str = event.httpMethod; + if (event.headers !== null) + str = event.headers[str]; + if (event.multiValueHeaders !== null) + str = event.multiValueHeaders[str][num]; + if (event.pathParameters !== null) + str = event.pathParameters[str]; + if (event.queryStringParameters !== null) + str = event.queryStringParameters[str]; + if (event.multiValueQueryStringParameters !== null) + str = event.multiValueQueryStringParameters[str][num]; + if (event.stageVariables !== null) + str = event.stageVariables[str]; + const requestContext: APIGatewayEventRequestContext = event.requestContext; + str = event.domainName; + str = event.apiId; + + const result = createAuthorizerResult(); + + callback(new Error()); + callback(null, result); + return result; +}; + +const requestAuthorizerWithCustomContext: APIGatewayRequestAuthorizerWithContextHandler = async (event, context, callback) => { + event.type; // $ExpectType "REQUEST" + + str = event.type; + str = event.methodArn; // $ExpectError + str = event.authorizationToken; // $ExpectError + + const result = createAuthorizerResultWithCustomContext(); + + callback(new Error()); + callback(null, result); + return result; +}; + +const legacyAuthorizerHandler: CustomAuthorizerHandler = async (event, context, callback) => { str = event.type; str = event.methodArn; strOrUndefined = event.authorizationToken; @@ -103,6 +333,14 @@ const authorizerHandler: CustomAuthorizerHandler = async (event, context, callba strOrUndefined = event.domainName; strOrUndefined = event.apiId; + const result = createAuthorizerResult(); + + callback(new Error()); + callback(null, result); + return result; +}; + +function createPolicyDocument(): PolicyDocument { let statement: Statement = { Action: str, Effect: str, @@ -159,16 +397,60 @@ const authorizerHandler: CustomAuthorizerHandler = async (event, context, callba policyDocument = { Version: str, Statement: [statement, statement] }; + return policyDocument; +} + +function createAuthorizerResultWithCustomContext(): APIGatewayAuthorizerWithContextResult { + let result: APIGatewayAuthorizerWithContextResult; + + // Requires context + // $ExpectError + result = { + principalId: str, + policyDocument: createPolicyDocument(), + usageIdentifierKey: strOrUndefinedOrNull, + }; + + // Invalid context + result = { + principalId: str, + policyDocument: createPolicyDocument(), + context: {}, // $ExpectError + usageIdentifierKey: strOrUndefinedOrNull, + }; + + result = { + principalId: str, + policyDocument: createPolicyDocument(), + context: { + valid: [str, num, bool, null, undefined][num], + str, + num, + bool, + numOrNull: [num, null][num], + numOrUndefined: [num, undefined][num], + und: undefined, + }, + }; + + return result; +} + +function createAuthorizerResult(): APIGatewayAuthorizerResult { + const policyDocument = createPolicyDocument(); + const authResponseContext: AuthResponseContext = { stringKey: str, numberKey: num, booleanKey: bool, + [str]: [str, num, bool][num], // string | number | bool }; let result: CustomAuthorizerResult = { principalId: str, policyDocument, context: authResponseContext, + usageIdentifierKey: strOrUndefinedOrNull, }; result = { @@ -176,10 +458,8 @@ const authorizerHandler: CustomAuthorizerHandler = async (event, context, callba policyDocument, }; - callback(new Error()); - callback(null, result); return result; -}; +} // Test old names const oldNameProxyHandler: ProxyHandler = (event: APIGatewayEvent, context: Context, cb: ProxyCallback) => {}; diff --git a/types/aws-lambda/trigger/api-gateway-authorizer.d.ts b/types/aws-lambda/trigger/api-gateway-authorizer.d.ts index 2f09a93ce3..f5a004075e 100644 --- a/types/aws-lambda/trigger/api-gateway-authorizer.d.ts +++ b/types/aws-lambda/trigger/api-gateway-authorizer.d.ts @@ -1,10 +1,79 @@ -import { APIGatewayEventRequestContext, AuthResponseContext } from "../common/api-gateway"; +import { + APIGatewayAuthorizerResultContext, + APIGatewayEventDefaultAuthorizerContext, + APIGatewayEventRequestContextWithAuthorizer, +} from "../common/api-gateway"; import { Callback, Handler } from "../handler"; -export type CustomAuthorizerHandler = Handler; -export type CustomAuthorizerCallback = Callback; +export type APIGatewayAuthorizerHandler = Handler; +export type APIGatewayAuthorizerWithContextHandler = + Handler>; -// API Gateway CustomAuthorizer "event" +export type APIGatewayAuthorizerCallback = Callback; +export type APIGatewayAuthorizerWithContextCallback = + Callback>; + +export type APIGatewayTokenAuthorizerHandler = + Handler; +export type APIGatewayTokenAuthorizerWithContextHandler = + Handler>; + +export type APIGatewayRequestAuthorizerHandler = + Handler; +export type APIGatewayRequestAuthorizerWithContextHandler = + Handler>; + +export type APIGatewayAuthorizerEvent = APIGatewayTokenAuthorizerEvent | APIGatewayRequestAuthorizerEvent; + +export interface APIGatewayTokenAuthorizerEvent { + type: "TOKEN"; + methodArn: string; + authorizationToken: string; +} + +// Note, when invoked by the tester in the AWS web console, the map values can be null, +// but they will be empty objects in the real object. +// Worse, it will include "body" and "isBase64Encoded" properties, unlike the real call! +export interface APIGatewayRequestAuthorizerEvent { + type: "REQUEST"; + resource: string; + path: string; + httpMethod: string; + headers: { [name: string]: string } | null; + multiValueHeaders: { [name: string]: string[] } | null; + pathParameters: { [name: string]: string } | null; + queryStringParameters: { [name: string]: string } | null; + multiValueQueryStringParameters: { [name: string]: string[] } | null; + stageVariables: { [name: string]: string } | null; + requestContext: APIGatewayEventRequestContextWithAuthorizer; + domainName: string; + apiId: string; +} + +export interface APIGatewayAuthorizerResult { + principalId: string; + policyDocument: PolicyDocument; + context?: APIGatewayAuthorizerResultContext | null; + usageIdentifierKey?: string | null; +} + +// Separate type so the context property is required, without pulling complex type magic. +export interface APIGatewayAuthorizerWithContextResult { + principalId: string; + policyDocument: PolicyDocument; + context: TAuthorizerContext; + usageIdentifierKey?: string | null; +} + +// Legacy event / names + +/** @deprecated Use APIGatewayAuthorizerHandler or a subtype */ +export type CustomAuthorizerHandler = Handler; + +// This one is actually fine. +export type CustomAuthorizerCallback = APIGatewayAuthorizerCallback; + +/** @deprecated Use APIGatewayAuthorizerEvent or a subtype */ export interface CustomAuthorizerEvent { type: string; methodArn: string; @@ -18,22 +87,14 @@ export interface CustomAuthorizerEvent { queryStringParameters?: { [name: string]: string } | null; multiValueQueryStringParameters?: { [name: string]: string[] } | null; stageVariables?: { [name: string]: string }; - requestContext?: APIGatewayEventRequestContext; + requestContext?: APIGatewayEventRequestContextWithAuthorizer; domainName?: string; apiId?: string; } -/** - * API Gateway CustomAuthorizer AuthResponse. - * http://docs.aws.amazon.com/apigateway/latest/developerguide/use-custom-authorizer.html#api-gateway-custom-authorizer-output - */ -export interface CustomAuthorizerResult { - principalId: string; - policyDocument: PolicyDocument; - context?: AuthResponseContext; - usageIdentifierKey?: string; -} -export type AuthResponse = CustomAuthorizerResult; +export type CustomAuthorizerResult = APIGatewayAuthorizerResult; +export type AuthResponse = APIGatewayAuthorizerResult; +export type AuthResponseContext = APIGatewayAuthorizerResultContext; /** * API Gateway CustomAuthorizer AuthResponse.PolicyDocument. diff --git a/types/aws-lambda/trigger/api-gateway-proxy.d.ts b/types/aws-lambda/trigger/api-gateway-proxy.d.ts index 40f1fe6551..b072956c63 100644 --- a/types/aws-lambda/trigger/api-gateway-proxy.d.ts +++ b/types/aws-lambda/trigger/api-gateway-proxy.d.ts @@ -1,16 +1,45 @@ -import { APIGatewayEventRequestContext } from "../common/api-gateway"; +import { + APIGatewayEventDefaultAuthorizerContext, + APIGatewayEventRequestContextWithAuthorizer, +} from "../common/api-gateway"; import { Callback, Handler } from "../handler"; export type APIGatewayProxyHandler = Handler; export type APIGatewayProxyCallback = Callback; -export type ProxyHandler = APIGatewayProxyHandler; // Old name -export type ProxyCallback = APIGatewayProxyCallback; // Old name -export type APIGatewayEvent = APIGatewayProxyEvent; // Old name -export type ProxyResult = APIGatewayProxyResult; // Old name +export type APIGatewayProxyEvent = APIGatewayProxyEventBase; -// API Gateway "event" -export interface APIGatewayProxyEvent { +export type APIGatewayProxyWithLambdaAuthorizerHandler = + Handler, APIGatewayProxyResult>; + +export type APIGatewayProxyWithCognitoAuthorizerHandler = + Handler; + +export type APIGatewayProxyWithLambdaAuthorizerEvent = + APIGatewayProxyEventBase>; + +export type APIGatewayProxyWithLambdaAuthorizerEventRequestContext = + APIGatewayEventRequestContextWithAuthorizer>; + +// API Gateway proxy integration mangles the context from a custom authorizer, +// converting all number or boolean properties to string, and adding some extra properties. +export type APIGatewayEventLambdaAuthorizerContext = { + [P in keyof TAuthorizerContext]: TAuthorizerContext[P] extends null ? null : string; +} & { + principalId: string; + integrationLatency: number; +}; + +export type APIGatewayProxyWithCognitoAuthorizerEvent = APIGatewayProxyEventBase; + +// All claims are coerced into strings. +export interface APIGatewayProxyCognitoAuthorizer { + claims: { + [name: string]: string; + }; +} + +export interface APIGatewayProxyEventBase { body: string | null; headers: { [name: string]: string }; multiValueHeaders: { [name: string]: string[] }; @@ -21,7 +50,7 @@ export interface APIGatewayProxyEvent { queryStringParameters: { [name: string]: string } | null; multiValueQueryStringParameters: { [name: string]: string[] } | null; stageVariables: { [name: string]: string } | null; - requestContext: APIGatewayEventRequestContext; + requestContext: APIGatewayEventRequestContextWithAuthorizer; resource: string; } @@ -36,3 +65,9 @@ export interface APIGatewayProxyResult { body: string; isBase64Encoded?: boolean; } + +// Legacy names +export type ProxyHandler = APIGatewayProxyHandler; +export type ProxyCallback = APIGatewayProxyCallback; +export type APIGatewayEvent = APIGatewayProxyEvent; +export type ProxyResult = APIGatewayProxyResult; diff --git a/types/cfn-response/index.d.ts b/types/cfn-response/index.d.ts index f3e3893f07..05dfc86402 100644 --- a/types/cfn-response/index.d.ts +++ b/types/cfn-response/index.d.ts @@ -2,7 +2,7 @@ // Project: https://github.com/LukeMizuhashi/cfn-response // Definitions by: Ivo Murrell // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped -// TypeScript Version: 2.8 +// TypeScript Version: 3.0 import { CloudFormationCustomResourceEvent, Context } from "aws-lambda";