@infoxchange/make-it-so-sst-v2 1.0.0-internal-testing-disambiguate-release-id-bc6e5bb.1

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 (99) hide show
  1. package/.editorconfig +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +377 -0
  4. package/commitlint.config.ts +14 -0
  5. package/dist/cdk-constructs/IxApi.d.ts +12 -0
  6. package/dist/cdk-constructs/IxApi.d.ts.map +1 -0
  7. package/dist/cdk-constructs/IxApi.js +56 -0
  8. package/dist/cdk-constructs/IxBucket.d.ts +9 -0
  9. package/dist/cdk-constructs/IxBucket.d.ts.map +1 -0
  10. package/dist/cdk-constructs/IxBucket.js +22 -0
  11. package/dist/cdk-constructs/IxCertificate.d.ts +16 -0
  12. package/dist/cdk-constructs/IxCertificate.d.ts.map +1 -0
  13. package/dist/cdk-constructs/IxCertificate.js +26 -0
  14. package/dist/cdk-constructs/IxDnsRecord.d.ts +23 -0
  15. package/dist/cdk-constructs/IxDnsRecord.d.ts.map +1 -0
  16. package/dist/cdk-constructs/IxDnsRecord.js +43 -0
  17. package/dist/cdk-constructs/IxElasticache.d.ts +17 -0
  18. package/dist/cdk-constructs/IxElasticache.d.ts.map +1 -0
  19. package/dist/cdk-constructs/IxElasticache.js +70 -0
  20. package/dist/cdk-constructs/IxNextjsSite.d.ts +16 -0
  21. package/dist/cdk-constructs/IxNextjsSite.d.ts.map +1 -0
  22. package/dist/cdk-constructs/IxNextjsSite.js +38 -0
  23. package/dist/cdk-constructs/IxQuicksightWorkspace.d.ts +17 -0
  24. package/dist/cdk-constructs/IxQuicksightWorkspace.d.ts.map +1 -0
  25. package/dist/cdk-constructs/IxQuicksightWorkspace.js +29 -0
  26. package/dist/cdk-constructs/IxSESIdentity.d.ts +12 -0
  27. package/dist/cdk-constructs/IxSESIdentity.d.ts.map +1 -0
  28. package/dist/cdk-constructs/IxSESIdentity.js +45 -0
  29. package/dist/cdk-constructs/IxStaticSite.d.ts +17 -0
  30. package/dist/cdk-constructs/IxStaticSite.d.ts.map +1 -0
  31. package/dist/cdk-constructs/IxStaticSite.js +38 -0
  32. package/dist/cdk-constructs/IxVpcDetails.d.ts +12 -0
  33. package/dist/cdk-constructs/IxVpcDetails.d.ts.map +1 -0
  34. package/dist/cdk-constructs/IxVpcDetails.js +26 -0
  35. package/dist/cdk-constructs/IxWebsiteRedirect.d.ts +35 -0
  36. package/dist/cdk-constructs/IxWebsiteRedirect.d.ts.map +1 -0
  37. package/dist/cdk-constructs/IxWebsiteRedirect.js +72 -0
  38. package/dist/cdk-constructs/SiteOidcAuth/auth-check-handler-body.d.ts +2 -0
  39. package/dist/cdk-constructs/SiteOidcAuth/auth-check-handler-body.d.ts.map +1 -0
  40. package/dist/cdk-constructs/SiteOidcAuth/auth-check-handler-body.js +130 -0
  41. package/dist/cdk-constructs/SiteOidcAuth/auth-route.d.ts +2 -0
  42. package/dist/cdk-constructs/SiteOidcAuth/auth-route.d.ts.map +1 -0
  43. package/dist/cdk-constructs/SiteOidcAuth/auth-route.js +59 -0
  44. package/dist/cdk-constructs/SiteOidcAuth/index.d.ts +197 -0
  45. package/dist/cdk-constructs/SiteOidcAuth/index.d.ts.map +1 -0
  46. package/dist/cdk-constructs/SiteOidcAuth/index.js +188 -0
  47. package/dist/cdk-constructs/index.d.ts +11 -0
  48. package/dist/cdk-constructs/index.d.ts.map +1 -0
  49. package/dist/cdk-constructs/index.js +10 -0
  50. package/dist/deployConfig.d.ts +72 -0
  51. package/dist/deployConfig.d.ts.map +1 -0
  52. package/dist/deployConfig.js +78 -0
  53. package/dist/lib/auth/index.d.ts +2 -0
  54. package/dist/lib/auth/index.d.ts.map +1 -0
  55. package/dist/lib/auth/index.js +1 -0
  56. package/dist/lib/auth/oidc.d.ts +26 -0
  57. package/dist/lib/auth/oidc.d.ts.map +1 -0
  58. package/dist/lib/auth/oidc.js +48 -0
  59. package/dist/lib/proxy/fetch.d.ts +4 -0
  60. package/dist/lib/proxy/fetch.d.ts.map +1 -0
  61. package/dist/lib/proxy/fetch.js +31 -0
  62. package/dist/lib/proxy/index.d.ts +2 -0
  63. package/dist/lib/proxy/index.d.ts.map +1 -0
  64. package/dist/lib/proxy/index.js +1 -0
  65. package/dist/lib/site/support.d.ts +71 -0
  66. package/dist/lib/site/support.d.ts.map +1 -0
  67. package/dist/lib/site/support.js +262 -0
  68. package/dist/lib/utils/hash.d.ts +2 -0
  69. package/dist/lib/utils/hash.d.ts.map +1 -0
  70. package/dist/lib/utils/hash.js +13 -0
  71. package/dist/lib/utils/objects.d.ts +4 -0
  72. package/dist/lib/utils/objects.d.ts.map +1 -0
  73. package/dist/lib/utils/objects.js +7 -0
  74. package/eslint.config.js +11 -0
  75. package/package.json +66 -0
  76. package/src/cdk-constructs/IxApi.ts +81 -0
  77. package/src/cdk-constructs/IxBucket.ts +35 -0
  78. package/src/cdk-constructs/IxCertificate.ts +54 -0
  79. package/src/cdk-constructs/IxDnsRecord.ts +79 -0
  80. package/src/cdk-constructs/IxElasticache.ts +106 -0
  81. package/src/cdk-constructs/IxNextjsSite.ts +72 -0
  82. package/src/cdk-constructs/IxQuicksightWorkspace.ts +54 -0
  83. package/src/cdk-constructs/IxSESIdentity.ts +70 -0
  84. package/src/cdk-constructs/IxStaticSite.ts +69 -0
  85. package/src/cdk-constructs/IxVpcDetails.ts +38 -0
  86. package/src/cdk-constructs/IxWebsiteRedirect.ts +133 -0
  87. package/src/cdk-constructs/SiteOidcAuth/auth-check-handler-body.ts +168 -0
  88. package/src/cdk-constructs/SiteOidcAuth/auth-route.ts +71 -0
  89. package/src/cdk-constructs/SiteOidcAuth/index.ts +299 -0
  90. package/src/cdk-constructs/index.ts +10 -0
  91. package/src/deployConfig.ts +87 -0
  92. package/src/lib/auth/index.ts +1 -0
  93. package/src/lib/auth/oidc.ts +73 -0
  94. package/src/lib/proxy/fetch.ts +41 -0
  95. package/src/lib/proxy/index.ts +1 -0
  96. package/src/lib/site/support.ts +439 -0
  97. package/src/lib/utils/hash.ts +14 -0
  98. package/src/lib/utils/objects.ts +19 -0
  99. package/tsconfig.json +9 -0
@@ -0,0 +1,130 @@
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. 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__";
9
+ const authRoutePrefix = "__placeholder-for-auth-route-prefix__";
10
+ // Set to true to enable console logging
11
+ const loggingEnabled = false;
12
+ // Simple logger that can be enabled/disabled via the loggingEnabled variable.
13
+ const log = function () {
14
+ if (!loggingEnabled)
15
+ return;
16
+ // CloudFront Function runtime only prints first argument passed to console.log so add other args to the first one if given.
17
+ // eslint-disable-next-line prefer-rest-params -- We can't use spread or rest parameters in CloudFront Functions
18
+ let message = arguments[0];
19
+ if (arguments.length > 1) {
20
+ const otherArgs = [];
21
+ for (let i = 1; i < arguments.length; i++) {
22
+ // eslint-disable-next-line prefer-rest-params
23
+ otherArgs[i - 1] = arguments[i];
24
+ }
25
+ message += " - additional args: " + JSON.stringify(otherArgs);
26
+ }
27
+ console.log(message);
28
+ };
29
+ //Response when JWT is not valid.
30
+ const redirectResponse = {
31
+ statusCode: 302,
32
+ headers: {
33
+ location: { value: `${authRoutePrefix}/oidc/authorize` },
34
+ },
35
+ };
36
+ // Takes a JWT token to decode and throws an error if invalid
37
+ function jwtDecode(token, key, noVerify) {
38
+ // check segments
39
+ const segments = token.split(".");
40
+ if (segments.length !== 3) {
41
+ throw new Error("Not enough or too many segments");
42
+ }
43
+ // All segment should be base64
44
+ const headerSeg = segments[0];
45
+ const payloadSeg = segments[1];
46
+ const signatureSeg = segments[2];
47
+ // base64 decode and parse JSON
48
+ const payload = JSON.parse(_base64urlDecode(payloadSeg));
49
+ if (noVerify) {
50
+ return payload;
51
+ }
52
+ const signingMethod = "sha256";
53
+ const signingType = "hmac";
54
+ // Verify signature. `sign` will return base64 string.
55
+ const signingInput = [headerSeg, payloadSeg].join(".");
56
+ if (!_verify(signingInput, key, signingMethod, signingType, signatureSeg)) {
57
+ throw new Error("Signature verification failed");
58
+ }
59
+ // Support for nbf and exp claims.
60
+ // According to the RFC, they should be in seconds.
61
+ if (payload.nbf && Date.now() < payload.nbf * 1000) {
62
+ throw new Error("Token not yet active");
63
+ }
64
+ if (payload.exp && Date.now() > payload.exp * 1000) {
65
+ throw new Error("Token expired");
66
+ }
67
+ return payload;
68
+ }
69
+ // Function to ensure a constant time comparison to prevent timing side channels.
70
+ function _constantTimeEquals(a, b) {
71
+ if (a.length != b.length) {
72
+ return false;
73
+ }
74
+ let xor = 0;
75
+ for (let i = 0; i < a.length; i++) {
76
+ xor |= a.charCodeAt(i) ^ b.charCodeAt(i);
77
+ }
78
+ return 0 === xor;
79
+ }
80
+ // Verifies some input matches an expected signature.
81
+ function _verify(input, key, method, type, signature) {
82
+ if (type === "hmac") {
83
+ return _constantTimeEquals(signature, _sign(input, key, method));
84
+ }
85
+ else {
86
+ throw new Error("Algorithm type not recognized");
87
+ }
88
+ }
89
+ // Signs some input with a key and method.
90
+ function _sign(input, key, method) {
91
+ return crypto.createHmac(method, key).update(input).digest("base64url");
92
+ }
93
+ // Very annoying that we have to implement this ourselves but it seems like the v1 runtime does not have atob/btoa or
94
+ // Buffer available.
95
+ function _base64urlDecode(str) {
96
+ str = str.replace(/-/g, "+").replace(/_/g, "/");
97
+ while (str.length % 4)
98
+ str += "=";
99
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
100
+ let output = "";
101
+ let bc = 0, bs = 0, buffer, i = 0;
102
+ for (; i < str.length; i++) {
103
+ buffer = chars.indexOf(str.charAt(i));
104
+ if (buffer === -1)
105
+ continue;
106
+ bs = (bs << 6) | buffer;
107
+ bc += 6;
108
+ if (bc >= 8) {
109
+ bc -= 8;
110
+ output += String.fromCharCode((bs >> bc) & 0xff);
111
+ }
112
+ }
113
+ return output;
114
+ }
115
+ const jwtToken = request.cookies["auth-token"] && request.cookies["auth-token"].value;
116
+ if (!jwtToken) {
117
+ log("Error: No JWT in the cookies");
118
+ // @ts-expect-error -- This code is added to a function body so we can use return here but typescript doesn't know that.
119
+ return redirectResponse;
120
+ }
121
+ try {
122
+ jwtDecode(jwtToken, jwtSecret);
123
+ }
124
+ catch (e) {
125
+ log(e);
126
+ // @ts-expect-error -- This code is added to a function body so we can use return here but typescript doesn't know that.
127
+ return redirectResponse;
128
+ }
129
+ log("Valid JWT token");
130
+ export {};
@@ -0,0 +1,2 @@
1
+ export declare const handler: (event: any, context: any) => Promise<any>;
2
+ //# sourceMappingURL=auth-route.d.ts.map
@@ -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,59 @@
1
+ import { AuthHandler, OidcAdapter } from "sst/node/auth";
2
+ import { Issuer } from "openid-client";
3
+ import jwt from "jsonwebtoken";
4
+ const oidcClientId = process.env.OIDC_CLIENT_ID;
5
+ if (!oidcClientId) {
6
+ throw new Error("OIDC_CLIENT_ID not set");
7
+ }
8
+ const oidcIssuerUrl = process.env.OIDC_ISSUER_URL;
9
+ if (!oidcIssuerUrl) {
10
+ throw new Error("OIDC_ISSUER_URL not set");
11
+ }
12
+ const oidcScope = process.env.OIDC_SCOPE;
13
+ if (!oidcScope) {
14
+ throw new Error("OIDC_SCOPE not set");
15
+ }
16
+ const jwtSecret = process.env.JWT_SECRET;
17
+ if (!jwtSecret) {
18
+ throw new Error("JWT_SECRET not set");
19
+ }
20
+ const oidcIssuerConfigUrl = new URL(`${process.env.OIDC_ISSUER_URL?.replace(/\/$/, "")}/.well-known/openid-configuration`);
21
+ export const handler = addRequiredContext(AuthHandler({
22
+ providers: {
23
+ oidc: OidcAdapter({
24
+ issuer: await Issuer.discover(oidcIssuerConfigUrl.href),
25
+ clientID: oidcClientId,
26
+ scope: oidcScope,
27
+ onSuccess: async (tokenset) => {
28
+ // Payload to include in the token
29
+ const payload = {
30
+ userID: tokenset.claims().sub,
31
+ };
32
+ const expiresInMs = 1000 * 60 * 60;
33
+ // Create the token
34
+ const token = jwt.sign(payload, jwtSecret, {
35
+ algorithm: "HS256",
36
+ expiresIn: expiresInMs / 1000,
37
+ });
38
+ const expiryDate = new Date(Date.now() + expiresInMs);
39
+ return {
40
+ statusCode: 302,
41
+ headers: {
42
+ location: "/",
43
+ },
44
+ cookies: [
45
+ `auth-token=${token}; HttpOnly; SameSite=None; Secure; Path=/; Expires=${expiryDate}`,
46
+ ],
47
+ };
48
+ },
49
+ }),
50
+ },
51
+ }));
52
+ function addRequiredContext(handler) {
53
+ return async function (...args) {
54
+ const [event] = args;
55
+ // Used by AuthHandler to create callback url sent to oidc server
56
+ event.requestContext.domainName = event.headers["x-forwarded-host"];
57
+ return await handler(...args);
58
+ };
59
+ }
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAuC1D,OAAO,CAAC,eAAe;IAgBvB,OAAO,CAAC,sBAAsB;IA8B9B,OAAO,CAAC,2BAA2B;IAsBnC,OAAO,CAAC,yCAAyC;IAgBjD,OAAO,CAAC,sBAAsB;CAkF/B"}
@@ -0,0 +1,188 @@
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.convertToCloudFrontFunctionCompatibleCode(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
+ }`, { minify: true })),
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
+ // When typescript builds the make-it-so code including "auth-check-handler-body.ts" it will add "export {}" to
117
+ // the end of the file if it's not already a module. This will cause a syntax error in CloudFront Functions so we
118
+ // remove it here.
119
+ .replace(/export {};\s*$/g, ""));
120
+ }
121
+ convertToCloudFrontFunctionCompatibleCode(sourceCode, esbuildOptions) {
122
+ // ESBuild doesn't currently support transforming const/let to var, which is required for CloudFront Functions
123
+ // JS runtime 1.0.
124
+ sourceCode = sourceCode
125
+ .replaceAll(/const /g, "var ")
126
+ .replaceAll(/let /g, "var ");
127
+ return transformSync(sourceCode, {
128
+ target: "es5",
129
+ ...esbuildOptions,
130
+ }).code;
131
+ }
132
+ // Get the behavior options for the auth route
133
+ getAuthBehaviorOptions(scope, jwtSecret, prefix) {
134
+ const authRouteFunction = new SST.Function(scope, `${this.id}AuthRouteFunction`, {
135
+ runtime: "nodejs20.x",
136
+ handler: path.join(import.meta.dirname, "auth-route.handler"),
137
+ environment: {
138
+ OIDC_ISSUER_URL: this.oidcIssuerUrl,
139
+ OIDC_CLIENT_ID: this.oidcClientId,
140
+ OIDC_SCOPE: this.oidcScope,
141
+ JWT_SECRET: jwtSecret.secretValue.toString(),
142
+ },
143
+ });
144
+ // authRouteFunction uses SST's AuthHandler construct which is normally run inside a lambda that's
145
+ // created by SST's Auth construct. AuthHandler expects certain environment variables to be set
146
+ // by the Auth construct so we have to set them ourselves here to keep it happy.
147
+ const envVarName = SSTInternalConfig.envFor({
148
+ type: "Auth",
149
+ id: "id", // It seems like the env var will still be found no matter what this value is
150
+ prop: "prefix",
151
+ });
152
+ authRouteFunction.addEnvironment(envVarName, prefix);
153
+ const authRouteFunctionUrl = authRouteFunction.addFunctionUrl({
154
+ authType: Lambda.FunctionUrlAuthType.NONE,
155
+ });
156
+ const forwardHostHeaderCfFunction = new CloudFront.Function(scope, `${this.id}ForwardHostHeaderFunction`, {
157
+ code: CloudFront.FunctionCode.fromInline(this.convertToCloudFrontFunctionCompatibleCode(`function handler(event) {
158
+ const request = event.request;
159
+ request.headers["x-forwarded-host"] = { value: request.headers.host.value };
160
+ return request;
161
+ }`, { minify: true })),
162
+ runtime: CloudFront.FunctionRuntime.JS_2_0,
163
+ });
164
+ return {
165
+ origin: new CloudFrontOrigins.HttpOrigin(CDK.Fn.parseDomainName(authRouteFunctionUrl.url)),
166
+ allowedMethods: CloudFront.AllowedMethods.ALLOW_ALL,
167
+ cachePolicy: new CloudFront.CachePolicy(scope, `${this.id}AllowAllCookiesPolicy`, {
168
+ cachePolicyName: `${this.id}-AllowAllCookiesPolicy`,
169
+ comment: "Cache policy that forwards all cookies",
170
+ defaultTtl: CDK.Duration.seconds(1),
171
+ minTtl: CDK.Duration.seconds(1),
172
+ maxTtl: CDK.Duration.seconds(1),
173
+ cookieBehavior: CloudFront.CacheCookieBehavior.all(),
174
+ headerBehavior: CloudFront.CacheHeaderBehavior.allowList("X-Forwarded-Host"),
175
+ queryStringBehavior: CloudFront.CacheQueryStringBehavior.all(),
176
+ enableAcceptEncodingGzip: true,
177
+ enableAcceptEncodingBrotli: true,
178
+ }),
179
+ viewerProtocolPolicy: CloudFront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
180
+ functionAssociations: [
181
+ {
182
+ function: forwardHostHeaderCfFunction,
183
+ eventType: CloudFront.FunctionEventType.VIEWER_REQUEST,
184
+ },
185
+ ],
186
+ };
187
+ }
188
+ }
@@ -0,0 +1,11 @@
1
+ export * from "./IxVpcDetails.js";
2
+ export * from "./IxCertificate.js";
3
+ export * from "./IxDnsRecord.js";
4
+ export * from "./IxSESIdentity.js";
5
+ export * from "./IxNextjsSite.js";
6
+ export * from "./IxStaticSite.js";
7
+ export * from "./IxElasticache.js";
8
+ export * from "./IxApi.js";
9
+ export * from "./IxQuicksightWorkspace.js";
10
+ export * from "./SiteOidcAuth/index.js";
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cdk-constructs/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,YAAY,CAAC;AAC3B,cAAc,4BAA4B,CAAC;AAC3C,cAAc,yBAAyB,CAAC"}
@@ -0,0 +1,10 @@
1
+ export * from "./IxVpcDetails.js";
2
+ export * from "./IxCertificate.js";
3
+ export * from "./IxDnsRecord.js";
4
+ export * from "./IxSESIdentity.js";
5
+ export * from "./IxNextjsSite.js";
6
+ export * from "./IxStaticSite.js";
7
+ export * from "./IxElasticache.js";
8
+ export * from "./IxApi.js";
9
+ export * from "./IxQuicksightWorkspace.js";
10
+ export * from "./SiteOidcAuth/index.js";