@infoxchange/make-it-so 2.10.0-internal-testing-vdt-199-add-oidc-auth.1 → 2.10.0-internal-testing-vdt-199-add-oidc-auth.3

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/README.md CHANGED
@@ -13,6 +13,8 @@ npm --save-dev @infoxchange/make-it-so
13
13
  yarn add --dev @infoxchange/make-it-so
14
14
  ```
15
15
 
16
+
17
+
16
18
  ## Features
17
19
 
18
20
  ### deployConfig
@@ -0,0 +1,14 @@
1
+ import type { UserConfig } from "@commitlint/types";
2
+
3
+ export default {
4
+ ignores: [
5
+ (message) =>
6
+ // Allow "wip" commits except when publishing a production release or on PR CI jobs
7
+ process.env.GITHUB_EVENT_NAME !== "pull_request" &&
8
+ (process.env.GITHUB_WORKFLOW !== "Publish" ||
9
+ (process.env.GITHUB_REF_NAME?.startsWith("internal-testing-") ??
10
+ true)) &&
11
+ (message === "wip" || message.startsWith("wip:")),
12
+ ],
13
+ extends: ["@commitlint/config-conventional"],
14
+ } satisfies UserConfig;
@@ -1,9 +1,9 @@
1
1
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2
2
  // @ts-nocheck
3
3
  // Based off: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/example_cloudfront_functions_kvs_jwt_verify_section.html
4
- import crypto from 'crypto';
4
+ import crypto from "crypto";
5
5
  // @ts-expect-error -- This library only exists in the CloudFront Functions runtime that this code runs in
6
- import cf from 'cloudfront';
6
+ import cf from "cloudfront";
7
7
  //Response when JWT is not valid.
8
8
  // const response401 = {
9
9
  // statusCode: 401,
@@ -12,23 +12,23 @@ import cf from 'cloudfront';
12
12
  const response401 = {
13
13
  statusCode: 302,
14
14
  headers: {
15
- location: { "value": '/auth/oidc/authorize' },
15
+ location: { value: "/auth/oidc/authorize" },
16
16
  },
17
17
  };
18
18
  // Remember to associate the KVS with your function before calling the const kvsKey = 'jwt.secret'.
19
19
  // https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/kvs-with-functions-associate.html
20
- const kvsKey = '__placeholder-for-jwt-secret-key__';
20
+ const kvsKey = "__placeholder-for-jwt-secret-key__";
21
21
  // set to true to enable console logging
22
22
  const loggingEnabled = true; // false;
23
23
  function jwt_decode(token, key, noVerify, algorithm) {
24
24
  // check token
25
25
  if (!token) {
26
- throw new Error('No token supplied');
26
+ throw new Error("No token supplied");
27
27
  }
28
28
  // check segments
29
- const segments = token.split('.');
29
+ const segments = token.split(".");
30
30
  if (segments.length !== 3) {
31
- throw new Error('Not enough or too many segments');
31
+ throw new Error("Not enough or too many segments");
32
32
  }
33
33
  // All segment should be base64
34
34
  const headerSeg = segments[0];
@@ -37,20 +37,20 @@ function jwt_decode(token, key, noVerify, algorithm) {
37
37
  // base64 decode and parse JSON
38
38
  const payload = JSON.parse(_base64urlDecode(payloadSeg));
39
39
  if (!noVerify) {
40
- const signingMethod = 'sha256';
41
- const signingType = 'hmac';
40
+ const signingMethod = "sha256";
41
+ const signingType = "hmac";
42
42
  // Verify signature. `sign` will return base64 string.
43
- const signingInput = [headerSeg, payloadSeg].join('.');
43
+ const signingInput = [headerSeg, payloadSeg].join(".");
44
44
  if (!_verify(signingInput, key, signingMethod, signingType, signatureSeg)) {
45
- throw new Error('Signature verification failed');
45
+ throw new Error("Signature verification failed");
46
46
  }
47
47
  // Support for nbf and exp claims.
48
48
  // According to the RFC, they should be in seconds.
49
49
  if (payload.nbf && Date.now() < payload.nbf * 1000) {
50
- throw new Error('Token not yet active');
50
+ throw new Error("Token not yet active");
51
51
  }
52
52
  if (payload.exp && Date.now() > payload.exp * 1000) {
53
- throw new Error('Token expired');
53
+ throw new Error("Token expired");
54
54
  }
55
55
  }
56
56
  return payload;
@@ -63,7 +63,7 @@ function _constantTimeEquals(a, b) {
63
63
  }
64
64
  let xor = 0;
65
65
  for (let i = 0; i < a.length; i++) {
66
- xor |= (a.charCodeAt(i) ^ b.charCodeAt(i));
66
+ xor |= a.charCodeAt(i) ^ b.charCodeAt(i);
67
67
  }
68
68
  return 0 === xor;
69
69
  }
@@ -72,17 +72,17 @@ function _verify(input, key, method, type, signature) {
72
72
  return _constantTimeEquals(signature, _sign(input, key, method));
73
73
  }
74
74
  else {
75
- throw new Error('Algorithm type not recognized');
75
+ throw new Error("Algorithm type not recognized");
76
76
  }
77
77
  }
78
78
  function _sign(input, key, method) {
79
- return crypto.createHmac(method, key).update(input).digest('base64url');
79
+ return crypto.createHmac(method, key).update(input).digest("base64url");
80
80
  }
81
81
  function _base64urlDecode(str) {
82
- return Buffer.from(str, 'base64url');
82
+ return Buffer.from(str, "base64url");
83
83
  }
84
84
  async function handler(event) {
85
- let request = event.request;
85
+ const request = event.request;
86
86
  //Secret key used to verify JWT token.
87
87
  //Update with your own key.
88
88
  const secret_key = await getSecret();
@@ -1 +1 @@
1
- {"version":3,"file":"auth-route.d.ts","sourceRoot":"","sources":["../../../src/cdk-constructs/CloudWatchOidcAuth/auth-route.ts"],"names":[],"mappings":"AA0BA,eAAO,MAAM,OAAO,4CAiDnB,CAAA"}
1
+ {"version":3,"file":"auth-route.d.ts","sourceRoot":"","sources":["../../../src/cdk-constructs/CloudWatchOidcAuth/auth-route.ts"],"names":[],"mappings":"AAyBA,eAAO,MAAM,OAAO,4CAiDnB,CAAC"}
@@ -33,14 +33,14 @@ export const handler = convertApiGatewayHandlerToCloudFrontHandler(AuthHandler({
33
33
  };
34
34
  // Options (optional)
35
35
  const options = {
36
- algorithm: 'HS256',
37
- expiresIn: '1h',
36
+ algorithm: "HS256",
37
+ expiresIn: "1h",
38
38
  };
39
39
  // Create the token
40
40
  const token = jwt.sign(payload, jwtSecret, options);
41
41
  const expires = new Date(
42
42
  // @ ts-ignore error in GH action
43
- Date.now() + (1000 * 60 * 60 * 24 * 7));
43
+ Date.now() + 1000 * 60 * 60 * 24 * 7);
44
44
  return {
45
45
  statusCode: 302,
46
46
  headers: {
@@ -58,7 +58,7 @@ export const handler = convertApiGatewayHandlerToCloudFrontHandler(AuthHandler({
58
58
  // },
59
59
  // });
60
60
  },
61
- })
61
+ }),
62
62
  },
63
63
  }));
64
64
  // @ts-expect-error - testing
@@ -66,8 +66,8 @@ function convertApiGatewayHandlerToCloudFrontHandler(callback) {
66
66
  // @ts-expect-error - testing
67
67
  return async function (event, context) {
68
68
  // Used by AuthHandler to create callback url sent to oidc server
69
- event.requestContext.domainName = event.headers['x-forwarded-host'];
70
- console.log('----', event, context);
69
+ event.requestContext.domainName = event.headers["x-forwarded-host"];
70
+ console.log("----", event, context);
71
71
  // console.log("event", event)
72
72
  // console.log("context", context)
73
73
  const response = await callback(event, context);
@@ -16,7 +16,7 @@ export declare class CloudWatchOidcAuth extends Construct {
16
16
  readonly oidcScope: string;
17
17
  readonly id: string;
18
18
  constructor(scope: ConstructScope, id: ConstructId, props: Props);
19
- addToDistributionDefinition<DistributionProps extends BaseSiteCdkDistributionProps>(scope: ConstructScope, { distributionDefinition, prefix }: {
19
+ addToDistributionDefinition<DistributionProps extends BaseSiteCdkDistributionProps>(scope: ConstructScope, { distributionDefinition, prefix, }: {
20
20
  distributionDefinition: Mutable<DistributionProps>;
21
21
  prefix?: string;
22
22
  }): Mutable<DistributionProps>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cdk-constructs/CloudWatchOidcAuth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAUvC,OAAO,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;AAE1E,KAAK,cAAc,GAAG,qBAAqB,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;AACjE,KAAK,WAAW,GAAG,qBAAqB,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;AAE9D,KAAK,OAAO,CAAC,CAAC,IAAI;IAChB,CAAC,UAAU,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAC/B,CAAC;AAGF,KAAK,KAAK,GAAG;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,qBAAa,kBAAmB,SAAQ,SAAS;IAC/C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;gBAER,KAAK,EAAE,cAAc,EAAE,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK;IAQhE,2BAA2B,CAAC,iBAAiB,SAAS,4BAA4B,EAChF,KAAK,EAAE,cAAc,EACrB,EAAE,sBAAsB,EAAE,MAAgB,EAAE,EAAE;QAAE,sBAAsB,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;IAmCvH,OAAO,CAAC,sBAAsB;IA4F9B,OAAO,CAAC,sBAAsB;CA4D/B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cdk-constructs/CloudWatchOidcAuth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AASvC,OAAO,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;AAI1E,KAAK,cAAc,GAAG,qBAAqB,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;AACjE,KAAK,WAAW,GAAG,qBAAqB,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;AAE9D,KAAK,OAAO,CAAC,CAAC,IAAI;IAChB,CAAC,UAAU,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAC/B,CAAC;AAEF,KAAK,KAAK,GAAG;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,qBAAa,kBAAmB,SAAQ,SAAS;IAC/C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;gBAER,KAAK,EAAE,cAAc,EAAE,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK;IAQhE,2BAA2B,CACzB,iBAAiB,SAAS,4BAA4B,EAEtD,KAAK,EAAE,cAAc,EACrB,EACE,sBAAsB,EACtB,MAAgB,GACjB,EAAE;QAAE,sBAAsB,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;IA6C5E,OAAO,CAAC,sBAAsB;IAyH9B,OAAO,CAAC,sBAAsB;CA0E/B"}
@@ -2,12 +2,13 @@ import { Construct } from "constructs";
2
2
  import SecretsManager from "aws-cdk-lib/aws-secretsmanager";
3
3
  import CloudFront from "aws-cdk-lib/aws-cloudfront";
4
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 { getFileContentsWithoutTypes } from "../../lib/utils/source-code.js";
5
+ import CdkCustomResources from "aws-cdk-lib/custom-resources";
6
+ import Lambda from "aws-cdk-lib/aws-lambda";
8
7
  import * as SST from "sst/constructs";
9
8
  import { Config as SSTInternalConfig } from "sst/config.js";
10
9
  import CloudFrontOrigins from "aws-cdk-lib/aws-cloudfront-origins";
10
+ import path from "node:path";
11
+ import fs from "node:fs";
11
12
  export class CloudWatchOidcAuth extends Construct {
12
13
  oidcIssuerUrl;
13
14
  oidcClientId;
@@ -20,23 +21,24 @@ export class CloudWatchOidcAuth extends Construct {
20
21
  this.oidcScope = props.oidcScope;
21
22
  this.id = id;
22
23
  }
23
- addToDistributionDefinition(scope, { distributionDefinition, prefix = "/auth" }) {
24
+ addToDistributionDefinition(scope, { distributionDefinition, prefix = "/auth", }) {
24
25
  console.log("------", import.meta.dirname, import.meta.url, import.meta.filename);
25
26
  const updatedDistributionDefinition = { ...distributionDefinition };
26
- const behaviourName = `${prefix.replace(/^\//g, '')}/*`;
27
- updatedDistributionDefinition.additionalBehaviors = updatedDistributionDefinition.additionalBehaviors
28
- ? { ...updatedDistributionDefinition.additionalBehaviors }
29
- : {};
27
+ const behaviourName = `${prefix.replace(/^\//g, "")}/*`;
28
+ updatedDistributionDefinition.additionalBehaviors =
29
+ updatedDistributionDefinition.additionalBehaviors
30
+ ? { ...updatedDistributionDefinition.additionalBehaviors }
31
+ : {};
30
32
  if (updatedDistributionDefinition.additionalBehaviors[behaviourName]) {
31
33
  throw new Error(`Behavior for prefix ${prefix} already exists in distribution definition`);
32
34
  }
33
35
  const jwtSecret = new SecretsManager.Secret(this, `${this.id}JwtSecret`, {
34
- description: 'JWT Signing Secret',
36
+ description: "JWT Signing Secret",
35
37
  generateSecretString: {
36
38
  passwordLength: 32,
37
39
  excludePunctuation: true,
38
40
  includeSpace: false,
39
- requireEachIncludedType: true
41
+ requireEachIncludedType: true,
40
42
  },
41
43
  // Secret is only used for sessions so it's safe to delete on stack removal
42
44
  removalPolicy: CDK.RemovalPolicy.DESTROY,
@@ -44,11 +46,13 @@ export class CloudWatchOidcAuth extends Construct {
44
46
  updatedDistributionDefinition.defaultBehavior = {
45
47
  ...updatedDistributionDefinition.defaultBehavior,
46
48
  functionAssociations: [
47
- ...(updatedDistributionDefinition.defaultBehavior?.functionAssociations || []),
48
- this.getFunctionAssociation(scope, jwtSecret)
49
+ ...(updatedDistributionDefinition.defaultBehavior
50
+ ?.functionAssociations || []),
51
+ this.getFunctionAssociation(scope, jwtSecret),
49
52
  ],
50
53
  };
51
- updatedDistributionDefinition.additionalBehaviors[behaviourName] = this.getAuthBehaviorOptions(scope, jwtSecret, prefix);
54
+ updatedDistributionDefinition.additionalBehaviors[behaviourName] =
55
+ this.getAuthBehaviorOptions(scope, jwtSecret, prefix);
52
56
  return updatedDistributionDefinition;
53
57
  }
54
58
  getFunctionAssociation(scope, jwtSecret) {
@@ -60,8 +64,9 @@ export class CloudWatchOidcAuth extends Construct {
60
64
  const getEtag = new CdkCustomResources.AwsCustomResource(this, `${this.id}GetKVStoreEtag`, {
61
65
  installLatestAwsSdk: false, // No real benefit in our case for the cost of a longer execution time
62
66
  onUpdate: {
63
- service: '@aws-sdk/client-cloudfront-keyvaluestore',
64
- action: 'describeKeyValueStore',
67
+ // Since there's no onCreate, onUpdate will be called for CREATE events
68
+ service: "@aws-sdk/client-cloudfront-keyvaluestore",
69
+ action: "describeKeyValueStore",
65
70
  parameters: { KvsARN: kvsArn },
66
71
  // We include a timestamp in the physicalResourceId to ensure we fetch the latest etag on every update
67
72
  physicalResourceId: CdkCustomResources.PhysicalResourceId.of(`${kvStoreId}-etag-${Date.now()}`),
@@ -70,7 +75,7 @@ export class CloudWatchOidcAuth extends Construct {
70
75
  resources: [kvsArn],
71
76
  }),
72
77
  });
73
- const etag = getEtag.getResponseField('ETag');
78
+ const etag = getEtag.getResponseField("ETag");
74
79
  // An annoying limitation of CloudFormation is that it won't resolve dynamic references for secrets when
75
80
  // used as a parameter to a custom resource. To get around this we manually resolve it with another custom
76
81
  // resource. Note this won't result in the secret being exposed in CloudFormation templates but it will
@@ -81,8 +86,8 @@ export class CloudWatchOidcAuth extends Construct {
81
86
  installLatestAwsSdk: false,
82
87
  // Since there's no onCreate, onUpdate will be called for CREATE events
83
88
  onUpdate: {
84
- service: '@aws-sdk/client-secrets-manager',
85
- action: 'getSecretValue',
89
+ service: "@aws-sdk/client-secrets-manager",
90
+ action: "getSecretValue",
86
91
  parameters: {
87
92
  SecretId: jwtSecret.secretArn,
88
93
  },
@@ -97,12 +102,13 @@ export class CloudWatchOidcAuth extends Construct {
97
102
  const putKeyValue = new CdkCustomResources.AwsCustomResource(this, `${this.id}PutKeyValue`, {
98
103
  installLatestAwsSdk: false, // No real benefit in our case for the cost of a longer execution time
99
104
  onUpdate: {
100
- service: '@aws-sdk/client-cloudfront-keyvaluestore',
101
- action: 'putKey',
105
+ // Since there's no onCreate, onUpdate will be called for CREATE events
106
+ service: "@aws-sdk/client-cloudfront-keyvaluestore",
107
+ action: "putKey",
102
108
  parameters: {
103
109
  KvsARN: kvsArn,
104
110
  Key: key,
105
- Value: secretValue.getResponseField('SecretString'),
111
+ Value: secretValue.getResponseField("SecretString"),
106
112
  IfMatch: etag,
107
113
  },
108
114
  physicalResourceId: CdkCustomResources.PhysicalResourceId.of(`${kvStoreId}-${key}`),
@@ -115,16 +121,15 @@ export class CloudWatchOidcAuth extends Construct {
115
121
  // as well. But AwsCustomResource doesn't give us direct access to the underlying Lambda function so we inject a
116
122
  // NODE_OPTIONS env var to import on start. At some point AwsCustomResource will presumably switch to a later node
117
123
  // version and we might need to update this to '--import=' instead of '--require='.
118
- const fn = putKeyValue.node.findChild('Provider');
124
+ const fn = putKeyValue.node.findChild("Provider");
119
125
  if (!(fn instanceof Lambda.SingletonFunction)) {
120
126
  throw new Error("Could not find the underlying Lambda function of the AwsCustomResource");
121
127
  }
122
- fn.addEnvironment('NODE_OPTIONS', '--require=@aws-sdk/signature-v4-crt');
128
+ fn.addEnvironment("NODE_OPTIONS", "--require=@aws-sdk/signature-v4-crt");
123
129
  const edgeFuncAuthCheck = new CloudFront.Function(scope, `${this.id}EdgeFunctionAuthCheck`, {
124
- code: CloudFront.FunctionCode.fromInline(getFileContentsWithoutTypes("CloudWatchOidcAuth/auth-check.ts")
125
- .replace("__placeholder-for-jwt-secret-key__", key)),
130
+ code: CloudFront.FunctionCode.fromInline(fs.readFileSync(path.join(import.meta.dirname, "auth-check.js"), "utf8").replace("__placeholder-for-jwt-secret-key__", key)),
126
131
  runtime: CloudFront.FunctionRuntime.JS_2_0,
127
- keyValueStore: cfKeyValueStore
132
+ keyValueStore: cfKeyValueStore,
128
133
  });
129
134
  return {
130
135
  function: edgeFuncAuthCheck,
@@ -134,13 +139,13 @@ export class CloudWatchOidcAuth extends Construct {
134
139
  getAuthBehaviorOptions(scope, jwtSecret, prefix) {
135
140
  const edgeFuncAuth = new SST.Function(scope, `${this.id}EdgeFunctionAuth`, {
136
141
  runtime: "nodejs20.x",
137
- handler: "CloudWatchOidcAuth/auth-route.handler",
142
+ handler: path.join(import.meta.dirname, "auth-route.handler"),
138
143
  environment: {
139
144
  OIDC_ISSUER_URL: this.oidcIssuerUrl,
140
145
  OIDC_CLIENT_ID: this.oidcClientId,
141
146
  OIDC_SCOPE: this.oidcScope,
142
147
  JWT_SECRET: jwtSecret.secretValue.toString(),
143
- }
148
+ },
144
149
  });
145
150
  // edgeFuncAuth uses SST's AuthHandler construct which is normally run inside a lambda that's
146
151
  // created by SST's Auth construct. AuthHandler expects certain environment variables to be set
@@ -168,8 +173,8 @@ export class CloudWatchOidcAuth extends Construct {
168
173
  origin: new CloudFrontOrigins.HttpOrigin(CDK.Fn.parseDomainName(edgeFuncAuthUrl.url)),
169
174
  allowedMethods: CloudFront.AllowedMethods.ALLOW_ALL,
170
175
  cachePolicy: new CloudFront.CachePolicy(scope, `${this.id}AllowAllCookiesPolicy`, {
171
- cachePolicyName: 'AllowAllCookiesPolicy',
172
- comment: 'Cache policy that forwards all cookies',
176
+ cachePolicyName: "AllowAllCookiesPolicy",
177
+ comment: "Cache policy that forwards all cookies",
173
178
  defaultTtl: CDK.Duration.seconds(1),
174
179
  minTtl: CDK.Duration.seconds(1),
175
180
  maxTtl: CDK.Duration.seconds(1),
@@ -183,7 +188,7 @@ export class CloudWatchOidcAuth extends Construct {
183
188
  {
184
189
  function: forwardHostHeaderCfFunction,
185
190
  eventType: CloudFront.FunctionEventType.VIEWER_REQUEST,
186
- }
191
+ },
187
192
  ],
188
193
  };
189
194
  }
@@ -1,2 +1,2 @@
1
- export declare function getFileContentsWithoutTypes(filePath: string): string;
1
+ export {};
2
2
  //# sourceMappingURL=source-code.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"source-code.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/source-code.ts"],"names":[],"mappings":"AAGA,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAOpE"}
1
+ {"version":3,"file":"source-code.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/source-code.ts"],"names":[],"mappings":""}
@@ -1,9 +1 @@
1
- import ts from "typescript";
2
- import fs from "fs";
3
- export function getFileContentsWithoutTypes(filePath) {
4
- const source = fs.readFileSync(filePath, "utf8");
5
- const result = ts.transpileModule(source, {
6
- compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2020 }
7
- });
8
- return result.outputText;
9
- }
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@infoxchange/make-it-so",
3
- "version": "2.10.0-internal-testing-vdt-199-add-oidc-auth.1",
3
+ "version": "2.10.0-internal-testing-vdt-199-add-oidc-auth.3",
4
4
  "description": "Makes deploying services to IX infra easy",
5
5
  "repository": "github:infoxchange/make-it-so",
6
6
  "type": "module",
@@ -2,9 +2,9 @@
2
2
  // @ts-nocheck
3
3
  // Based off: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/example_cloudfront_functions_kvs_jwt_verify_section.html
4
4
 
5
- import crypto from 'crypto';
5
+ import crypto from "crypto";
6
6
  // @ts-expect-error -- This library only exists in the CloudFront Functions runtime that this code runs in
7
- import cf from 'cloudfront';
7
+ import cf from "cloudfront";
8
8
 
9
9
  //Response when JWT is not valid.
10
10
  // const response401 = {
@@ -14,154 +14,148 @@ import cf from 'cloudfront';
14
14
  const response401 = {
15
15
  statusCode: 302,
16
16
  headers: {
17
- location: { "value": '/auth/oidc/authorize' },
17
+ location: { value: "/auth/oidc/authorize" },
18
18
  },
19
- }
19
+ };
20
20
 
21
21
  // Remember to associate the KVS with your function before calling the const kvsKey = 'jwt.secret'.
22
22
  // https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/kvs-with-functions-associate.html
23
- const kvsKey = '__placeholder-for-jwt-secret-key__';
23
+ const kvsKey = "__placeholder-for-jwt-secret-key__";
24
24
  // set to true to enable console logging
25
25
  const loggingEnabled = true; // false;
26
26
 
27
-
28
27
  function jwt_decode(token, key, noVerify, algorithm) {
29
- // check token
30
- if (!token) {
31
- throw new Error('No token supplied');
28
+ // check token
29
+ if (!token) {
30
+ throw new Error("No token supplied");
31
+ }
32
+ // check segments
33
+ const segments = token.split(".");
34
+ if (segments.length !== 3) {
35
+ throw new Error("Not enough or too many segments");
36
+ }
37
+
38
+ // All segment should be base64
39
+ const headerSeg = segments[0];
40
+ const payloadSeg = segments[1];
41
+ const signatureSeg = segments[2];
42
+
43
+ // base64 decode and parse JSON
44
+ const payload = JSON.parse(_base64urlDecode(payloadSeg));
45
+
46
+ if (!noVerify) {
47
+ const signingMethod = "sha256";
48
+ const signingType = "hmac";
49
+
50
+ // Verify signature. `sign` will return base64 string.
51
+ const signingInput = [headerSeg, payloadSeg].join(".");
52
+
53
+ if (!_verify(signingInput, key, signingMethod, signingType, signatureSeg)) {
54
+ throw new Error("Signature verification failed");
32
55
  }
33
- // check segments
34
- const segments = token.split('.');
35
- if (segments.length !== 3) {
36
- throw new Error('Not enough or too many segments');
37
- }
38
-
39
- // All segment should be base64
40
- const headerSeg = segments[0];
41
- const payloadSeg = segments[1];
42
- const signatureSeg = segments[2];
43
-
44
- // base64 decode and parse JSON
45
- const payload = JSON.parse(_base64urlDecode(payloadSeg));
46
-
47
- if (!noVerify) {
48
- const signingMethod = 'sha256';
49
- const signingType = 'hmac';
50
-
51
- // Verify signature. `sign` will return base64 string.
52
- const signingInput = [headerSeg, payloadSeg].join('.');
53
56
 
54
- if (!_verify(signingInput, key, signingMethod, signingType, signatureSeg)) {
55
- throw new Error('Signature verification failed');
56
- }
57
-
58
- // Support for nbf and exp claims.
59
- // According to the RFC, they should be in seconds.
60
- if (payload.nbf && Date.now() < payload.nbf*1000) {
61
- throw new Error('Token not yet active');
62
- }
57
+ // Support for nbf and exp claims.
58
+ // According to the RFC, they should be in seconds.
59
+ if (payload.nbf && Date.now() < payload.nbf * 1000) {
60
+ throw new Error("Token not yet active");
61
+ }
63
62
 
64
- if (payload.exp && Date.now() > payload.exp*1000) {
65
- throw new Error('Token expired');
66
- }
63
+ if (payload.exp && Date.now() > payload.exp * 1000) {
64
+ throw new Error("Token expired");
67
65
  }
66
+ }
68
67
 
69
- return payload;
68
+ return payload;
70
69
  }
71
70
 
72
71
  //Function to ensure a constant time comparison to prevent
73
72
  //timing side channels.
74
73
  function _constantTimeEquals(a, b) {
75
- if (a.length != b.length) {
76
- return false;
77
- }
74
+ if (a.length != b.length) {
75
+ return false;
76
+ }
78
77
 
79
- let xor = 0;
80
- for (let i = 0; i < a.length; i++) {
81
- xor |= (a.charCodeAt(i) ^ b.charCodeAt(i));
82
- }
78
+ let xor = 0;
79
+ for (let i = 0; i < a.length; i++) {
80
+ xor |= a.charCodeAt(i) ^ b.charCodeAt(i);
81
+ }
83
82
 
84
- return 0 === xor;
83
+ return 0 === xor;
85
84
  }
86
85
 
87
86
  function _verify(input, key, method, type, signature) {
88
- if(type === "hmac") {
89
- return _constantTimeEquals(signature, _sign(input, key, method));
90
- }
91
- else {
92
- throw new Error('Algorithm type not recognized');
93
- }
87
+ if (type === "hmac") {
88
+ return _constantTimeEquals(signature, _sign(input, key, method));
89
+ } else {
90
+ throw new Error("Algorithm type not recognized");
91
+ }
94
92
  }
95
93
 
96
94
  function _sign(input, key, method) {
97
- return crypto.createHmac(method, key).update(input).digest('base64url');
95
+ return crypto.createHmac(method, key).update(input).digest("base64url");
98
96
  }
99
97
 
100
98
  function _base64urlDecode(str) {
101
- return Buffer.from(str, 'base64url')
99
+ return Buffer.from(str, "base64url");
102
100
  }
103
101
 
104
102
  async function handler(event) {
105
- let request = event.request;
106
-
107
- //Secret key used to verify JWT token.
108
- //Update with your own key.
109
- const secret_key = await getSecret()
110
-
111
- if(!secret_key) {
112
- return response401;
113
- }
114
-
115
- console.log("request")
116
- console.log(request)
117
- console.log(request.cookies)
118
- console.log(request.cookies["auth-token"])
119
- console.log(Object.keys(request.cookies))
120
- // console.logObject.keys(request.cookies))
121
-
122
- // If no JWT token, then generate HTTP redirect 401 response.
123
- if(!request.cookies["auth-token"]) {
124
- log("Error: No JWT in the cookies");
125
- return response401;
126
- }
127
-
128
- const jwtToken = request.cookies["auth-token"].value;
129
-
130
- try{
131
- jwt_decode(jwtToken, secret_key);
132
- }
133
- catch(e) {
134
- log(e);
135
- return response401;
136
- }
137
-
138
- //Remove the JWT from the query string if valid and return.
139
- delete request.querystring.jwt;
140
- log("Valid JWT token");
141
- return request;
103
+ const request = event.request;
104
+
105
+ //Secret key used to verify JWT token.
106
+ //Update with your own key.
107
+ const secret_key = await getSecret();
108
+
109
+ if (!secret_key) {
110
+ return response401;
111
+ }
112
+
113
+ console.log("request");
114
+ console.log(request);
115
+ console.log(request.cookies);
116
+ console.log(request.cookies["auth-token"]);
117
+ console.log(Object.keys(request.cookies));
118
+ // console.logObject.keys(request.cookies))
119
+
120
+ // If no JWT token, then generate HTTP redirect 401 response.
121
+ if (!request.cookies["auth-token"]) {
122
+ log("Error: No JWT in the cookies");
123
+ return response401;
124
+ }
125
+
126
+ const jwtToken = request.cookies["auth-token"].value;
127
+
128
+ try {
129
+ jwt_decode(jwtToken, secret_key);
130
+ } catch (e) {
131
+ log(e);
132
+ return response401;
133
+ }
134
+
135
+ //Remove the JWT from the query string if valid and return.
136
+ delete request.querystring.jwt;
137
+ log("Valid JWT token");
138
+ return request;
142
139
  }
143
140
 
144
- const publicKey =
145
- `very-secret`
141
+ const publicKey = `very-secret`;
146
142
 
147
143
  // get secret from key value store
148
144
  async function getSecret() {
149
- // console.log("auth key is:", publicKey)
150
- // return publicKey
151
- // initialize cloudfront kv store and get the key value
152
- try {
153
- const kvsHandle = cf.kvs();
154
- return await kvsHandle.get(kvsKey);
155
- } catch (err) {
156
- log(`Error reading value for key: ${kvsKey}, error: ${err}`);
157
- return null;
158
- }
159
-
145
+ // console.log("auth key is:", publicKey)
146
+ // return publicKey
147
+ // initialize cloudfront kv store and get the key value
148
+ try {
149
+ const kvsHandle = cf.kvs();
150
+ return await kvsHandle.get(kvsKey);
151
+ } catch (err) {
152
+ log(`Error reading value for key: ${kvsKey}, error: ${err}`);
153
+ return null;
154
+ }
160
155
  }
161
156
 
162
157
  function log(message) {
163
- if (loggingEnabled) {
164
- console.log(message);
165
- }
158
+ if (loggingEnabled) {
159
+ console.log(message);
160
+ }
166
161
  }
167
-
@@ -1,28 +1,27 @@
1
1
  import { AuthHandler, OidcAdapter } from "sst/node/auth";
2
2
  import { Issuer } from "openid-client";
3
- import jwt from "jsonwebtoken"
3
+ import jwt from "jsonwebtoken";
4
4
 
5
- const oidcClientId = process.env.OIDC_CLIENT_ID
5
+ const oidcClientId = process.env.OIDC_CLIENT_ID;
6
6
  if (!oidcClientId) {
7
7
  throw new Error("OIDC_CLIENT_ID not set");
8
8
  }
9
- const oidcIssuerUrl = process.env.OIDC_ISSUER_URL
9
+ const oidcIssuerUrl = process.env.OIDC_ISSUER_URL;
10
10
  if (!oidcIssuerUrl) {
11
11
  throw new Error("OIDC_ISSUER_URL not set");
12
12
  }
13
- const oidcScope = process.env.OIDC_SCOPE
13
+ const oidcScope = process.env.OIDC_SCOPE;
14
14
  if (!oidcScope) {
15
15
  throw new Error("OIDC_SCOPE not set");
16
16
  }
17
- const jwtSecret = process.env.JWT_SECRET
17
+ const jwtSecret = process.env.JWT_SECRET;
18
18
  if (!jwtSecret) {
19
19
  throw new Error("JWT_SECRET not set");
20
20
  }
21
21
 
22
-
23
-
24
-
25
- const oidcIssuerConfigUrl = new URL(`${process.env.OIDC_ISSUER_URL?.replace(/\/$/, "")}/.well-known/openid-configuration`);
22
+ const oidcIssuerConfigUrl = new URL(
23
+ `${process.env.OIDC_ISSUER_URL?.replace(/\/$/, "")}/.well-known/openid-configuration`,
24
+ );
26
25
 
27
26
  export const handler = convertApiGatewayHandlerToCloudFrontHandler(
28
27
  AuthHandler({
@@ -39,19 +38,19 @@ export const handler = convertApiGatewayHandlerToCloudFrontHandler(
39
38
  // Payload to include in the token
40
39
  const payload = {
41
40
  userID: tokenset.claims().sub,
42
- }
41
+ };
43
42
 
44
43
  // Options (optional)
45
44
  const options = {
46
- algorithm: 'HS256',
47
- expiresIn: '1h',
45
+ algorithm: "HS256",
46
+ expiresIn: "1h",
48
47
  } as const;
49
48
 
50
49
  // Create the token
51
50
  const token = jwt.sign(payload, jwtSecret, options);
52
51
  const expires = new Date(
53
52
  // @ ts-ignore error in GH action
54
- Date.now() + (1000 * 60 * 60 * 24 * 7)
53
+ Date.now() + 1000 * 60 * 60 * 24 * 7,
55
54
  );
56
55
  return {
57
56
  statusCode: 302,
@@ -70,21 +69,21 @@ export const handler = convertApiGatewayHandlerToCloudFrontHandler(
70
69
  // },
71
70
  // });
72
71
  },
73
- })
72
+ }),
74
73
  },
75
- })
76
- )
74
+ }),
75
+ );
77
76
 
78
77
  // @ts-expect-error - testing
79
78
  function convertApiGatewayHandlerToCloudFrontHandler(callback) {
80
79
  // @ts-expect-error - testing
81
80
  return async function (event, context) {
82
81
  // Used by AuthHandler to create callback url sent to oidc server
83
- event.requestContext.domainName = event.headers['x-forwarded-host']
84
- console.log('----', event, context)
82
+ event.requestContext.domainName = event.headers["x-forwarded-host"];
83
+ console.log("----", event, context);
85
84
  // console.log("event", event)
86
85
  // console.log("context", context)
87
- const response = await callback(event, context)
86
+ const response = await callback(event, context);
88
87
  // if (response.cookies) {
89
88
  // if (!response.headers) {
90
89
  // response.headers = {}
@@ -93,6 +92,6 @@ function convertApiGatewayHandlerToCloudFrontHandler(callback) {
93
92
  // }
94
93
  // response.headers.location += "&cake=blar"
95
94
  // response.headers.foo = "bar"
96
- return response
97
- }
95
+ return response;
96
+ };
98
97
  }
@@ -2,13 +2,14 @@ import { Construct } from "constructs";
2
2
  import SecretsManager from "aws-cdk-lib/aws-secretsmanager";
3
3
  import CloudFront from "aws-cdk-lib/aws-cloudfront";
4
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 { getFileContentsWithoutTypes } from "../../lib/utils/source-code.js";
5
+ import CdkCustomResources from "aws-cdk-lib/custom-resources";
6
+ import Lambda from "aws-cdk-lib/aws-lambda";
8
7
  import * as SST from "sst/constructs";
9
- import { Config as SSTInternalConfig } from "sst/config.js"
8
+ import { Config as SSTInternalConfig } from "sst/config.js";
10
9
  import CloudFrontOrigins from "aws-cdk-lib/aws-cloudfront-origins";
11
10
  import { BaseSiteCdkDistributionProps } from "sst/constructs/BaseSite.js";
11
+ import path from "node:path";
12
+ import fs from "node:fs";
12
13
 
13
14
  type ConstructScope = ConstructorParameters<typeof Construct>[0];
14
15
  type ConstructId = ConstructorParameters<typeof Construct>[1];
@@ -17,7 +18,6 @@ type Mutable<T> = {
17
18
  -readonly [P in keyof T]: T[P];
18
19
  };
19
20
 
20
-
21
21
  type Props = {
22
22
  oidcIssuerUrl: string;
23
23
  oidcClientId: string;
@@ -38,27 +38,40 @@ export class CloudWatchOidcAuth extends Construct {
38
38
  this.id = id;
39
39
  }
40
40
 
41
- addToDistributionDefinition<DistributionProps extends BaseSiteCdkDistributionProps>(
41
+ addToDistributionDefinition<
42
+ DistributionProps extends BaseSiteCdkDistributionProps,
43
+ >(
42
44
  scope: ConstructScope,
43
- { distributionDefinition, prefix = "/auth" }: { distributionDefinition: Mutable<DistributionProps>, prefix?: string }
45
+ {
46
+ distributionDefinition,
47
+ prefix = "/auth",
48
+ }: { distributionDefinition: Mutable<DistributionProps>; prefix?: string },
44
49
  ) {
45
- console.log("------", import.meta.dirname, import.meta.url, import.meta.filename)
50
+ console.log(
51
+ "------",
52
+ import.meta.dirname,
53
+ import.meta.url,
54
+ import.meta.filename,
55
+ );
46
56
  const updatedDistributionDefinition = { ...distributionDefinition };
47
- const behaviourName = `${prefix.replace(/^\//g, '')}/*`
48
- updatedDistributionDefinition.additionalBehaviors = updatedDistributionDefinition.additionalBehaviors
49
- ? {...updatedDistributionDefinition.additionalBehaviors}
50
- : {};
57
+ const behaviourName = `${prefix.replace(/^\//g, "")}/*`;
58
+ updatedDistributionDefinition.additionalBehaviors =
59
+ updatedDistributionDefinition.additionalBehaviors
60
+ ? { ...updatedDistributionDefinition.additionalBehaviors }
61
+ : {};
51
62
  if (updatedDistributionDefinition.additionalBehaviors[behaviourName]) {
52
- throw new Error(`Behavior for prefix ${prefix} already exists in distribution definition`);
63
+ throw new Error(
64
+ `Behavior for prefix ${prefix} already exists in distribution definition`,
65
+ );
53
66
  }
54
67
 
55
68
  const jwtSecret = new SecretsManager.Secret(this, `${this.id}JwtSecret`, {
56
- description: 'JWT Signing Secret',
69
+ description: "JWT Signing Secret",
57
70
  generateSecretString: {
58
71
  passwordLength: 32,
59
72
  excludePunctuation: true,
60
73
  includeSpace: false,
61
- requireEachIncludedType: true
74
+ requireEachIncludedType: true,
62
75
  },
63
76
  // Secret is only used for sessions so it's safe to delete on stack removal
64
77
  removalPolicy: CDK.RemovalPolicy.DESTROY,
@@ -67,116 +80,151 @@ export class CloudWatchOidcAuth extends Construct {
67
80
  updatedDistributionDefinition.defaultBehavior = {
68
81
  ...updatedDistributionDefinition.defaultBehavior,
69
82
  functionAssociations: [
70
- ...(updatedDistributionDefinition.defaultBehavior?.functionAssociations || []),
71
- this.getFunctionAssociation(scope, jwtSecret)
83
+ ...(updatedDistributionDefinition.defaultBehavior
84
+ ?.functionAssociations || []),
85
+ this.getFunctionAssociation(scope, jwtSecret),
72
86
  ],
73
- }
74
- updatedDistributionDefinition.additionalBehaviors[behaviourName] = this.getAuthBehaviorOptions(scope, jwtSecret, prefix);
87
+ };
88
+ updatedDistributionDefinition.additionalBehaviors[behaviourName] =
89
+ this.getAuthBehaviorOptions(scope, jwtSecret, prefix);
75
90
  return updatedDistributionDefinition;
76
91
  }
77
92
 
78
- private getFunctionAssociation(scope: ConstructScope, jwtSecret: SecretsManager.Secret) : CloudFront.FunctionAssociation {
79
- const cfKeyValueStore = new CloudFront.KeyValueStore(scope, `${this.id}CFKeyValueStore`);
93
+ private getFunctionAssociation(
94
+ scope: ConstructScope,
95
+ jwtSecret: SecretsManager.Secret,
96
+ ): CloudFront.FunctionAssociation {
97
+ const cfKeyValueStore = new CloudFront.KeyValueStore(
98
+ scope,
99
+ `${this.id}CFKeyValueStore`,
100
+ );
80
101
 
81
102
  const kvStoreId = cfKeyValueStore.keyValueStoreId; // Your KV store ID
82
103
  const key = "jwt-secret";
83
104
  const kvsArn = `arn:aws:cloudfront::${CDK.Stack.of(this).account}:key-value-store/${kvStoreId}`;
84
105
 
85
106
  // Updating the KVM requires a valid ETag to be provided in the IfMatch parameter so we first must fetch the ETag
86
- const getEtag = new CdkCustomResources.AwsCustomResource(this, `${this.id}GetKVStoreEtag`, {
87
- installLatestAwsSdk: false, // No real benefit in our case for the cost of a longer execution time
88
- onUpdate: { // Since there's no onCreate, onUpdate will be called for CREATE events
89
- service: '@aws-sdk/client-cloudfront-keyvaluestore',
90
- action: 'describeKeyValueStore',
91
- parameters: { KvsARN: kvsArn },
92
- // We include a timestamp in the physicalResourceId to ensure we fetch the latest etag on every update
93
- physicalResourceId: CdkCustomResources.PhysicalResourceId.of(`${kvStoreId}-etag-${Date.now()}`),
107
+ const getEtag = new CdkCustomResources.AwsCustomResource(
108
+ this,
109
+ `${this.id}GetKVStoreEtag`,
110
+ {
111
+ installLatestAwsSdk: false, // No real benefit in our case for the cost of a longer execution time
112
+ onUpdate: {
113
+ // Since there's no onCreate, onUpdate will be called for CREATE events
114
+ service: "@aws-sdk/client-cloudfront-keyvaluestore",
115
+ action: "describeKeyValueStore",
116
+ parameters: { KvsARN: kvsArn },
117
+ // We include a timestamp in the physicalResourceId to ensure we fetch the latest etag on every update
118
+ physicalResourceId: CdkCustomResources.PhysicalResourceId.of(
119
+ `${kvStoreId}-etag-${Date.now()}`,
120
+ ),
121
+ },
122
+ policy: CdkCustomResources.AwsCustomResourcePolicy.fromSdkCalls({
123
+ resources: [kvsArn],
124
+ }),
94
125
  },
95
- policy: CdkCustomResources.AwsCustomResourcePolicy.fromSdkCalls({
96
- resources: [kvsArn],
97
- }),
98
- });
99
- const etag = getEtag.getResponseField('ETag');
100
-
126
+ );
127
+ const etag = getEtag.getResponseField("ETag");
101
128
 
102
129
  // An annoying limitation of CloudFormation is that it won't resolve dynamic references for secrets when
103
130
  // used as a parameter to a custom resource. To get around this we manually resolve it with another custom
104
131
  // resource. Note this won't result in the secret being exposed in CloudFormation templates but it will
105
132
  // be visible in the CloudWatch logs of the custom resource lambda. In our case that is acceptable.
106
133
  // https://github.com/aws-cloudformation/cloudformation-coverage-roadmap/issues/341
107
- const secretValue = new CdkCustomResources.AwsCustomResource(this, `${this.id}GetSecret`, {
108
- // There's no real benefit of fetching the latest sdk our case for the cost of a longer execution time
109
- installLatestAwsSdk: false,
110
- // Since there's no onCreate, onUpdate will be called for CREATE events
111
- onUpdate: {
112
- service: '@aws-sdk/client-secrets-manager',
113
- action: 'getSecretValue',
114
- parameters: {
115
- SecretId: jwtSecret.secretArn,
134
+ const secretValue = new CdkCustomResources.AwsCustomResource(
135
+ this,
136
+ `${this.id}GetSecret`,
137
+ {
138
+ // There's no real benefit of fetching the latest sdk our case for the cost of a longer execution time
139
+ installLatestAwsSdk: false,
140
+ // Since there's no onCreate, onUpdate will be called for CREATE events
141
+ onUpdate: {
142
+ service: "@aws-sdk/client-secrets-manager",
143
+ action: "getSecretValue",
144
+ parameters: {
145
+ SecretId: jwtSecret.secretArn,
146
+ },
147
+ // We include a timestamp in the physicalResourceId to ensure we fetch the latest secret value on every update
148
+ physicalResourceId: CdkCustomResources.PhysicalResourceId.of(
149
+ `${this.id}GetSecret-${Date.now()}`,
150
+ ),
116
151
  },
117
- // We include a timestamp in the physicalResourceId to ensure we fetch the latest secret value on every update
118
- physicalResourceId: CdkCustomResources.PhysicalResourceId.of(`${this.id}GetSecret-${Date.now()}`),
152
+ policy: CdkCustomResources.AwsCustomResourcePolicy.fromSdkCalls({
153
+ resources: [jwtSecret.secretArn],
154
+ }),
119
155
  },
120
- policy: CdkCustomResources.AwsCustomResourcePolicy.fromSdkCalls({
121
- resources: [jwtSecret.secretArn],
122
- }),
123
- });
124
-
156
+ );
125
157
 
126
158
  // Now we can actually update the KVS with the secret value
127
- const putKeyValue = new CdkCustomResources.AwsCustomResource(this, `${this.id}PutKeyValue`, {
128
- installLatestAwsSdk: false, // No real benefit in our case for the cost of a longer execution time
129
- onUpdate: { // Since there's no onCreate, onUpdate will be called for CREATE events
130
- service: '@aws-sdk/client-cloudfront-keyvaluestore',
131
- action: 'putKey',
132
- parameters: {
133
- KvsARN: kvsArn,
134
- Key: key,
135
- Value: secretValue.getResponseField('SecretString'),
136
- IfMatch: etag,
159
+ const putKeyValue = new CdkCustomResources.AwsCustomResource(
160
+ this,
161
+ `${this.id}PutKeyValue`,
162
+ {
163
+ installLatestAwsSdk: false, // No real benefit in our case for the cost of a longer execution time
164
+ onUpdate: {
165
+ // Since there's no onCreate, onUpdate will be called for CREATE events
166
+ service: "@aws-sdk/client-cloudfront-keyvaluestore",
167
+ action: "putKey",
168
+ parameters: {
169
+ KvsARN: kvsArn,
170
+ Key: key,
171
+ Value: secretValue.getResponseField("SecretString"),
172
+ IfMatch: etag,
173
+ },
174
+ physicalResourceId: CdkCustomResources.PhysicalResourceId.of(
175
+ `${kvStoreId}-${key}`,
176
+ ),
137
177
  },
138
- physicalResourceId: CdkCustomResources.PhysicalResourceId.of(`${kvStoreId}-${key}`),
178
+ policy: CdkCustomResources.AwsCustomResourcePolicy.fromSdkCalls({
179
+ resources: [kvsArn],
180
+ }),
139
181
  },
140
- policy: CdkCustomResources.AwsCustomResourcePolicy.fromSdkCalls({
141
- resources: [kvsArn],
142
- }),
143
- });
182
+ );
144
183
 
145
184
  // putKey in the @aws-sdk/client-cloudfront-keyvaluestore package requires @aws-sdk/signature-v4-crt to be imported
146
185
  // as well. But AwsCustomResource doesn't give us direct access to the underlying Lambda function so we inject a
147
186
  // NODE_OPTIONS env var to import on start. At some point AwsCustomResource will presumably switch to a later node
148
187
  // version and we might need to update this to '--import=' instead of '--require='.
149
- const fn = putKeyValue.node.findChild('Provider');
188
+ const fn = putKeyValue.node.findChild("Provider");
150
189
  if (!(fn instanceof Lambda.SingletonFunction)) {
151
- throw new Error("Could not find the underlying Lambda function of the AwsCustomResource");
190
+ throw new Error(
191
+ "Could not find the underlying Lambda function of the AwsCustomResource",
192
+ );
152
193
  }
153
- fn.addEnvironment('NODE_OPTIONS', '--require=@aws-sdk/signature-v4-crt');
194
+ fn.addEnvironment("NODE_OPTIONS", "--require=@aws-sdk/signature-v4-crt");
154
195
 
155
- const edgeFuncAuthCheck = new CloudFront.Function(scope, `${this.id}EdgeFunctionAuthCheck`, {
156
- code: CloudFront.FunctionCode.fromInline(
157
- getFileContentsWithoutTypes("CloudWatchOidcAuth/auth-check.ts")
158
- .replace("__placeholder-for-jwt-secret-key__", key)
159
- ),
160
- runtime: CloudFront.FunctionRuntime.JS_2_0,
161
- keyValueStore: cfKeyValueStore
162
- });
196
+ const edgeFuncAuthCheck = new CloudFront.Function(
197
+ scope,
198
+ `${this.id}EdgeFunctionAuthCheck`,
199
+ {
200
+ code: CloudFront.FunctionCode.fromInline(
201
+ fs.readFileSync(path.join(import.meta.dirname, "auth-check.js"), "utf8").replace("__placeholder-for-jwt-secret-key__", key),
202
+ ),
203
+ runtime: CloudFront.FunctionRuntime.JS_2_0,
204
+ keyValueStore: cfKeyValueStore,
205
+ },
206
+ );
163
207
 
164
208
  return {
165
209
  function: edgeFuncAuthCheck,
166
210
  eventType: CloudFront.FunctionEventType.VIEWER_REQUEST,
167
- }
211
+ };
168
212
  }
169
213
 
170
- private getAuthBehaviorOptions(scope: ConstructScope, jwtSecret: SecretsManager.Secret, prefix: string): CloudFront.BehaviorOptions {
214
+ private getAuthBehaviorOptions(
215
+ scope: ConstructScope,
216
+ jwtSecret: SecretsManager.Secret,
217
+ prefix: string,
218
+ ): CloudFront.BehaviorOptions {
171
219
  const edgeFuncAuth = new SST.Function(scope, `${this.id}EdgeFunctionAuth`, {
172
220
  runtime: "nodejs20.x",
173
- handler: "CloudWatchOidcAuth/auth-route.handler",
221
+ handler: path.join(import.meta.dirname, "auth-route.handler"),
174
222
  environment: {
175
223
  OIDC_ISSUER_URL: this.oidcIssuerUrl,
176
224
  OIDC_CLIENT_ID: this.oidcClientId,
177
225
  OIDC_SCOPE: this.oidcScope,
178
226
  JWT_SECRET: jwtSecret.secretValue.toString(),
179
- }
227
+ },
180
228
  });
181
229
 
182
230
  // edgeFuncAuth uses SST's AuthHandler construct which is normally run inside a lambda that's
@@ -192,39 +240,49 @@ export class CloudWatchOidcAuth extends Construct {
192
240
  const edgeFuncAuthUrl = edgeFuncAuth.addFunctionUrl({
193
241
  authType: Lambda.FunctionUrlAuthType.NONE,
194
242
  });
195
- const forwardHostHeaderCfFunction = new CloudFront.Function(scope, `${this.id}ForwardHostHeaderFunction`, {
196
- code: CloudFront.FunctionCode.fromInline(`
243
+ const forwardHostHeaderCfFunction = new CloudFront.Function(
244
+ scope,
245
+ `${this.id}ForwardHostHeaderFunction`,
246
+ {
247
+ code: CloudFront.FunctionCode.fromInline(`
197
248
  function handler(event) {
198
249
  const request = event.request;
199
250
  request.headers["x-forwarded-host"] = { value: request.headers.host.value };
200
251
  return request;
201
252
  }
202
253
  `),
203
- runtime: CloudFront.FunctionRuntime.JS_2_0,
204
- });
254
+ runtime: CloudFront.FunctionRuntime.JS_2_0,
255
+ },
256
+ );
205
257
 
206
258
  return {
207
- origin: new CloudFrontOrigins.HttpOrigin(CDK.Fn.parseDomainName(edgeFuncAuthUrl.url)),
259
+ origin: new CloudFrontOrigins.HttpOrigin(
260
+ CDK.Fn.parseDomainName(edgeFuncAuthUrl.url),
261
+ ),
208
262
  allowedMethods: CloudFront.AllowedMethods.ALLOW_ALL,
209
- cachePolicy: new CloudFront.CachePolicy(scope, `${this.id}AllowAllCookiesPolicy`, {
210
- cachePolicyName: 'AllowAllCookiesPolicy',
211
- comment: 'Cache policy that forwards all cookies',
212
- defaultTtl: CDK.Duration.seconds(1),
213
- minTtl: CDK.Duration.seconds(1),
214
- maxTtl: CDK.Duration.seconds(1),
215
- cookieBehavior: CloudFront.CacheCookieBehavior.all(),
216
- headerBehavior: CloudFront.CacheHeaderBehavior.allowList("X-Forwarded-Host"),
217
- queryStringBehavior: CloudFront.CacheQueryStringBehavior.all(),
218
- enableAcceptEncodingGzip: true,
219
- enableAcceptEncodingBrotli: true,
220
- }),
263
+ cachePolicy: new CloudFront.CachePolicy(
264
+ scope,
265
+ `${this.id}AllowAllCookiesPolicy`,
266
+ {
267
+ cachePolicyName: "AllowAllCookiesPolicy",
268
+ comment: "Cache policy that forwards all cookies",
269
+ defaultTtl: CDK.Duration.seconds(1),
270
+ minTtl: CDK.Duration.seconds(1),
271
+ maxTtl: CDK.Duration.seconds(1),
272
+ cookieBehavior: CloudFront.CacheCookieBehavior.all(),
273
+ headerBehavior:
274
+ CloudFront.CacheHeaderBehavior.allowList("X-Forwarded-Host"),
275
+ queryStringBehavior: CloudFront.CacheQueryStringBehavior.all(),
276
+ enableAcceptEncodingGzip: true,
277
+ enableAcceptEncodingBrotli: true,
278
+ },
279
+ ),
221
280
  functionAssociations: [
222
281
  {
223
282
  function: forwardHostHeaderCfFunction,
224
283
  eventType: CloudFront.FunctionEventType.VIEWER_REQUEST,
225
- }
284
+ },
226
285
  ],
227
- }
286
+ };
228
287
  }
229
-
230
288
  }
@@ -1,11 +0,0 @@
1
- import ts from "typescript";
2
- import fs from "fs";
3
-
4
- export function getFileContentsWithoutTypes(filePath: string): string {
5
- const source = fs.readFileSync(filePath, "utf8");
6
-
7
- const result = ts.transpileModule(source, {
8
- compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2020 }
9
- });
10
- return result.outputText
11
- }