@aligent/cdk-secure-rest-api 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/README.md +46 -1
- package/index.d.ts +3 -0
- package/index.js +1 -1
- package/lib/secure-rest-api.d.ts +65 -0
- package/lib/secure-rest-api.js +4 -3
- package/package.json +3 -3
- package/index.ts +0 -7
- package/jest.config.ts +0 -11
- package/lib/secure-rest-api.test.ts +0 -308
- package/lib/secure-rest-api.ts +0 -129
- package/project.json +0 -36
- package/tsconfig.app.json +0 -11
- package/tsconfig.json +0 -16
- package/tsconfig.spec.json +0 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @aligent/cdk-secure-rest-api
|
|
2
2
|
|
|
3
|
+
## 1.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#1720](https://github.com/aligent/cdk-constructs/pull/1720) [`2649bf8`](https://github.com/aligent/cdk-constructs/commit/2649bf833e9b8bec9261918bd013b4b71f368fe5) Thanks [@toddhainsworth](https://github.com/toddhainsworth)! - Support nested (multi-segment) route paths in the `routes` helper. Paths such as `rewards/accounts/{accountId}/redeem` now create the intermediate resources instead of throwing `ResourceSPathPartOnly` at synth time. Routes sharing a common prefix resolve idempotently.
|
|
8
|
+
|
|
9
|
+
Add a `deployOptions` prop (CDK `StageOptions`) so the deployed stage name and other stage settings can be configured. Defaults to the CDK `prod` stage when omitted.
|
|
10
|
+
|
|
11
|
+
## 1.1.2
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- [#1688](https://github.com/aligent/cdk-constructs/pull/1688) [`5fc3de2`](https://github.com/aligent/cdk-constructs/commit/5fc3de2da74962c1e11a57a375fb11c71406d5c8) Thanks [@dependabot](https://github.com/apps/dependabot)! - chore(deps): bump the aws group across 1 directory with 10 updates
|
|
16
|
+
|
|
17
|
+
## 1.1.1
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- [#1689](https://github.com/aligent/cdk-constructs/pull/1689) [`6f1930f`](https://github.com/aligent/cdk-constructs/commit/6f1930f8d8b6d646252217a4c7363458bd22f597) Thanks [@toddhainsworth](https://github.com/toddhainsworth)! - Fix published artifact: declarations (`*.d.ts`) were missing from the tarball because the package had no `.npmignore`, so `npm pack` fell back to the workspace `.gitignore` (which excludes build outputs). Consumers with `isolatedModules: true` then resolved `index.ts` directly and hit `TS1205` on the type re-exports. This release adds the package's own `.npmignore` (matching sibling packages) and converts the type re-exports in `index.ts` to `export type`.
|
|
22
|
+
|
|
3
23
|
## 1.1.0
|
|
4
24
|
|
|
5
25
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -12,6 +12,8 @@ A CDK construct for provisioning an API Gateway REST API secured with API Key au
|
|
|
12
12
|
- Usage plan with configurable throttle rate and burst limits
|
|
13
13
|
- Configurable CORS preflight options
|
|
14
14
|
- Accepts any CDK `Integration` per route (Lambda, HTTP, Mock, Step Functions, etc.)
|
|
15
|
+
- Supports nested, multi-segment route paths (e.g. `rewards/accounts/{accountId}/redeem`)
|
|
16
|
+
- Configurable deployment stage via `deployOptions` (stage name defaults to `prod`)
|
|
15
17
|
|
|
16
18
|
## Installation
|
|
17
19
|
|
|
@@ -74,6 +76,29 @@ const api = new SecureRestApi(this, 'Api', {
|
|
|
74
76
|
});
|
|
75
77
|
```
|
|
76
78
|
|
|
79
|
+
### Nested route paths
|
|
80
|
+
|
|
81
|
+
Multi-segment paths create the intermediate resources automatically. Routes
|
|
82
|
+
sharing a common prefix resolve to the same parent resource.
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
const api = new SecureRestApi(this, 'Api', {
|
|
86
|
+
apiName: 'rewards-api',
|
|
87
|
+
routes: [
|
|
88
|
+
{
|
|
89
|
+
path: 'rewards/accounts/{accountId}/redeem',
|
|
90
|
+
methods: [HttpMethod.POST],
|
|
91
|
+
integration: new LambdaIntegration(redeemFunction),
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
path: 'rewards/reversal', // reuses the shared `rewards` resource
|
|
95
|
+
methods: [HttpMethod.POST],
|
|
96
|
+
integration: new LambdaIntegration(reversalFunction),
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
77
102
|
### Custom throttling
|
|
78
103
|
|
|
79
104
|
```typescript
|
|
@@ -87,6 +112,19 @@ const api = new SecureRestApi(this, 'Api', {
|
|
|
87
112
|
});
|
|
88
113
|
```
|
|
89
114
|
|
|
115
|
+
### Custom stage name
|
|
116
|
+
|
|
117
|
+
By default the API deploys to a `prod` stage. Pass `deployOptions` to override
|
|
118
|
+
the stage name (or any other CDK `StageOptions`, e.g. logging or tracing).
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
const api = new SecureRestApi(this, 'Api', {
|
|
122
|
+
apiName: 'my-api',
|
|
123
|
+
deployOptions: { stageName: 'staging' },
|
|
124
|
+
routes: [...],
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
90
128
|
### Custom CORS configuration
|
|
91
129
|
|
|
92
130
|
```typescript
|
|
@@ -119,10 +157,17 @@ Routes to register on the API. Each route requires:
|
|
|
119
157
|
|
|
120
158
|
| Property | Type | Description |
|
|
121
159
|
|----------|------|-------------|
|
|
122
|
-
| `path` | `string` | The resource path (leading slash is stripped automatically) |
|
|
160
|
+
| `path` | `string` | The resource path; may be nested/multi-segment (leading slash is stripped automatically) |
|
|
123
161
|
| `methods` | `HttpMethod[]` | HTTP methods to register on the resource |
|
|
124
162
|
| `integration` | `Integration` | Any CDK API Gateway integration |
|
|
125
163
|
|
|
164
|
+
### `deployOptions` (StageOptions)
|
|
165
|
+
|
|
166
|
+
Stage options for the API's default deployment, passed through to the underlying
|
|
167
|
+
CDK `RestApi`. Use `stageName` to override the deployed stage name.
|
|
168
|
+
|
|
169
|
+
Default: CDK default (`prod` stage).
|
|
170
|
+
|
|
126
171
|
### `throttle` (object)
|
|
127
172
|
|
|
128
173
|
Throttling limits applied to the usage plan.
|
package/index.d.ts
ADDED
package/index.js
CHANGED
|
@@ -3,4 +3,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.SecureRestApi = void 0;
|
|
4
4
|
const secure_rest_api_1 = require("./lib/secure-rest-api");
|
|
5
5
|
Object.defineProperty(exports, "SecureRestApi", { enumerable: true, get: function () { return secure_rest_api_1.SecureRestApi; } });
|
|
6
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
6
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSwyREFJK0I7QUFFdEIsOEZBTFAsK0JBQWEsT0FLTyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gIFNlY3VyZVJlc3RBcGksXG4gIFNlY3VyZVJlc3RBcGlQcm9wcyxcbiAgU2VjdXJlUmVzdEFwaVJvdXRlLFxufSBmcm9tIFwiLi9saWIvc2VjdXJlLXJlc3QtYXBpXCI7XG5cbmV4cG9ydCB7IFNlY3VyZVJlc3RBcGkgfTtcbmV4cG9ydCB0eXBlIHsgU2VjdXJlUmVzdEFwaVByb3BzLCBTZWN1cmVSZXN0QXBpUm91dGUgfTtcbiJdfQ==
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { ApiKey, Integration, RestApi, StageOptions, UsagePlan } from "aws-cdk-lib/aws-apigateway";
|
|
2
|
+
import { HttpMethod } from "aws-cdk-lib/aws-apigatewayv2";
|
|
3
|
+
import { Construct } from "constructs";
|
|
4
|
+
export interface SecureRestApiRoute {
|
|
5
|
+
path: string;
|
|
6
|
+
methods: HttpMethod[];
|
|
7
|
+
integration: Integration;
|
|
8
|
+
}
|
|
9
|
+
export interface SecureRestApiProps {
|
|
10
|
+
/**
|
|
11
|
+
* The name of the API.
|
|
12
|
+
*/
|
|
13
|
+
apiName: string;
|
|
14
|
+
/**
|
|
15
|
+
* Description for the API.
|
|
16
|
+
*/
|
|
17
|
+
description?: string;
|
|
18
|
+
/**
|
|
19
|
+
* CORS configuration.
|
|
20
|
+
*
|
|
21
|
+
* `allowOrigins` overrides the default (all origins).
|
|
22
|
+
* `additionalMethods` and `additionalHeaders` are appended to the defaults
|
|
23
|
+
* (GET/OPTIONS and Content-Type/X-Api-Key respectively).
|
|
24
|
+
*/
|
|
25
|
+
corsOptions?: {
|
|
26
|
+
allowOrigins?: string[];
|
|
27
|
+
additionalMethods?: string[];
|
|
28
|
+
additionalHeaders?: string[];
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Routes to register on the API.
|
|
32
|
+
*/
|
|
33
|
+
routes: SecureRestApiRoute[];
|
|
34
|
+
/**
|
|
35
|
+
* Stage options for the API's default deployment.
|
|
36
|
+
*
|
|
37
|
+
* Use `stageName` to override the deployed stage name.
|
|
38
|
+
* @default stageName "prod" (CDK default)
|
|
39
|
+
*/
|
|
40
|
+
deployOptions?: StageOptions;
|
|
41
|
+
/**
|
|
42
|
+
* Throttling limits for the usage plan.
|
|
43
|
+
* @default { rateLimit: 100, burstLimit: 200 }
|
|
44
|
+
*/
|
|
45
|
+
throttle?: {
|
|
46
|
+
rateLimit: number;
|
|
47
|
+
burstLimit: number;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Override the generated API key name.
|
|
51
|
+
* @default `{apiName}-api-key`
|
|
52
|
+
*/
|
|
53
|
+
apiKeyName?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Override the generated usage plan name.
|
|
56
|
+
* @default `{apiName}-usage-plan`
|
|
57
|
+
*/
|
|
58
|
+
usagePlanName?: string;
|
|
59
|
+
}
|
|
60
|
+
export declare class SecureRestApi extends Construct {
|
|
61
|
+
readonly api: RestApi;
|
|
62
|
+
readonly apiKey: ApiKey;
|
|
63
|
+
readonly usagePlan: UsagePlan;
|
|
64
|
+
constructor(scope: Construct, id: string, props: SecureRestApiProps);
|
|
65
|
+
}
|
package/lib/secure-rest-api.js
CHANGED
|
@@ -7,10 +7,11 @@ class SecureRestApi extends constructs_1.Construct {
|
|
|
7
7
|
constructor(scope, id, props) {
|
|
8
8
|
var _a, _b, _c;
|
|
9
9
|
super(scope, id);
|
|
10
|
-
const { apiName, description, corsOptions, routes, throttle = { rateLimit: 100, burstLimit: 200 }, apiKeyName, usagePlanName, } = props;
|
|
10
|
+
const { apiName, description, corsOptions, routes, deployOptions, throttle = { rateLimit: 100, burstLimit: 200 }, apiKeyName, usagePlanName, } = props;
|
|
11
11
|
this.api = new aws_apigateway_1.RestApi(this, "Api", {
|
|
12
12
|
restApiName: apiName,
|
|
13
13
|
description: description !== null && description !== void 0 ? description : `REST API for ${apiName} service`,
|
|
14
|
+
deployOptions,
|
|
14
15
|
defaultCorsPreflightOptions: {
|
|
15
16
|
allowOrigins: (_a = corsOptions === null || corsOptions === void 0 ? void 0 : corsOptions.allowOrigins) !== null && _a !== void 0 ? _a : aws_apigateway_1.Cors.ALL_ORIGINS,
|
|
16
17
|
allowMethods: [
|
|
@@ -26,7 +27,7 @@ class SecureRestApi extends constructs_1.Construct {
|
|
|
26
27
|
},
|
|
27
28
|
});
|
|
28
29
|
for (const route of routes) {
|
|
29
|
-
const resource = this.api.root.
|
|
30
|
+
const resource = this.api.root.resourceForPath(route.path.replace(/^\//, ""));
|
|
30
31
|
for (const method of route.methods) {
|
|
31
32
|
resource.addMethod(method, route.integration, { apiKeyRequired: true });
|
|
32
33
|
}
|
|
@@ -48,4 +49,4 @@ class SecureRestApi extends constructs_1.Construct {
|
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
51
|
exports.SecureRestApi = SecureRestApi;
|
|
51
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
52
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VjdXJlLXJlc3QtYXBpLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsic2VjdXJlLXJlc3QtYXBpLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLCtEQVFvQztBQUVwQywyQ0FBdUM7QUFtRXZDLE1BQWEsYUFBYyxTQUFRLHNCQUFTO0lBSzFDLFlBQVksS0FBZ0IsRUFBRSxFQUFVLEVBQUUsS0FBeUI7O1FBQ2pFLEtBQUssQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFFakIsTUFBTSxFQUNKLE9BQU8sRUFDUCxXQUFXLEVBQ1gsV0FBVyxFQUNYLE1BQU0sRUFDTixhQUFhLEVBQ2IsUUFBUSxHQUFHLEVBQUUsU0FBUyxFQUFFLEdBQUcsRUFBRSxVQUFVLEVBQUUsR0FBRyxFQUFFLEVBQzlDLFVBQVUsRUFDVixhQUFhLEdBQ2QsR0FBRyxLQUFLLENBQUM7UUFFVixJQUFJLENBQUMsR0FBRyxHQUFHLElBQUksd0JBQU8sQ0FBQyxJQUFJLEVBQUUsS0FBSyxFQUFFO1lBQ2xDLFdBQVcsRUFBRSxPQUFPO1lBQ3BCLFdBQVcsRUFBRSxXQUFXLGFBQVgsV0FBVyxjQUFYLFdBQVcsR0FBSSxnQkFBZ0IsT0FBTyxVQUFVO1lBQzdELGFBQWE7WUFDYiwyQkFBMkIsRUFBRTtnQkFDM0IsWUFBWSxFQUFFLE1BQUEsV0FBVyxhQUFYLFdBQVcsdUJBQVgsV0FBVyxDQUFFLFlBQVksbUNBQUkscUJBQUksQ0FBQyxXQUFXO2dCQUMzRCxZQUFZLEVBQUU7b0JBQ1osS0FBSztvQkFDTCxTQUFTO29CQUNULEdBQUcsQ0FBQyxNQUFBLFdBQVcsYUFBWCxXQUFXLHVCQUFYLFdBQVcsQ0FBRSxpQkFBaUIsbUNBQUksRUFBRSxDQUFDO2lCQUMxQztnQkFDRCxZQUFZLEVBQUU7b0JBQ1osY0FBYztvQkFDZCxXQUFXO29CQUNYLEdBQUcsQ0FBQyxNQUFBLFdBQVcsYUFBWCxXQUFXLHVCQUFYLFdBQVcsQ0FBRSxpQkFBaUIsbUNBQUksRUFBRSxDQUFDO2lCQUMxQzthQUNGO1NBQ0YsQ0FBQyxDQUFDO1FBRUgsS0FBSyxNQUFNLEtBQUssSUFBSSxNQUFNLEVBQUUsQ0FBQztZQUMzQixNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxlQUFlLENBQzVDLEtBQUssQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FDOUIsQ0FBQztZQUNGLEtBQUssTUFBTSxNQUFNLElBQUksS0FBSyxDQUFDLE9BQU8sRUFBRSxDQUFDO2dCQUNuQyxRQUFRLENBQUMsU0FBUyxDQUFDLE1BQU0sRUFBRSxLQUFLLENBQUMsV0FBVyxFQUFFLEVBQUUsY0FBYyxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7WUFDMUUsQ0FBQztRQUNILENBQUM7UUFFRCxJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksdUJBQU0sQ0FBQyxJQUFJLEVBQUUsUUFBUSxFQUFFO1lBQ3ZDLFdBQVcsRUFBRSxlQUFlLE9BQU8sVUFBVTtZQUM3QyxVQUFVLEVBQUUsVUFBVSxhQUFWLFVBQVUsY0FBVixVQUFVLEdBQUksR0FBRyxPQUFPLFVBQVU7U0FDL0MsQ0FBQyxDQUFDO1FBRUgsSUFBSSxDQUFDLFNBQVMsR0FBRyxJQUFJLDBCQUFTLENBQUMsSUFBSSxFQUFFLFdBQVcsRUFBRTtZQUNoRCxJQUFJLEVBQUUsYUFBYSxhQUFiLGFBQWEsY0FBYixhQUFhLEdBQUksR0FBRyxPQUFPLGFBQWE7WUFDOUMsV0FBVyxFQUFFLGtCQUFrQixPQUFPLFVBQVU7WUFDaEQsUUFBUTtTQUNULENBQUMsQ0FBQztRQUVILElBQUksQ0FBQyxTQUFTLENBQUMsV0FBVyxDQUFDO1lBQ3pCLEdBQUcsRUFBRSxJQUFJLENBQUMsR0FBZTtZQUN6QixLQUFLLEVBQUUsSUFBSSxDQUFDLEdBQUcsQ0FBQyxlQUFlO1NBQ2hDLENBQUMsQ0FBQztRQUNILElBQUksQ0FBQyxTQUFTLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUN4QyxDQUFDO0NBQ0Y7QUFoRUQsc0NBZ0VDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHtcbiAgQXBpS2V5LFxuICBDb3JzLFxuICBJbnRlZ3JhdGlvbixcbiAgSVJlc3RBcGksXG4gIFJlc3RBcGksXG4gIFN0YWdlT3B0aW9ucyxcbiAgVXNhZ2VQbGFuLFxufSBmcm9tIFwiYXdzLWNkay1saWIvYXdzLWFwaWdhdGV3YXlcIjtcbmltcG9ydCB7IEh0dHBNZXRob2QgfSBmcm9tIFwiYXdzLWNkay1saWIvYXdzLWFwaWdhdGV3YXl2MlwiO1xuaW1wb3J0IHsgQ29uc3RydWN0IH0gZnJvbSBcImNvbnN0cnVjdHNcIjtcblxuZXhwb3J0IGludGVyZmFjZSBTZWN1cmVSZXN0QXBpUm91dGUge1xuICBwYXRoOiBzdHJpbmc7XG4gIG1ldGhvZHM6IEh0dHBNZXRob2RbXTtcbiAgaW50ZWdyYXRpb246IEludGVncmF0aW9uO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIFNlY3VyZVJlc3RBcGlQcm9wcyB7XG4gIC8qKlxuICAgKiBUaGUgbmFtZSBvZiB0aGUgQVBJLlxuICAgKi9cbiAgYXBpTmFtZTogc3RyaW5nO1xuXG4gIC8qKlxuICAgKiBEZXNjcmlwdGlvbiBmb3IgdGhlIEFQSS5cbiAgICovXG4gIGRlc2NyaXB0aW9uPzogc3RyaW5nO1xuXG4gIC8qKlxuICAgKiBDT1JTIGNvbmZpZ3VyYXRpb24uXG4gICAqXG4gICAqIGBhbGxvd09yaWdpbnNgIG92ZXJyaWRlcyB0aGUgZGVmYXVsdCAoYWxsIG9yaWdpbnMpLlxuICAgKiBgYWRkaXRpb25hbE1ldGhvZHNgIGFuZCBgYWRkaXRpb25hbEhlYWRlcnNgIGFyZSBhcHBlbmRlZCB0byB0aGUgZGVmYXVsdHNcbiAgICogKEdFVC9PUFRJT05TIGFuZCBDb250ZW50LVR5cGUvWC1BcGktS2V5IHJlc3BlY3RpdmVseSkuXG4gICAqL1xuICBjb3JzT3B0aW9ucz86IHtcbiAgICBhbGxvd09yaWdpbnM/OiBzdHJpbmdbXTtcbiAgICBhZGRpdGlvbmFsTWV0aG9kcz86IHN0cmluZ1tdO1xuICAgIGFkZGl0aW9uYWxIZWFkZXJzPzogc3RyaW5nW107XG4gIH07XG5cbiAgLyoqXG4gICAqIFJvdXRlcyB0byByZWdpc3RlciBvbiB0aGUgQVBJLlxuICAgKi9cbiAgcm91dGVzOiBTZWN1cmVSZXN0QXBpUm91dGVbXTtcblxuICAvKipcbiAgICogU3RhZ2Ugb3B0aW9ucyBmb3IgdGhlIEFQSSdzIGRlZmF1bHQgZGVwbG95bWVudC5cbiAgICpcbiAgICogVXNlIGBzdGFnZU5hbWVgIHRvIG92ZXJyaWRlIHRoZSBkZXBsb3llZCBzdGFnZSBuYW1lLlxuICAgKiBAZGVmYXVsdCBzdGFnZU5hbWUgXCJwcm9kXCIgKENESyBkZWZhdWx0KVxuICAgKi9cbiAgZGVwbG95T3B0aW9ucz86IFN0YWdlT3B0aW9ucztcblxuICAvKipcbiAgICogVGhyb3R0bGluZyBsaW1pdHMgZm9yIHRoZSB1c2FnZSBwbGFuLlxuICAgKiBAZGVmYXVsdCB7IHJhdGVMaW1pdDogMTAwLCBidXJzdExpbWl0OiAyMDAgfVxuICAgKi9cbiAgdGhyb3R0bGU/OiB7XG4gICAgcmF0ZUxpbWl0OiBudW1iZXI7XG4gICAgYnVyc3RMaW1pdDogbnVtYmVyO1xuICB9O1xuXG4gIC8qKlxuICAgKiBPdmVycmlkZSB0aGUgZ2VuZXJhdGVkIEFQSSBrZXkgbmFtZS5cbiAgICogQGRlZmF1bHQgYHthcGlOYW1lfS1hcGkta2V5YFxuICAgKi9cbiAgYXBpS2V5TmFtZT86IHN0cmluZztcblxuICAvKipcbiAgICogT3ZlcnJpZGUgdGhlIGdlbmVyYXRlZCB1c2FnZSBwbGFuIG5hbWUuXG4gICAqIEBkZWZhdWx0IGB7YXBpTmFtZX0tdXNhZ2UtcGxhbmBcbiAgICovXG4gIHVzYWdlUGxhbk5hbWU/OiBzdHJpbmc7XG59XG5cbmV4cG9ydCBjbGFzcyBTZWN1cmVSZXN0QXBpIGV4dGVuZHMgQ29uc3RydWN0IHtcbiAgcHVibGljIHJlYWRvbmx5IGFwaTogUmVzdEFwaTtcbiAgcHVibGljIHJlYWRvbmx5IGFwaUtleTogQXBpS2V5O1xuICBwdWJsaWMgcmVhZG9ubHkgdXNhZ2VQbGFuOiBVc2FnZVBsYW47XG5cbiAgY29uc3RydWN0b3Ioc2NvcGU6IENvbnN0cnVjdCwgaWQ6IHN0cmluZywgcHJvcHM6IFNlY3VyZVJlc3RBcGlQcm9wcykge1xuICAgIHN1cGVyKHNjb3BlLCBpZCk7XG5cbiAgICBjb25zdCB7XG4gICAgICBhcGlOYW1lLFxuICAgICAgZGVzY3JpcHRpb24sXG4gICAgICBjb3JzT3B0aW9ucyxcbiAgICAgIHJvdXRlcyxcbiAgICAgIGRlcGxveU9wdGlvbnMsXG4gICAgICB0aHJvdHRsZSA9IHsgcmF0ZUxpbWl0OiAxMDAsIGJ1cnN0TGltaXQ6IDIwMCB9LFxuICAgICAgYXBpS2V5TmFtZSxcbiAgICAgIHVzYWdlUGxhbk5hbWUsXG4gICAgfSA9IHByb3BzO1xuXG4gICAgdGhpcy5hcGkgPSBuZXcgUmVzdEFwaSh0aGlzLCBcIkFwaVwiLCB7XG4gICAgICByZXN0QXBpTmFtZTogYXBpTmFtZSxcbiAgICAgIGRlc2NyaXB0aW9uOiBkZXNjcmlwdGlvbiA/PyBgUkVTVCBBUEkgZm9yICR7YXBpTmFtZX0gc2VydmljZWAsXG4gICAgICBkZXBsb3lPcHRpb25zLFxuICAgICAgZGVmYXVsdENvcnNQcmVmbGlnaHRPcHRpb25zOiB7XG4gICAgICAgIGFsbG93T3JpZ2luczogY29yc09wdGlvbnM/LmFsbG93T3JpZ2lucyA/PyBDb3JzLkFMTF9PUklHSU5TLFxuICAgICAgICBhbGxvd01ldGhvZHM6IFtcbiAgICAgICAgICBcIkdFVFwiLFxuICAgICAgICAgIFwiT1BUSU9OU1wiLFxuICAgICAgICAgIC4uLihjb3JzT3B0aW9ucz8uYWRkaXRpb25hbE1ldGhvZHMgPz8gW10pLFxuICAgICAgICBdLFxuICAgICAgICBhbGxvd0hlYWRlcnM6IFtcbiAgICAgICAgICBcIkNvbnRlbnQtVHlwZVwiLFxuICAgICAgICAgIFwiWC1BcGktS2V5XCIsXG4gICAgICAgICAgLi4uKGNvcnNPcHRpb25zPy5hZGRpdGlvbmFsSGVhZGVycyA/PyBbXSksXG4gICAgICAgIF0sXG4gICAgICB9LFxuICAgIH0pO1xuXG4gICAgZm9yIChjb25zdCByb3V0ZSBvZiByb3V0ZXMpIHtcbiAgICAgIGNvbnN0IHJlc291cmNlID0gdGhpcy5hcGkucm9vdC5yZXNvdXJjZUZvclBhdGgoXG4gICAgICAgIHJvdXRlLnBhdGgucmVwbGFjZSgvXlxcLy8sIFwiXCIpXG4gICAgICApO1xuICAgICAgZm9yIChjb25zdCBtZXRob2Qgb2Ygcm91dGUubWV0aG9kcykge1xuICAgICAgICByZXNvdXJjZS5hZGRNZXRob2QobWV0aG9kLCByb3V0ZS5pbnRlZ3JhdGlvbiwgeyBhcGlLZXlSZXF1aXJlZDogdHJ1ZSB9KTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICB0aGlzLmFwaUtleSA9IG5ldyBBcGlLZXkodGhpcywgXCJBcGlLZXlcIiwge1xuICAgICAgZGVzY3JpcHRpb246IGBBUEkgS2V5IGZvciAke2FwaU5hbWV9IHNlcnZpY2VgLFxuICAgICAgYXBpS2V5TmFtZTogYXBpS2V5TmFtZSA/PyBgJHthcGlOYW1lfS1hcGkta2V5YCxcbiAgICB9KTtcblxuICAgIHRoaXMudXNhZ2VQbGFuID0gbmV3IFVzYWdlUGxhbih0aGlzLCBcIlVzYWdlUGxhblwiLCB7XG4gICAgICBuYW1lOiB1c2FnZVBsYW5OYW1lID8/IGAke2FwaU5hbWV9LXVzYWdlLXBsYW5gLFxuICAgICAgZGVzY3JpcHRpb246IGBVc2FnZSBwbGFuIGZvciAke2FwaU5hbWV9IHNlcnZpY2VgLFxuICAgICAgdGhyb3R0bGUsXG4gICAgfSk7XG5cbiAgICB0aGlzLnVzYWdlUGxhbi5hZGRBcGlTdGFnZSh7XG4gICAgICBhcGk6IHRoaXMuYXBpIGFzIElSZXN0QXBpLFxuICAgICAgc3RhZ2U6IHRoaXMuYXBpLmRlcGxveW1lbnRTdGFnZSxcbiAgICB9KTtcbiAgICB0aGlzLnVzYWdlUGxhbi5hZGRBcGlLZXkodGhpcy5hcGlLZXkpO1xuICB9XG59XG4iXX0=
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aligent/cdk-secure-rest-api",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "A CDK construct for creating API Gateway REST APIs secured with API Key authentication and usage plan throttling",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/jest": "^29.5.10",
|
|
23
|
-
"@types/node": "^
|
|
24
|
-
"aws-cdk": "^2.
|
|
23
|
+
"@types/node": "^24.13.2",
|
|
24
|
+
"aws-cdk": "^2.1124.1",
|
|
25
25
|
"jest": "^29.7.0",
|
|
26
26
|
"ts-jest": "^29.4.9",
|
|
27
27
|
"ts-node": "^10.9.1",
|
package/index.ts
DELETED
package/jest.config.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/* eslint-disable */
|
|
2
|
-
export default {
|
|
3
|
-
displayName: "secure-rest-api",
|
|
4
|
-
preset: "../../../jest.preset.js",
|
|
5
|
-
testEnvironment: "node",
|
|
6
|
-
transform: {
|
|
7
|
-
"^.+\\.[tj]s$": ["ts-jest", { tsconfig: "<rootDir>/tsconfig.spec.json" }],
|
|
8
|
-
},
|
|
9
|
-
moduleFileExtensions: ["ts", "js", "html"],
|
|
10
|
-
coverageDirectory: "../../../coverage/packages/secure-rest-api",
|
|
11
|
-
};
|
|
@@ -1,308 +0,0 @@
|
|
|
1
|
-
import { App, Stack } from "aws-cdk-lib";
|
|
2
|
-
import { Template } from "aws-cdk-lib/assertions";
|
|
3
|
-
import { LambdaIntegration, MockIntegration } from "aws-cdk-lib/aws-apigateway";
|
|
4
|
-
import { HttpMethod } from "aws-cdk-lib/aws-apigatewayv2";
|
|
5
|
-
import { Function, InlineCode, Runtime } from "aws-cdk-lib/aws-lambda";
|
|
6
|
-
import { Construct } from "constructs";
|
|
7
|
-
import { SecureRestApi } from "./secure-rest-api";
|
|
8
|
-
|
|
9
|
-
const createStack = () => {
|
|
10
|
-
const app = new App();
|
|
11
|
-
const stack = new Stack(app, "TestStack");
|
|
12
|
-
return { app, stack };
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
const mockIntegration = () =>
|
|
16
|
-
new MockIntegration({
|
|
17
|
-
integrationResponses: [{ statusCode: "200" }],
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
const lambdaIntegration = (scope: Construct) => {
|
|
21
|
-
const fn = new Function(scope, "Handler", {
|
|
22
|
-
runtime: Runtime.NODEJS_22_X,
|
|
23
|
-
handler: "index.handler",
|
|
24
|
-
code: InlineCode.fromInline("exports.handler = () => {}"),
|
|
25
|
-
});
|
|
26
|
-
return new LambdaIntegration(fn);
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
describe("SecureRestApi", () => {
|
|
30
|
-
describe("API Gateway", () => {
|
|
31
|
-
it("creates a REST API with the given name", () => {
|
|
32
|
-
const { stack } = createStack();
|
|
33
|
-
new SecureRestApi(stack, "Api", {
|
|
34
|
-
apiName: "my-api",
|
|
35
|
-
routes: [
|
|
36
|
-
{
|
|
37
|
-
path: "items",
|
|
38
|
-
methods: [HttpMethod.GET],
|
|
39
|
-
integration: mockIntegration(),
|
|
40
|
-
},
|
|
41
|
-
],
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
Template.fromStack(stack).hasResourceProperties(
|
|
45
|
-
"AWS::ApiGateway::RestApi",
|
|
46
|
-
{ Name: "my-api" }
|
|
47
|
-
);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("uses the provided description", () => {
|
|
51
|
-
const { stack } = createStack();
|
|
52
|
-
new SecureRestApi(stack, "Api", {
|
|
53
|
-
apiName: "my-api",
|
|
54
|
-
description: "Custom description",
|
|
55
|
-
routes: [
|
|
56
|
-
{
|
|
57
|
-
path: "items",
|
|
58
|
-
methods: [HttpMethod.GET],
|
|
59
|
-
integration: mockIntegration(),
|
|
60
|
-
},
|
|
61
|
-
],
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
Template.fromStack(stack).hasResourceProperties(
|
|
65
|
-
"AWS::ApiGateway::RestApi",
|
|
66
|
-
{ Description: "Custom description" }
|
|
67
|
-
);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it("registers each route method with apiKeyRequired", () => {
|
|
71
|
-
const { stack } = createStack();
|
|
72
|
-
new SecureRestApi(stack, "Api", {
|
|
73
|
-
apiName: "my-api",
|
|
74
|
-
routes: [
|
|
75
|
-
{
|
|
76
|
-
path: "items",
|
|
77
|
-
methods: [HttpMethod.GET, HttpMethod.POST],
|
|
78
|
-
integration: mockIntegration(),
|
|
79
|
-
},
|
|
80
|
-
],
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
const template = Template.fromStack(stack);
|
|
84
|
-
template.hasResourceProperties("AWS::ApiGateway::Method", {
|
|
85
|
-
HttpMethod: "GET",
|
|
86
|
-
ApiKeyRequired: true,
|
|
87
|
-
});
|
|
88
|
-
template.hasResourceProperties("AWS::ApiGateway::Method", {
|
|
89
|
-
HttpMethod: "POST",
|
|
90
|
-
ApiKeyRequired: true,
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("accepts a LambdaIntegration as route integration", () => {
|
|
95
|
-
const { stack } = createStack();
|
|
96
|
-
new SecureRestApi(stack, "Api", {
|
|
97
|
-
apiName: "my-api",
|
|
98
|
-
routes: [
|
|
99
|
-
{
|
|
100
|
-
path: "items",
|
|
101
|
-
methods: [HttpMethod.GET],
|
|
102
|
-
integration: lambdaIntegration(stack),
|
|
103
|
-
},
|
|
104
|
-
],
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
Template.fromStack(stack).resourceCountIs("AWS::Lambda::Function", 1);
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
describe("API Key", () => {
|
|
112
|
-
it("creates an API key with a default name", () => {
|
|
113
|
-
const { stack } = createStack();
|
|
114
|
-
new SecureRestApi(stack, "Api", {
|
|
115
|
-
apiName: "my-api",
|
|
116
|
-
routes: [
|
|
117
|
-
{
|
|
118
|
-
path: "items",
|
|
119
|
-
methods: [HttpMethod.GET],
|
|
120
|
-
integration: mockIntegration(),
|
|
121
|
-
},
|
|
122
|
-
],
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
Template.fromStack(stack).hasResourceProperties(
|
|
126
|
-
"AWS::ApiGateway::ApiKey",
|
|
127
|
-
{ Name: "my-api-api-key" }
|
|
128
|
-
);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it("uses a custom API key name when provided", () => {
|
|
132
|
-
const { stack } = createStack();
|
|
133
|
-
new SecureRestApi(stack, "Api", {
|
|
134
|
-
apiName: "my-api",
|
|
135
|
-
apiKeyName: "custom-key",
|
|
136
|
-
routes: [
|
|
137
|
-
{
|
|
138
|
-
path: "items",
|
|
139
|
-
methods: [HttpMethod.GET],
|
|
140
|
-
integration: mockIntegration(),
|
|
141
|
-
},
|
|
142
|
-
],
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
Template.fromStack(stack).hasResourceProperties(
|
|
146
|
-
"AWS::ApiGateway::ApiKey",
|
|
147
|
-
{ Name: "custom-key" }
|
|
148
|
-
);
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
describe("Usage Plan", () => {
|
|
153
|
-
it("creates a usage plan with default throttle settings", () => {
|
|
154
|
-
const { stack } = createStack();
|
|
155
|
-
new SecureRestApi(stack, "Api", {
|
|
156
|
-
apiName: "my-api",
|
|
157
|
-
routes: [
|
|
158
|
-
{
|
|
159
|
-
path: "items",
|
|
160
|
-
methods: [HttpMethod.GET],
|
|
161
|
-
integration: mockIntegration(),
|
|
162
|
-
},
|
|
163
|
-
],
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
Template.fromStack(stack).hasResourceProperties(
|
|
167
|
-
"AWS::ApiGateway::UsagePlan",
|
|
168
|
-
{
|
|
169
|
-
Throttle: {
|
|
170
|
-
BurstLimit: 200,
|
|
171
|
-
RateLimit: 100,
|
|
172
|
-
},
|
|
173
|
-
}
|
|
174
|
-
);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it("uses custom throttle settings when provided", () => {
|
|
178
|
-
const { stack } = createStack();
|
|
179
|
-
new SecureRestApi(stack, "Api", {
|
|
180
|
-
apiName: "my-api",
|
|
181
|
-
throttle: { rateLimit: 50, burstLimit: 100 },
|
|
182
|
-
routes: [
|
|
183
|
-
{
|
|
184
|
-
path: "items",
|
|
185
|
-
methods: [HttpMethod.GET],
|
|
186
|
-
integration: mockIntegration(),
|
|
187
|
-
},
|
|
188
|
-
],
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
Template.fromStack(stack).hasResourceProperties(
|
|
192
|
-
"AWS::ApiGateway::UsagePlan",
|
|
193
|
-
{
|
|
194
|
-
Throttle: {
|
|
195
|
-
BurstLimit: 100,
|
|
196
|
-
RateLimit: 50,
|
|
197
|
-
},
|
|
198
|
-
}
|
|
199
|
-
);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it("uses a custom usage plan name when provided", () => {
|
|
203
|
-
const { stack } = createStack();
|
|
204
|
-
new SecureRestApi(stack, "Api", {
|
|
205
|
-
apiName: "my-api",
|
|
206
|
-
usagePlanName: "custom-plan",
|
|
207
|
-
routes: [
|
|
208
|
-
{
|
|
209
|
-
path: "items",
|
|
210
|
-
methods: [HttpMethod.GET],
|
|
211
|
-
integration: mockIntegration(),
|
|
212
|
-
},
|
|
213
|
-
],
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
Template.fromStack(stack).hasResourceProperties(
|
|
217
|
-
"AWS::ApiGateway::UsagePlan",
|
|
218
|
-
{ UsagePlanName: "custom-plan" }
|
|
219
|
-
);
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
describe("CORS", () => {
|
|
224
|
-
it("applies default CORS settings", () => {
|
|
225
|
-
const { stack } = createStack();
|
|
226
|
-
new SecureRestApi(stack, "Api", {
|
|
227
|
-
apiName: "my-api",
|
|
228
|
-
routes: [
|
|
229
|
-
{
|
|
230
|
-
path: "items",
|
|
231
|
-
methods: [HttpMethod.GET],
|
|
232
|
-
integration: mockIntegration(),
|
|
233
|
-
},
|
|
234
|
-
],
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
Template.fromStack(stack).hasResourceProperties(
|
|
238
|
-
"AWS::ApiGateway::Method",
|
|
239
|
-
{ HttpMethod: "OPTIONS" }
|
|
240
|
-
);
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
it("appends additionalHeaders to the defaults", () => {
|
|
244
|
-
const { stack } = createStack();
|
|
245
|
-
new SecureRestApi(stack, "Api", {
|
|
246
|
-
apiName: "my-api",
|
|
247
|
-
corsOptions: { additionalHeaders: ["Authorization"] },
|
|
248
|
-
routes: [
|
|
249
|
-
{
|
|
250
|
-
path: "items",
|
|
251
|
-
methods: [HttpMethod.GET],
|
|
252
|
-
integration: mockIntegration(),
|
|
253
|
-
},
|
|
254
|
-
],
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
// OPTIONS mock integration response headers include the allow-headers value
|
|
258
|
-
Template.fromStack(stack).hasResourceProperties(
|
|
259
|
-
"AWS::ApiGateway::Method",
|
|
260
|
-
{
|
|
261
|
-
HttpMethod: "OPTIONS",
|
|
262
|
-
Integration: {
|
|
263
|
-
IntegrationResponses: [
|
|
264
|
-
{
|
|
265
|
-
ResponseParameters: {
|
|
266
|
-
"method.response.header.Access-Control-Allow-Headers":
|
|
267
|
-
"'Content-Type,X-Api-Key,Authorization'",
|
|
268
|
-
},
|
|
269
|
-
},
|
|
270
|
-
],
|
|
271
|
-
},
|
|
272
|
-
}
|
|
273
|
-
);
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
it("appends additionalMethods to the defaults", () => {
|
|
277
|
-
const { stack } = createStack();
|
|
278
|
-
new SecureRestApi(stack, "Api", {
|
|
279
|
-
apiName: "my-api",
|
|
280
|
-
corsOptions: { additionalMethods: ["POST"] },
|
|
281
|
-
routes: [
|
|
282
|
-
{
|
|
283
|
-
path: "items",
|
|
284
|
-
methods: [HttpMethod.GET],
|
|
285
|
-
integration: mockIntegration(),
|
|
286
|
-
},
|
|
287
|
-
],
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
Template.fromStack(stack).hasResourceProperties(
|
|
291
|
-
"AWS::ApiGateway::Method",
|
|
292
|
-
{
|
|
293
|
-
HttpMethod: "OPTIONS",
|
|
294
|
-
Integration: {
|
|
295
|
-
IntegrationResponses: [
|
|
296
|
-
{
|
|
297
|
-
ResponseParameters: {
|
|
298
|
-
"method.response.header.Access-Control-Allow-Methods":
|
|
299
|
-
"'GET,OPTIONS,POST'",
|
|
300
|
-
},
|
|
301
|
-
},
|
|
302
|
-
],
|
|
303
|
-
},
|
|
304
|
-
}
|
|
305
|
-
);
|
|
306
|
-
});
|
|
307
|
-
});
|
|
308
|
-
});
|
package/lib/secure-rest-api.ts
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ApiKey,
|
|
3
|
-
Cors,
|
|
4
|
-
Integration,
|
|
5
|
-
IRestApi,
|
|
6
|
-
RestApi,
|
|
7
|
-
UsagePlan,
|
|
8
|
-
} from "aws-cdk-lib/aws-apigateway";
|
|
9
|
-
import { HttpMethod } from "aws-cdk-lib/aws-apigatewayv2";
|
|
10
|
-
import { Construct } from "constructs";
|
|
11
|
-
|
|
12
|
-
export interface SecureRestApiRoute {
|
|
13
|
-
path: string;
|
|
14
|
-
methods: HttpMethod[];
|
|
15
|
-
integration: Integration;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface SecureRestApiProps {
|
|
19
|
-
/**
|
|
20
|
-
* The name of the API.
|
|
21
|
-
*/
|
|
22
|
-
apiName: string;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Description for the API.
|
|
26
|
-
*/
|
|
27
|
-
description?: string;
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* CORS configuration.
|
|
31
|
-
*
|
|
32
|
-
* `allowOrigins` overrides the default (all origins).
|
|
33
|
-
* `additionalMethods` and `additionalHeaders` are appended to the defaults
|
|
34
|
-
* (GET/OPTIONS and Content-Type/X-Api-Key respectively).
|
|
35
|
-
*/
|
|
36
|
-
corsOptions?: {
|
|
37
|
-
allowOrigins?: string[];
|
|
38
|
-
additionalMethods?: string[];
|
|
39
|
-
additionalHeaders?: string[];
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Routes to register on the API.
|
|
44
|
-
*/
|
|
45
|
-
routes: SecureRestApiRoute[];
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Throttling limits for the usage plan.
|
|
49
|
-
* @default { rateLimit: 100, burstLimit: 200 }
|
|
50
|
-
*/
|
|
51
|
-
throttle?: {
|
|
52
|
-
rateLimit: number;
|
|
53
|
-
burstLimit: number;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Override the generated API key name.
|
|
58
|
-
* @default `{apiName}-api-key`
|
|
59
|
-
*/
|
|
60
|
-
apiKeyName?: string;
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Override the generated usage plan name.
|
|
64
|
-
* @default `{apiName}-usage-plan`
|
|
65
|
-
*/
|
|
66
|
-
usagePlanName?: string;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export class SecureRestApi extends Construct {
|
|
70
|
-
public readonly api: RestApi;
|
|
71
|
-
public readonly apiKey: ApiKey;
|
|
72
|
-
public readonly usagePlan: UsagePlan;
|
|
73
|
-
|
|
74
|
-
constructor(scope: Construct, id: string, props: SecureRestApiProps) {
|
|
75
|
-
super(scope, id);
|
|
76
|
-
|
|
77
|
-
const {
|
|
78
|
-
apiName,
|
|
79
|
-
description,
|
|
80
|
-
corsOptions,
|
|
81
|
-
routes,
|
|
82
|
-
throttle = { rateLimit: 100, burstLimit: 200 },
|
|
83
|
-
apiKeyName,
|
|
84
|
-
usagePlanName,
|
|
85
|
-
} = props;
|
|
86
|
-
|
|
87
|
-
this.api = new RestApi(this, "Api", {
|
|
88
|
-
restApiName: apiName,
|
|
89
|
-
description: description ?? `REST API for ${apiName} service`,
|
|
90
|
-
defaultCorsPreflightOptions: {
|
|
91
|
-
allowOrigins: corsOptions?.allowOrigins ?? Cors.ALL_ORIGINS,
|
|
92
|
-
allowMethods: [
|
|
93
|
-
"GET",
|
|
94
|
-
"OPTIONS",
|
|
95
|
-
...(corsOptions?.additionalMethods ?? []),
|
|
96
|
-
],
|
|
97
|
-
allowHeaders: [
|
|
98
|
-
"Content-Type",
|
|
99
|
-
"X-Api-Key",
|
|
100
|
-
...(corsOptions?.additionalHeaders ?? []),
|
|
101
|
-
],
|
|
102
|
-
},
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
for (const route of routes) {
|
|
106
|
-
const resource = this.api.root.addResource(route.path.replace(/^\//, ""));
|
|
107
|
-
for (const method of route.methods) {
|
|
108
|
-
resource.addMethod(method, route.integration, { apiKeyRequired: true });
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
this.apiKey = new ApiKey(this, "ApiKey", {
|
|
113
|
-
description: `API Key for ${apiName} service`,
|
|
114
|
-
apiKeyName: apiKeyName ?? `${apiName}-api-key`,
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
this.usagePlan = new UsagePlan(this, "UsagePlan", {
|
|
118
|
-
name: usagePlanName ?? `${apiName}-usage-plan`,
|
|
119
|
-
description: `Usage plan for ${apiName} service`,
|
|
120
|
-
throttle,
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
this.usagePlan.addApiStage({
|
|
124
|
-
api: this.api as IRestApi,
|
|
125
|
-
stage: this.api.deploymentStage,
|
|
126
|
-
});
|
|
127
|
-
this.usagePlan.addApiKey(this.apiKey);
|
|
128
|
-
}
|
|
129
|
-
}
|
package/project.json
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "secure-rest-api",
|
|
3
|
-
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
-
"sourceRoot": "packages/constructs/secure-rest-api/lib",
|
|
5
|
-
"projectType": "application",
|
|
6
|
-
"targets": {
|
|
7
|
-
"build": {
|
|
8
|
-
"executor": "nx:run-commands",
|
|
9
|
-
"options": {
|
|
10
|
-
"command": "tsc --project tsconfig.app.json",
|
|
11
|
-
"cwd": "packages/constructs/secure-rest-api"
|
|
12
|
-
}
|
|
13
|
-
},
|
|
14
|
-
"lint": {
|
|
15
|
-
"executor": "@nx/eslint:lint",
|
|
16
|
-
"outputs": ["{options.outputFile}"]
|
|
17
|
-
},
|
|
18
|
-
"test": {
|
|
19
|
-
"executor": "@nx/jest:jest",
|
|
20
|
-
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
|
21
|
-
"options": {
|
|
22
|
-
"jestConfig": "packages/constructs/secure-rest-api/jest.config.ts",
|
|
23
|
-
"passWithNoTests": true
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
"publish": {
|
|
27
|
-
"executor": "nx:run-commands",
|
|
28
|
-
"options": {
|
|
29
|
-
"command": "npm publish --access public",
|
|
30
|
-
"cwd": "packages/constructs/secure-rest-api"
|
|
31
|
-
},
|
|
32
|
-
"dependsOn": ["build"]
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
"tags": []
|
|
36
|
-
}
|
package/tsconfig.app.json
DELETED
package/tsconfig.json
DELETED