@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
package/README.md CHANGED
@@ -89,6 +89,12 @@ Unlike [NextjsSite](https://v2.sst.dev/constructs/NextjsSite), any environment v
89
89
  | customDomain.isIxManagedDomain | boolean | (optional) If true will attempt to create DNS records and certs for it using the IX shared infra. Only required if explicitly setting customDomains and you want DNS records + certs setup for them |
90
90
  | customDomain.additionalDomainAliases | string[] | (optional) Works like `customDomain.domainAlias` but `domainAlias` only allows one domain, additionalDomainAliases allows setting additional domains |
91
91
  | environment | Record<string, string \| {buildtime?: string, runtime?: string}> | (optional) As well as accepting strings for environment variable values as is already done by [NextjsSite](https://v2.sst.dev/constructs/NextjsSite) it also accepts an object with the properties `buildtime` and/or `runtime` which allows you to customise the environment variable value during those different steps. |
92
+ | auth | object | (optional) If provided will put the site behind auth. |
93
+ | auth.oidc | object | |
94
+ | auth.oidc.issuerUrl | string | An issuer URL for the OIDC server to use. |
95
+ | auth.oidc.clientId | string | The OIDC client ID to use. |
96
+ | auth.oidc.scope | string | The scope used for the auth request. |
97
+ | auth.prefix | string | (optional) A custom path to be used for the auth route. |
92
98
 
93
99
  ```typescript
94
100
  import { IxNextjsSite } from "@infoxchange/make-it-so/cdk-constructs";
@@ -123,9 +129,15 @@ Also if `isIxManagedDomain` is true DNS records will be automatically created fo
123
129
 
124
130
  | Prop | Type | Description |
125
131
  | ------------------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
126
- | [...NextjsSiteProps] | | Any props accepted by [SST's StaticSite](https://v2.sst.dev/constructs/StaticSite) |
132
+ | [...StaticSiteProps] | | Any props accepted by [SST's StaticSite](https://v2.sst.dev/constructs/StaticSite) |
127
133
  | customDomain.isIxManagedDomain | boolean | (optional) If true will attempt to create DNS records and certs for it using the IX shared infra. Only required if explicitly setting customDomains and you want DNS records + certs setup for them |
128
134
  | customDomain.additionalDomainAliases | string[] | (optional) Works like `customDomain.domainAlias` but `domainAlias` only allows one domain, additionalDomainAliases allows setting additional domains |
135
+ | auth | object | (optional) If provided will put the site behind auth. |
136
+ | auth.oidc | object | |
137
+ | auth.oidc.issuerUrl | string | An issuer URL for the OIDC server to use. |
138
+ | auth.oidc.clientId | string | The OIDC client ID to use. |
139
+ | auth.oidc.scope | string | The scope used for the auth request. |
140
+ | auth.prefix | string | (optional) A custom path to be used for the auth route. |
129
141
 
130
142
  ```typescript
131
143
  import { IxStaticSite } from "@infoxchange/make-it-so/cdk-constructs";
@@ -290,31 +302,7 @@ const vpcDetails = new IxVpcDetails(scope, "VpcDetails");
290
302
 
291
303
  </details>
292
304
 
293
- <details>
294
- <summary><strong>CloudFrontOidcAuth</strong> - Adds OIDC authentication to a CloudFront distribution.</summary>
295
-
296
- ```typescript
297
- import { CloudFrontOidcAuth } from "@infoxchange/make-it-so/cdk-constructs";
298
-
299
- // You first create an instance of CloudFrontOidcAuth
300
- const auth = new CloudFrontOidcAuth(stack, "CloudFrontOidcAuth", {
301
- oidcIssuerUrl: "https://your-oidc-server.com/path/",
302
- oidcClientId: "your-client-id",
303
- oidcScope: "email",
304
- });
305
-
306
- // Then you apply it to the a CloudFront backed site when it's created
307
- const site = new IxStaticSite(stack, "IxStaticSite", {
308
- path: "path/to/site/files",
309
- cdk: {
310
- distribution: auth.addToDistributionDefinition(stack, {
311
- distributionDefinition: {},
312
- }),
313
- },
314
- });
315
- ```
316
-
317
- ## Full Example
305
+ ## Example App Using Make It So
318
306
 
319
307
  To deploy a Next.js based site you would include a `sst.config.ts` file at the root of repo with contents like this:
320
308
 
@@ -348,9 +336,7 @@ export default {
348
336
  } satisfies SSTConfig;
349
337
  ```
350
338
 
351
- Then simply configure the IX pipeline to deploy that repo as a serverless app.
352
-
353
- important that sst and aws lib version match those used in ix-deploy-support
339
+ Then simply configure the IX pipeline to deploy that repo as a serverless app. Note it is important that any AWS CDK libraries included in package.json match version match the version used by SST.
354
340
 
355
341
  # The Name
356
342
 
@@ -1 +1 @@
1
- {"version":3,"file":"IxNextjsSite.d.ts","sourceRoot":"","sources":["../../src/cdk-constructs/IxNextjsSite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5C,OAAO,EACL,KAAK,uBAAuB,EAe7B,MAAM,wBAAwB,CAAC;AAEhC,KAAK,cAAc,GAAG,qBAAqB,CAAC,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;AAClE,KAAK,WAAW,GAAG,qBAAqB,CAAC,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/D,KAAK,cAAc,GAAG,uBAAuB,CAAC;AAE9C,qBAAa,YAAa,SAAQ,UAAU;gBAExC,KAAK,EAAE,cAAc,EACrB,EAAE,EAAE,WAAW,EACf,KAAK,GAAE,cAAmB;IAkB5B,IAAW,aAAa,IAAI,MAAM,EAAE,CAEnC;IAED,IAAW,mBAAmB,IAAI,MAAM,GAAG,IAAI,CAE9C;IAED,IAAW,WAAW,IAAI,MAAM,GAAG,IAAI,CAEtC;IAED,IAAW,kBAAkB,IAAI,MAAM,EAAE,CAExC;IAED,IAAW,aAAa,IAAI,MAAM,GAAG,IAAI,CAExC;IAED,IAAW,aAAa,IAAI,MAAM,GAAG,IAAI,CAExC;CACF"}
1
+ {"version":3,"file":"IxNextjsSite.d.ts","sourceRoot":"","sources":["../../src/cdk-constructs/IxNextjsSite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5C,OAAO,EACL,KAAK,uBAAuB,EAgB7B,MAAM,wBAAwB,CAAC;AAEhC,KAAK,cAAc,GAAG,qBAAqB,CAAC,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;AAClE,KAAK,WAAW,GAAG,qBAAqB,CAAC,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/D,KAAK,cAAc,GAAG,uBAAuB,CAAC;AAE9C,qBAAa,YAAa,SAAQ,UAAU;gBAExC,KAAK,EAAE,cAAc,EACrB,EAAE,EAAE,WAAW,EACf,KAAK,GAAE,cAAmB;IAmB5B,IAAW,aAAa,IAAI,MAAM,EAAE,CAEnC;IAED,IAAW,mBAAmB,IAAI,MAAM,GAAG,IAAI,CAE9C;IAED,IAAW,WAAW,IAAI,MAAM,GAAG,IAAI,CAEtC;IAED,IAAW,kBAAkB,IAAI,MAAM,EAAE,CAExC;IAED,IAAW,aAAa,IAAI,MAAM,GAAG,IAAI,CAExC;IAED,IAAW,aAAa,IAAI,MAAM,GAAG,IAAI,CAExC;CACF"}
@@ -1,6 +1,6 @@
1
1
  import { NextjsSite } from "sst/constructs";
2
2
  import ixDeployConfig from "../deployConfig.js";
3
- import { getAliasDomain, getAlternativeDomains, getCustomDomains, getPrimaryCustomDomain, getPrimaryDomain, getPrimaryOrigin, setupCertificate, setupCustomDomain, setupDnsRecords, setupDomainAliasRedirect, setupVpcDetails, setupDefaultEnvVars, applyConditionalEnvironmentVariables, parentCompatibleSsrProps, } from "../lib/site/support.js";
3
+ import { getAliasDomain, getAlternativeDomains, getCustomDomains, getPrimaryCustomDomain, getPrimaryDomain, getPrimaryOrigin, setupCertificate, setupCustomDomain, setupDnsRecords, setupDomainAliasRedirect, setupVpcDetails, setupDefaultEnvVars, applyConditionalEnvironmentVariables, parentCompatibleSsrProps, processAuthProps, } from "../lib/site/support.js";
4
4
  export class IxNextjsSite extends NextjsSite {
5
5
  constructor(scope, id, props = {}) {
6
6
  if (ixDeployConfig.isIxDeploy) {
@@ -9,6 +9,7 @@ export class IxNextjsSite extends NextjsSite {
9
9
  props = setupCertificate(scope, id, props);
10
10
  props = setupDomainAliasRedirect(scope, id, props);
11
11
  }
12
+ props = processAuthProps(scope, id, "SsrSite", props);
12
13
  props = setupDefaultEnvVars(scope, id, props);
13
14
  props = applyConditionalEnvironmentVariables(scope, id, props);
14
15
  super(scope, id, parentCompatibleSsrProps(props));
@@ -1 +1 @@
1
- {"version":3,"file":"IxStaticSite.d.ts","sourceRoot":"","sources":["../../src/cdk-constructs/IxStaticSite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5C,OAAO,EACL,uBAAuB,EAWxB,MAAM,wBAAwB,CAAC;AAEhC,KAAK,cAAc,GAAG,qBAAqB,CAAC,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;AAClE,KAAK,WAAW,GAAG,qBAAqB,CAAC,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/D,KAAK,cAAc,GAAG,uBAAuB,CAAC;AAE9C,qBAAa,YAAa,SAAQ,UAAU;IAE1C,OAAO,CAAC,aAAa,CAAiB;gBAGpC,KAAK,EAAE,cAAc,EACrB,EAAE,EAAE,WAAW,EACf,KAAK,GAAE,cAAmB;IAgB5B,IAAW,aAAa,IAAI,MAAM,EAAE,CAEnC;IAED,IAAW,mBAAmB,IAAI,MAAM,GAAG,IAAI,CAE9C;IAED,IAAW,WAAW,IAAI,MAAM,GAAG,IAAI,CAEtC;IAED,IAAW,kBAAkB,IAAI,MAAM,EAAE,CAExC;IAED,IAAW,aAAa,IAAI,MAAM,GAAG,IAAI,CAExC;IAED,IAAW,aAAa,IAAI,MAAM,GAAG,IAAI,CAExC;CACF"}
1
+ {"version":3,"file":"IxStaticSite.d.ts","sourceRoot":"","sources":["../../src/cdk-constructs/IxStaticSite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5C,OAAO,EACL,uBAAuB,EAYxB,MAAM,wBAAwB,CAAC;AAEhC,KAAK,cAAc,GAAG,qBAAqB,CAAC,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;AAClE,KAAK,WAAW,GAAG,qBAAqB,CAAC,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/D,KAAK,cAAc,GAAG,uBAAuB,CAAC;AAE9C,qBAAa,YAAa,SAAQ,UAAU;IAE1C,OAAO,CAAC,aAAa,CAAiB;gBAGpC,KAAK,EAAE,cAAc,EACrB,EAAE,EAAE,WAAW,EACf,KAAK,GAAE,cAAmB;IAiB5B,IAAW,aAAa,IAAI,MAAM,EAAE,CAEnC;IAED,IAAW,mBAAmB,IAAI,MAAM,GAAG,IAAI,CAE9C;IAED,IAAW,WAAW,IAAI,MAAM,GAAG,IAAI,CAEtC;IAED,IAAW,kBAAkB,IAAI,MAAM,EAAE,CAExC;IAED,IAAW,aAAa,IAAI,MAAM,GAAG,IAAI,CAExC;IAED,IAAW,aAAa,IAAI,MAAM,GAAG,IAAI,CAExC;CACF"}
@@ -1,6 +1,6 @@
1
1
  import { StaticSite } from "sst/constructs";
2
2
  import ixDeployConfig from "../deployConfig.js";
3
- import { getAliasDomain, getAlternativeDomains, getCustomDomains, getPrimaryCustomDomain, getPrimaryDomain, getPrimaryOrigin, setupCertificate, setupCustomDomain, setupDnsRecords, setupDomainAliasRedirect, } from "../lib/site/support.js";
3
+ import { getAliasDomain, getAlternativeDomains, getCustomDomains, getPrimaryCustomDomain, getPrimaryDomain, getPrimaryOrigin, processAuthProps, setupCertificate, setupCustomDomain, setupDnsRecords, setupDomainAliasRedirect, } from "../lib/site/support.js";
4
4
  export class IxStaticSite extends StaticSite {
5
5
  // StaticSite's props are private, so we need to store them separately
6
6
  propsExtended;
@@ -10,6 +10,7 @@ export class IxStaticSite extends StaticSite {
10
10
  props = setupCertificate(scope, id, props);
11
11
  props = setupDomainAliasRedirect(scope, id, props);
12
12
  }
13
+ props = processAuthProps(scope, id, "StaticSite", props);
13
14
  super(scope, id, props);
14
15
  this.propsExtended = props;
15
16
  if (ixDeployConfig.isIxDeploy) {
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=auth-check-handler-body.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-check-handler-body.d.ts","sourceRoot":"","sources":["../../../src/cdk-constructs/SiteOidcAuth/auth-check-handler-body.ts"],"names":[],"mappings":""}
@@ -1,10 +1,30 @@
1
1
  // Based off: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/example_cloudfront_functions_kvs_jwt_verify_section.html
2
- // Note that as a CloudFront Function, this code has limitations compared to a Lambda@Edge function.
3
- // For example, no external libraries can be used, and the runtime is more limited.
4
- import crypto from "crypto";
5
- import cf from "cloudfront";
6
- const kvsKey = "__placeholder-for-jwt-secret-key__";
2
+ // Note that as a CloudFront Function, this code has limitations compared to a Lambda@Edge function. For example, no
3
+ // external libraries can be used, and the runtime is more limited. Because SST v2's SsrSite construct uses JS v1.0
4
+ // runtime for CloudFront Functions, this code must also be compatible with that runtime. Also this is used in the body
5
+ // of a function where the variables event and request are already defined.
6
+ // eslint-disable-next-line @typescript-eslint/no-var-requires -- v1 runtime for CloudFront Functions do not support import statements
7
+ const crypto = require("crypto");
8
+ const jwtSecret = "__placeholder-for-jwt-secret__";
7
9
  const authRoutePrefix = "__placeholder-for-auth-route-prefix__";
10
+ // Set to true to enable console logging
11
+ const loggingEnabled = false;
12
+ const log = function () {
13
+ if (!loggingEnabled)
14
+ return;
15
+ // CloudFront Function runtime only prints first argument passed to console.log so add other args to the first one if given.
16
+ // eslint-disable-next-line prefer-rest-params -- We can't use spread or rest parameters in CloudFront Functions
17
+ let message = arguments[0];
18
+ if (arguments.length > 1) {
19
+ const otherArgs = [];
20
+ for (let i = 1; i < arguments.length; i++) {
21
+ // eslint-disable-next-line prefer-rest-params
22
+ otherArgs[i - 1] = arguments[i];
23
+ }
24
+ message += " - additional args: " + JSON.stringify(otherArgs);
25
+ }
26
+ console.log(message);
27
+ };
8
28
  //Response when JWT is not valid.
9
29
  const redirectResponse = {
10
30
  statusCode: 302,
@@ -12,8 +32,6 @@ const redirectResponse = {
12
32
  location: { value: `${authRoutePrefix}/oidc/authorize` },
13
33
  },
14
34
  };
15
- // Set to true to enable console logging
16
- const loggingEnabled = false;
17
35
  function jwtDecode(token, key, noVerify) {
18
36
  // check segments
19
37
  const segments = token.split(".");
@@ -71,55 +89,19 @@ function _sign(input, key, method) {
71
89
  function _base64urlDecode(str) {
72
90
  return Buffer.from(str, "base64url").toString();
73
91
  }
74
- async function handler(event) {
75
- const request = event.request;
76
- const secret_key = await getSecret();
77
- if (!secret_key) {
78
- // It's not possible for us to validate requests without the secret key so we have no choice but to block all requests.
79
- throw new Error("Error retrieving JWT secret key");
80
- }
81
- const jwtToken = request.cookies["auth-token"] && request.cookies["auth-token"].value;
82
- if (!jwtToken) {
83
- log("Error: No JWT in the cookies");
84
- return redirectResponse;
85
- }
86
- try {
87
- jwtDecode(jwtToken, secret_key);
88
- }
89
- catch (e) {
90
- log(e);
91
- return redirectResponse;
92
- }
93
- log("Valid JWT token");
94
- return request;
92
+ const jwtToken = request.cookies["auth-token"] && request.cookies["auth-token"].value;
93
+ if (!jwtToken) {
94
+ log("Error: No JWT in the cookies");
95
+ // @ts-expect-error -- This code is added to a function body so we can use return here but typescript doesn't know that.
96
+ return redirectResponse;
95
97
  }
96
- // Get secret from key value store
97
- async function getSecret() {
98
- try {
99
- const kvsHandle = cf.kvs();
100
- return await kvsHandle.get(kvsKey);
101
- }
102
- catch (err) {
103
- log(`Error reading value for key: ${kvsKey}, error: ${err}`);
104
- return null;
105
- }
98
+ try {
99
+ jwtDecode(jwtToken, jwtSecret);
106
100
  }
107
- const log = function () {
108
- if (!loggingEnabled)
109
- return;
110
- // CloudFront Function runtime only prints first argument passed to console.log so add other args to the first one if given.
111
- // eslint-disable-next-line prefer-rest-params -- We can't use spread or rest parameters in CloudFront Functions
112
- let message = arguments[0];
113
- if (arguments.length > 1) {
114
- const otherArgs = [];
115
- for (let i = 1; i < arguments.length; i++) {
116
- // eslint-disable-next-line prefer-rest-params
117
- otherArgs[i - 1] = arguments[i];
118
- }
119
- message += " - additional args: " + JSON.stringify(otherArgs);
120
- }
121
- console.log(message);
122
- };
123
- // This serves no purpose other than to make TypeScript and eslint happy by showing that that handler is used. We can't
124
- // export handler as an alterative because CloudFront Functions don't support exports.
125
- handler;
101
+ catch (e) {
102
+ log(e);
103
+ // @ts-expect-error -- This code is added to a function body so we can use return here but typescript doesn't know that.
104
+ return redirectResponse;
105
+ }
106
+ log("Valid JWT token");
107
+ export {};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-route.d.ts","sourceRoot":"","sources":["../../../src/cdk-constructs/SiteOidcAuth/auth-route.ts"],"names":[],"mappings":"AAyBA,eAAO,MAAM,OAAO,4CAiCnB,CAAC"}
@@ -0,0 +1,197 @@
1
+ import { Construct } from "constructs";
2
+ import CloudFront from "aws-cdk-lib/aws-cloudfront";
3
+ import CDK from "aws-cdk-lib";
4
+ import * as SST from "sst/constructs";
5
+ import type { ExtendedNextjsSiteProps, ExtendedStaticSiteProps } from "../../lib/site/support.js";
6
+ type ConstructScope = ConstructorParameters<typeof Construct>[0];
7
+ type ConstructId = ConstructorParameters<typeof Construct>[1];
8
+ export type Props = {
9
+ oidcIssuerUrl: string;
10
+ oidcClientId: string;
11
+ oidcScope: string;
12
+ };
13
+ export type AddToSiteProps = {
14
+ prefix?: string;
15
+ };
16
+ export declare class SiteOidcAuth extends Construct {
17
+ readonly oidcIssuerUrl: string;
18
+ readonly oidcClientId: string;
19
+ readonly oidcScope: string;
20
+ readonly id: string;
21
+ constructor(scope: ConstructScope, id: ConstructId, props: Props);
22
+ addToStaticSiteProps<SiteProps extends ExtendedStaticSiteProps>(scope: ConstructScope, siteProps: SiteProps, { prefix }?: AddToSiteProps): SiteProps & {
23
+ cdk: {
24
+ distribution: {
25
+ additionalBehaviors: Record<string, CloudFront.BehaviorOptions>;
26
+ defaultBehavior: {
27
+ functionAssociations: CloudFront.FunctionAssociation[];
28
+ allowedMethods?: CloudFront.AllowedMethods | undefined;
29
+ cachedMethods?: CloudFront.CachedMethods | undefined;
30
+ cachePolicy?: CloudFront.ICachePolicy | undefined;
31
+ compress?: boolean | undefined;
32
+ originRequestPolicy?: CloudFront.IOriginRequestPolicy | undefined;
33
+ realtimeLogConfig?: CloudFront.IRealtimeLogConfig | undefined;
34
+ responseHeadersPolicy?: CloudFront.IResponseHeadersPolicy | undefined;
35
+ smoothStreaming?: boolean | undefined;
36
+ viewerProtocolPolicy?: CloudFront.ViewerProtocolPolicy | undefined;
37
+ edgeLambdas?: CloudFront.EdgeLambda[] | undefined;
38
+ trustedKeyGroups?: CloudFront.IKeyGroup[] | undefined;
39
+ origin?: CloudFront.IOrigin | undefined;
40
+ };
41
+ } | {
42
+ additionalBehaviors: Record<string, CloudFront.BehaviorOptions>;
43
+ defaultBehavior: {
44
+ functionAssociations: CloudFront.FunctionAssociation[];
45
+ allowedMethods?: CloudFront.AllowedMethods | undefined;
46
+ cachedMethods?: CloudFront.CachedMethods | undefined;
47
+ cachePolicy?: CloudFront.ICachePolicy | undefined;
48
+ compress?: boolean | undefined;
49
+ originRequestPolicy?: CloudFront.IOriginRequestPolicy | undefined;
50
+ realtimeLogConfig?: CloudFront.IRealtimeLogConfig | undefined;
51
+ responseHeadersPolicy?: CloudFront.IResponseHeadersPolicy | undefined;
52
+ smoothStreaming?: boolean | undefined;
53
+ viewerProtocolPolicy?: CloudFront.ViewerProtocolPolicy | undefined;
54
+ edgeLambdas?: CloudFront.EdgeLambda[] | undefined;
55
+ trustedKeyGroups?: CloudFront.IKeyGroup[] | undefined;
56
+ origin?: CloudFront.IOrigin | undefined;
57
+ };
58
+ distributionDomainName: string;
59
+ distributionId: string;
60
+ grant(identity: CDK.aws_iam.IGrantable, ...actions: string[]): CDK.aws_iam.Grant;
61
+ grantCreateInvalidation(identity: CDK.aws_iam.IGrantable): CDK.aws_iam.Grant;
62
+ stack: CDK.Stack;
63
+ env: CDK.ResourceEnvironment;
64
+ applyRemovalPolicy(policy: CDK.RemovalPolicy): void;
65
+ node: import("constructs").Node;
66
+ } | {
67
+ additionalBehaviors: Record<string, CloudFront.BehaviorOptions>;
68
+ defaultBehavior: {
69
+ functionAssociations: CloudFront.FunctionAssociation[];
70
+ allowedMethods?: CloudFront.AllowedMethods | undefined;
71
+ cachedMethods?: CloudFront.CachedMethods | undefined;
72
+ cachePolicy?: CloudFront.ICachePolicy | undefined;
73
+ compress?: boolean | undefined;
74
+ originRequestPolicy?: CloudFront.IOriginRequestPolicy | undefined;
75
+ realtimeLogConfig?: CloudFront.IRealtimeLogConfig | undefined;
76
+ responseHeadersPolicy?: CloudFront.IResponseHeadersPolicy | undefined;
77
+ smoothStreaming?: boolean | undefined;
78
+ viewerProtocolPolicy?: CloudFront.ViewerProtocolPolicy | undefined;
79
+ edgeLambdas?: CloudFront.EdgeLambda[] | undefined;
80
+ trustedKeyGroups?: CloudFront.IKeyGroup[] | undefined;
81
+ origin?: CloudFront.IOrigin | undefined;
82
+ };
83
+ certificate?: CDK.aws_certificatemanager.ICertificate | undefined;
84
+ comment?: string | undefined;
85
+ defaultRootObject?: string | undefined;
86
+ domainNames?: string[] | undefined;
87
+ enabled?: boolean | undefined;
88
+ enableIpv6?: boolean | undefined;
89
+ enableLogging?: boolean | undefined;
90
+ geoRestriction?: CloudFront.GeoRestriction | undefined;
91
+ httpVersion?: CloudFront.HttpVersion | undefined;
92
+ logBucket?: CDK.aws_s3.IBucket | undefined;
93
+ logIncludesCookies?: boolean | undefined;
94
+ logFilePrefix?: string | undefined;
95
+ priceClass?: CloudFront.PriceClass | undefined;
96
+ webAclId?: string | undefined;
97
+ errorResponses?: CloudFront.ErrorResponse[] | undefined;
98
+ minimumProtocolVersion?: CloudFront.SecurityPolicyProtocol | undefined;
99
+ sslSupportMethod?: CloudFront.SSLMethod | undefined;
100
+ publishAdditionalMetrics?: boolean | undefined;
101
+ };
102
+ id?: string | undefined;
103
+ bucket?: CDK.aws_s3.IBucket | CDK.aws_s3.BucketProps | undefined;
104
+ };
105
+ };
106
+ addToSsrSiteProps<SiteProps extends ExtendedNextjsSiteProps>(scope: ConstructScope, siteProps: SiteProps, { prefix }?: AddToSiteProps): SiteProps & {
107
+ cdk: {
108
+ distribution: {
109
+ additionalBehaviors: Record<string, CloudFront.BehaviorOptions>;
110
+ defaultBehavior?: (Omit<CloudFront.BehaviorOptions, "origin"> & {
111
+ origin?: CloudFront.IOrigin | undefined;
112
+ }) | undefined;
113
+ certificate?: CDK.aws_certificatemanager.ICertificate | undefined;
114
+ comment?: string | undefined;
115
+ defaultRootObject?: string | undefined;
116
+ domainNames?: string[] | undefined;
117
+ enabled?: boolean | undefined;
118
+ enableIpv6?: boolean | undefined;
119
+ enableLogging?: boolean | undefined;
120
+ geoRestriction?: CloudFront.GeoRestriction | undefined;
121
+ httpVersion?: CloudFront.HttpVersion | undefined;
122
+ logBucket?: CDK.aws_s3.IBucket | undefined;
123
+ logIncludesCookies?: boolean | undefined;
124
+ logFilePrefix?: string | undefined;
125
+ priceClass?: CloudFront.PriceClass | undefined;
126
+ webAclId?: string | undefined;
127
+ errorResponses?: CloudFront.ErrorResponse[] | undefined;
128
+ minimumProtocolVersion?: CloudFront.SecurityPolicyProtocol | undefined;
129
+ sslSupportMethod?: CloudFront.SSLMethod | undefined;
130
+ publishAdditionalMetrics?: boolean | undefined;
131
+ };
132
+ id?: string | undefined;
133
+ bucket?: CDK.aws_s3.IBucket | CDK.aws_s3.BucketProps | undefined;
134
+ s3Origin?: CDK.aws_cloudfront_origins.S3OriginProps | undefined;
135
+ serverCachePolicy?: CloudFront.ICachePolicy | undefined;
136
+ responseHeadersPolicy?: CloudFront.IResponseHeadersPolicy | undefined;
137
+ viewerProtocolPolicy?: CloudFront.ViewerProtocolPolicy | undefined;
138
+ server?: (Pick<CDK.aws_lambda.FunctionProps, "vpc" | "layers" | "vpcSubnets" | "securityGroups" | "allowAllOutbound" | "allowPublicSubnet" | "architecture" | "logRetention"> & Pick<SST.FunctionProps, "copyFiles">) | undefined;
139
+ transform?: ((args: {
140
+ cloudFrontFunctions?: Record<string, {
141
+ constructId: string;
142
+ injections: string[];
143
+ }> | undefined;
144
+ edgeFunctions?: Record<string, {
145
+ constructId: string;
146
+ function: import("sst/constructs/EdgeFunction.js").EdgeFunctionProps;
147
+ }> | undefined;
148
+ origins: Record<string, {
149
+ type: "function";
150
+ constructId: string;
151
+ function: import("sst/constructs/SsrFunction.js").SsrFunctionProps;
152
+ injections?: string[] | undefined;
153
+ streaming?: boolean | undefined;
154
+ } | {
155
+ type: "image-optimization-function";
156
+ function: CDK.aws_lambda.FunctionProps;
157
+ } | {
158
+ type: "s3";
159
+ originPath?: string | undefined;
160
+ copy: {
161
+ from: string;
162
+ to: string;
163
+ cached: boolean;
164
+ versionedSubDir?: string | undefined;
165
+ }[];
166
+ } | {
167
+ type: "group";
168
+ primaryOriginName: string;
169
+ fallbackOriginName: string;
170
+ fallbackStatusCodes?: number[] | undefined;
171
+ }>;
172
+ edge: boolean;
173
+ behaviors: {
174
+ cacheType: "server" | "static";
175
+ pattern?: string | undefined;
176
+ origin: string;
177
+ allowedMethods?: CloudFront.AllowedMethods | undefined;
178
+ cfFunction?: string | undefined;
179
+ edgeFunction?: string | undefined;
180
+ }[];
181
+ errorResponses?: CloudFront.ErrorResponse[] | undefined;
182
+ serverCachePolicy?: {
183
+ allowedHeaders?: string[] | undefined;
184
+ } | undefined;
185
+ buildId?: string | undefined;
186
+ }) => void) | undefined;
187
+ revalidation?: Pick<CDK.aws_lambda.FunctionProps, "vpc" | "vpcSubnets"> | undefined;
188
+ };
189
+ };
190
+ private createJwtSecret;
191
+ private getFunctionAssociation;
192
+ private getAuthCheckHandlerBodyCode;
193
+ private convertToCloudFrontFunctionCompatibleCode;
194
+ private getAuthBehaviorOptions;
195
+ }
196
+ export {};
197
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cdk-constructs/SiteOidcAuth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,UAAU,MAAM,4BAA4B,CAAC;AACpD,OAAO,GAAG,MAAM,aAAa,CAAC;AAE9B,OAAO,KAAK,GAAG,MAAM,gBAAgB,CAAC;AAOtC,OAAO,KAAK,EACV,uBAAuB,EACvB,uBAAuB,EACxB,MAAM,2BAA2B,CAAC;AAEnC,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,MAAM,MAAM,KAAK,GAAG;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AACF,MAAM,MAAM,cAAc,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAIjD,qBAAa,YAAa,SAAQ,SAAS;IACzC,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,oBAAoB,CAAC,SAAS,SAAS,uBAAuB,EAC5D,KAAK,EAAE,cAAc,EACrB,SAAS,EAAE,SAAS,EACpB,EAAE,MAA+B,EAAE,GAAE,cAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA4C1D,iBAAiB,CAAC,SAAS,SAAS,uBAAuB,EACzD,KAAK,EAAE,cAAc,EACrB,SAAS,EAAE,SAAS,EACpB,EAAE,MAA+B,EAAE,GAAE,cAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAqC1D,OAAO,CAAC,eAAe;IAgBvB,OAAO,CAAC,sBAAsB;IA6B9B,OAAO,CAAC,2BAA2B;IAgBnC,OAAO,CAAC,yCAAyC;IAejD,OAAO,CAAC,sBAAsB;CAiF/B"}
@@ -0,0 +1,184 @@
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 Lambda from "aws-cdk-lib/aws-lambda";
6
+ import * as SST from "sst/constructs";
7
+ import { isCDKConstruct } from "sst/constructs/Construct.js";
8
+ import { Config as SSTInternalConfig } from "sst/config.js";
9
+ import CloudFrontOrigins from "aws-cdk-lib/aws-cloudfront-origins";
10
+ import path from "node:path";
11
+ import fs from "node:fs";
12
+ import { transformSync } from "esbuild";
13
+ const defaultAuthRoutePrefix = "/auth";
14
+ export class SiteOidcAuth extends Construct {
15
+ oidcIssuerUrl;
16
+ oidcClientId;
17
+ oidcScope;
18
+ id;
19
+ constructor(scope, id, props) {
20
+ super(scope, id);
21
+ this.oidcIssuerUrl = props.oidcIssuerUrl;
22
+ this.oidcClientId = props.oidcClientId;
23
+ this.oidcScope = props.oidcScope;
24
+ this.id = id;
25
+ }
26
+ addToStaticSiteProps(scope, siteProps, { prefix = defaultAuthRoutePrefix } = {}) {
27
+ prefix = prefix.replace(/\/$/, ""); // Remove trailing slash from prefix if it has one
28
+ const behaviourName = `${prefix.replace(/^\//g, "")}/*`;
29
+ const distribution = siteProps.cdk?.distribution;
30
+ if (isCDKConstruct(distribution)) {
31
+ throw new Error(`CDK Construct for distribution is not supported when adding CloudFront OIDC Auth behavior for prefix ${prefix}`);
32
+ }
33
+ const updatedSiteProps = {
34
+ ...siteProps,
35
+ cdk: {
36
+ ...siteProps.cdk,
37
+ distribution: {
38
+ ...siteProps.cdk?.distribution,
39
+ additionalBehaviors: distribution?.additionalBehaviors ?? {},
40
+ defaultBehavior: {
41
+ ...distribution?.defaultBehavior,
42
+ functionAssociations: distribution?.defaultBehavior?.functionAssociations ?? [],
43
+ },
44
+ },
45
+ },
46
+ };
47
+ if (updatedSiteProps.cdk.distribution.additionalBehaviors[behaviourName]) {
48
+ throw new Error(`Behavior for prefix ${prefix} already exists in distribution definition`);
49
+ }
50
+ const jwtSecret = this.createJwtSecret();
51
+ updatedSiteProps.cdk.distribution.defaultBehavior.functionAssociations.push(this.getFunctionAssociation(scope, jwtSecret, prefix));
52
+ updatedSiteProps.cdk.distribution.additionalBehaviors[behaviourName] =
53
+ this.getAuthBehaviorOptions(scope, jwtSecret, prefix);
54
+ return updatedSiteProps;
55
+ }
56
+ addToSsrSiteProps(scope, siteProps, { prefix = defaultAuthRoutePrefix } = {}) {
57
+ prefix = prefix.replace(/\/$/, ""); // Remove trailing slash from prefix if it has one
58
+ const behaviourName = `${prefix.replace(/^\//g, "")}/*`;
59
+ const updatedSiteProps = {
60
+ ...siteProps,
61
+ cdk: {
62
+ ...siteProps.cdk,
63
+ distribution: {
64
+ ...siteProps.cdk?.distribution,
65
+ additionalBehaviors: siteProps.cdk?.distribution?.additionalBehaviors ?? {},
66
+ },
67
+ },
68
+ };
69
+ if (updatedSiteProps.cdk.distribution.additionalBehaviors[behaviourName]) {
70
+ throw new Error(`Behavior for prefix ${prefix} already exists in distribution definition`);
71
+ }
72
+ const jwtSecret = this.createJwtSecret();
73
+ updatedSiteProps.cdk.transform = (plan) => {
74
+ siteProps?.cdk?.transform?.(plan);
75
+ plan.cloudFrontFunctions?.serverCfFunction.injections.push(this.getAuthCheckHandlerBodyCode(jwtSecret, prefix));
76
+ };
77
+ updatedSiteProps.cdk.distribution.additionalBehaviors[behaviourName] =
78
+ this.getAuthBehaviorOptions(scope, jwtSecret, prefix);
79
+ return updatedSiteProps;
80
+ }
81
+ createJwtSecret() {
82
+ return new SecretsManager.Secret(this, `${this.id}JwtSecret`, {
83
+ description: "JWT Signing Secret",
84
+ generateSecretString: {
85
+ passwordLength: 32,
86
+ excludePunctuation: true,
87
+ includeSpace: false,
88
+ requireEachIncludedType: true,
89
+ },
90
+ // Secret is only used for sessions so it's safe to delete on stack removal
91
+ removalPolicy: CDK.RemovalPolicy.DESTROY,
92
+ });
93
+ }
94
+ // Get the CloudFront Function Association for auth checking
95
+ // Roughly based off https://github.com/sst/v2/blob/4283d706f251724308b397996ff307929bf3a976/packages/sst/src/constructs/SsrSite.ts#L941
96
+ getFunctionAssociation(scope, jwtSecret, authRoutePrefix) {
97
+ const authCheckFunction = new CloudFront.Function(scope, `${this.id}AuthCheckFunction`, {
98
+ code: CloudFront.FunctionCode.fromInline(this.convertToCloudFrontFunctionCompatibleCode(`function handler(event) {
99
+ var request = event.request;
100
+ ${this.getAuthCheckHandlerBodyCode(jwtSecret, authRoutePrefix)}
101
+ return request;
102
+ }`)),
103
+ // We could specify the JS v2.0 runtime here but for SSR sites SST does the function creation and that currently
104
+ // uses JS v1.0 so no point using v2.0 here as the code has to be compatible with v1.0 anyway.
105
+ });
106
+ return {
107
+ function: authCheckFunction,
108
+ eventType: CloudFront.FunctionEventType.VIEWER_REQUEST,
109
+ };
110
+ }
111
+ getAuthCheckHandlerBodyCode(jwtSecret, authRoutePrefix) {
112
+ return fs
113
+ .readFileSync(path.join(import.meta.dirname, "auth-check-handler-body.js"), "utf8")
114
+ .replace("__placeholder-for-jwt-secret__", jwtSecret.secretValue.toString())
115
+ .replace("__placeholder-for-auth-route-prefix__", authRoutePrefix);
116
+ }
117
+ convertToCloudFrontFunctionCompatibleCode(sourceCode) {
118
+ // ESBuild doesn't currently support transforming const/let to var, which is required for CloudFront Functions
119
+ // JS runtime 1.0.
120
+ sourceCode = sourceCode
121
+ .replaceAll(/const /g, "var ")
122
+ .replaceAll(/let /g, "var ");
123
+ return transformSync(sourceCode, {
124
+ minify: true,
125
+ target: "es5",
126
+ }).code;
127
+ }
128
+ // Get the behavior options for the auth route
129
+ getAuthBehaviorOptions(scope, jwtSecret, prefix) {
130
+ const authRouteFunction = new SST.Function(scope, `${this.id}AuthRouteFunction`, {
131
+ runtime: "nodejs20.x",
132
+ handler: path.join(import.meta.dirname, "auth-route.handler"),
133
+ environment: {
134
+ OIDC_ISSUER_URL: this.oidcIssuerUrl,
135
+ OIDC_CLIENT_ID: this.oidcClientId,
136
+ OIDC_SCOPE: this.oidcScope,
137
+ JWT_SECRET: jwtSecret.secretValue.toString(),
138
+ },
139
+ });
140
+ // authRouteFunction uses SST's AuthHandler construct which is normally run inside a lambda that's
141
+ // created by SST's Auth construct. AuthHandler expects certain environment variables to be set
142
+ // by the Auth construct so we have to set them ourselves here to keep it happy.
143
+ const envVarName = SSTInternalConfig.envFor({
144
+ type: "Auth",
145
+ id: "id", // It seems like the env var will still be found no matter what this value is
146
+ prop: "prefix",
147
+ });
148
+ authRouteFunction.addEnvironment(envVarName, prefix);
149
+ const authRouteFunctionUrl = authRouteFunction.addFunctionUrl({
150
+ authType: Lambda.FunctionUrlAuthType.NONE,
151
+ });
152
+ const forwardHostHeaderCfFunction = new CloudFront.Function(scope, `${this.id}ForwardHostHeaderFunction`, {
153
+ code: CloudFront.FunctionCode.fromInline(this.convertToCloudFrontFunctionCompatibleCode(`function handler(event) {
154
+ const request = event.request;
155
+ request.headers["x-forwarded-host"] = { value: request.headers.host.value };
156
+ return request;
157
+ }`)),
158
+ runtime: CloudFront.FunctionRuntime.JS_2_0,
159
+ });
160
+ return {
161
+ origin: new CloudFrontOrigins.HttpOrigin(CDK.Fn.parseDomainName(authRouteFunctionUrl.url)),
162
+ allowedMethods: CloudFront.AllowedMethods.ALLOW_ALL,
163
+ cachePolicy: new CloudFront.CachePolicy(scope, `${this.id}AllowAllCookiesPolicy`, {
164
+ cachePolicyName: `${this.id}-AllowAllCookiesPolicy`,
165
+ comment: "Cache policy that forwards all cookies",
166
+ defaultTtl: CDK.Duration.seconds(1),
167
+ minTtl: CDK.Duration.seconds(1),
168
+ maxTtl: CDK.Duration.seconds(1),
169
+ cookieBehavior: CloudFront.CacheCookieBehavior.all(),
170
+ headerBehavior: CloudFront.CacheHeaderBehavior.allowList("X-Forwarded-Host"),
171
+ queryStringBehavior: CloudFront.CacheQueryStringBehavior.all(),
172
+ enableAcceptEncodingGzip: true,
173
+ enableAcceptEncodingBrotli: true,
174
+ }),
175
+ viewerProtocolPolicy: CloudFront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
176
+ functionAssociations: [
177
+ {
178
+ function: forwardHostHeaderCfFunction,
179
+ eventType: CloudFront.FunctionEventType.VIEWER_REQUEST,
180
+ },
181
+ ],
182
+ };
183
+ }
184
+ }
@@ -6,5 +6,5 @@ export * from "./IxStaticSite.js";
6
6
  export * from "./IxElasticache.js";
7
7
  export * from "./IxApi.js";
8
8
  export * from "./IxQuicksightWorkspace.js";
9
- export * from "./CloudFrontOidcAuth/index.js";
9
+ export * from "./SiteOidcAuth/index.js";
10
10
  //# sourceMappingURL=index.d.ts.map