@infoxchange/make-it-so 2.11.0-internal-testing-vdt-199-add-auth-token-verify-function.4 → 2.11.0-internal-testing-vdt-199-add-auth-token-verify-function-2.2

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.
Files changed (36) hide show
  1. package/README.md +15 -29
  2. package/dist/cdk-constructs/IxNextjsSite.d.ts.map +1 -1
  3. package/dist/cdk-constructs/IxNextjsSite.js +2 -1
  4. package/dist/cdk-constructs/IxStaticSite.d.ts.map +1 -1
  5. package/dist/cdk-constructs/IxStaticSite.js +2 -1
  6. package/dist/cdk-constructs/SiteOidcAuth/auth-check-handler-body.d.ts +2 -0
  7. package/dist/cdk-constructs/SiteOidcAuth/auth-check-handler-body.d.ts.map +1 -0
  8. package/dist/cdk-constructs/{CloudFrontOidcAuth/auth-check.js → SiteOidcAuth/auth-check-handler-body.js} +39 -57
  9. package/dist/cdk-constructs/SiteOidcAuth/auth-route.d.ts.map +1 -0
  10. package/dist/cdk-constructs/SiteOidcAuth/index.d.ts +197 -0
  11. package/dist/cdk-constructs/SiteOidcAuth/index.d.ts.map +1 -0
  12. package/dist/cdk-constructs/SiteOidcAuth/index.js +184 -0
  13. package/dist/cdk-constructs/index.d.ts +1 -1
  14. package/dist/cdk-constructs/index.d.ts.map +1 -1
  15. package/dist/cdk-constructs/index.js +1 -1
  16. package/dist/lib/site/support.d.ts +15 -5
  17. package/dist/lib/site/support.d.ts.map +1 -1
  18. package/dist/lib/site/support.js +19 -0
  19. package/package.json +1 -1
  20. package/src/cdk-constructs/IxNextjsSite.ts +2 -0
  21. package/src/cdk-constructs/IxStaticSite.ts +2 -0
  22. package/src/cdk-constructs/{CloudFrontOidcAuth/auth-check.ts → SiteOidcAuth/auth-check-handler-body.ts} +44 -63
  23. package/src/cdk-constructs/SiteOidcAuth/index.ts +288 -0
  24. package/src/cdk-constructs/index.ts +1 -1
  25. package/src/lib/site/support.ts +80 -29
  26. package/dist/cdk-constructs/CloudFrontOidcAuth/auth-check.d.ts +0 -2
  27. package/dist/cdk-constructs/CloudFrontOidcAuth/auth-check.d.ts.map +0 -1
  28. package/dist/cdk-constructs/CloudFrontOidcAuth/auth-route.d.ts.map +0 -1
  29. package/dist/cdk-constructs/CloudFrontOidcAuth/index.d.ts +0 -27
  30. package/dist/cdk-constructs/CloudFrontOidcAuth/index.d.ts.map +0 -1
  31. package/dist/cdk-constructs/CloudFrontOidcAuth/index.js +0 -198
  32. package/src/cdk-constructs/CloudFrontOidcAuth/cloudfront.d.ts +0 -245
  33. package/src/cdk-constructs/CloudFrontOidcAuth/index.ts +0 -294
  34. /package/dist/cdk-constructs/{CloudFrontOidcAuth → SiteOidcAuth}/auth-route.d.ts +0 -0
  35. /package/dist/cdk-constructs/{CloudFrontOidcAuth → SiteOidcAuth}/auth-route.js +0 -0
  36. /package/src/cdk-constructs/{CloudFrontOidcAuth → SiteOidcAuth}/auth-route.ts +0 -0
@@ -1,294 +0,0 @@
1
- import { Construct } from "constructs";
2
- import SecretsManager from "aws-cdk-lib/aws-secretsmanager";
3
- import CloudFront from "aws-cdk-lib/aws-cloudfront";
4
- import CDK from "aws-cdk-lib";
5
- import CdkCustomResources from "aws-cdk-lib/custom-resources";
6
- import Lambda from "aws-cdk-lib/aws-lambda";
7
- import * as SST from "sst/constructs";
8
- import { Config as SSTInternalConfig } from "sst/config.js";
9
- import CloudFrontOrigins from "aws-cdk-lib/aws-cloudfront-origins";
10
- import { BaseSiteCdkDistributionProps } from "sst/constructs/BaseSite.js";
11
- import path from "node:path";
12
- import fs from "node:fs";
13
-
14
- type ConstructScope = ConstructorParameters<typeof Construct>[0];
15
- type ConstructId = ConstructorParameters<typeof Construct>[1];
16
-
17
- type Mutable<T> = {
18
- -readonly [P in keyof T]: T[P];
19
- };
20
-
21
- type Props = {
22
- oidcIssuerUrl: string;
23
- oidcClientId: string;
24
- oidcScope: string;
25
- };
26
-
27
- export class CloudFrontOidcAuth extends Construct {
28
- readonly oidcIssuerUrl: string;
29
- readonly oidcClientId: string;
30
- readonly oidcScope: string;
31
- readonly id: string;
32
-
33
- constructor(scope: ConstructScope, id: ConstructId, props: Props) {
34
- super(scope, id);
35
- this.oidcIssuerUrl = props.oidcIssuerUrl;
36
- this.oidcClientId = props.oidcClientId;
37
- this.oidcScope = props.oidcScope;
38
- this.id = id;
39
- }
40
-
41
- addToDistributionDefinition<
42
- DistributionProps extends BaseSiteCdkDistributionProps,
43
- >(
44
- scope: ConstructScope,
45
- {
46
- distributionDefinition,
47
- prefix = "/auth",
48
- }: { distributionDefinition: Mutable<DistributionProps>; prefix?: string },
49
- ) {
50
- prefix = prefix.replace(/\/$/, ""); // Remove trailing slash from prefix if it has one
51
- const updatedDistributionDefinition = { ...distributionDefinition };
52
- const behaviourName = `${prefix.replace(/^\//g, "")}/*`;
53
- updatedDistributionDefinition.additionalBehaviors =
54
- updatedDistributionDefinition.additionalBehaviors
55
- ? { ...updatedDistributionDefinition.additionalBehaviors }
56
- : {};
57
- if (updatedDistributionDefinition.additionalBehaviors[behaviourName]) {
58
- throw new Error(
59
- `Behavior for prefix ${prefix} already exists in distribution definition`,
60
- );
61
- }
62
-
63
- const jwtSecret = new SecretsManager.Secret(this, `${this.id}JwtSecret`, {
64
- description: "JWT Signing Secret",
65
- generateSecretString: {
66
- passwordLength: 32,
67
- excludePunctuation: true,
68
- includeSpace: false,
69
- requireEachIncludedType: true,
70
- },
71
- // Secret is only used for sessions so it's safe to delete on stack removal
72
- removalPolicy: CDK.RemovalPolicy.DESTROY,
73
- });
74
-
75
- updatedDistributionDefinition.defaultBehavior = {
76
- ...updatedDistributionDefinition.defaultBehavior,
77
- functionAssociations: [
78
- ...(updatedDistributionDefinition.defaultBehavior
79
- ?.functionAssociations || []),
80
- this.getFunctionAssociation(scope, jwtSecret, prefix),
81
- ],
82
- };
83
- updatedDistributionDefinition.additionalBehaviors[behaviourName] =
84
- this.getAuthBehaviorOptions(scope, jwtSecret, prefix);
85
- return updatedDistributionDefinition;
86
- }
87
-
88
- private getFunctionAssociation(
89
- scope: ConstructScope,
90
- jwtSecret: SecretsManager.Secret,
91
- authRoutePrefix: string,
92
- ): CloudFront.FunctionAssociation {
93
- const cfKeyValueStore = new CloudFront.KeyValueStore(
94
- scope,
95
- `${this.id}CFKeyValueStore`,
96
- );
97
-
98
- const kvStoreId = cfKeyValueStore.keyValueStoreId; // Your KV store ID
99
- const key = "jwt-secret";
100
- const kvsArn = `arn:aws:cloudfront::${CDK.Stack.of(this).account}:key-value-store/${kvStoreId}`;
101
-
102
- // Updating the KVM requires a valid ETag to be provided in the IfMatch parameter so we first must fetch the ETag
103
- const getEtag = new CdkCustomResources.AwsCustomResource(
104
- this,
105
- `${this.id}GetKVStoreEtag`,
106
- {
107
- installLatestAwsSdk: false, // No real benefit in our case for the cost of a longer execution time
108
- onUpdate: {
109
- // Since there's no onCreate, onUpdate will be called for CREATE events
110
- service: "@aws-sdk/client-cloudfront-keyvaluestore",
111
- action: "describeKeyValueStore",
112
- parameters: { KvsARN: kvsArn },
113
- // We include a timestamp in the physicalResourceId to ensure we fetch the latest etag on every update
114
- physicalResourceId: CdkCustomResources.PhysicalResourceId.of(
115
- `${kvStoreId}-etag-${Date.now()}`,
116
- ),
117
- },
118
- policy: CdkCustomResources.AwsCustomResourcePolicy.fromSdkCalls({
119
- resources: [kvsArn],
120
- }),
121
- },
122
- );
123
- const etag = getEtag.getResponseField("ETag");
124
-
125
- // An annoying limitation of CloudFormation is that it won't resolve dynamic references for secrets when
126
- // used as a parameter to a custom resource. To get around this we manually resolve it with another custom
127
- // resource. Note this won't result in the secret being exposed in CloudFormation templates but it will
128
- // be visible in the CloudWatch logs of the custom resource lambda. In our case that is acceptable.
129
- // https://github.com/aws-cloudformation/cloudformation-coverage-roadmap/issues/341
130
- const secretValue = new CdkCustomResources.AwsCustomResource(
131
- this,
132
- `${this.id}GetSecret`,
133
- {
134
- // There's no real benefit of fetching the latest sdk our case for the cost of a longer execution time
135
- installLatestAwsSdk: false,
136
- // Since there's no onCreate, onUpdate will be called for CREATE events
137
- onUpdate: {
138
- service: "@aws-sdk/client-secrets-manager",
139
- action: "getSecretValue",
140
- parameters: {
141
- SecretId: jwtSecret.secretArn,
142
- },
143
- // We include a timestamp in the physicalResourceId to ensure we fetch the latest secret value on every update
144
- physicalResourceId: CdkCustomResources.PhysicalResourceId.of(
145
- `${this.id}GetSecret-${Date.now()}`,
146
- ),
147
- },
148
- policy: CdkCustomResources.AwsCustomResourcePolicy.fromSdkCalls({
149
- resources: [jwtSecret.secretArn],
150
- }),
151
- },
152
- );
153
-
154
- // Now we can actually update the KVS with the secret value
155
- const putKeyValue = new CdkCustomResources.AwsCustomResource(
156
- this,
157
- `${this.id}PutKeyValue`,
158
- {
159
- installLatestAwsSdk: false, // No real benefit in our case for the cost of a longer execution time
160
- onUpdate: {
161
- // Since there's no onCreate, onUpdate will be called for CREATE events
162
- service: "@aws-sdk/client-cloudfront-keyvaluestore",
163
- action: "putKey",
164
- parameters: {
165
- KvsARN: kvsArn,
166
- Key: key,
167
- Value: secretValue.getResponseField("SecretString"),
168
- IfMatch: etag,
169
- },
170
- physicalResourceId: CdkCustomResources.PhysicalResourceId.of(
171
- `${kvStoreId}-${key}`,
172
- ),
173
- },
174
- policy: CdkCustomResources.AwsCustomResourcePolicy.fromSdkCalls({
175
- resources: [kvsArn],
176
- }),
177
- },
178
- );
179
-
180
- // putKey in the @aws-sdk/client-cloudfront-keyvaluestore package requires @aws-sdk/signature-v4-crt to be imported
181
- // as well. But AwsCustomResource doesn't give us direct access to the underlying Lambda function so we inject a
182
- // NODE_OPTIONS env var to import on start. At some point AwsCustomResource will presumably switch to a later node
183
- // version and we might need to update this to '--import=' instead of '--require='.
184
- const fn = putKeyValue.node.findChild("Provider");
185
- if (!(fn instanceof Lambda.SingletonFunction)) {
186
- throw new Error(
187
- "Could not find the underlying Lambda function of the AwsCustomResource",
188
- );
189
- }
190
- fn.addEnvironment("NODE_OPTIONS", "--require=@aws-sdk/signature-v4-crt");
191
-
192
- const authCheckFunction = new CloudFront.Function(
193
- scope,
194
- `${this.id}AuthCheckFunction`,
195
- {
196
- code: CloudFront.FunctionCode.fromInline(
197
- fs
198
- .readFileSync(
199
- path.join(import.meta.dirname, "auth-check.js"),
200
- "utf8",
201
- )
202
- .replace("__placeholder-for-jwt-secret-key__", key)
203
- .replace("__placeholder-for-auth-route-prefix__", authRoutePrefix),
204
- ),
205
- runtime: CloudFront.FunctionRuntime.JS_2_0,
206
- keyValueStore: cfKeyValueStore,
207
- },
208
- );
209
-
210
- return {
211
- function: authCheckFunction,
212
- eventType: CloudFront.FunctionEventType.VIEWER_REQUEST,
213
- };
214
- }
215
-
216
- private getAuthBehaviorOptions(
217
- scope: ConstructScope,
218
- jwtSecret: SecretsManager.Secret,
219
- prefix: string,
220
- ): CloudFront.BehaviorOptions {
221
- const authRouteFunction = new SST.Function(
222
- scope,
223
- `${this.id}AuthRouteFunction`,
224
- {
225
- runtime: "nodejs20.x",
226
- handler: path.join(import.meta.dirname, "auth-route.handler"),
227
- environment: {
228
- OIDC_ISSUER_URL: this.oidcIssuerUrl,
229
- OIDC_CLIENT_ID: this.oidcClientId,
230
- OIDC_SCOPE: this.oidcScope,
231
- JWT_SECRET: jwtSecret.secretValue.toString(),
232
- },
233
- },
234
- );
235
-
236
- // authRouteFunction uses SST's AuthHandler construct which is normally run inside a lambda that's
237
- // created by SST's Auth construct. AuthHandler expects certain environment variables to be set
238
- // by the Auth construct so we have to set them ourselves here to keep it happy.
239
- const envVarName = SSTInternalConfig.envFor({
240
- type: "Auth",
241
- id: "id", // It seems like the env var will still be found no matter what this value is
242
- prop: "prefix",
243
- });
244
- authRouteFunction.addEnvironment(envVarName, prefix);
245
-
246
- const authRouteFunctionUrl = authRouteFunction.addFunctionUrl({
247
- authType: Lambda.FunctionUrlAuthType.NONE,
248
- });
249
- const forwardHostHeaderCfFunction = new CloudFront.Function(
250
- scope,
251
- `${this.id}ForwardHostHeaderFunction`,
252
- {
253
- code: CloudFront.FunctionCode.fromInline(`
254
- function handler(event) {
255
- const request = event.request;
256
- request.headers["x-forwarded-host"] = { value: request.headers.host.value };
257
- return request;
258
- }
259
- `),
260
- runtime: CloudFront.FunctionRuntime.JS_2_0,
261
- },
262
- );
263
-
264
- return {
265
- origin: new CloudFrontOrigins.HttpOrigin(
266
- CDK.Fn.parseDomainName(authRouteFunctionUrl.url),
267
- ),
268
- allowedMethods: CloudFront.AllowedMethods.ALLOW_ALL,
269
- cachePolicy: new CloudFront.CachePolicy(
270
- scope,
271
- `${this.id}AllowAllCookiesPolicy`,
272
- {
273
- cachePolicyName: `${this.id}-AllowAllCookiesPolicy`,
274
- comment: "Cache policy that forwards all cookies",
275
- defaultTtl: CDK.Duration.seconds(1),
276
- minTtl: CDK.Duration.seconds(1),
277
- maxTtl: CDK.Duration.seconds(1),
278
- cookieBehavior: CloudFront.CacheCookieBehavior.all(),
279
- headerBehavior:
280
- CloudFront.CacheHeaderBehavior.allowList("X-Forwarded-Host"),
281
- queryStringBehavior: CloudFront.CacheQueryStringBehavior.all(),
282
- enableAcceptEncodingGzip: true,
283
- enableAcceptEncodingBrotli: true,
284
- },
285
- ),
286
- functionAssociations: [
287
- {
288
- function: forwardHostHeaderCfFunction,
289
- eventType: CloudFront.FunctionEventType.VIEWER_REQUEST,
290
- },
291
- ],
292
- };
293
- }
294
- }