headroom-cms 0.1.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/README.md +282 -0
- package/admin/assets/AdminsPage-CnrQqwKA.js +1 -0
- package/admin/assets/AllContentPage-ByN1h3PP.js +1 -0
- package/admin/assets/ApiKeysPage-FgNHZPBS.js +1 -0
- package/admin/assets/AuditPage-DAPpo-sj.js +1 -0
- package/admin/assets/BlockEditor-CZTwex-o.js +179 -0
- package/admin/assets/BlockEditor-Cp_wZ2xN.css +1 -0
- package/admin/assets/BlockTypeEditPage-Buuwbx1P.js +1 -0
- package/admin/assets/BlockTypesPage-Dj0qmsqX.js +1 -0
- package/admin/assets/BulkActionBar-BMcUBJSH.js +1 -0
- package/admin/assets/CollectionEditPage-CLgQu2HS.js +1 -0
- package/admin/assets/CollectionsPage-BnCaxALz.js +1 -0
- package/admin/assets/ContentCreatePage-CJI326o-.js +1 -0
- package/admin/assets/ContentEditPage-A4i8P2Jd.js +3 -0
- package/admin/assets/ContentListPage-Bc4mBIkB.js +1 -0
- package/admin/assets/CustomBlockPreview-CCssn6vF.js +479 -0
- package/admin/assets/FieldBuilder-YJGSk0nY.js +3 -0
- package/admin/assets/LoginPage-Jrne8-Wr.js +1 -0
- package/admin/assets/MediaPage-DfPQBmNf.css +1 -0
- package/admin/assets/MediaPage-_qNXqsZg.js +1 -0
- package/admin/assets/SiteSettingsPage-CoZnavij.js +1 -0
- package/admin/assets/SitesPage-ETqFT3nO.js +1 -0
- package/admin/assets/TagsPage-BGpp0XZM.js +1 -0
- package/admin/assets/UsersPage-CKRJpAb6.js +1 -0
- package/admin/assets/WebhookEditPage-BOcLe5OJ.js +1 -0
- package/admin/assets/WebhooksPage-Czco583Y.js +1 -0
- package/admin/assets/badge-0Z1nL6DI.js +1 -0
- package/admin/assets/card-D1-S-QZ6.js +1 -0
- package/admin/assets/check-BGA0ADyt.js +1 -0
- package/admin/assets/checkbox-BPqrj_XS.js +1 -0
- package/admin/assets/command-ChD319uJ.js +1 -0
- package/admin/assets/contentStatus-DfWHjFVB.js +1 -0
- package/admin/assets/copy-BqH9rXYM.js +1 -0
- package/admin/assets/core.esm-Csvubn5Q.js +5 -0
- package/admin/assets/format-CZ9bpk32.js +1 -0
- package/admin/assets/index-DOqKbrpW.css +1 -0
- package/admin/assets/index-Ds50UTAc.js +18 -0
- package/admin/assets/lib-BrI1UB_t.js +38 -0
- package/admin/assets/media-url-DIg_vSyf.js +1 -0
- package/admin/assets/module-RjUF93sV.js +716 -0
- package/admin/assets/native-48B9X9Wg.js +1 -0
- package/admin/assets/plus-BgHSYWJN.js +1 -0
- package/admin/assets/radix-DQ3amgxj.js +51 -0
- package/admin/assets/react-vendor-DNVhVxD7.js +4 -0
- package/admin/assets/search-DIzcfCVh.js +1 -0
- package/admin/assets/select-CJXZv4wv.js +1 -0
- package/admin/assets/sortable.esm-Zh-9QRSf.js +1 -0
- package/admin/assets/table-B3EHrN_H.js +1 -0
- package/admin/assets/tanstack-BO6c-AOu.js +1 -0
- package/admin/assets/trash-2-Gny2Upn-.js +1 -0
- package/admin/assets/useAdminResolver-BsQc_N4z.js +1 -0
- package/admin/assets/useContent-CSobIico.js +1 -0
- package/admin/assets/useDebouncedValue-Bf8UizjU.js +1 -0
- package/admin/assets/useMedia-CQnmMz4N.js +1 -0
- package/admin/assets/useTags-CYqbj5cK.js +1 -0
- package/admin/assets/useWebhooks-DXgtQ3aU.js +1 -0
- package/admin/index.html +21 -0
- package/admin/vite.svg +1 -0
- package/dist/admin-site.d.ts +30 -0
- package/dist/admin-site.d.ts.map +1 -0
- package/dist/admin-site.js +80 -0
- package/dist/admin-site.js.map +1 -0
- package/dist/api.d.ts +26 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +91 -0
- package/dist/api.js.map +1 -0
- package/dist/auth.d.ts +27 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +86 -0
- package/dist/auth.js.map +1 -0
- package/dist/cdn.d.ts +27 -0
- package/dist/cdn.d.ts.map +1 -0
- package/dist/cdn.js +382 -0
- package/dist/cdn.js.map +1 -0
- package/dist/image.d.ts +21 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +48 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +85 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +124 -0
- package/dist/index.js.map +1 -0
- package/dist/storage.d.ts +21 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +125 -0
- package/dist/storage.js.map +1 -0
- package/dist/webhooks.d.ts +23 -0
- package/dist/webhooks.d.ts.map +1 -0
- package/dist/webhooks.js +91 -0
- package/dist/webhooks.js.map +1 -0
- package/lambda/api/bootstrap +0 -0
- package/lambda/api/resource.enc +0 -0
- package/lambda/functions/custom-message/index.mjs +112 -0
- package/lambda/functions/custom-message/resource.enc +1 -0
- package/lambda/image-lambda/index.mjs +188 -0
- package/lambda/image-lambda/node_modules/.package-lock.json +160 -0
- package/lambda/image-lambda/node_modules/@img/sharp-libvips-linux-arm64/README.md +46 -0
- package/lambda/image-lambda/node_modules/@img/sharp-libvips-linux-arm64/lib/glib-2.0/include/glibconfig.h +220 -0
- package/lambda/image-lambda/node_modules/@img/sharp-libvips-linux-arm64/lib/index.js +1 -0
- package/lambda/image-lambda/node_modules/@img/sharp-libvips-linux-arm64/lib/libvips-cpp.so.42 +0 -0
- package/lambda/image-lambda/node_modules/@img/sharp-libvips-linux-arm64/package.json +42 -0
- package/lambda/image-lambda/node_modules/@img/sharp-libvips-linux-arm64/versions.json +30 -0
- package/lambda/image-lambda/node_modules/@img/sharp-linux-arm64/LICENSE +191 -0
- package/lambda/image-lambda/node_modules/@img/sharp-linux-arm64/README.md +18 -0
- package/lambda/image-lambda/node_modules/@img/sharp-linux-arm64/lib/sharp-linux-arm64.node +0 -0
- package/lambda/image-lambda/node_modules/@img/sharp-linux-arm64/package.json +46 -0
- package/lambda/image-lambda/node_modules/color/LICENSE +21 -0
- package/lambda/image-lambda/node_modules/color/README.md +123 -0
- package/lambda/image-lambda/node_modules/color/index.js +496 -0
- package/lambda/image-lambda/node_modules/color/package.json +47 -0
- package/lambda/image-lambda/node_modules/color-convert/CHANGELOG.md +54 -0
- package/lambda/image-lambda/node_modules/color-convert/LICENSE +21 -0
- package/lambda/image-lambda/node_modules/color-convert/README.md +68 -0
- package/lambda/image-lambda/node_modules/color-convert/conversions.js +839 -0
- package/lambda/image-lambda/node_modules/color-convert/index.js +81 -0
- package/lambda/image-lambda/node_modules/color-convert/package.json +48 -0
- package/lambda/image-lambda/node_modules/color-convert/route.js +97 -0
- package/lambda/image-lambda/node_modules/color-name/LICENSE +8 -0
- package/lambda/image-lambda/node_modules/color-name/README.md +11 -0
- package/lambda/image-lambda/node_modules/color-name/index.js +152 -0
- package/lambda/image-lambda/node_modules/color-name/package.json +28 -0
- package/lambda/image-lambda/node_modules/color-string/LICENSE +21 -0
- package/lambda/image-lambda/node_modules/color-string/README.md +62 -0
- package/lambda/image-lambda/node_modules/color-string/index.js +242 -0
- package/lambda/image-lambda/node_modules/color-string/package.json +39 -0
- package/lambda/image-lambda/node_modules/detect-libc/LICENSE +201 -0
- package/lambda/image-lambda/node_modules/detect-libc/README.md +163 -0
- package/lambda/image-lambda/node_modules/detect-libc/index.d.ts +14 -0
- package/lambda/image-lambda/node_modules/detect-libc/lib/detect-libc.js +313 -0
- package/lambda/image-lambda/node_modules/detect-libc/lib/elf.js +39 -0
- package/lambda/image-lambda/node_modules/detect-libc/lib/filesystem.js +51 -0
- package/lambda/image-lambda/node_modules/detect-libc/lib/process.js +24 -0
- package/lambda/image-lambda/node_modules/detect-libc/package.json +44 -0
- package/lambda/image-lambda/node_modules/is-arrayish/LICENSE +21 -0
- package/lambda/image-lambda/node_modules/is-arrayish/README.md +16 -0
- package/lambda/image-lambda/node_modules/is-arrayish/index.js +9 -0
- package/lambda/image-lambda/node_modules/is-arrayish/package.json +45 -0
- package/lambda/image-lambda/node_modules/semver/LICENSE +15 -0
- package/lambda/image-lambda/node_modules/semver/README.md +665 -0
- package/lambda/image-lambda/node_modules/semver/bin/semver.js +191 -0
- package/lambda/image-lambda/node_modules/semver/classes/comparator.js +143 -0
- package/lambda/image-lambda/node_modules/semver/classes/index.js +7 -0
- package/lambda/image-lambda/node_modules/semver/classes/range.js +557 -0
- package/lambda/image-lambda/node_modules/semver/classes/semver.js +333 -0
- package/lambda/image-lambda/node_modules/semver/functions/clean.js +8 -0
- package/lambda/image-lambda/node_modules/semver/functions/cmp.js +54 -0
- package/lambda/image-lambda/node_modules/semver/functions/coerce.js +62 -0
- package/lambda/image-lambda/node_modules/semver/functions/compare-build.js +9 -0
- package/lambda/image-lambda/node_modules/semver/functions/compare-loose.js +5 -0
- package/lambda/image-lambda/node_modules/semver/functions/compare.js +7 -0
- package/lambda/image-lambda/node_modules/semver/functions/diff.js +60 -0
- package/lambda/image-lambda/node_modules/semver/functions/eq.js +5 -0
- package/lambda/image-lambda/node_modules/semver/functions/gt.js +5 -0
- package/lambda/image-lambda/node_modules/semver/functions/gte.js +5 -0
- package/lambda/image-lambda/node_modules/semver/functions/inc.js +21 -0
- package/lambda/image-lambda/node_modules/semver/functions/lt.js +5 -0
- package/lambda/image-lambda/node_modules/semver/functions/lte.js +5 -0
- package/lambda/image-lambda/node_modules/semver/functions/major.js +5 -0
- package/lambda/image-lambda/node_modules/semver/functions/minor.js +5 -0
- package/lambda/image-lambda/node_modules/semver/functions/neq.js +5 -0
- package/lambda/image-lambda/node_modules/semver/functions/parse.js +18 -0
- package/lambda/image-lambda/node_modules/semver/functions/patch.js +5 -0
- package/lambda/image-lambda/node_modules/semver/functions/prerelease.js +8 -0
- package/lambda/image-lambda/node_modules/semver/functions/rcompare.js +5 -0
- package/lambda/image-lambda/node_modules/semver/functions/rsort.js +5 -0
- package/lambda/image-lambda/node_modules/semver/functions/satisfies.js +12 -0
- package/lambda/image-lambda/node_modules/semver/functions/sort.js +5 -0
- package/lambda/image-lambda/node_modules/semver/functions/valid.js +8 -0
- package/lambda/image-lambda/node_modules/semver/index.js +91 -0
- package/lambda/image-lambda/node_modules/semver/internal/constants.js +37 -0
- package/lambda/image-lambda/node_modules/semver/internal/debug.js +11 -0
- package/lambda/image-lambda/node_modules/semver/internal/identifiers.js +29 -0
- package/lambda/image-lambda/node_modules/semver/internal/lrucache.js +42 -0
- package/lambda/image-lambda/node_modules/semver/internal/parse-options.js +17 -0
- package/lambda/image-lambda/node_modules/semver/internal/re.js +223 -0
- package/lambda/image-lambda/node_modules/semver/package.json +78 -0
- package/lambda/image-lambda/node_modules/semver/preload.js +4 -0
- package/lambda/image-lambda/node_modules/semver/range.bnf +16 -0
- package/lambda/image-lambda/node_modules/semver/ranges/gtr.js +6 -0
- package/lambda/image-lambda/node_modules/semver/ranges/intersects.js +9 -0
- package/lambda/image-lambda/node_modules/semver/ranges/ltr.js +6 -0
- package/lambda/image-lambda/node_modules/semver/ranges/max-satisfying.js +27 -0
- package/lambda/image-lambda/node_modules/semver/ranges/min-satisfying.js +26 -0
- package/lambda/image-lambda/node_modules/semver/ranges/min-version.js +63 -0
- package/lambda/image-lambda/node_modules/semver/ranges/outside.js +82 -0
- package/lambda/image-lambda/node_modules/semver/ranges/simplify.js +49 -0
- package/lambda/image-lambda/node_modules/semver/ranges/subset.js +249 -0
- package/lambda/image-lambda/node_modules/semver/ranges/to-comparators.js +10 -0
- package/lambda/image-lambda/node_modules/semver/ranges/valid.js +13 -0
- package/lambda/image-lambda/node_modules/sharp/LICENSE +191 -0
- package/lambda/image-lambda/node_modules/sharp/README.md +118 -0
- package/lambda/image-lambda/node_modules/sharp/install/check.js +41 -0
- package/lambda/image-lambda/node_modules/sharp/lib/channel.js +174 -0
- package/lambda/image-lambda/node_modules/sharp/lib/colour.js +180 -0
- package/lambda/image-lambda/node_modules/sharp/lib/composite.js +210 -0
- package/lambda/image-lambda/node_modules/sharp/lib/constructor.js +452 -0
- package/lambda/image-lambda/node_modules/sharp/lib/index.d.ts +1754 -0
- package/lambda/image-lambda/node_modules/sharp/lib/index.js +16 -0
- package/lambda/image-lambda/node_modules/sharp/lib/input.js +658 -0
- package/lambda/image-lambda/node_modules/sharp/lib/is.js +169 -0
- package/lambda/image-lambda/node_modules/sharp/lib/libvips.js +203 -0
- package/lambda/image-lambda/node_modules/sharp/lib/operation.js +958 -0
- package/lambda/image-lambda/node_modules/sharp/lib/output.js +1587 -0
- package/lambda/image-lambda/node_modules/sharp/lib/resize.js +587 -0
- package/lambda/image-lambda/node_modules/sharp/lib/sharp.js +114 -0
- package/lambda/image-lambda/node_modules/sharp/lib/utility.js +296 -0
- package/lambda/image-lambda/node_modules/sharp/package.json +222 -0
- package/lambda/image-lambda/node_modules/sharp/src/binding.gyp +280 -0
- package/lambda/image-lambda/node_modules/sharp/src/common.cc +1091 -0
- package/lambda/image-lambda/node_modules/sharp/src/common.h +393 -0
- package/lambda/image-lambda/node_modules/sharp/src/metadata.cc +320 -0
- package/lambda/image-lambda/node_modules/sharp/src/metadata.h +85 -0
- package/lambda/image-lambda/node_modules/sharp/src/operations.cc +475 -0
- package/lambda/image-lambda/node_modules/sharp/src/operations.h +125 -0
- package/lambda/image-lambda/node_modules/sharp/src/pipeline.cc +1758 -0
- package/lambda/image-lambda/node_modules/sharp/src/pipeline.h +393 -0
- package/lambda/image-lambda/node_modules/sharp/src/sharp.cc +40 -0
- package/lambda/image-lambda/node_modules/sharp/src/stats.cc +183 -0
- package/lambda/image-lambda/node_modules/sharp/src/stats.h +59 -0
- package/lambda/image-lambda/node_modules/sharp/src/utilities.cc +269 -0
- package/lambda/image-lambda/node_modules/sharp/src/utilities.h +19 -0
- package/lambda/image-lambda/node_modules/simple-swizzle/LICENSE +21 -0
- package/lambda/image-lambda/node_modules/simple-swizzle/README.md +43 -0
- package/lambda/image-lambda/node_modules/simple-swizzle/index.js +29 -0
- package/lambda/image-lambda/node_modules/simple-swizzle/package.json +36 -0
- package/lambda/webhook-worker/bootstrap +0 -0
- package/lambda/webhook-worker/resource.enc +0 -0
- package/package.json +50 -0
- package/src/admin-site.ts +108 -0
- package/src/api.ts +113 -0
- package/src/auth.ts +110 -0
- package/src/cdn.ts +449 -0
- package/src/image.ts +62 -0
- package/src/index.ts +216 -0
- package/src/sst-env.d.ts +143 -0
- package/src/storage.ts +138 -0
- package/src/webhooks.ts +114 -0
package/src/api.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Infrastructure
|
|
3
|
+
*
|
|
4
|
+
* Go Lambda function serving the Headroom API with access to all resources.
|
|
5
|
+
* Supports dev mode (Go source with live reload) and package mode (pre-compiled binary).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import path from "path";
|
|
9
|
+
import type { StorageResources } from "./storage.js";
|
|
10
|
+
import type { AuthResources } from "./auth.js";
|
|
11
|
+
import type { WebhookResources } from "./webhooks.js";
|
|
12
|
+
import type { ImageResources } from "./image.js";
|
|
13
|
+
|
|
14
|
+
export interface ApiArgs {
|
|
15
|
+
storage: StorageResources;
|
|
16
|
+
auth: AuthResources;
|
|
17
|
+
webhooks: WebhookResources;
|
|
18
|
+
image: ImageResources;
|
|
19
|
+
pkgRoot: string;
|
|
20
|
+
dev?: {
|
|
21
|
+
/** Go source path, e.g. "packages/api" */
|
|
22
|
+
handler: string;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function createApi(name: string, args: ApiArgs) {
|
|
27
|
+
const { storage, auth, webhooks, image } = args;
|
|
28
|
+
|
|
29
|
+
const handlerConfig = args.dev
|
|
30
|
+
? {
|
|
31
|
+
handler: args.dev.handler,
|
|
32
|
+
runtime: "go" as const,
|
|
33
|
+
}
|
|
34
|
+
: {
|
|
35
|
+
bundle: path.join(args.pkgRoot, "lambda/api"),
|
|
36
|
+
handler: "bootstrap",
|
|
37
|
+
runtime: "provided.al2023" as const,
|
|
38
|
+
architecture: "arm64" as const,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const api = new sst.aws.Function(`${name}Api`, {
|
|
42
|
+
...handlerConfig,
|
|
43
|
+
timeout: "60 seconds",
|
|
44
|
+
url: {
|
|
45
|
+
cors: false,
|
|
46
|
+
},
|
|
47
|
+
environment: {
|
|
48
|
+
SITES_TABLE: storage.sites.name,
|
|
49
|
+
CONTENT_TABLE: storage.content.name,
|
|
50
|
+
DRAFT_CONTENT_TABLE: storage.draftContent.name,
|
|
51
|
+
BLOCKS_TABLE: storage.blocks.name,
|
|
52
|
+
MEDIA_TABLE: storage.media.name,
|
|
53
|
+
COLLECTIONS_TABLE: storage.collections.name,
|
|
54
|
+
BLOCK_TYPES_TABLE: storage.blockTypes.name,
|
|
55
|
+
ADMIN_AUDIT_TABLE: storage.adminAudit.name,
|
|
56
|
+
CONTENT_BUCKET: storage.contentBucket.name,
|
|
57
|
+
USER_POOL_ID: auth.userPool.id,
|
|
58
|
+
USER_POOL_CLIENT_ID: auth.userPoolClient.id,
|
|
59
|
+
KVS_ARN: storage.kvs.arn,
|
|
60
|
+
AWS_REGION_NAME: aws.getRegionOutput().name,
|
|
61
|
+
WEBHOOKS_TABLE: webhooks.webhooks.name,
|
|
62
|
+
WEBHOOK_DELIVERIES_TABLE: webhooks.webhookDeliveries.name,
|
|
63
|
+
WEBHOOK_QUEUE_URL: webhooks.webhookDeliveryQueue.url,
|
|
64
|
+
IMAGE_SIGNING_SECRET: image.imageSigningSecret.value,
|
|
65
|
+
IMAGE_LAMBDA_NAME: image.imageLambda.name,
|
|
66
|
+
RELATIONSHIPS_TABLE: storage.relationships.name,
|
|
67
|
+
},
|
|
68
|
+
link: [
|
|
69
|
+
storage.sites,
|
|
70
|
+
storage.content,
|
|
71
|
+
storage.draftContent,
|
|
72
|
+
storage.blocks,
|
|
73
|
+
storage.media,
|
|
74
|
+
storage.collections,
|
|
75
|
+
storage.blockTypes,
|
|
76
|
+
storage.adminAudit,
|
|
77
|
+
storage.contentBucket,
|
|
78
|
+
webhooks.webhooks,
|
|
79
|
+
webhooks.webhookDeliveries,
|
|
80
|
+
webhooks.webhookDeliveryQueue,
|
|
81
|
+
image.imageSigningSecret,
|
|
82
|
+
storage.relationships,
|
|
83
|
+
],
|
|
84
|
+
permissions: [
|
|
85
|
+
{
|
|
86
|
+
actions: [
|
|
87
|
+
"cloudfront-keyvaluestore:DescribeKeyValueStore",
|
|
88
|
+
"cloudfront-keyvaluestore:PutKey",
|
|
89
|
+
"cloudfront-keyvaluestore:DeleteKey",
|
|
90
|
+
"cloudfront-keyvaluestore:GetKey",
|
|
91
|
+
],
|
|
92
|
+
resources: [storage.kvs.arn],
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
actions: [
|
|
96
|
+
"cognito-idp:ListUsers",
|
|
97
|
+
"cognito-idp:AdminCreateUser",
|
|
98
|
+
"cognito-idp:AdminDeleteUser",
|
|
99
|
+
"cognito-idp:AdminGetUser",
|
|
100
|
+
],
|
|
101
|
+
resources: [auth.userPool.arn],
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
actions: ["lambda:InvokeFunction"],
|
|
105
|
+
resources: [image.imageLambda.arn],
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return { api };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export type ApiResources = ReturnType<typeof createApi>;
|
package/src/auth.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication Infrastructure
|
|
3
|
+
*
|
|
4
|
+
* Cognito User Pool, SES email identity, and custom message Lambda.
|
|
5
|
+
* Supports dev mode (source handler) and package mode (pre-bundled handler).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import path from "path";
|
|
9
|
+
|
|
10
|
+
export interface AuthArgs {
|
|
11
|
+
senderEmail: string;
|
|
12
|
+
passwordPolicy?: {
|
|
13
|
+
minimumLength?: number;
|
|
14
|
+
requireLowercase?: boolean;
|
|
15
|
+
requireUppercase?: boolean;
|
|
16
|
+
requireNumbers?: boolean;
|
|
17
|
+
requireSymbols?: boolean;
|
|
18
|
+
};
|
|
19
|
+
pkgRoot: string;
|
|
20
|
+
dev?: {
|
|
21
|
+
/** SST handler string, e.g. "packages/functions/custom-message.handler" */
|
|
22
|
+
handler: string;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function createAuth(name: string, args: AuthArgs) {
|
|
27
|
+
const emailIdentity = new aws.ses.EmailIdentity(`${name}EmailIdentity`, {
|
|
28
|
+
email: args.senderEmail,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Custom message Lambda: customizes Cognito invite emails
|
|
32
|
+
let customMessageFn: sst.aws.Function;
|
|
33
|
+
if (args.dev) {
|
|
34
|
+
customMessageFn = new sst.aws.Function(`${name}CustomMessage`, {
|
|
35
|
+
handler: args.dev.handler,
|
|
36
|
+
runtime: "nodejs20.x",
|
|
37
|
+
});
|
|
38
|
+
} else {
|
|
39
|
+
customMessageFn = new sst.aws.Function(`${name}CustomMessage`, {
|
|
40
|
+
bundle: path.join(args.pkgRoot, "lambda/functions/custom-message"),
|
|
41
|
+
handler: "index.handler",
|
|
42
|
+
runtime: "nodejs20.x",
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const pp = args.passwordPolicy ?? {};
|
|
47
|
+
|
|
48
|
+
const userPool = new sst.aws.CognitoUserPool(`${name}UserPool`, {
|
|
49
|
+
usernames: ["email"],
|
|
50
|
+
transform: {
|
|
51
|
+
userPool: {
|
|
52
|
+
passwordPolicy: {
|
|
53
|
+
minimumLength: pp.minimumLength ?? 12,
|
|
54
|
+
requireLowercase: pp.requireLowercase ?? true,
|
|
55
|
+
requireUppercase: pp.requireUppercase ?? true,
|
|
56
|
+
requireNumbers: pp.requireNumbers ?? true,
|
|
57
|
+
requireSymbols: pp.requireSymbols ?? true,
|
|
58
|
+
},
|
|
59
|
+
accountRecoverySetting: {
|
|
60
|
+
recoveryMechanisms: [{ name: "verified_email", priority: 1 }],
|
|
61
|
+
},
|
|
62
|
+
lambdaConfig: {
|
|
63
|
+
customMessage: customMessageFn.arn,
|
|
64
|
+
},
|
|
65
|
+
emailConfiguration: {
|
|
66
|
+
emailSendingAccount: "DEVELOPER",
|
|
67
|
+
sourceArn: emailIdentity.arn,
|
|
68
|
+
fromEmailAddress: `Headroom CMS <${args.senderEmail}>`,
|
|
69
|
+
},
|
|
70
|
+
mfaConfiguration: "OPTIONAL",
|
|
71
|
+
softwareTokenMfaConfiguration: {
|
|
72
|
+
enabled: true,
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Grant Cognito permission to invoke the custom message Lambda
|
|
79
|
+
new aws.lambda.Permission(`${name}CustomMessagePermission`, {
|
|
80
|
+
action: "lambda:InvokeFunction",
|
|
81
|
+
function: customMessageFn.arn,
|
|
82
|
+
principal: "cognito-idp.amazonaws.com",
|
|
83
|
+
sourceArn: userPool.arn,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const userPoolClient = userPool.addClient(`${name}Client`, {
|
|
87
|
+
transform: {
|
|
88
|
+
client: {
|
|
89
|
+
explicitAuthFlows: [
|
|
90
|
+
"ALLOW_USER_SRP_AUTH",
|
|
91
|
+
"ALLOW_REFRESH_TOKEN_AUTH",
|
|
92
|
+
"ALLOW_ADMIN_USER_PASSWORD_AUTH",
|
|
93
|
+
],
|
|
94
|
+
accessTokenValidity: 1,
|
|
95
|
+
idTokenValidity: 1,
|
|
96
|
+
refreshTokenValidity: 365,
|
|
97
|
+
tokenValidityUnits: {
|
|
98
|
+
accessToken: "hours",
|
|
99
|
+
idToken: "hours",
|
|
100
|
+
refreshToken: "days",
|
|
101
|
+
},
|
|
102
|
+
preventUserExistenceErrors: "ENABLED",
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return { userPool, userPoolClient };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export type AuthResources = ReturnType<typeof createAuth>;
|
package/src/cdn.ts
ADDED
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CDN Infrastructure
|
|
3
|
+
*
|
|
4
|
+
* CloudFront distribution with edge authentication, version-based caching,
|
|
5
|
+
* direct S3 media serving, and Sharp Lambda image transforms.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { StorageResources } from "./storage.js";
|
|
9
|
+
import type { ApiResources } from "./api.js";
|
|
10
|
+
import type { ImageResources } from "./image.js";
|
|
11
|
+
|
|
12
|
+
export interface CdnArgs {
|
|
13
|
+
api: ApiResources;
|
|
14
|
+
image: ImageResources;
|
|
15
|
+
contentBucket: StorageResources["contentBucket"];
|
|
16
|
+
kvs: StorageResources["kvs"];
|
|
17
|
+
priceClass?: "PriceClass_100" | "PriceClass_200" | "PriceClass_All";
|
|
18
|
+
apiCacheTtl?: number;
|
|
19
|
+
domain?: {
|
|
20
|
+
name: string;
|
|
21
|
+
certificateArn: string;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function createCdn(name: string, args: CdnArgs) {
|
|
26
|
+
const { api, image, contentBucket, kvs } = args;
|
|
27
|
+
const apiCacheTtl = args.apiCacheTtl ?? 3600;
|
|
28
|
+
|
|
29
|
+
// Extract KVS UUID from ARN (cf.kvs() needs the UUID, not the name)
|
|
30
|
+
const kvsUuid = kvs.arn.apply((arn: string) => arn.split("/").pop()!);
|
|
31
|
+
|
|
32
|
+
// =========================================================================
|
|
33
|
+
// CloudFront Function: Edge Auth
|
|
34
|
+
// =========================================================================
|
|
35
|
+
const edgeFunction = new aws.cloudfront.Function(`${name}EdgeAuth`, {
|
|
36
|
+
name: $interpolate`${$app.name}-${$app.stage}-edge-auth`,
|
|
37
|
+
runtime: "cloudfront-js-2.0",
|
|
38
|
+
publish: true,
|
|
39
|
+
keyValueStoreAssociations: [kvs.arn],
|
|
40
|
+
code: $interpolate`
|
|
41
|
+
import cf from 'cloudfront';
|
|
42
|
+
import crypto from 'crypto';
|
|
43
|
+
|
|
44
|
+
const kvsId = '${kvsUuid}';
|
|
45
|
+
const kvsHandle = cf.kvs(kvsId);
|
|
46
|
+
|
|
47
|
+
async function handler(event) {
|
|
48
|
+
const request = event.request;
|
|
49
|
+
const uri = request.uri;
|
|
50
|
+
const headers = request.headers;
|
|
51
|
+
|
|
52
|
+
// Skip auth for admin paths (use JWT auth at origin)
|
|
53
|
+
if (uri.startsWith('/v1/admin/')) {
|
|
54
|
+
return request;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Skip auth for health endpoint
|
|
58
|
+
if (uri === '/health') {
|
|
59
|
+
return request;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Skip auth for non-API paths
|
|
63
|
+
if (!uri.startsWith('/v1/')) {
|
|
64
|
+
return request;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Extract site from path: /v1/{site}/...
|
|
68
|
+
var pathParts = uri.split('/');
|
|
69
|
+
if (pathParts.length < 3) {
|
|
70
|
+
return {
|
|
71
|
+
statusCode: 400,
|
|
72
|
+
statusDescription: 'Bad Request',
|
|
73
|
+
body: { encoding: 'text', data: 'Invalid path' }
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
var site = pathParts[2];
|
|
78
|
+
|
|
79
|
+
// Get API key from header
|
|
80
|
+
var apiKeyHeader = headers['x-headroom-key'];
|
|
81
|
+
if (!apiKeyHeader) {
|
|
82
|
+
return {
|
|
83
|
+
statusCode: 401,
|
|
84
|
+
statusDescription: 'Unauthorized',
|
|
85
|
+
headers: { 'content-type': { value: 'application/json' } },
|
|
86
|
+
body: { encoding: 'text', data: JSON.stringify({ error: 'API key required' }) }
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Hash the API key (needed for KVS lookup key)
|
|
91
|
+
var keyHash = crypto.createHash('sha256').update(apiKeyHeader.value).digest('hex');
|
|
92
|
+
var hashPrefix = keyHash.substring(0, 8);
|
|
93
|
+
|
|
94
|
+
// Fetch API key hash and version in parallel
|
|
95
|
+
var results = await Promise.all([
|
|
96
|
+
kvsHandle.get('site:' + site + ':key:' + hashPrefix).catch(function() { return null; }),
|
|
97
|
+
kvsHandle.get('site:' + site + ':version').catch(function() { return '0'; }),
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
var storedHash = results[0];
|
|
101
|
+
var versionResult = results[1];
|
|
102
|
+
|
|
103
|
+
// Validate API key - compare full hash
|
|
104
|
+
if (!storedHash || storedHash !== keyHash) {
|
|
105
|
+
return {
|
|
106
|
+
statusCode: 403,
|
|
107
|
+
statusDescription: 'Forbidden',
|
|
108
|
+
headers: { 'content-type': { value: 'application/json' } },
|
|
109
|
+
body: { encoding: 'text', data: JSON.stringify({ error: 'Invalid API key' }) }
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Add version header for cache key discrimination
|
|
114
|
+
request.headers['x-site-version'] = { value: versionResult || '0' };
|
|
115
|
+
|
|
116
|
+
return request;
|
|
117
|
+
}
|
|
118
|
+
`,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// =========================================================================
|
|
122
|
+
// CloudFront Function: Media Rewrite
|
|
123
|
+
// =========================================================================
|
|
124
|
+
const mediaRewriteFunction = new aws.cloudfront.Function(
|
|
125
|
+
`${name}MediaRewrite`,
|
|
126
|
+
{
|
|
127
|
+
name: $interpolate`${$app.name}-${$app.stage}-media-rewrite`,
|
|
128
|
+
runtime: "cloudfront-js-2.0",
|
|
129
|
+
publish: true,
|
|
130
|
+
code: `
|
|
131
|
+
function handler(event) {
|
|
132
|
+
var request = event.request;
|
|
133
|
+
var uri = request.uri;
|
|
134
|
+
|
|
135
|
+
// Rewrite /media/{site}/{mediaId}/{file} → /sites/{site}/media/{mediaId}/{file}
|
|
136
|
+
var parts = uri.split('/');
|
|
137
|
+
// parts: ['', 'media', '{site}', '{mediaId}', '{file}']
|
|
138
|
+
if (parts.length >= 5 && parts[1] === 'media') {
|
|
139
|
+
var site = parts[2];
|
|
140
|
+
var mediaId = parts[3];
|
|
141
|
+
var rest = parts.slice(4).join('/');
|
|
142
|
+
request.uri = '/sites/' + site + '/media/' + mediaId + '/' + rest;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return request;
|
|
146
|
+
}
|
|
147
|
+
`,
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// =========================================================================
|
|
152
|
+
// Cache Policies
|
|
153
|
+
// =========================================================================
|
|
154
|
+
|
|
155
|
+
const apiCachePolicy = new aws.cloudfront.CachePolicy(
|
|
156
|
+
`${name}APICachePolicy`,
|
|
157
|
+
{
|
|
158
|
+
name: $interpolate`${$app.name}-${$app.stage}-api-cache`,
|
|
159
|
+
comment:
|
|
160
|
+
"Cache policy for Headroom API with version-based cache busting",
|
|
161
|
+
defaultTtl: apiCacheTtl,
|
|
162
|
+
maxTtl: 86400,
|
|
163
|
+
minTtl: 0,
|
|
164
|
+
parametersInCacheKeyAndForwardedToOrigin: {
|
|
165
|
+
cookiesConfig: { cookieBehavior: "none" },
|
|
166
|
+
headersConfig: {
|
|
167
|
+
headerBehavior: "whitelist",
|
|
168
|
+
headers: { items: ["x-site-version"] },
|
|
169
|
+
},
|
|
170
|
+
queryStringsConfig: {
|
|
171
|
+
queryStringBehavior: "whitelist",
|
|
172
|
+
queryStrings: {
|
|
173
|
+
items: ["collection", "limit", "cursor", "before", "after", "tag"],
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
enableAcceptEncodingBrotli: true,
|
|
177
|
+
enableAcceptEncodingGzip: true,
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const imageCachePolicy = new aws.cloudfront.CachePolicy(
|
|
183
|
+
`${name}ImageCachePolicy`,
|
|
184
|
+
{
|
|
185
|
+
name: $interpolate`${$app.name}-${$app.stage}-image-cache`,
|
|
186
|
+
comment: "Cache policy for transformed images (immutable)",
|
|
187
|
+
defaultTtl: 31536000,
|
|
188
|
+
maxTtl: 31536000,
|
|
189
|
+
minTtl: 31536000,
|
|
190
|
+
parametersInCacheKeyAndForwardedToOrigin: {
|
|
191
|
+
cookiesConfig: { cookieBehavior: "none" },
|
|
192
|
+
headersConfig: { headerBehavior: "none" },
|
|
193
|
+
queryStringsConfig: {
|
|
194
|
+
queryStringBehavior: "whitelist",
|
|
195
|
+
queryStrings: {
|
|
196
|
+
items: ["w", "h", "fit", "format", "q", "sig"],
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
enableAcceptEncodingBrotli: true,
|
|
200
|
+
enableAcceptEncodingGzip: true,
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
const mediaCachePolicy = new aws.cloudfront.CachePolicy(
|
|
206
|
+
`${name}MediaCachePolicy`,
|
|
207
|
+
{
|
|
208
|
+
name: $interpolate`${$app.name}-${$app.stage}-media-cache`,
|
|
209
|
+
comment: "Cache policy for original media files (immutable)",
|
|
210
|
+
defaultTtl: 31536000,
|
|
211
|
+
maxTtl: 31536000,
|
|
212
|
+
minTtl: 31536000,
|
|
213
|
+
parametersInCacheKeyAndForwardedToOrigin: {
|
|
214
|
+
cookiesConfig: { cookieBehavior: "none" },
|
|
215
|
+
headersConfig: { headerBehavior: "none" },
|
|
216
|
+
queryStringsConfig: { queryStringBehavior: "none" },
|
|
217
|
+
enableAcceptEncodingBrotli: true,
|
|
218
|
+
enableAcceptEncodingGzip: true,
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
// =========================================================================
|
|
224
|
+
// Origin Request Policy
|
|
225
|
+
// =========================================================================
|
|
226
|
+
|
|
227
|
+
const originRequestPolicy = new aws.cloudfront.OriginRequestPolicy(
|
|
228
|
+
`${name}OriginRequestPolicy`,
|
|
229
|
+
{
|
|
230
|
+
name: $interpolate`${$app.name}-${$app.stage}-origin-request`,
|
|
231
|
+
comment: "Forward necessary headers to Lambda origin",
|
|
232
|
+
cookiesConfig: { cookieBehavior: "none" },
|
|
233
|
+
headersConfig: {
|
|
234
|
+
headerBehavior: "whitelist",
|
|
235
|
+
headers: {
|
|
236
|
+
items: [
|
|
237
|
+
"x-headroom-key",
|
|
238
|
+
"content-type",
|
|
239
|
+
"accept",
|
|
240
|
+
"x-site-version",
|
|
241
|
+
],
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
queryStringsConfig: { queryStringBehavior: "all" },
|
|
245
|
+
},
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
// Managed AllViewerExceptHostHeader policy for admin paths
|
|
249
|
+
const adminOriginRequestPolicyId =
|
|
250
|
+
"b689b0a8-53d0-40ab-baf2-68738e2966ac";
|
|
251
|
+
|
|
252
|
+
// =========================================================================
|
|
253
|
+
// Origin Access Controls (OAC)
|
|
254
|
+
// =========================================================================
|
|
255
|
+
|
|
256
|
+
const mediaOAC = new aws.cloudfront.OriginAccessControl(`${name}MediaOAC`, {
|
|
257
|
+
name: $interpolate`${$app.name}-${$app.stage}-media-oac`,
|
|
258
|
+
description: "OAC for S3 media origin",
|
|
259
|
+
originAccessControlOriginType: "s3",
|
|
260
|
+
signingBehavior: "always",
|
|
261
|
+
signingProtocol: "sigv4",
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const imageOAC = new aws.cloudfront.OriginAccessControl(`${name}ImageOAC`, {
|
|
265
|
+
name: $interpolate`${$app.name}-${$app.stage}-image-oac`,
|
|
266
|
+
description: "OAC for image transform Lambda origin",
|
|
267
|
+
originAccessControlOriginType: "lambda",
|
|
268
|
+
signingBehavior: "always",
|
|
269
|
+
signingProtocol: "sigv4",
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// =========================================================================
|
|
273
|
+
// CloudFront Distribution
|
|
274
|
+
// =========================================================================
|
|
275
|
+
|
|
276
|
+
const originDomain = api.api.url.apply((url: string) => {
|
|
277
|
+
const parsed = new URL(url);
|
|
278
|
+
return parsed.hostname;
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
const imageLambdaDomain = image.imageLambda.url.apply((url: string) => {
|
|
282
|
+
const parsed = new URL(url);
|
|
283
|
+
return parsed.hostname;
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
const s3RegionalDomain = $interpolate`${contentBucket.name}.s3.${aws.getRegionOutput().name}.amazonaws.com`;
|
|
287
|
+
|
|
288
|
+
const priceClass = args.priceClass ?? "PriceClass_100";
|
|
289
|
+
|
|
290
|
+
// Build aliases and certificate config for custom domain
|
|
291
|
+
const aliases = args.domain ? [args.domain.name] : undefined;
|
|
292
|
+
const viewerCertificate = args.domain
|
|
293
|
+
? {
|
|
294
|
+
acmCertificateArn: args.domain.certificateArn,
|
|
295
|
+
sslSupportMethod: "sni-only" as const,
|
|
296
|
+
minimumProtocolVersion: "TLSv1.2_2021" as const,
|
|
297
|
+
}
|
|
298
|
+
: {
|
|
299
|
+
cloudfrontDefaultCertificate: true,
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const distribution = new aws.cloudfront.Distribution(
|
|
303
|
+
`${name}Distribution`,
|
|
304
|
+
{
|
|
305
|
+
enabled: true,
|
|
306
|
+
comment: $interpolate`Headroom CMS API - ${$app.stage}`,
|
|
307
|
+
httpVersion: "http2and3",
|
|
308
|
+
priceClass,
|
|
309
|
+
aliases,
|
|
310
|
+
|
|
311
|
+
origins: [
|
|
312
|
+
{
|
|
313
|
+
originId: "api",
|
|
314
|
+
domainName: originDomain,
|
|
315
|
+
customOriginConfig: {
|
|
316
|
+
httpPort: 80,
|
|
317
|
+
httpsPort: 443,
|
|
318
|
+
originProtocolPolicy: "https-only",
|
|
319
|
+
originSslProtocols: ["TLSv1.2"],
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
originId: "media-s3",
|
|
324
|
+
domainName: s3RegionalDomain,
|
|
325
|
+
originAccessControlId: mediaOAC.id,
|
|
326
|
+
s3OriginConfig: {
|
|
327
|
+
originAccessIdentity: "",
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
originId: "image-lambda",
|
|
332
|
+
domainName: imageLambdaDomain,
|
|
333
|
+
originAccessControlId: imageOAC.id,
|
|
334
|
+
customOriginConfig: {
|
|
335
|
+
httpPort: 80,
|
|
336
|
+
httpsPort: 443,
|
|
337
|
+
originProtocolPolicy: "https-only",
|
|
338
|
+
originSslProtocols: ["TLSv1.2"],
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
],
|
|
342
|
+
|
|
343
|
+
defaultCacheBehavior: {
|
|
344
|
+
targetOriginId: "api",
|
|
345
|
+
viewerProtocolPolicy: "redirect-to-https",
|
|
346
|
+
allowedMethods: [
|
|
347
|
+
"GET",
|
|
348
|
+
"HEAD",
|
|
349
|
+
"OPTIONS",
|
|
350
|
+
"PUT",
|
|
351
|
+
"POST",
|
|
352
|
+
"PATCH",
|
|
353
|
+
"DELETE",
|
|
354
|
+
],
|
|
355
|
+
cachedMethods: ["GET", "HEAD", "OPTIONS"],
|
|
356
|
+
compress: true,
|
|
357
|
+
cachePolicyId: apiCachePolicy.id,
|
|
358
|
+
originRequestPolicyId: originRequestPolicy.id,
|
|
359
|
+
functionAssociations: [
|
|
360
|
+
{
|
|
361
|
+
eventType: "viewer-request",
|
|
362
|
+
functionArn: edgeFunction.arn,
|
|
363
|
+
},
|
|
364
|
+
],
|
|
365
|
+
},
|
|
366
|
+
|
|
367
|
+
orderedCacheBehaviors: [
|
|
368
|
+
// Admin paths: no caching, forwards Authorization header
|
|
369
|
+
{
|
|
370
|
+
pathPattern: "/v1/admin/*",
|
|
371
|
+
targetOriginId: "api",
|
|
372
|
+
viewerProtocolPolicy: "redirect-to-https",
|
|
373
|
+
allowedMethods: [
|
|
374
|
+
"GET",
|
|
375
|
+
"HEAD",
|
|
376
|
+
"OPTIONS",
|
|
377
|
+
"PUT",
|
|
378
|
+
"POST",
|
|
379
|
+
"PATCH",
|
|
380
|
+
"DELETE",
|
|
381
|
+
],
|
|
382
|
+
cachedMethods: ["GET", "HEAD", "OPTIONS"],
|
|
383
|
+
compress: true,
|
|
384
|
+
cachePolicyId: "4135ea2d-6df8-44a3-9df3-4b5a84be39ad",
|
|
385
|
+
originRequestPolicyId: adminOriginRequestPolicyId,
|
|
386
|
+
},
|
|
387
|
+
// Media originals: served directly from S3 via OAC
|
|
388
|
+
{
|
|
389
|
+
pathPattern: "/media/*",
|
|
390
|
+
targetOriginId: "media-s3",
|
|
391
|
+
viewerProtocolPolicy: "redirect-to-https",
|
|
392
|
+
allowedMethods: ["GET", "HEAD", "OPTIONS"],
|
|
393
|
+
cachedMethods: ["GET", "HEAD", "OPTIONS"],
|
|
394
|
+
compress: true,
|
|
395
|
+
cachePolicyId: mediaCachePolicy.id,
|
|
396
|
+
functionAssociations: [
|
|
397
|
+
{
|
|
398
|
+
eventType: "viewer-request",
|
|
399
|
+
functionArn: mediaRewriteFunction.arn,
|
|
400
|
+
},
|
|
401
|
+
],
|
|
402
|
+
},
|
|
403
|
+
// Image transforms: served via Sharp Lambda
|
|
404
|
+
{
|
|
405
|
+
pathPattern: "/img/*",
|
|
406
|
+
targetOriginId: "image-lambda",
|
|
407
|
+
viewerProtocolPolicy: "redirect-to-https",
|
|
408
|
+
allowedMethods: ["GET", "HEAD", "OPTIONS"],
|
|
409
|
+
cachedMethods: ["GET", "HEAD", "OPTIONS"],
|
|
410
|
+
compress: true,
|
|
411
|
+
cachePolicyId: imageCachePolicy.id,
|
|
412
|
+
},
|
|
413
|
+
// Health endpoint: no caching, no auth
|
|
414
|
+
{
|
|
415
|
+
pathPattern: "/health",
|
|
416
|
+
targetOriginId: "api",
|
|
417
|
+
viewerProtocolPolicy: "redirect-to-https",
|
|
418
|
+
allowedMethods: ["GET", "HEAD"],
|
|
419
|
+
cachedMethods: ["GET", "HEAD"],
|
|
420
|
+
compress: true,
|
|
421
|
+
cachePolicyId: "4135ea2d-6df8-44a3-9df3-4b5a84be39ad",
|
|
422
|
+
originRequestPolicyId: originRequestPolicy.id,
|
|
423
|
+
},
|
|
424
|
+
],
|
|
425
|
+
|
|
426
|
+
restrictions: {
|
|
427
|
+
geoRestriction: { restrictionType: "none" },
|
|
428
|
+
},
|
|
429
|
+
|
|
430
|
+
viewerCertificate,
|
|
431
|
+
},
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
// Allow CloudFront to invoke the image Lambda via OAC
|
|
435
|
+
new aws.lambda.Permission(`${name}ImageLambdaCFPermission`, {
|
|
436
|
+
action: "lambda:InvokeFunctionUrl",
|
|
437
|
+
function: image.imageLambda.name,
|
|
438
|
+
principal: "cloudfront.amazonaws.com",
|
|
439
|
+
sourceArn: distribution.arn,
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
const url = args.domain
|
|
443
|
+
? $interpolate`https://${args.domain.name}`
|
|
444
|
+
: $interpolate`https://${distribution.domainName}`;
|
|
445
|
+
|
|
446
|
+
return { distribution, url };
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export type CdnResources = ReturnType<typeof createCdn>;
|