@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.
- package/.editorconfig +16 -0
- package/LICENSE +21 -0
- package/README.md +377 -0
- package/commitlint.config.ts +14 -0
- package/dist/cdk-constructs/IxApi.d.ts +12 -0
- package/dist/cdk-constructs/IxApi.d.ts.map +1 -0
- package/dist/cdk-constructs/IxApi.js +56 -0
- package/dist/cdk-constructs/IxBucket.d.ts +9 -0
- package/dist/cdk-constructs/IxBucket.d.ts.map +1 -0
- package/dist/cdk-constructs/IxBucket.js +22 -0
- package/dist/cdk-constructs/IxCertificate.d.ts +16 -0
- package/dist/cdk-constructs/IxCertificate.d.ts.map +1 -0
- package/dist/cdk-constructs/IxCertificate.js +26 -0
- package/dist/cdk-constructs/IxDnsRecord.d.ts +23 -0
- package/dist/cdk-constructs/IxDnsRecord.d.ts.map +1 -0
- package/dist/cdk-constructs/IxDnsRecord.js +43 -0
- package/dist/cdk-constructs/IxElasticache.d.ts +17 -0
- package/dist/cdk-constructs/IxElasticache.d.ts.map +1 -0
- package/dist/cdk-constructs/IxElasticache.js +70 -0
- package/dist/cdk-constructs/IxNextjsSite.d.ts +16 -0
- package/dist/cdk-constructs/IxNextjsSite.d.ts.map +1 -0
- package/dist/cdk-constructs/IxNextjsSite.js +38 -0
- package/dist/cdk-constructs/IxQuicksightWorkspace.d.ts +17 -0
- package/dist/cdk-constructs/IxQuicksightWorkspace.d.ts.map +1 -0
- package/dist/cdk-constructs/IxQuicksightWorkspace.js +29 -0
- package/dist/cdk-constructs/IxSESIdentity.d.ts +12 -0
- package/dist/cdk-constructs/IxSESIdentity.d.ts.map +1 -0
- package/dist/cdk-constructs/IxSESIdentity.js +45 -0
- package/dist/cdk-constructs/IxStaticSite.d.ts +17 -0
- package/dist/cdk-constructs/IxStaticSite.d.ts.map +1 -0
- package/dist/cdk-constructs/IxStaticSite.js +38 -0
- package/dist/cdk-constructs/IxVpcDetails.d.ts +12 -0
- package/dist/cdk-constructs/IxVpcDetails.d.ts.map +1 -0
- package/dist/cdk-constructs/IxVpcDetails.js +26 -0
- package/dist/cdk-constructs/IxWebsiteRedirect.d.ts +35 -0
- package/dist/cdk-constructs/IxWebsiteRedirect.d.ts.map +1 -0
- package/dist/cdk-constructs/IxWebsiteRedirect.js +72 -0
- package/dist/cdk-constructs/SiteOidcAuth/auth-check-handler-body.d.ts +2 -0
- package/dist/cdk-constructs/SiteOidcAuth/auth-check-handler-body.d.ts.map +1 -0
- package/dist/cdk-constructs/SiteOidcAuth/auth-check-handler-body.js +130 -0
- package/dist/cdk-constructs/SiteOidcAuth/auth-route.d.ts +2 -0
- package/dist/cdk-constructs/SiteOidcAuth/auth-route.d.ts.map +1 -0
- package/dist/cdk-constructs/SiteOidcAuth/auth-route.js +59 -0
- package/dist/cdk-constructs/SiteOidcAuth/index.d.ts +197 -0
- package/dist/cdk-constructs/SiteOidcAuth/index.d.ts.map +1 -0
- package/dist/cdk-constructs/SiteOidcAuth/index.js +188 -0
- package/dist/cdk-constructs/index.d.ts +11 -0
- package/dist/cdk-constructs/index.d.ts.map +1 -0
- package/dist/cdk-constructs/index.js +10 -0
- package/dist/deployConfig.d.ts +72 -0
- package/dist/deployConfig.d.ts.map +1 -0
- package/dist/deployConfig.js +78 -0
- package/dist/lib/auth/index.d.ts +2 -0
- package/dist/lib/auth/index.d.ts.map +1 -0
- package/dist/lib/auth/index.js +1 -0
- package/dist/lib/auth/oidc.d.ts +26 -0
- package/dist/lib/auth/oidc.d.ts.map +1 -0
- package/dist/lib/auth/oidc.js +48 -0
- package/dist/lib/proxy/fetch.d.ts +4 -0
- package/dist/lib/proxy/fetch.d.ts.map +1 -0
- package/dist/lib/proxy/fetch.js +31 -0
- package/dist/lib/proxy/index.d.ts +2 -0
- package/dist/lib/proxy/index.d.ts.map +1 -0
- package/dist/lib/proxy/index.js +1 -0
- package/dist/lib/site/support.d.ts +71 -0
- package/dist/lib/site/support.d.ts.map +1 -0
- package/dist/lib/site/support.js +262 -0
- package/dist/lib/utils/hash.d.ts +2 -0
- package/dist/lib/utils/hash.d.ts.map +1 -0
- package/dist/lib/utils/hash.js +13 -0
- package/dist/lib/utils/objects.d.ts +4 -0
- package/dist/lib/utils/objects.d.ts.map +1 -0
- package/dist/lib/utils/objects.js +7 -0
- package/eslint.config.js +11 -0
- package/package.json +66 -0
- package/src/cdk-constructs/IxApi.ts +81 -0
- package/src/cdk-constructs/IxBucket.ts +35 -0
- package/src/cdk-constructs/IxCertificate.ts +54 -0
- package/src/cdk-constructs/IxDnsRecord.ts +79 -0
- package/src/cdk-constructs/IxElasticache.ts +106 -0
- package/src/cdk-constructs/IxNextjsSite.ts +72 -0
- package/src/cdk-constructs/IxQuicksightWorkspace.ts +54 -0
- package/src/cdk-constructs/IxSESIdentity.ts +70 -0
- package/src/cdk-constructs/IxStaticSite.ts +69 -0
- package/src/cdk-constructs/IxVpcDetails.ts +38 -0
- package/src/cdk-constructs/IxWebsiteRedirect.ts +133 -0
- package/src/cdk-constructs/SiteOidcAuth/auth-check-handler-body.ts +168 -0
- package/src/cdk-constructs/SiteOidcAuth/auth-route.ts +71 -0
- package/src/cdk-constructs/SiteOidcAuth/index.ts +299 -0
- package/src/cdk-constructs/index.ts +10 -0
- package/src/deployConfig.ts +87 -0
- package/src/lib/auth/index.ts +1 -0
- package/src/lib/auth/oidc.ts +73 -0
- package/src/lib/proxy/fetch.ts +41 -0
- package/src/lib/proxy/index.ts +1 -0
- package/src/lib/site/support.ts +439 -0
- package/src/lib/utils/hash.ts +14 -0
- package/src/lib/utils/objects.ts +19 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,299 @@
|
|
|
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 { TransformOptions, transformSync } from "esbuild";
|
|
13
|
+
import type {
|
|
14
|
+
ExtendedNextjsSiteProps,
|
|
15
|
+
ExtendedStaticSiteProps,
|
|
16
|
+
} from "../../lib/site/support.js";
|
|
17
|
+
|
|
18
|
+
type ConstructScope = ConstructorParameters<typeof Construct>[0];
|
|
19
|
+
type ConstructId = ConstructorParameters<typeof Construct>[1];
|
|
20
|
+
|
|
21
|
+
export type Props = {
|
|
22
|
+
oidcIssuerUrl: string;
|
|
23
|
+
oidcClientId: string;
|
|
24
|
+
oidcScope: string;
|
|
25
|
+
};
|
|
26
|
+
export type AddToSiteProps = { prefix?: string };
|
|
27
|
+
|
|
28
|
+
const defaultAuthRoutePrefix = "/auth";
|
|
29
|
+
|
|
30
|
+
export class SiteOidcAuth extends Construct {
|
|
31
|
+
readonly oidcIssuerUrl: string;
|
|
32
|
+
readonly oidcClientId: string;
|
|
33
|
+
readonly oidcScope: string;
|
|
34
|
+
readonly id: string;
|
|
35
|
+
|
|
36
|
+
constructor(scope: ConstructScope, id: ConstructId, props: Props) {
|
|
37
|
+
super(scope, id);
|
|
38
|
+
this.oidcIssuerUrl = props.oidcIssuerUrl;
|
|
39
|
+
this.oidcClientId = props.oidcClientId;
|
|
40
|
+
this.oidcScope = props.oidcScope;
|
|
41
|
+
this.id = id;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
addToStaticSiteProps<SiteProps extends ExtendedStaticSiteProps>(
|
|
45
|
+
scope: ConstructScope,
|
|
46
|
+
siteProps: SiteProps,
|
|
47
|
+
{ prefix = defaultAuthRoutePrefix }: AddToSiteProps = {},
|
|
48
|
+
) {
|
|
49
|
+
prefix = prefix.replace(/\/$/, ""); // Remove trailing slash from prefix if it has one
|
|
50
|
+
const behaviourName = `${prefix.replace(/^\//g, "")}/*`;
|
|
51
|
+
const distribution = siteProps.cdk?.distribution;
|
|
52
|
+
if (isCDKConstruct(distribution)) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`CDK Construct for distribution is not supported when adding CloudFront OIDC Auth behavior for prefix ${prefix}`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const updatedSiteProps = {
|
|
59
|
+
...siteProps,
|
|
60
|
+
cdk: {
|
|
61
|
+
...siteProps.cdk,
|
|
62
|
+
distribution: {
|
|
63
|
+
...siteProps.cdk?.distribution,
|
|
64
|
+
additionalBehaviors: distribution?.additionalBehaviors ?? {},
|
|
65
|
+
defaultBehavior: {
|
|
66
|
+
...distribution?.defaultBehavior,
|
|
67
|
+
functionAssociations:
|
|
68
|
+
distribution?.defaultBehavior?.functionAssociations ?? [],
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
if (updatedSiteProps.cdk.distribution.additionalBehaviors[behaviourName]) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
`Behavior for prefix ${prefix} already exists in distribution definition`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const jwtSecret = this.createJwtSecret();
|
|
81
|
+
|
|
82
|
+
updatedSiteProps.cdk.distribution.defaultBehavior.functionAssociations.push(
|
|
83
|
+
this.getFunctionAssociation(scope, jwtSecret, prefix),
|
|
84
|
+
);
|
|
85
|
+
updatedSiteProps.cdk.distribution.additionalBehaviors[behaviourName] =
|
|
86
|
+
this.getAuthBehaviorOptions(scope, jwtSecret, prefix);
|
|
87
|
+
|
|
88
|
+
return updatedSiteProps;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
addToSsrSiteProps<SiteProps extends ExtendedNextjsSiteProps>(
|
|
92
|
+
scope: ConstructScope,
|
|
93
|
+
siteProps: SiteProps,
|
|
94
|
+
{ prefix = defaultAuthRoutePrefix }: AddToSiteProps = {},
|
|
95
|
+
) {
|
|
96
|
+
prefix = prefix.replace(/\/$/, ""); // Remove trailing slash from prefix if it has one
|
|
97
|
+
const behaviourName = `${prefix.replace(/^\//g, "")}/*`;
|
|
98
|
+
const updatedSiteProps = {
|
|
99
|
+
...siteProps,
|
|
100
|
+
cdk: {
|
|
101
|
+
...siteProps.cdk,
|
|
102
|
+
distribution: {
|
|
103
|
+
...siteProps.cdk?.distribution,
|
|
104
|
+
additionalBehaviors:
|
|
105
|
+
siteProps.cdk?.distribution?.additionalBehaviors ?? {},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
if (updatedSiteProps.cdk.distribution.additionalBehaviors[behaviourName]) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`Behavior for prefix ${prefix} already exists in distribution definition`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const jwtSecret = this.createJwtSecret();
|
|
116
|
+
|
|
117
|
+
updatedSiteProps.cdk.transform = (plan) => {
|
|
118
|
+
siteProps?.cdk?.transform?.(plan);
|
|
119
|
+
|
|
120
|
+
plan.cloudFrontFunctions?.serverCfFunction.injections.push(
|
|
121
|
+
this.convertToCloudFrontFunctionCompatibleCode(
|
|
122
|
+
this.getAuthCheckHandlerBodyCode(jwtSecret, prefix),
|
|
123
|
+
),
|
|
124
|
+
);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
updatedSiteProps.cdk.distribution.additionalBehaviors[behaviourName] =
|
|
128
|
+
this.getAuthBehaviorOptions(scope, jwtSecret, prefix);
|
|
129
|
+
|
|
130
|
+
return updatedSiteProps;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private createJwtSecret() {
|
|
134
|
+
return new SecretsManager.Secret(this, `${this.id}JwtSecret`, {
|
|
135
|
+
description: "JWT Signing Secret",
|
|
136
|
+
generateSecretString: {
|
|
137
|
+
passwordLength: 32,
|
|
138
|
+
excludePunctuation: true,
|
|
139
|
+
includeSpace: false,
|
|
140
|
+
requireEachIncludedType: true,
|
|
141
|
+
},
|
|
142
|
+
// Secret is only used for sessions so it's safe to delete on stack removal
|
|
143
|
+
removalPolicy: CDK.RemovalPolicy.DESTROY,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Get the CloudFront Function Association for auth checking
|
|
148
|
+
// Roughly based off https://github.com/sst/v2/blob/4283d706f251724308b397996ff307929bf3a976/packages/sst/src/constructs/SsrSite.ts#L941
|
|
149
|
+
private getFunctionAssociation(
|
|
150
|
+
scope: ConstructScope,
|
|
151
|
+
jwtSecret: SecretsManager.Secret,
|
|
152
|
+
authRoutePrefix: string,
|
|
153
|
+
): CloudFront.FunctionAssociation {
|
|
154
|
+
const authCheckFunction = new CloudFront.Function(
|
|
155
|
+
scope,
|
|
156
|
+
`${this.id}AuthCheckFunction`,
|
|
157
|
+
{
|
|
158
|
+
code: CloudFront.FunctionCode.fromInline(
|
|
159
|
+
this.convertToCloudFrontFunctionCompatibleCode(
|
|
160
|
+
`function handler(event) {
|
|
161
|
+
var request = event.request;
|
|
162
|
+
${this.getAuthCheckHandlerBodyCode(jwtSecret, authRoutePrefix)}
|
|
163
|
+
return request;
|
|
164
|
+
}`,
|
|
165
|
+
{ minify: true },
|
|
166
|
+
),
|
|
167
|
+
),
|
|
168
|
+
// We could specify the JS v2.0 runtime here but for SSR sites SST does the function creation and that currently
|
|
169
|
+
// uses JS v1.0 so no point using v2.0 here as the code has to be compatible with v1.0 anyway.
|
|
170
|
+
},
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
function: authCheckFunction,
|
|
175
|
+
eventType: CloudFront.FunctionEventType.VIEWER_REQUEST,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private getAuthCheckHandlerBodyCode(
|
|
180
|
+
jwtSecret: SecretsManager.Secret,
|
|
181
|
+
authRoutePrefix: string,
|
|
182
|
+
): string {
|
|
183
|
+
return (
|
|
184
|
+
fs
|
|
185
|
+
.readFileSync(
|
|
186
|
+
path.join(import.meta.dirname, "auth-check-handler-body.js"),
|
|
187
|
+
"utf8",
|
|
188
|
+
)
|
|
189
|
+
.replace(
|
|
190
|
+
"__placeholder-for-jwt-secret__",
|
|
191
|
+
jwtSecret.secretValue.toString(),
|
|
192
|
+
)
|
|
193
|
+
.replace("__placeholder-for-auth-route-prefix__", authRoutePrefix)
|
|
194
|
+
// When typescript builds the make-it-so code including "auth-check-handler-body.ts" it will add "export {}" to
|
|
195
|
+
// the end of the file if it's not already a module. This will cause a syntax error in CloudFront Functions so we
|
|
196
|
+
// remove it here.
|
|
197
|
+
.replace(/export {};\s*$/g, "")
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private convertToCloudFrontFunctionCompatibleCode(
|
|
202
|
+
sourceCode: string,
|
|
203
|
+
esbuildOptions?: TransformOptions,
|
|
204
|
+
): string {
|
|
205
|
+
// ESBuild doesn't currently support transforming const/let to var, which is required for CloudFront Functions
|
|
206
|
+
// JS runtime 1.0.
|
|
207
|
+
sourceCode = sourceCode
|
|
208
|
+
.replaceAll(/const /g, "var ")
|
|
209
|
+
.replaceAll(/let /g, "var ");
|
|
210
|
+
return transformSync(sourceCode, {
|
|
211
|
+
target: "es5",
|
|
212
|
+
...esbuildOptions,
|
|
213
|
+
}).code;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Get the behavior options for the auth route
|
|
217
|
+
private getAuthBehaviorOptions(
|
|
218
|
+
scope: ConstructScope,
|
|
219
|
+
jwtSecret: SecretsManager.Secret,
|
|
220
|
+
prefix: string,
|
|
221
|
+
): CloudFront.BehaviorOptions {
|
|
222
|
+
const authRouteFunction = new SST.Function(
|
|
223
|
+
scope,
|
|
224
|
+
`${this.id}AuthRouteFunction`,
|
|
225
|
+
{
|
|
226
|
+
runtime: "nodejs20.x",
|
|
227
|
+
handler: path.join(import.meta.dirname, "auth-route.handler"),
|
|
228
|
+
environment: {
|
|
229
|
+
OIDC_ISSUER_URL: this.oidcIssuerUrl,
|
|
230
|
+
OIDC_CLIENT_ID: this.oidcClientId,
|
|
231
|
+
OIDC_SCOPE: this.oidcScope,
|
|
232
|
+
JWT_SECRET: jwtSecret.secretValue.toString(),
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
// authRouteFunction uses SST's AuthHandler construct which is normally run inside a lambda that's
|
|
238
|
+
// created by SST's Auth construct. AuthHandler expects certain environment variables to be set
|
|
239
|
+
// by the Auth construct so we have to set them ourselves here to keep it happy.
|
|
240
|
+
const envVarName = SSTInternalConfig.envFor({
|
|
241
|
+
type: "Auth",
|
|
242
|
+
id: "id", // It seems like the env var will still be found no matter what this value is
|
|
243
|
+
prop: "prefix",
|
|
244
|
+
});
|
|
245
|
+
authRouteFunction.addEnvironment(envVarName, prefix);
|
|
246
|
+
|
|
247
|
+
const authRouteFunctionUrl = authRouteFunction.addFunctionUrl({
|
|
248
|
+
authType: Lambda.FunctionUrlAuthType.NONE,
|
|
249
|
+
});
|
|
250
|
+
const forwardHostHeaderCfFunction = new CloudFront.Function(
|
|
251
|
+
scope,
|
|
252
|
+
`${this.id}ForwardHostHeaderFunction`,
|
|
253
|
+
{
|
|
254
|
+
code: CloudFront.FunctionCode.fromInline(
|
|
255
|
+
this.convertToCloudFrontFunctionCompatibleCode(
|
|
256
|
+
`function handler(event) {
|
|
257
|
+
const request = event.request;
|
|
258
|
+
request.headers["x-forwarded-host"] = { value: request.headers.host.value };
|
|
259
|
+
return request;
|
|
260
|
+
}`,
|
|
261
|
+
{ minify: true },
|
|
262
|
+
),
|
|
263
|
+
),
|
|
264
|
+
runtime: CloudFront.FunctionRuntime.JS_2_0,
|
|
265
|
+
},
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
origin: new CloudFrontOrigins.HttpOrigin(
|
|
270
|
+
CDK.Fn.parseDomainName(authRouteFunctionUrl.url),
|
|
271
|
+
),
|
|
272
|
+
allowedMethods: CloudFront.AllowedMethods.ALLOW_ALL,
|
|
273
|
+
cachePolicy: new CloudFront.CachePolicy(
|
|
274
|
+
scope,
|
|
275
|
+
`${this.id}AllowAllCookiesPolicy`,
|
|
276
|
+
{
|
|
277
|
+
cachePolicyName: `${this.id}-AllowAllCookiesPolicy`,
|
|
278
|
+
comment: "Cache policy that forwards all cookies",
|
|
279
|
+
defaultTtl: CDK.Duration.seconds(1),
|
|
280
|
+
minTtl: CDK.Duration.seconds(1),
|
|
281
|
+
maxTtl: CDK.Duration.seconds(1),
|
|
282
|
+
cookieBehavior: CloudFront.CacheCookieBehavior.all(),
|
|
283
|
+
headerBehavior:
|
|
284
|
+
CloudFront.CacheHeaderBehavior.allowList("X-Forwarded-Host"),
|
|
285
|
+
queryStringBehavior: CloudFront.CacheQueryStringBehavior.all(),
|
|
286
|
+
enableAcceptEncodingGzip: true,
|
|
287
|
+
enableAcceptEncodingBrotli: true,
|
|
288
|
+
},
|
|
289
|
+
),
|
|
290
|
+
viewerProtocolPolicy: CloudFront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
|
|
291
|
+
functionAssociations: [
|
|
292
|
+
{
|
|
293
|
+
function: forwardHostHeaderCfFunction,
|
|
294
|
+
eventType: CloudFront.FunctionEventType.VIEWER_REQUEST,
|
|
295
|
+
},
|
|
296
|
+
],
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
}
|
|
@@ -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";
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
const getEnvVars = () =>
|
|
4
|
+
({
|
|
5
|
+
isIxDeploy: process.env.IX_DEPLOYMENT?.toLowerCase() === "true", // This needs to start as a bool for the discriminated union
|
|
6
|
+
appName: process.env.IX_APP_NAME ?? "",
|
|
7
|
+
environment: process.env.IX_ENVIRONMENT ?? "",
|
|
8
|
+
workloadGroup: process.env.IX_WORKLOAD_GROUP ?? "",
|
|
9
|
+
primaryAwsRegion: process.env.IX_PRIMARY_AWS_REGION ?? "",
|
|
10
|
+
siteDomains: process.env.IX_SITE_DOMAINS ?? "",
|
|
11
|
+
siteDomainAliases: process.env.IX_SITE_DOMAIN_ALIASES ?? "",
|
|
12
|
+
isInternalApp: process.env.IX_INTERNAL_APP ?? "",
|
|
13
|
+
deploymentType: process.env.IX_DEPLOYMENT_TYPE ?? "",
|
|
14
|
+
sourceCommitRef: process.env.IX_SOURCE_COMMIT_REF ?? "",
|
|
15
|
+
sourceCommitHash: process.env.IX_SOURCE_COMMIT_HASH ?? "",
|
|
16
|
+
deployTriggeredBy: process.env.IX_DEPLOY_TRIGGERED_BY ?? "",
|
|
17
|
+
smtpHost: process.env.SMTP_HOST ?? "",
|
|
18
|
+
smtpPort: process.env.SMTP_PORT ?? "",
|
|
19
|
+
clamAVUrl: process.env.CLAMAV_URL ?? "",
|
|
20
|
+
vpcHttpProxy: process.env.VPC_HTTP_PROXY ?? "",
|
|
21
|
+
}) satisfies Record<string, string | boolean>;
|
|
22
|
+
|
|
23
|
+
const ixDeployConfigSchema = z
|
|
24
|
+
.object({
|
|
25
|
+
isIxDeploy: z.literal(true),
|
|
26
|
+
appName: z.string().min(1),
|
|
27
|
+
environment: z.enum(["dev", "test", "uat", "prod"]),
|
|
28
|
+
workloadGroup: z.enum(["ds", "srs"]),
|
|
29
|
+
primaryAwsRegion: z.literal("ap-southeast-2"),
|
|
30
|
+
siteDomains: z
|
|
31
|
+
.string()
|
|
32
|
+
.transform((val) => val.split(",").map((domain) => domain.trim())),
|
|
33
|
+
siteDomainAliases: z
|
|
34
|
+
.string()
|
|
35
|
+
.transform((val) => val.split(",").map((domain) => domain.trim())),
|
|
36
|
+
isInternalApp: z.coerce.boolean(),
|
|
37
|
+
deploymentType: z.enum(["docker", "serverless"]),
|
|
38
|
+
sourceCommitRef: z.string().min(1),
|
|
39
|
+
sourceCommitHash: z.string().min(1),
|
|
40
|
+
deployTriggeredBy: z.string().min(1),
|
|
41
|
+
smtpHost: z.string().min(1),
|
|
42
|
+
smtpPort: z.coerce.number().int(),
|
|
43
|
+
clamAVUrl: z.string().url(),
|
|
44
|
+
vpcHttpProxy: z.string().url(),
|
|
45
|
+
} satisfies Record<keyof ReturnType<typeof getEnvVars>, unknown>)
|
|
46
|
+
.strip();
|
|
47
|
+
|
|
48
|
+
const nonIxDeployConfigSchema = z
|
|
49
|
+
.object({
|
|
50
|
+
isIxDeploy: z.literal(false),
|
|
51
|
+
appName: z.string(),
|
|
52
|
+
environment: z.string(),
|
|
53
|
+
workloadGroup: z.string(),
|
|
54
|
+
primaryAwsRegion: z.string(),
|
|
55
|
+
siteDomains: z
|
|
56
|
+
.string()
|
|
57
|
+
.transform((val) => val.split(",").map((domain) => domain.trim())),
|
|
58
|
+
siteDomainAliases: z
|
|
59
|
+
.string()
|
|
60
|
+
.transform((val) => val.split(",").map((domain) => domain.trim())),
|
|
61
|
+
isInternalApp: z
|
|
62
|
+
.string()
|
|
63
|
+
.transform((val) => (val ? val.toLowerCase() === "true" : undefined)),
|
|
64
|
+
deploymentType: z.string(),
|
|
65
|
+
sourceCommitRef: z.string(),
|
|
66
|
+
sourceCommitHash: z.string(),
|
|
67
|
+
deployTriggeredBy: z.string(),
|
|
68
|
+
smtpHost: z.string(),
|
|
69
|
+
smtpPort: z
|
|
70
|
+
.string()
|
|
71
|
+
.transform((val) =>
|
|
72
|
+
isNaN(parseInt(val, 10)) ? undefined : parseInt(val, 10),
|
|
73
|
+
),
|
|
74
|
+
clamAVUrl: z.string(),
|
|
75
|
+
vpcHttpProxy: z.string(),
|
|
76
|
+
} satisfies Record<keyof ReturnType<typeof getEnvVars>, unknown>)
|
|
77
|
+
.strip();
|
|
78
|
+
|
|
79
|
+
const schema = z.discriminatedUnion("isIxDeploy", [
|
|
80
|
+
ixDeployConfigSchema,
|
|
81
|
+
nonIxDeployConfigSchema,
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
export default schema.parse(getEnvVars());
|
|
85
|
+
|
|
86
|
+
// process.env values can change at runtime so we provide a way to re-parse the config as needed
|
|
87
|
+
export const getDeployConfig = () => schema.parse(getEnvVars());
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./oidc.js";
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Issuer } from "openid-client";
|
|
2
|
+
import { createRemoteJWKSet, JWTPayload, jwtVerify } from "jose";
|
|
3
|
+
|
|
4
|
+
type VerifyAccessTokenParams<SafeVerify extends boolean = false> = {
|
|
5
|
+
token: string;
|
|
6
|
+
issuerUrl: string;
|
|
7
|
+
audience: string;
|
|
8
|
+
safeVerify?: SafeVerify;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Checks an OIDC access token against the issuer's details to determine if it's valid.
|
|
13
|
+
*
|
|
14
|
+
* @param params - The parameters for verifying the access token.
|
|
15
|
+
* @param params.token - The JWT access token to verify.
|
|
16
|
+
* @param params.issuerUrl - The OIDC issuer URL to discover JWKS and metadata.
|
|
17
|
+
* @param params.audience - The expected audience value to match against the token's claims.
|
|
18
|
+
* @param params.safeVerify - If true, returns a result object with error and payload fields instead of throwing on error.
|
|
19
|
+
* @returns If `safeVerify` is true, returns an object with either the verified payload or an error. Otherwise, returns the verified JWT payload or throws an error.
|
|
20
|
+
*/
|
|
21
|
+
export async function verifyAccessToken<SafeVerify extends boolean = false>({
|
|
22
|
+
token,
|
|
23
|
+
issuerUrl,
|
|
24
|
+
audience,
|
|
25
|
+
safeVerify,
|
|
26
|
+
}: VerifyAccessTokenParams<SafeVerify>): Promise<
|
|
27
|
+
SafeVerify extends true
|
|
28
|
+
?
|
|
29
|
+
| { error: Error | unknown; payload: null }
|
|
30
|
+
| { error: null; payload: JWTPayload }
|
|
31
|
+
: JWTPayload
|
|
32
|
+
> {
|
|
33
|
+
try {
|
|
34
|
+
const issuer = await Issuer.discover(issuerUrl);
|
|
35
|
+
const jwksUri = issuer.metadata.jwks_uri;
|
|
36
|
+
if (!jwksUri) {
|
|
37
|
+
throw new Error("JWKS URI not found in issuer metadata");
|
|
38
|
+
}
|
|
39
|
+
const JWKS = createRemoteJWKSet(new URL(jwksUri));
|
|
40
|
+
|
|
41
|
+
// Verify the signature and basic claims
|
|
42
|
+
const { payload } = await jwtVerify(token, JWKS, {
|
|
43
|
+
issuer: issuer.metadata.issuer,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const tokenAud = payload.aud ?? payload.client_id;
|
|
47
|
+
let audienceMatches = false;
|
|
48
|
+
for (const aud of Array.isArray(tokenAud) ? tokenAud : [tokenAud]) {
|
|
49
|
+
if (aud === audience) {
|
|
50
|
+
audienceMatches = true;
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (!audienceMatches) {
|
|
55
|
+
console.info("Token data:", payload);
|
|
56
|
+
throw new Error(
|
|
57
|
+
`Token audience does not match expected audience ${audience}`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (safeVerify) {
|
|
62
|
+
return { payload, error: null };
|
|
63
|
+
}
|
|
64
|
+
return payload as SafeVerify extends true
|
|
65
|
+
? { error: null; payload: JWTPayload }
|
|
66
|
+
: JWTPayload;
|
|
67
|
+
} catch (err) {
|
|
68
|
+
if (safeVerify) {
|
|
69
|
+
return { error: err, payload: null };
|
|
70
|
+
}
|
|
71
|
+
throw err;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
setGlobalDispatcher,
|
|
3
|
+
getGlobalDispatcher,
|
|
4
|
+
EnvHttpProxyAgent,
|
|
5
|
+
fetch as undiciFetch,
|
|
6
|
+
} from "undici";
|
|
7
|
+
import { bootstrap } from "global-agent";
|
|
8
|
+
|
|
9
|
+
export function setupProxyGlobally() {
|
|
10
|
+
// Make operation idempotent
|
|
11
|
+
if (getGlobalDispatcher() instanceof EnvHttpProxyAgent) return;
|
|
12
|
+
|
|
13
|
+
if (!process.env.HTTP_PROXY || !process.env.HTTPS_PROXY) return;
|
|
14
|
+
|
|
15
|
+
// To cover libraries that use fetch
|
|
16
|
+
// See https://nodejs.org/api/globals.html#custom-dispatcher
|
|
17
|
+
// This might stop being needed at some point: https://github.com/actions/create-github-app-token/pull/143#discussion_r1747641337
|
|
18
|
+
const envHttpProxyAgent = new EnvHttpProxyAgent();
|
|
19
|
+
setGlobalDispatcher(envHttpProxyAgent);
|
|
20
|
+
|
|
21
|
+
// To cover libraries that use the http/https object
|
|
22
|
+
if (!process.env.GLOBAL_AGENT_HTTP_PROXY) {
|
|
23
|
+
process.env.GLOBAL_AGENT_HTTP_PROXY = process.env.HTTP_PROXY;
|
|
24
|
+
process.env.GLOBAL_AGENT_HTTPS_PROXY =
|
|
25
|
+
process.env.HTTPS_PROXY ?? process.env.HTTP_PROXY;
|
|
26
|
+
}
|
|
27
|
+
bootstrap();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function getProxiedFetch() {
|
|
31
|
+
const fetch: typeof undiciFetch = (input, init = {}) => {
|
|
32
|
+
if (init.dispatcher) {
|
|
33
|
+
console.warn(
|
|
34
|
+
"A custom dispatcher was provided to fetch but this is ignored as a proxy agent is being used.",
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
const envHttpProxyAgent = new EnvHttpProxyAgent();
|
|
38
|
+
return undiciFetch(input, { ...init, dispatcher: envHttpProxyAgent });
|
|
39
|
+
};
|
|
40
|
+
return fetch;
|
|
41
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./fetch.js";
|