@fishawack/lab-env 5.2.0 → 5.3.0-beta.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/CHANGELOG.md +20 -0
- package/commands/create/cmds/dekey.js +5 -0
- package/commands/create/cmds/key.js +5 -0
- package/commands/create/cmds/provision.js +195 -14
- package/commands/create/libs/aws-cloudfront-request.js +156 -0
- package/commands/create/libs/aws-cloudfront-response.js +19 -8
- package/commands/create/services/aws/index.js +12 -5
- package/globals.js +2 -2
- package/package.json +1 -1
- package/python/0/Dockerfile +1 -1
- package/commands/create/libs/aws-cloudfront-auth.js +0 -93
- package/commands/create/libs/aws-cloudfront-simple.js +0 -61
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
## Changelog
|
|
2
2
|
|
|
3
|
+
### 5.3.0-beta.1 (2026-02-04)
|
|
4
|
+
|
|
5
|
+
#### Features
|
|
6
|
+
|
|
7
|
+
* can now customize headers for static provisions ([a3bc776](https://bitbucket.org/fishawackdigital/lab-env/commits/a3bc776c6acca3555de4ef5667ffdf9f8b37a518))
|
|
8
|
+
* can now specify redirects for use in cloudfront functions ([67706d7](https://bitbucket.org/fishawackdigital/lab-env/commits/67706d76300fc6743f7a5ceb9bf31748cf8acb31))
|
|
9
|
+
|
|
10
|
+
#### Bug Fixes
|
|
11
|
+
|
|
12
|
+
* deprov now correctly removes the response function for static sites ([69fbc2b](https://bitbucket.org/fishawackdigital/lab-env/commits/69fbc2b8f60898d3d33416328752897dff6f752b))
|
|
13
|
+
* prompt if redirects are needed first ([e087bf0](https://bitbucket.org/fishawackdigital/lab-env/commits/e087bf0f6d86c543f31d9f0c401793222f8f0d9a))
|
|
14
|
+
* set aws client on key command ([3b15512](https://bitbucket.org/fishawackdigital/lab-env/commits/3b15512b168d4ba35871047fd0ea9230cf394804))
|
|
15
|
+
|
|
16
|
+
### 5.2.1-beta.1 (2026-01-26)
|
|
17
|
+
|
|
18
|
+
#### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* issue with lab-env calling lab-env and concatanating repo env variable ([03313c3](https://bitbucket.org/fishawackdigital/lab-env/commits/03313c3c43c793222620170c381af05ec4b1c348))
|
|
21
|
+
* use correct prod base image on python images ([db0ace4](https://bitbucket.org/fishawackdigital/lab-env/commits/db0ace419e8c635c727aa33330a3156c0956af0b))
|
|
22
|
+
|
|
3
23
|
### 5.2.0 (2026-01-17)
|
|
4
24
|
|
|
5
25
|
#### Features
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
const _ = require("../../../globals.js");
|
|
2
2
|
const inquirer = require("inquirer");
|
|
3
3
|
const aws = require("../services/aws/index.js");
|
|
4
|
+
const {
|
|
5
|
+
setAWSClientDefaults,
|
|
6
|
+
} = require("../../../commands/create/services/aws/misc.js");
|
|
4
7
|
|
|
5
8
|
module.exports = [
|
|
6
9
|
"dekey",
|
|
@@ -63,6 +66,8 @@ module.exports = [
|
|
|
63
66
|
for (let i = 0; i < clients.length; i++) {
|
|
64
67
|
let client = clients[i];
|
|
65
68
|
|
|
69
|
+
setAWSClientDefaults(client);
|
|
70
|
+
|
|
66
71
|
for (let j = 0; j < users.length; j++) {
|
|
67
72
|
let user = users[j];
|
|
68
73
|
|
|
@@ -7,6 +7,9 @@ const htmlEmail = require("fs").readFileSync(
|
|
|
7
7
|
`${__dirname}/../templates/credentials.html`,
|
|
8
8
|
{ encoding: "utf8" },
|
|
9
9
|
);
|
|
10
|
+
const {
|
|
11
|
+
setAWSClientDefaults,
|
|
12
|
+
} = require("../../../commands/create/services/aws/misc.js");
|
|
10
13
|
|
|
11
14
|
module.exports = [
|
|
12
15
|
"key",
|
|
@@ -83,6 +86,8 @@ module.exports = [
|
|
|
83
86
|
for (let i = 0; i < clients.length; i++) {
|
|
84
87
|
let client = clients[i];
|
|
85
88
|
|
|
89
|
+
setAWSClientDefaults(client);
|
|
90
|
+
|
|
86
91
|
for (let j = 0; j < users.length; j++) {
|
|
87
92
|
let user = users[j];
|
|
88
93
|
|
|
@@ -32,12 +32,169 @@ module.exports = [
|
|
|
32
32
|
},
|
|
33
33
|
]);
|
|
34
34
|
|
|
35
|
+
// Check for existing redirects in config
|
|
36
|
+
let redirects = _.coreConfig?.attributes?.deploy?.redirects || [];
|
|
37
|
+
|
|
35
38
|
if (!answer.check) {
|
|
36
39
|
process.exit(1);
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
let answers = await inquirer.prompt([stack]);
|
|
40
43
|
|
|
44
|
+
// Prompt for redirects if static stack and no existing redirects
|
|
45
|
+
if (answers.stack === "static" && redirects.length === 0) {
|
|
46
|
+
let wantsRedirects = await inquirer.prompt([
|
|
47
|
+
{
|
|
48
|
+
type: "confirm",
|
|
49
|
+
name: "setup",
|
|
50
|
+
message: "Setup redirects?",
|
|
51
|
+
default: false,
|
|
52
|
+
},
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
if (wantsRedirects.setup) {
|
|
56
|
+
let addRedirect = { continue: true };
|
|
57
|
+
|
|
58
|
+
while (addRedirect.continue) {
|
|
59
|
+
addRedirect = await inquirer.prompt([
|
|
60
|
+
{
|
|
61
|
+
type: "input",
|
|
62
|
+
name: "src",
|
|
63
|
+
message:
|
|
64
|
+
"Redirect source path (e.g., /old-path or /news/*)",
|
|
65
|
+
validate: (input) => {
|
|
66
|
+
if (!input || !input.startsWith("/")) {
|
|
67
|
+
return "Source must start with /";
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
type: "input",
|
|
74
|
+
name: "dest",
|
|
75
|
+
message:
|
|
76
|
+
"Redirect destination (e.g., /new-path or https://example.com/news/*)",
|
|
77
|
+
validate: (input) => !!input.length,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
type: "confirm",
|
|
81
|
+
name: "continue",
|
|
82
|
+
message: "Add another redirect?",
|
|
83
|
+
default: true,
|
|
84
|
+
},
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
if (addRedirect.src && addRedirect.dest) {
|
|
88
|
+
redirects.push({
|
|
89
|
+
src: addRedirect.src,
|
|
90
|
+
dest: addRedirect.dest,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Prompt for custom headers if static stack and no existing headers
|
|
98
|
+
let headers = _.coreConfig?.attributes?.deploy?.headers || [];
|
|
99
|
+
|
|
100
|
+
if (answers.stack === "static" && headers.length === 0) {
|
|
101
|
+
let customHeaders = await inquirer.prompt([
|
|
102
|
+
{
|
|
103
|
+
type: "confirm",
|
|
104
|
+
name: "customize",
|
|
105
|
+
message: "Customize response headers?",
|
|
106
|
+
default: false,
|
|
107
|
+
},
|
|
108
|
+
]);
|
|
109
|
+
|
|
110
|
+
if (customHeaders.customize) {
|
|
111
|
+
// Prompt for standard headers with defaults
|
|
112
|
+
let standardHeaders = await inquirer.prompt([
|
|
113
|
+
{
|
|
114
|
+
type: "input",
|
|
115
|
+
name: "strict-transport-security",
|
|
116
|
+
message: "strict-transport-security header value:",
|
|
117
|
+
default: "max-age=31536000; includeSubDomains",
|
|
118
|
+
validate: (input) => !!input.length,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
type: "input",
|
|
122
|
+
name: "content-security-policy",
|
|
123
|
+
message: "content-security-policy header value:",
|
|
124
|
+
default:
|
|
125
|
+
"default-src 'self' https: data: 'unsafe-inline';",
|
|
126
|
+
validate: (input) => !!input.length,
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
type: "input",
|
|
130
|
+
name: "x-content-type-options",
|
|
131
|
+
message: "x-content-type-options header value:",
|
|
132
|
+
default: "nosniff",
|
|
133
|
+
validate: (input) => !!input.length,
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
type: "input",
|
|
137
|
+
name: "x-frame-options",
|
|
138
|
+
message: "x-frame-options header value:",
|
|
139
|
+
default: "sameorigin",
|
|
140
|
+
validate: (input) => !!input.length,
|
|
141
|
+
},
|
|
142
|
+
]);
|
|
143
|
+
|
|
144
|
+
// Add standard headers to array
|
|
145
|
+
Object.keys(standardHeaders).forEach((name) => {
|
|
146
|
+
headers.push({
|
|
147
|
+
name: name,
|
|
148
|
+
value: standardHeaders[name],
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Prompt for additional custom headers
|
|
153
|
+
let wantsCustomHeaders = await inquirer.prompt([
|
|
154
|
+
{
|
|
155
|
+
type: "confirm",
|
|
156
|
+
name: "add",
|
|
157
|
+
message: "Add custom headers?",
|
|
158
|
+
default: false,
|
|
159
|
+
},
|
|
160
|
+
]);
|
|
161
|
+
|
|
162
|
+
if (wantsCustomHeaders.add) {
|
|
163
|
+
let addHeader = { continue: true };
|
|
164
|
+
|
|
165
|
+
while (addHeader.continue) {
|
|
166
|
+
addHeader = await inquirer.prompt([
|
|
167
|
+
{
|
|
168
|
+
type: "input",
|
|
169
|
+
name: "name",
|
|
170
|
+
message: "Custom header name:",
|
|
171
|
+
validate: (input) => !!input.length,
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
type: "input",
|
|
175
|
+
name: "value",
|
|
176
|
+
message: "Custom header value:",
|
|
177
|
+
validate: (input) => !!input.length,
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
type: "confirm",
|
|
181
|
+
name: "continue",
|
|
182
|
+
message: "Add another custom header?",
|
|
183
|
+
default: false,
|
|
184
|
+
},
|
|
185
|
+
]);
|
|
186
|
+
|
|
187
|
+
if (addHeader.name && addHeader.value) {
|
|
188
|
+
headers.push({
|
|
189
|
+
name: addHeader.name,
|
|
190
|
+
value: addHeader.value,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
41
198
|
if (answers.stack === "fullstack") {
|
|
42
199
|
answers = {
|
|
43
200
|
...answers,
|
|
@@ -135,20 +292,36 @@ module.exports = [
|
|
|
135
292
|
setAWSClientDefaults(answers.client, answers.region);
|
|
136
293
|
|
|
137
294
|
try {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
295
|
+
if (answers.stack === "static") {
|
|
296
|
+
infastructure = await aws.static(
|
|
297
|
+
slug,
|
|
298
|
+
[
|
|
299
|
+
{ Key: "repository", Value: _.repo },
|
|
300
|
+
{ Key: "environment", Value: branch },
|
|
301
|
+
{ Key: "automated", Value: true },
|
|
302
|
+
],
|
|
303
|
+
credentials,
|
|
304
|
+
_.repoSafe,
|
|
305
|
+
branch,
|
|
306
|
+
redirects,
|
|
307
|
+
headers,
|
|
308
|
+
);
|
|
309
|
+
} else {
|
|
310
|
+
infastructure = await aws.fullstack(
|
|
311
|
+
slug,
|
|
312
|
+
[
|
|
313
|
+
{ Key: "repository", Value: _.repo },
|
|
314
|
+
{ Key: "environment", Value: branch },
|
|
315
|
+
{ Key: "automated", Value: true },
|
|
316
|
+
],
|
|
317
|
+
credentials,
|
|
318
|
+
_.repoSafe,
|
|
319
|
+
branch,
|
|
320
|
+
answers.framework,
|
|
321
|
+
answers.availability,
|
|
322
|
+
answers.database,
|
|
323
|
+
);
|
|
324
|
+
}
|
|
152
325
|
} catch (e) {
|
|
153
326
|
console.log(e.message);
|
|
154
327
|
process.exit(1);
|
|
@@ -175,6 +348,14 @@ module.exports = [
|
|
|
175
348
|
config[branch].deploy.users = credentials;
|
|
176
349
|
}
|
|
177
350
|
|
|
351
|
+
if (redirects.length) {
|
|
352
|
+
config[branch].deploy.redirects = redirects;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (headers.length) {
|
|
356
|
+
config[branch].deploy.headers = headers;
|
|
357
|
+
}
|
|
358
|
+
|
|
178
359
|
let stringify = JSON.stringify(config, null, 4);
|
|
179
360
|
let output = stringify.substring(1, stringify.length - 1).trim();
|
|
180
361
|
execSync(`printf '${output}' | pbcopy`);
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// eslint-disable-next-line no-unused-vars
|
|
2
|
+
function handler(event) {
|
|
3
|
+
var query, index, key, response;
|
|
4
|
+
|
|
5
|
+
// Redirect if www to non-www
|
|
6
|
+
if (event.request.headers.host.value.includes("www.")) {
|
|
7
|
+
query = "";
|
|
8
|
+
index = 0;
|
|
9
|
+
|
|
10
|
+
for (key in event.request.querystring) {
|
|
11
|
+
query += `${index ? "&" : "?"}${key}=${event.request.querystring[key].value}`;
|
|
12
|
+
index++;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
response = {
|
|
16
|
+
statusCode: 301,
|
|
17
|
+
statusDescription: "Moved Permanently",
|
|
18
|
+
headers: {
|
|
19
|
+
location: {
|
|
20
|
+
value: `https://${event.request.headers.host.value.split("www.")[1]}${event.request.uri}${query}`,
|
|
21
|
+
},
|
|
22
|
+
"strict-transport-security": {
|
|
23
|
+
value: "max-age=31536000; includeSubDomains",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return response;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Check authentication if credentials are configured
|
|
32
|
+
var expected = "<%= credentials %>";
|
|
33
|
+
|
|
34
|
+
if (expected && expected.length > 0) {
|
|
35
|
+
var authHeaders = event.request.headers.authorization;
|
|
36
|
+
|
|
37
|
+
// If no auth header or credentials don't match, return 401
|
|
38
|
+
if (!authHeaders || !expected.find((d) => d === authHeaders.value)) {
|
|
39
|
+
response = {
|
|
40
|
+
statusCode: 401,
|
|
41
|
+
statusDescription: "Unauthorized",
|
|
42
|
+
headers: {
|
|
43
|
+
"www-authenticate": { value: "Basic" },
|
|
44
|
+
"strict-transport-security": {
|
|
45
|
+
value: "max-age=31536000; includeSubDomains",
|
|
46
|
+
},
|
|
47
|
+
"content-security-policy": {
|
|
48
|
+
value: "default-src 'self' https: data: 'unsafe-inline';",
|
|
49
|
+
},
|
|
50
|
+
"x-content-type-options": { value: "nosniff" },
|
|
51
|
+
"x-frame-options": { value: "sameorigin" },
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return response;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Redirect if no trailing slash (BEFORE custom redirects)
|
|
60
|
+
if (!event.request.uri.includes(".") && !event.request.uri.endsWith("/")) {
|
|
61
|
+
query = "";
|
|
62
|
+
index = 0;
|
|
63
|
+
|
|
64
|
+
for (key in event.request.querystring) {
|
|
65
|
+
query += `${index ? "&" : "?"}${key}=${event.request.querystring[key].value}`;
|
|
66
|
+
index++;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
statusCode: 301,
|
|
71
|
+
statusDescription: "Moved Permanently",
|
|
72
|
+
headers: { location: { value: `${event.request.uri}/${query}` } },
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Check for custom redirects (URI is guaranteed to have trailing slash now)
|
|
77
|
+
var customRedirects = "<%= redirects %>";
|
|
78
|
+
var currentUri = event.request.uri;
|
|
79
|
+
var i, redirect, srcPattern, captured, destination;
|
|
80
|
+
|
|
81
|
+
for (i = 0; i < customRedirects.length; i++) {
|
|
82
|
+
redirect = customRedirects[i];
|
|
83
|
+
|
|
84
|
+
// Handle wildcard redirects
|
|
85
|
+
if (redirect.src.endsWith("/*")) {
|
|
86
|
+
srcPattern = redirect.src.slice(0, -2);
|
|
87
|
+
if (
|
|
88
|
+
currentUri.startsWith(srcPattern + "/") ||
|
|
89
|
+
currentUri === srcPattern
|
|
90
|
+
) {
|
|
91
|
+
captured = currentUri.slice(srcPattern.length);
|
|
92
|
+
destination = redirect.dest;
|
|
93
|
+
|
|
94
|
+
// Replace wildcard in destination with captured path
|
|
95
|
+
if (destination.endsWith("/*")) {
|
|
96
|
+
destination = destination.slice(0, -2) + captured;
|
|
97
|
+
} else {
|
|
98
|
+
destination = destination + captured;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Handle relative vs absolute destinations
|
|
102
|
+
if (!destination.startsWith("http")) {
|
|
103
|
+
destination =
|
|
104
|
+
"https://" +
|
|
105
|
+
event.request.headers.host.value +
|
|
106
|
+
destination;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
statusCode: 301,
|
|
111
|
+
statusDescription: "Moved Permanently",
|
|
112
|
+
headers: { location: { value: destination } },
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Handle exact match redirects (normalize src to have trailing slash)
|
|
117
|
+
else {
|
|
118
|
+
var normalizedSrc = redirect.src.endsWith("/")
|
|
119
|
+
? redirect.src
|
|
120
|
+
: redirect.src + "/";
|
|
121
|
+
|
|
122
|
+
if (normalizedSrc === currentUri) {
|
|
123
|
+
destination = redirect.dest;
|
|
124
|
+
|
|
125
|
+
// Handle relative vs absolute destinations
|
|
126
|
+
if (!destination.startsWith("http")) {
|
|
127
|
+
destination =
|
|
128
|
+
"https://" +
|
|
129
|
+
event.request.headers.host.value +
|
|
130
|
+
destination;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
statusCode: 301,
|
|
135
|
+
statusDescription: "Moved Permanently",
|
|
136
|
+
headers: { location: { value: destination } },
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Rewrite url to append index.html if not present
|
|
143
|
+
var request = event.request;
|
|
144
|
+
var uri = request.uri;
|
|
145
|
+
|
|
146
|
+
// Check whether the URI is missing a file name.
|
|
147
|
+
if (uri.endsWith("/")) {
|
|
148
|
+
request.uri += "index.html";
|
|
149
|
+
}
|
|
150
|
+
// Check whether the URI is missing a file extension.
|
|
151
|
+
else if (!uri.includes(".")) {
|
|
152
|
+
request.uri += "/index.html";
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return request;
|
|
156
|
+
}
|
|
@@ -3,15 +3,26 @@ function handler(event) {
|
|
|
3
3
|
// Add security headers
|
|
4
4
|
var response = event.response;
|
|
5
5
|
var headers = response.headers;
|
|
6
|
+
var customHeaders = "<%= headers %>";
|
|
7
|
+
var i, header;
|
|
6
8
|
|
|
7
|
-
headers
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
// If custom headers provided, use them; otherwise use defaults
|
|
10
|
+
if (customHeaders && customHeaders.length > 0) {
|
|
11
|
+
for (i = 0; i < customHeaders.length; i++) {
|
|
12
|
+
header = customHeaders[i];
|
|
13
|
+
headers[header.name] = { value: header.value };
|
|
14
|
+
}
|
|
15
|
+
} else {
|
|
16
|
+
// Default headers
|
|
17
|
+
headers["strict-transport-security"] = {
|
|
18
|
+
value: "max-age=31536000; includeSubDomains",
|
|
19
|
+
};
|
|
20
|
+
headers["content-security-policy"] = {
|
|
21
|
+
value: "default-src 'self' https: data: 'unsafe-inline';",
|
|
22
|
+
};
|
|
23
|
+
headers["x-content-type-options"] = { value: "nosniff" };
|
|
24
|
+
headers["x-frame-options"] = { value: "sameorigin" };
|
|
25
|
+
}
|
|
15
26
|
|
|
16
27
|
return response;
|
|
17
28
|
}
|
|
@@ -66,6 +66,8 @@ module.exports.static = async (
|
|
|
66
66
|
credentials = [],
|
|
67
67
|
repo,
|
|
68
68
|
branch,
|
|
69
|
+
redirects = [],
|
|
70
|
+
headers = [],
|
|
69
71
|
) => {
|
|
70
72
|
let s3 = await module.exports.s3.createS3Bucket(name, tags);
|
|
71
73
|
|
|
@@ -76,14 +78,13 @@ module.exports.static = async (
|
|
|
76
78
|
(
|
|
77
79
|
await module.exports.cloudfront.createCloudFrontFunction(
|
|
78
80
|
name,
|
|
79
|
-
|
|
80
|
-
? "aws-cloudfront-auth"
|
|
81
|
-
: "aws-cloudfront-simple",
|
|
81
|
+
"aws-cloudfront-request",
|
|
82
82
|
{
|
|
83
83
|
credentials: credentials.map(
|
|
84
84
|
(d) =>
|
|
85
85
|
`Basic ${Buffer.from(`${d.username}:${d.password}`).toString("base64")}`,
|
|
86
86
|
),
|
|
87
|
+
redirects: redirects,
|
|
87
88
|
},
|
|
88
89
|
)
|
|
89
90
|
).FunctionSummary.FunctionMetadata.FunctionARN,
|
|
@@ -95,7 +96,9 @@ module.exports.static = async (
|
|
|
95
96
|
branch,
|
|
96
97
|
),
|
|
97
98
|
"aws-cloudfront-response",
|
|
98
|
-
{
|
|
99
|
+
{
|
|
100
|
+
headers: headers,
|
|
101
|
+
},
|
|
99
102
|
)
|
|
100
103
|
).FunctionSummary.FunctionMetadata.FunctionARN,
|
|
101
104
|
);
|
|
@@ -143,7 +146,11 @@ module.exports.staticTerminate = async (name, repo, branch, id) => {
|
|
|
143
146
|
|
|
144
147
|
try {
|
|
145
148
|
await module.exports.cloudfront.removeCloudFrontFunction(
|
|
146
|
-
module.exports.slug(
|
|
149
|
+
module.exports.slug(
|
|
150
|
+
`${repo}-response`,
|
|
151
|
+
process.env.AWS_PROFILE,
|
|
152
|
+
branch,
|
|
153
|
+
),
|
|
147
154
|
);
|
|
148
155
|
} catch {
|
|
149
156
|
/* empty */
|
package/globals.js
CHANGED
|
@@ -847,7 +847,7 @@ module.exports = {
|
|
|
847
847
|
let alreadyRunning = running;
|
|
848
848
|
|
|
849
849
|
if (!running) {
|
|
850
|
-
execSync(
|
|
850
|
+
execSync(`${docker} up -d`, opts);
|
|
851
851
|
running = true;
|
|
852
852
|
}
|
|
853
853
|
|
|
@@ -858,7 +858,7 @@ module.exports = {
|
|
|
858
858
|
throw e;
|
|
859
859
|
} finally {
|
|
860
860
|
if (!alreadyRunning) {
|
|
861
|
-
execSync(
|
|
861
|
+
execSync(`${docker} down`, opts);
|
|
862
862
|
running = false;
|
|
863
863
|
}
|
|
864
864
|
}
|
package/package.json
CHANGED
package/python/0/Dockerfile
CHANGED
|
@@ -2,7 +2,7 @@ FROM ghcr.io/astral-sh/uv:python3.13-bookworm AS development
|
|
|
2
2
|
|
|
3
3
|
LABEL org.opencontainers.image.authors="Mike Mellor <mike.mellor@avalerehealth.com>"
|
|
4
4
|
|
|
5
|
-
FROM fishawack/lab-env-
|
|
5
|
+
FROM fishawack/lab-env-python-0:latest AS production
|
|
6
6
|
|
|
7
7
|
# Copy source code into container
|
|
8
8
|
COPY . /app
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
// eslint-disable-next-line no-unused-vars
|
|
2
|
-
function handler(event) {
|
|
3
|
-
var query, index, key, response;
|
|
4
|
-
|
|
5
|
-
// Redirect if www to non-www
|
|
6
|
-
if (event.request.headers.host.value.includes("www.")) {
|
|
7
|
-
query = "";
|
|
8
|
-
index = 0;
|
|
9
|
-
|
|
10
|
-
for (key in event.request.querystring) {
|
|
11
|
-
query += `${index ? "&" : "?"}${key}=${event.request.querystring[key].value}`;
|
|
12
|
-
index++;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
response = {
|
|
16
|
-
statusCode: 301,
|
|
17
|
-
statusDescription: "Moved Permanently",
|
|
18
|
-
headers: {
|
|
19
|
-
location: {
|
|
20
|
-
value: `https://${event.request.headers.host.value.split("www.")[1]}${event.request.uri}${query}`,
|
|
21
|
-
},
|
|
22
|
-
"strict-transport-security": {
|
|
23
|
-
value: "max-age=31536000; includeSubDomains",
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
return response;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Redirect if no trailing slash
|
|
32
|
-
if (!event.request.uri.includes(".") && !event.request.uri.endsWith("/")) {
|
|
33
|
-
query = "";
|
|
34
|
-
index = 0;
|
|
35
|
-
|
|
36
|
-
for (key in event.request.querystring) {
|
|
37
|
-
query += `${index ? "&" : "?"}${key}=${event.request.querystring[key].value}`;
|
|
38
|
-
index++;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
statusCode: 301,
|
|
43
|
-
statusDescription: "Moved Permanently",
|
|
44
|
-
headers: { location: { value: `${event.request.uri}/${query}` } },
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
var authHeaders = event.request.headers.authorization;
|
|
49
|
-
|
|
50
|
-
// The Base64-encoded Auth string that should be present.
|
|
51
|
-
// It is an encoding of `Basic base64([username]:[password])`
|
|
52
|
-
var expected = "<%= credentials %>";
|
|
53
|
-
|
|
54
|
-
// If an Authorization header is supplied and it's an exact match, pass the
|
|
55
|
-
// request on through to CF/the origin without any modification.
|
|
56
|
-
if (authHeaders && expected.find((d) => d === authHeaders.value)) {
|
|
57
|
-
// Rewrite url to append index.html if not present
|
|
58
|
-
var request = event.request;
|
|
59
|
-
var uri = request.uri;
|
|
60
|
-
|
|
61
|
-
// Check whether the URI is missing a file name.
|
|
62
|
-
if (uri.endsWith("/")) {
|
|
63
|
-
request.uri += "index.html";
|
|
64
|
-
}
|
|
65
|
-
// Check whether the URI is missing a file extension.
|
|
66
|
-
else if (!uri.includes(".")) {
|
|
67
|
-
request.uri += "/index.html";
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return request;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// But if we get here, we must either be missing the auth header or the
|
|
74
|
-
// credentials failed to match what we expected.
|
|
75
|
-
// Request the browser present the Basic Auth dialog.
|
|
76
|
-
response = {
|
|
77
|
-
statusCode: 401,
|
|
78
|
-
statusDescription: "Unauthorized",
|
|
79
|
-
headers: {
|
|
80
|
-
"www-authenticate": { value: "Basic" },
|
|
81
|
-
"strict-transport-security": {
|
|
82
|
-
value: "max-age=31536000; includeSubDomains",
|
|
83
|
-
},
|
|
84
|
-
"content-security-policy": {
|
|
85
|
-
value: "default-src 'self' https: data: 'unsafe-inline';",
|
|
86
|
-
},
|
|
87
|
-
"x-content-type-options": { value: "nosniff" },
|
|
88
|
-
"x-frame-options": { value: "sameorigin" },
|
|
89
|
-
},
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
return response;
|
|
93
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
// eslint-disable-next-line no-unused-vars
|
|
2
|
-
function handler(event) {
|
|
3
|
-
var query, index, key, response;
|
|
4
|
-
|
|
5
|
-
// Redirect if www to non-www
|
|
6
|
-
if (event.request.headers.host.value.includes("www.")) {
|
|
7
|
-
query = "";
|
|
8
|
-
index = 0;
|
|
9
|
-
|
|
10
|
-
for (key in event.request.querystring) {
|
|
11
|
-
query += `${index ? "&" : "?"}${key}=${event.request.querystring[key].value}`;
|
|
12
|
-
index++;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
response = {
|
|
16
|
-
statusCode: 301,
|
|
17
|
-
statusDescription: "Moved Permanently",
|
|
18
|
-
headers: {
|
|
19
|
-
location: {
|
|
20
|
-
value: `https://${event.request.headers.host.value.split("www.")[1]}${event.request.uri}${query}`,
|
|
21
|
-
},
|
|
22
|
-
"strict-transport-security": {
|
|
23
|
-
value: "max-age=31536000; includeSubDomains",
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
return response;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Redirect if no trailing slash
|
|
32
|
-
if (!event.request.uri.includes(".") && !event.request.uri.endsWith("/")) {
|
|
33
|
-
query = "";
|
|
34
|
-
index = 0;
|
|
35
|
-
|
|
36
|
-
for (key in event.request.querystring) {
|
|
37
|
-
query += `${index ? "&" : "?"}${key}=${event.request.querystring[key].value}`;
|
|
38
|
-
index++;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
statusCode: 301,
|
|
43
|
-
statusDescription: "Moved Permanently",
|
|
44
|
-
headers: { location: { value: `${event.request.uri}/${query}` } },
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
var request = event.request;
|
|
49
|
-
var uri = request.uri;
|
|
50
|
-
|
|
51
|
-
// Check whether the URI is missing a file name.
|
|
52
|
-
if (uri.endsWith("/")) {
|
|
53
|
-
request.uri += "index.html";
|
|
54
|
-
}
|
|
55
|
-
// Check whether the URI is missing a file extension.
|
|
56
|
-
else if (!uri.includes(".")) {
|
|
57
|
-
request.uri += "/index.html";
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return request;
|
|
61
|
-
}
|