@fjall/components-infrastructure 2.29.0 → 2.30.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/dist/lib/__tests__/setup.d.ts +48 -0
  2. package/dist/lib/__tests__/setup.js +1 -0
  3. package/dist/lib/app.d.ts +8 -4
  4. package/dist/lib/app.js +24 -6
  5. package/dist/lib/config/aws/accountId.d.ts +6 -0
  6. package/dist/lib/config/aws/accountId.js +33 -0
  7. package/dist/lib/config/aws/backupGlobalSettings.d.ts +29 -0
  8. package/dist/lib/config/aws/backupGlobalSettings.js +51 -0
  9. package/dist/lib/config/aws/costAllocationTags.d.ts +12 -0
  10. package/dist/lib/config/aws/costAllocationTags.js +48 -0
  11. package/dist/lib/config/aws/eventBus.d.ts +7 -0
  12. package/dist/lib/config/aws/eventBus.js +24 -0
  13. package/dist/lib/config/aws/identityCenterGroupMembership.d.ts +10 -0
  14. package/dist/lib/config/aws/identityCenterGroupMembership.js +115 -0
  15. package/dist/lib/config/aws/ipamDelegateAdmin.d.ts +8 -0
  16. package/dist/lib/config/aws/ipamDelegateAdmin.js +58 -0
  17. package/dist/lib/config/aws/ipamPoolId.d.ts +10 -0
  18. package/dist/lib/config/aws/ipamPoolId.js +39 -0
  19. package/dist/lib/config/aws/organisation.d.ts +30 -0
  20. package/dist/lib/config/aws/organisation.js +95 -0
  21. package/dist/lib/config/aws/organisationId.d.ts +7 -0
  22. package/dist/lib/config/aws/organisationId.js +46 -0
  23. package/dist/lib/config/aws/organisationsAccess.d.ts +10 -0
  24. package/dist/lib/config/aws/organisationsAccess.js +50 -0
  25. package/dist/lib/config/aws/ramSharing.d.ts +4 -0
  26. package/dist/lib/config/aws/ramSharing.js +35 -0
  27. package/dist/lib/config/monitoring.d.ts +18 -0
  28. package/dist/lib/config/monitoring.js +22 -0
  29. package/dist/lib/lambda-assets/static-site-forms/asset/index.js +175 -0
  30. package/dist/lib/lambda-assets/static-site-forms/asset/package.json +4 -0
  31. package/dist/lib/patterns/aws/cdn.d.ts +19 -2
  32. package/dist/lib/patterns/aws/cdn.js +13 -2
  33. package/dist/lib/patterns/aws/cicdRole.d.ts +67 -0
  34. package/dist/lib/patterns/aws/cicdRole.js +68 -0
  35. package/dist/lib/patterns/aws/compute.js +9 -0
  36. package/dist/lib/patterns/aws/computeLambda.d.ts +38 -3
  37. package/dist/lib/patterns/aws/computeLambda.js +29 -2
  38. package/dist/lib/patterns/aws/hostedZone.d.ts +28 -0
  39. package/dist/lib/patterns/aws/hostedZone.js +150 -0
  40. package/dist/lib/patterns/aws/interfaces/pattern.d.ts +134 -5
  41. package/dist/lib/patterns/aws/interfaces/pattern.js +6 -0
  42. package/dist/lib/patterns/aws/managedAccount.d.ts +9 -0
  43. package/dist/lib/patterns/aws/managedAccount.js +55 -0
  44. package/dist/lib/patterns/aws/managedIdentityCenter.d.ts +4 -0
  45. package/dist/lib/patterns/aws/managedIdentityCenter.js +24 -0
  46. package/dist/lib/patterns/aws/managedOrganisation.d.ts +36 -0
  47. package/dist/lib/patterns/aws/managedOrganisation.js +97 -0
  48. package/dist/lib/patterns/aws/managedPlatform.d.ts +12 -0
  49. package/dist/lib/patterns/aws/managedPlatform.js +29 -0
  50. package/dist/lib/patterns/aws/pattern.d.ts +20 -5
  51. package/dist/lib/patterns/aws/pattern.js +12 -6
  52. package/dist/lib/patterns/aws/staticSite.d.ts +59 -0
  53. package/dist/lib/patterns/aws/staticSite.js +307 -0
  54. package/dist/lib/patterns/aws/subdomainHostedZone.d.ts +9 -0
  55. package/dist/lib/patterns/aws/subdomainHostedZone.js +39 -0
  56. package/dist/lib/resources/aws/cdn/cloudFront.d.ts +35 -2
  57. package/dist/lib/resources/aws/cdn/cloudFront.js +63 -25
  58. package/dist/lib/resources/aws/cdn/index.d.ts +1 -0
  59. package/dist/lib/resources/aws/cdn/index.js +1 -0
  60. package/dist/lib/resources/aws/cdn/responseHeadersPolicy.d.ts +56 -0
  61. package/dist/lib/resources/aws/cdn/responseHeadersPolicy.js +92 -0
  62. package/dist/lib/resources/aws/cicd/cicdRole.d.ts +65 -0
  63. package/dist/lib/resources/aws/cicd/cicdRole.js +191 -0
  64. package/dist/lib/resources/aws/compute/ecsFreeTier.d.ts +75 -0
  65. package/dist/lib/resources/aws/compute/ecsFreeTier.js +1 -0
  66. package/dist/lib/resources/aws/compute/ecsImages.js +3 -25
  67. package/dist/lib/resources/aws/compute/ecsSpot.d.ts +75 -0
  68. package/dist/lib/resources/aws/compute/ecsSpot.js +1 -0
  69. package/dist/lib/resources/aws/compute/imageTagParameter.d.ts +12 -0
  70. package/dist/lib/resources/aws/compute/imageTagParameter.js +32 -0
  71. package/dist/lib/resources/aws/compute/lambda.d.ts +7 -0
  72. package/dist/lib/resources/aws/compute/utilities/capacityProviderDrainWaiter.d.ts +20 -0
  73. package/dist/lib/resources/aws/compute/utilities/capacityProviderDrainWaiter.js +1 -0
  74. package/dist/lib/resources/aws/database/database.d.ts +14 -0
  75. package/dist/lib/resources/aws/database/database.js +28 -0
  76. package/dist/lib/resources/aws/database/databaseInstance.d.ts +15 -0
  77. package/dist/lib/resources/aws/database/databaseInstance.js +30 -0
  78. package/dist/lib/resources/aws/iam/identityCenter/attachManagedPolicy.d.ts +13 -0
  79. package/dist/lib/resources/aws/iam/identityCenter/attachManagedPolicy.js +52 -0
  80. package/dist/lib/resources/aws/iam/securityGroup.d.ts +5 -0
  81. package/dist/lib/resources/aws/iam/securityGroup.js +14 -0
  82. package/dist/lib/resources/aws/monitoring/monitoringRole.d.ts +28 -0
  83. package/dist/lib/resources/aws/monitoring/monitoringRole.js +125 -0
  84. package/dist/lib/resources/aws/networking/domainCertificate.d.ts +7 -0
  85. package/dist/lib/resources/aws/networking/domainCertificate.js +9 -7
  86. package/dist/lib/resources/aws/utilities/cfnOutput.d.ts +5 -0
  87. package/dist/lib/resources/aws/utilities/cfnOutput.js +1 -0
  88. package/dist/lib/utils/addSuffixToEmail.js +5 -1
  89. package/dist/lib/utils/capitalizeString.d.ts +1 -0
  90. package/dist/lib/utils/capitalizeString.js +7 -0
  91. package/package.json +7 -5
  92. package/dist/lib/config/aws/identityCenterMembership.d.ts +0 -11
  93. package/dist/lib/config/aws/identityCenterMembership.js +0 -61
@@ -0,0 +1,30 @@
1
+ import { CfnOutput, type StackProps } from "aws-cdk-lib";
2
+ import { Construct } from "constructs";
3
+ import { type Account as ExistingAccounts } from "@aws-sdk/client-organizations";
4
+ import { type KeyValue } from "../../types";
5
+ interface OrganisationProps extends StackProps {
6
+ organisationName: string;
7
+ accounts: AccountsConfig;
8
+ orgEmail: string;
9
+ existingAccounts?: ExistingAccounts[];
10
+ }
11
+ type AccountsConfig = {
12
+ readonly [key: string]: readonly string[] | string;
13
+ };
14
+ /**
15
+ * A construct for creating multi environment organisations with a production, staging and development account.
16
+ */
17
+ export default class Organisation extends Construct {
18
+ accountId: CfnOutput;
19
+ private existingAccounts?;
20
+ private accounts;
21
+ private organisation;
22
+ private organisationalUnits;
23
+ constructor(scope: Construct, id: string, props: OrganisationProps);
24
+ private addOrganisation;
25
+ private addOrganisationUnitsAndAccounts;
26
+ private addOrganisationalUnit;
27
+ private addAccount;
28
+ getAccounts(): KeyValue;
29
+ }
30
+ export {};
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const aws_cdk_lib_1 = require("aws-cdk-lib");
4
+ const constructs_1 = require("constructs");
5
+ const stripAndCamelCase_1 = require("../../utils/stripAndCamelCase");
6
+ const cdk_organizations_1 = require("@pepperize/cdk-organizations");
7
+ const addSuffixToEmail_1 = require("../../utils/addSuffixToEmail");
8
+ /**
9
+ * A construct for creating multi environment organisations with a production, staging and development account.
10
+ */
11
+ class Organisation extends constructs_1.Construct {
12
+ constructor(scope, id, props) {
13
+ super(scope, id);
14
+ this.accounts = {};
15
+ this.organisationalUnits = [];
16
+ this.existingAccounts = props.existingAccounts;
17
+ this.addOrganisation(props.organisationName);
18
+ this.addOrganisationUnitsAndAccounts(props.accounts, props.orgEmail);
19
+ }
20
+ addOrganisation(organisationName) {
21
+ this.organisation = new cdk_organizations_1.Organization(this, organisationName, {
22
+ featureSet: cdk_organizations_1.FeatureSet.ALL
23
+ });
24
+ // TODO: Validate this enables trusted access
25
+ this.organisation.enableAwsServiceAccess("account.amazonaws.com");
26
+ const enforcedPolicyTypes = [
27
+ cdk_organizations_1.PolicyType.SERVICE_CONTROL_POLICY,
28
+ cdk_organizations_1.PolicyType.TAG_POLICY,
29
+ cdk_organizations_1.PolicyType.BACKUP_POLICY,
30
+ cdk_organizations_1.PolicyType.AISERVICES_OPT_OUT_POLICY
31
+ ];
32
+ for (const policyType of enforcedPolicyTypes) {
33
+ this.organisation.enablePolicyType(policyType);
34
+ }
35
+ }
36
+ addOrganisationUnitsAndAccounts(accounts, orgEmail) {
37
+ for (const [environmentType, accountList] of Object.entries(accounts)) {
38
+ // Create OU for each environment type
39
+ const ouName = environmentType.charAt(0).toUpperCase() + environmentType.slice(1);
40
+ const orgUnit = this.addOrganisationalUnit(`${ouName}OrganizationalUnit`, ouName);
41
+ // Convert single account to array for uniform processing
42
+ const accountsArray = Array.isArray(accountList)
43
+ ? accountList
44
+ : [accountList];
45
+ // Add accounts to this OU
46
+ for (const accountName of accountsArray) {
47
+ const accountNameId = (0, stripAndCamelCase_1.stripAndCamelCase)(accountName);
48
+ const importedAccount = this.existingAccounts?.find((account) => account.Name === accountName);
49
+ const accountEmail = importedAccount
50
+ ? importedAccount.Email
51
+ : (0, addSuffixToEmail_1.default)(orgEmail, accountNameId.toLowerCase());
52
+ if (accountEmail) {
53
+ this.addAccount(`${accountNameId}Account`, accountName, accountEmail, { env: environmentType }, orgUnit);
54
+ }
55
+ }
56
+ }
57
+ }
58
+ addOrganisationalUnit(id, name) {
59
+ const ou = new cdk_organizations_1.OrganizationalUnit(this, id, {
60
+ organizationalUnitName: name,
61
+ parent: this.organisation.root,
62
+ importOnDuplicate: true
63
+ });
64
+ this.organisationalUnits.push(ou);
65
+ return ou;
66
+ }
67
+ addAccount(id, name, email, tags, organisationalUnit) {
68
+ const acc = new cdk_organizations_1.Account(this, id, {
69
+ accountName: name,
70
+ email: email,
71
+ parent: organisationalUnit,
72
+ importOnDuplicate: true
73
+ });
74
+ for (const tag in tags) {
75
+ aws_cdk_lib_1.Tags.of(acc).add(tag, tags[tag]);
76
+ }
77
+ this.accounts[name] = acc.accountId;
78
+ new aws_cdk_lib_1.CfnOutput(this, `${id}Id`, {
79
+ key: `${id}Id`,
80
+ value: acc.accountId,
81
+ exportName: `${id}Id`
82
+ });
83
+ new aws_cdk_lib_1.CfnOutput(this, `${id}Arn`, {
84
+ key: `${id}Arn`,
85
+ value: acc.accountArn,
86
+ exportName: `${id}Arn`
87
+ });
88
+ return acc;
89
+ }
90
+ getAccounts() {
91
+ return this.accounts;
92
+ }
93
+ }
94
+ exports.default = Organisation;
95
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3JnYW5pc2F0aW9uLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vbGliL2NvbmZpZy9hd3Mvb3JnYW5pc2F0aW9uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEsNkNBQStEO0FBQy9ELDJDQUF1QztBQUN2QyxxRUFBa0U7QUFFbEUsb0VBTXNDO0FBQ3RDLG1FQUE0RDtBQWM1RDs7R0FFRztBQUVILE1BQXFCLFlBQWEsU0FBUSxzQkFBUztJQVFqRCxZQUFZLEtBQWdCLEVBQUUsRUFBVSxFQUFFLEtBQXdCO1FBQ2hFLEtBQUssQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFMWCxhQUFRLEdBQWEsRUFBRSxDQUFDO1FBRXhCLHdCQUFtQixHQUF5QixFQUFFLENBQUM7UUFLckQsSUFBSSxDQUFDLGdCQUFnQixHQUFHLEtBQUssQ0FBQyxnQkFBZ0IsQ0FBQztRQUMvQyxJQUFJLENBQUMsZUFBZSxDQUFDLEtBQUssQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO1FBQzdDLElBQUksQ0FBQywrQkFBK0IsQ0FBQyxLQUFLLENBQUMsUUFBUSxFQUFFLEtBQUssQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUN2RSxDQUFDO0lBRU8sZUFBZSxDQUFDLGdCQUF3QjtRQUM5QyxJQUFJLENBQUMsWUFBWSxHQUFHLElBQUksZ0NBQVksQ0FBQyxJQUFJLEVBQUUsZ0JBQWdCLEVBQUU7WUFDM0QsVUFBVSxFQUFFLDhCQUFVLENBQUMsR0FBRztTQUMzQixDQUFDLENBQUM7UUFFSCw2Q0FBNkM7UUFDN0MsSUFBSSxDQUFDLFlBQVksQ0FBQyxzQkFBc0IsQ0FBQyx1QkFBdUIsQ0FBQyxDQUFDO1FBRWxFLE1BQU0sbUJBQW1CLEdBQWlCO1lBQ3hDLDhCQUFVLENBQUMsc0JBQXNCO1lBQ2pDLDhCQUFVLENBQUMsVUFBVTtZQUNyQiw4QkFBVSxDQUFDLGFBQWE7WUFDeEIsOEJBQVUsQ0FBQyx5QkFBeUI7U0FDckMsQ0FBQztRQUVGLEtBQUssTUFB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dUJBQVMsQ0FBQyxJQUFJLEVBQUUsR0FBRyxFQUFFLElBQUksRUFBRTtZQUM3QixHQUFHLEVBQUUsR0FBRyxFQUFFLElBQUk7WUFDZCxLQUFLLEVBQUUsR0FBRyxDQUFDLFNBQVM7WUFDcEIsVUFBVSxFQUFFLEdBQUcsRUFBRSxJQUFJO1NBQ3RCLENBQUMsQ0FBQztRQUNILElBQUksdUJBQVMsQ0FBQyxJQUFJLEVBQUUsR0FBRyxFQUFFLEtBQUssRUFBRTtZQUM5QixHQUFHLEVBQUUsR0FBRyxFQUFFLEtBQUs7WUFDZixLQUFLLEVBQUUsR0FBRyxDQUFDLFVBQVU7WUFDckIsVUFBVSxFQUFFLEdBQUcsRUFBRSxLQUFLO1NBQ3ZCLENBQUMsQ0FBQztRQUVILE9BQU8sR0FBRyxDQUFDO0lBQ2IsQ0FBQztJQUVNLFdBQVc7UUFDaEIsT0FBTyxJQUFJLENBQUMsUUFBUSxDQUFDO0lBQ3ZCLENBQUM7Q0FDRjtBQTlIRCwrQkE4SEMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDZm5PdXRwdXQsIFRhZ3MsIHR5cGUgU3RhY2tQcm9wcyB9IGZyb20gXCJhd3MtY2RrLWxpYlwiO1xuaW1wb3J0IHsgQ29uc3RydWN0IH0gZnJvbSBcImNvbnN0cnVjdHNcIjtcbmltcG9ydCB7IHN0cmlwQW5kQ2FtZWxDYXNlIH0gZnJvbSBcIi4uLy4uL3V0aWxzL3N0cmlwQW5kQ2FtZWxDYXNlXCI7XG5pbXBvcnQgeyB0eXBlIEFjY291bnQgYXMgRXhpc3RpbmdBY2NvdW50cyB9IGZyb20gXCJAYXdzLXNkay9jbGllbnQtb3JnYW5pemF0aW9uc1wiO1xuaW1wb3J0IHtcbiAgQWNjb3VudCxcbiAgRmVhdHVyZVNldCxcbiAgT3JnYW5pemF0aW9uLFxuICBPcmdhbml6YXRpb25hbFVuaXQsXG4gIFBvbGljeVR5cGVcbn0gZnJvbSBcIkBwZXBwZXJpemUvY2RrLW9yZ2FuaXphdGlvbnNcIjtcbmltcG9ydCBhZGRTdWZmaXhUb0VtYWlsIGZyb20gXCIuLi8uLi91dGlscy9hZGRTdWZmaXhUb0VtYWlsXCI7XG5pbXBvcnQgeyB0eXBlIEtleVZhbHVlIH0gZnJvbSBcIi4uLy4uL3R5cGVzXCI7XG5cbmludGVyZmFjZSBPcmdhbmlzYXRpb25Qcm9wcyBleHRlbmRzIFN0YWNrUHJvcHMge1xuICBvcmdhbmlzYXRpb25OYW1lOiBzdHJpbmc7XG4gIGFjY291bnRzOiBBY2NvdW50c0NvbmZpZztcbiAgb3JnRW1haWw6IHN0cmluZztcbiAgZXhpc3RpbmdBY2NvdW50cz86IEV4aXN0aW5nQWNjb3VudHNbXTtcbn1cblxudHlwZSBBY2NvdW50c0NvbmZpZyA9IHtcbiAgcmVhZG9ubHkgW2tleTogc3RyaW5nXTogcmVhZG9ubHkgc3RyaW5nW10gfCBzdHJpbmc7XG59O1xuXG4vKipcbiAqIEEgY29uc3RydWN0IGZvciBjcmVhdGluZyBtdWx0aSBlbnZpcm9ubWVudCBvcmdhbmlzYXRpb25zIHdpdGggYSBwcm9kdWN0aW9uLCBzdGFnaW5nIGFuZCBkZXZlbG9wbWVudCBhY2NvdW50LlxuICovXG5cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIE9yZ2FuaXNhdGlvbiBleHRlbmRzIENvbnN0cnVjdCB7XG4gIHB1YmxpYyBhY2NvdW50SWQ6IENmbk91dHB1dDtcblxuICBwcml2YXRlIGV4aXN0aW5nQWNjb3VudHM/OiBFeGlzdGluZ0FjY291bnRzW107XG4gIHByaXZhdGUgYWNjb3VudHM6IEtleVZhbHVlID0ge307XG4gIHByaXZhdGUgb3JnYW5pc2F0aW9uOiBPcmdhbml6YXRpb247XG4gIHByaXZhdGUgb3JnYW5pc2F0aW9uYWxVbml0czogT3JnYW5pemF0aW9uYWxVbml0W10gPSBbXTtcblxuICBjb25zdHJ1Y3RvcihzY29wZTogQ29uc3RydWN0LCBpZDogc3RyaW5nLCBwcm9wczogT3JnYW5pc2F0aW9uUHJvcHMpIHtcbiAgICBzdXBlcihzY29wZSwgaWQpO1xuXG4gICAgdGhpcy5leGlzdGluZ0FjY291bnRzID0gcHJvcHMuZXhpc3RpbmdBY2NvdW50cztcbiAgICB0aGlzLmFkZE9yZ2FuaXNhdGlvbihwcm9wcy5vcmdhbmlzYXRpb25OYW1lKTtcbiAgICB0aGlzLmFkZE9yZ2FuaXNhdGlvblVuaXRzQW5kQWNjb3VudHMocHJvcHMuYWNjb3VudHMsIHByb3BzLm9yZ0VtYWlsKTtcbiAgfVxuXG4gIHByaXZhdGUgYWRkT3JnYW5pc2F0aW9uKG9yZ2FuaXNhdGlvbk5hbWU6IHN0cmluZykge1xuICAgIHRoaXMub3JnYW5pc2F0aW9uID0gbmV3IE9yZ2FuaXphdGlvbih0aGlzLCBvcmdhbmlzYXRpb25OYW1lLCB7XG4gICAgICBmZWF0dXJlU2V0OiBGZWF0dXJlU2V0LkFMTFxuICAgIH0pO1xuXG4gICAgLy8gVE9ETzogVmFsaWRhdGUgdGhpcyBlbmFibGVzIHRydXN0ZWQgYWNjZXNzXG4gICAgdGhpcy5vcmdhbmlzYXRpb24uZW5hYmxlQXdzU2VydmljZUFjY2VzcyhcImFjY291bnQuYW1hem9uYXdzLmNvbVwiKTtcblxuICAgIGNvbnN0IGVuZm9yY2VkUG9saWN5VHlwZXM6IFBvbGljeVR5cGVbXSA9IFtcbiAgICAgIFBvbGljeVR5cGUuU0VSVklDRV9DT05UUk9MX1BPTElDWSxcbiAgICAgIFBvbGljeVR5cGUuVEFHX1BPTElDWSxcbiAgICAgIFBvbGljeVR5cGUuQkFDS1VQX1BPTElDWSxcbiAgICAgIFBvbGljeVR5cGUuQUlTRVJWSUNFU19PUFRfT1VUX1BPTElDWVxuICAgIF07XG5cbiAgICBmb3IgKGNvbnN0IHBvbGljeVR5cGUgb2YgZW5mb3JjZWRQb2xpY3lUeXBlcykge1xuICAgICAgdGhpcy5vcmdhbmlzYXRpb24uZW5hYmxlUG9saWN5VHlwZShwb2xpY3lUeXBlKTtcbiAgICB9XG4gIH1cblxuICBwcml2YXRlIGFkZE9yZ2FuaXNhdGlvblVuaXRzQW5kQWNjb3VudHMoXG4gICAgYWNjb3VudHM6IEFjY291bnRzQ29uZmlnLFxuICAgIG9yZ0VtYWlsOiBzdHJpbmdcbiAgKSB7XG4gICAgZm9yIChjb25zdCBbZW52aXJvbm1lbnRUeXBlLCBhY2NvdW50TGlzdF0gb2YgT2JqZWN0LmVudHJpZXMoYWNjb3VudHMpKSB7XG4gICAgICAvLyBDcmVhdGUgT1UgZm9yIGVhY2ggZW52aXJvbm1lbnQgdHlwZVxuICAgICAgY29uc3Qgb3VOYW1lID1cbiAgICAgICAgZW52aXJvbm1lbnRUeXBlLmNoYXJBdCgwKS50b1VwcGVyQ2FzZSgpICsgZW52aXJvbm1lbnRUeXBlLnNsaWNlKDEpO1xuICAgICAgY29uc3Qgb3JnVW5pdCA9IHRoaXMuYWRkT3JnYW5pc2F0aW9uYWxVbml0KFxuICAgICAgICBgJHtvdU5hbWV9T3JnYW5pemF0aW9uYWxVbml0YCxcbiAgICAgICAgb3VOYW1lXG4gICAgICApO1xuXG4gICAgICAvLyBDb252ZXJ0IHNpbmdsZSBhY2NvdW50IHRvIGFycmF5IGZvciB1bmlmb3JtIHByb2Nlc3NpbmdcbiAgICAgIGNvbnN0IGFjY291bnRzQXJyYXkgPSBBcnJheS5pc0FycmF5KGFjY291bnRMaXN0KVxuICAgICAgICA/IGFjY291bnRMaXN0XG4gICAgICAgIDogW2FjY291bnRMaXN0XTtcblxuICAgICAgLy8gQWRkIGFjY291bnRzIHRvIHRoaXMgT1VcbiAgICAgIGZvciAoY29uc3QgYWNjb3VudE5hbWUgb2YgYWNjb3VudHNBcnJheSkge1xuICAgICAgICBjb25zdCBhY2NvdW50TmFtZUlkID0gc3RyaXBBbmRDYW1lbENhc2UoYWNjb3VudE5hbWUpO1xuXG4gICAgICAgIGNvbnN0IGltcG9ydGVkQWNjb3VudCA9IHRoaXMuZXhpc3RpbmdBY2NvdW50cz8uZmluZChcbiAgICAgICAgICAoYWNjb3VudCkgPT4gYWNjb3VudC5OYW1lID09PSBhY2NvdW50TmFtZVxuICAgICAgICApO1xuICAgICAgICBjb25zdCBhY2NvdW50RW1haWwgPSBpbXBvcnRlZEFjY291bnRcbiAgICAgICAgICA/IGltcG9ydGVkQWNjb3VudC5FbWFpbFxuICAgICAgICAgIDogYWRkU3VmZml4VG9FbWFpbChvcmdFbWFpbCwgYWNjb3VudE5hbWVJZC50b0xvd2VyQ2FzZSgpKTtcblxuICAgICAgICBpZiAoYWNjb3VudEVtYWlsKSB7XG4gICAgICAgICAgdGhpcy5hZGRBY2NvdW50KFxuICAgICAgICAgICAgYCR7YWNjb3VudE5hbWVJZH1BY2NvdW50YCxcbiAgICAgICAgICAgIGFjY291bnROYW1lLFxuICAgICAgICAgICAgYWNjb3VudEVtYWlsLFxuICAgICAgICAgICAgeyBlbnY6IGVudmlyb25tZW50VHlwZSB9LFxuICAgICAgICAgICAgb3JnVW5pdFxuICAgICAgICAgICk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICBwcml2YXRlIGFkZE9yZ2FuaXNhdGlvbmFsVW5pdChpZDogc3RyaW5nLCBuYW1lOiBzdHJpbmcpOiBPcmdhbml6YXRpb25hbFVuaXQge1xuICAgIGNvbnN0IG91ID0gbmV3IE9yZ2FuaXphdGlvbmFsVW5pdCh0aGlzLCBpZCwge1xuICAgICAgb3JnYW5pemF0aW9uYWxVbml0TmFtZTogbmFtZSxcbiAgICAgIHBhcmVudDogdGhpcy5vcmdhbmlzYXRpb24ucm9vdCxcbiAgICAgIGltcG9ydE9uRHVwbGljYXRlOiB0cnVlXG4gICAgfSk7XG5cbiAgICB0aGlzLm9yZ2FuaXNhdGlvbmFsVW5pdHMucHVzaChvdSk7XG4gICAgcmV0dXJuIG91O1xuICB9XG5cbiAgcHJpdmF0ZSBhZGRBY2NvdW50KFxuICAgIGlkOiBzdHJpbmcsXG4gICAgbmFtZTogc3RyaW5nLFxuICAgIGVtYWlsOiBzdHJpbmcsXG4gICAgdGFnczogeyBba2V5OiBzdHJpbmddOiBzdHJpbmcgfSxcbiAgICBvcmdhbmlzYXRpb25hbFVuaXQ6IE9yZ2FuaXphdGlvbmFsVW5pdFxuICApOiBBY2NvdW50IHtcbiAgICBjb25zdCBhY2MgPSBuZXcgQWNjb3VudCh0aGlzLCBpZCwge1xuICAgICAgYWNjb3VudE5hbWU6IG5hbWUsXG4gICAgICBlbWFpbDogZW1haWwsXG4gICAgICBwYXJlbnQ6IG9yZ2FuaXNhdGlvbmFsVW5pdCxcbiAgICAgIGltcG9ydE9uRHVwbGljYXRlOiB0cnVlXG4gICAgfSk7XG5cbiAgICBmb3IgKGNvbnN0IHRhZyBpbiB0YWdzKSB7XG4gICAgICBUYWdzLm9mKGFjYykuYWRkKHRhZywgdGFnc1t0YWddKTtcbiAgICB9XG5cbiAgICB0aGlzLmFjY291bnRzW25hbWVdID0gYWNjLmFjY291bnRJZDtcblxuICAgIG5ldyBDZm5PdXRwdXQodGhpcywgYCR7aWR9SWRgLCB7XG4gICAgICBrZXk6IGAke2lkfUlkYCxcbiAgICAgIHZhbHVlOiBhY2MuYWNjb3VudElkLFxuICAgICAgZXhwb3J0TmFtZTogYCR7aWR9SWRgXG4gICAgfSk7XG4gICAgbmV3IENmbk91dHB1dCh0aGlzLCBgJHtpZH1Bcm5gLCB7XG4gICAgICBrZXk6IGAke2lkfUFybmAsXG4gICAgICB2YWx1ZTogYWNjLmFjY291bnRBcm4sXG4gICAgICBleHBvcnROYW1lOiBgJHtpZH1Bcm5gXG4gICAgfSk7XG5cbiAgICByZXR1cm4gYWNjO1xuICB9XG5cbiAgcHVibGljIGdldEFjY291bnRzKCkge1xuICAgIHJldHVybiB0aGlzLmFjY291bnRzO1xuICB9XG59XG4iXX0=
@@ -0,0 +1,7 @@
1
+ import { CfnOutput } from "aws-cdk-lib";
2
+ import { Construct } from "constructs";
3
+ export declare class OrganisationId extends Construct {
4
+ readonly organisationId: CfnOutput;
5
+ readonly organisationAccountArn: CfnOutput;
6
+ constructor(scope: Construct, id: string);
7
+ }
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OrganisationId = void 0;
4
+ const aws_cdk_lib_1 = require("aws-cdk-lib");
5
+ const customResources = require("aws-cdk-lib/custom-resources");
6
+ const aws_iam_1 = require("aws-cdk-lib/aws-iam");
7
+ const constructs_1 = require("constructs");
8
+ const awsCustomResource_1 = require("../../resources/aws/utilities/awsCustomResource");
9
+ class OrganisationId extends constructs_1.Construct {
10
+ constructor(scope, id) {
11
+ super(scope, id);
12
+ const organisation = new awsCustomResource_1.AwsCustomResource(this, "OrganisationResource", {
13
+ functionName: "organisationId",
14
+ onCreate: {
15
+ service: "organizations",
16
+ action: "DescribeOrganizationCommand", // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Organizations.html#describeOrganization-property
17
+ physicalResourceId: customResources.PhysicalResourceId.of("organisationId")
18
+ },
19
+ onUpdate: {
20
+ service: "organizations",
21
+ action: "DescribeOrganizationCommand", // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Organizations.html#describeOrganization-property
22
+ physicalResourceId: customResources.PhysicalResourceId.of("organisationId")
23
+ },
24
+ resourceType: "Custom::DescribeOrganisation",
25
+ policy: customResources.AwsCustomResourcePolicy.fromStatements([
26
+ new aws_iam_1.PolicyStatement({
27
+ actions: ["organizations:DescribeOrganization"],
28
+ resources: ["*"],
29
+ effect: aws_iam_1.Effect.ALLOW
30
+ })
31
+ ])
32
+ });
33
+ this.organisationId = new aws_cdk_lib_1.CfnOutput(this, "OrganisationIdOutput", {
34
+ key: "OrganisationId",
35
+ value: organisation.getResponseField("Organization.Id"),
36
+ exportName: "OrganisationId"
37
+ });
38
+ this.organisationAccountArn = new aws_cdk_lib_1.CfnOutput(this, "OrganisationAccountArnOutput", {
39
+ key: "OrganisationAccountArn",
40
+ value: organisation.getResponseField("Organization.MasterAccountArn"),
41
+ exportName: "OrganisationAccountArn"
42
+ });
43
+ }
44
+ }
45
+ exports.OrganisationId = OrganisationId;
46
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3JnYW5pc2F0aW9uSWQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9saWIvY29uZmlnL2F3cy9vcmdhbmlzYXRpb25JZC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSw2Q0FBd0M7QUFDeEMsZ0VBQWdFO0FBQ2hFLGlEQUE4RDtBQUM5RCwyQ0FBdUM7QUFFdkMsdUZBQW9GO0FBRXBGLE1BQWEsY0FBZSxTQUFRLHNCQUFTO0lBSTNDLFlBQVksS0FBZ0IsRUFBRSxFQUFVO1FBQ3RDLEtBQUssQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFFakIsTUFBTSxZQUFZLEdBQUcsSUFBSSxxQ0FBaUIsQ0FBQyxJQUFJLEVBQUUsc0JBQXNCLEVBQUU7WUFDdkUsWUFBWSxFQUFFLGdCQUFnQjtZQUM5QixRQUFRLEVBQUU7Z0JBQ1IsT0FBTyxFQUFFLGVBQWU7Z0JBQ3hCLE1BQU0sRUFBRSw2QkFBNkIsRUFBRSwyR0FBMkc7Z0JBQ2xKLGtCQUFrQixFQUNoQixlQUFlLENBQUMsa0JBQWtCLENBQUMsRUFBRSxDQUFDLGdCQUFnQixDQUFDO2FBQzFEO1lBQ0QsUUFBUSxFQUFFO2dCQUNSLE9BQU8sRUFBRSxlQUFlO2dCQUN4QixNQUFNLEVBQUUsNkJBQTZCLEVBQUUsMkdBQTJHO2dCQUNsSixrQkFBa0IsRUFDaEIsZUFBZSxDQUFDLGtCQUFrQixDQUFDLEVBQUUsQ0FBQyxnQkFBZ0IsQ0FBQzthQUMxRDtZQUNELFlBQVksRUFBRSw4QkFBOEI7WUFDNUMsTUFBTSxFQUFFLGVBQWUsQ0FBQyx1QkFBdUIsQ0FBQyxjQUFjLENBQUM7Z0JBQzdELElBQUkseUJBQWUsQ0FBQztvQkFDbEIsT0FBTyxFQUFFLENBQUMsb0NBQW9DLENBQUM7b0JBQy9DLFNBQVMsRUFBRSxDQUFDLEdBQUcsQ0FBQztvQkFDaEIsTUFBTSxFQUFFLGdCQUFNLENBQUMsS0FBSztpQkFDckIsQ0FBQzthQUNILENBQUM7U0FDSCxDQUFDLENBQUM7UUFFSCxJQUFJLENBQUMsY0FBYyxHQUFHLElBQUksdUJBQVMsQ0FBQyxJQUFJLEVBQUUsc0JBQXNCLEVBQUU7WUFDaEUsR0FBRyxFQUFFLGdCQUFnQjtZQUNyQixLQUFLLEVBQUUsWUFBWSxDQUFDLGdCQUFnQixDQUFDLGlCQUFpQixDQUFDO1lBQ3ZELFVBQVUsRUFBRSxnQkFBZ0I7U0FDN0IsQ0FBQyxDQUFDO1FBRUgsSUFBSSxDQUFDLHNCQUFzQixHQUFHLElBQUksdUJBQVMsQ0FDekMsSUFBSSxFQUNKLDhCQUE4QixFQUM5QjtZQUNFLEdBQUcsRUFBRSx3QkFBd0I7WUFDN0IsS0FBSyxFQUFFLFlBQVksQ0FBQyxnQkFBZ0IsQ0FBQywrQkFBK0IsQ0FBQztZQUNyRSxVQUFVLEVBQUUsd0JBQXdCO1NBQ3JDLENBQ0YsQ0FBQztJQUNKLENBQUM7Q0FDRjtBQS9DRCx3Q0ErQ0MiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDZm5PdXRwdXQgfSBmcm9tIFwiYXdzLWNkay1saWJcIjtcbmltcG9ydCAqIGFzIGN1c3RvbVJlc291cmNlcyBmcm9tIFwiYXdzLWNkay1saWIvY3VzdG9tLXJlc291cmNlc1wiO1xuaW1wb3J0IHsgRWZmZWN0LCBQb2xpY3lTdGF0ZW1lbnQgfSBmcm9tIFwiYXdzLWNkay1saWIvYXdzLWlhbVwiO1xuaW1wb3J0IHsgQ29uc3RydWN0IH0gZnJvbSBcImNvbnN0cnVjdHNcIjtcblxuaW1wb3J0IHsgQXdzQ3VzdG9tUmVzb3VyY2UgfSBmcm9tIFwiLi4vLi4vcmVzb3VyY2VzL2F3cy91dGlsaXRpZXMvYXdzQ3VzdG9tUmVzb3VyY2VcIjtcblxuZXhwb3J0IGNsYXNzIE9yZ2FuaXNhdGlvbklkIGV4dGVuZHMgQ29uc3RydWN0IHtcbiAgcHVibGljIHJlYWRvbmx5IG9yZ2FuaXNhdGlvbklkOiBDZm5PdXRwdXQ7XG4gIHB1YmxpYyByZWFkb25seSBvcmdhbmlzYXRpb25BY2NvdW50QXJuOiBDZm5PdXRwdXQ7XG5cbiAgY29uc3RydWN0b3Ioc2NvcGU6IENvbnN0cnVjdCwgaWQ6IHN0cmluZykge1xuICAgIHN1cGVyKHNjb3BlLCBpZCk7XG5cbiAgICBjb25zdCBvcmdhbmlzYXRpb24gPSBuZXcgQXdzQ3VzdG9tUmVzb3VyY2UodGhpcywgXCJPcmdhbmlzYXRpb25SZXNvdXJjZVwiLCB7XG4gICAgICBmdW5jdGlvbk5hbWU6IFwib3JnYW5pc2F0aW9uSWRcIixcbiAgICAgIG9uQ3JlYXRlOiB7XG4gICAgICAgIHNlcnZpY2U6IFwib3JnYW5pemF0aW9uc1wiLFxuICAgICAgICBhY3Rpb246IFwiRGVzY3JpYmVPcmdhbml6YXRpb25Db21tYW5kXCIsIC8vIGh0dHBzOi8vZG9jcy5hd3MuYW1hem9uLmNvbS9BV1NKYXZhU2NyaXB0U0RLL2xhdGVzdC9BV1MvT3JnYW5pemF0aW9ucy5odG1sI2Rlc2NyaWJlT3JnYW5pemF0aW9uLXByb3BlcnR5XG4gICAgICAgIHBoeXNpY2FsUmVzb3VyY2VJZDpcbiAgICAgICAgICBjdXN0b21SZXNvdXJjZXMuUGh5c2ljYWxSZXNvdXJjZUlkLm9mKFwib3JnYW5pc2F0aW9uSWRcIilcbiAgICAgIH0sXG4gICAgICBvblVwZGF0ZToge1xuICAgICAgICBzZXJ2aWNlOiBcIm9yZ2FuaXphdGlvbnNcIixcbiAgICAgICAgYWN0aW9uOiBcIkRlc2NyaWJlT3JnYW5pemF0aW9uQ29tbWFuZFwiLCAvLyBodHRwczovL2RvY3MuYXdzLmFtYXpvbi5jb20vQVdTSmF2YVNjcmlwdFNESy9sYXRlc3QvQVdTL09yZ2FuaXphdGlvbnMuaHRtbCNkZXNjcmliZU9yZ2FuaXphdGlvbi1wcm9wZXJ0eVxuICAgICAgICBwaHlzaWNhbFJlc291cmNlSWQ6XG4gICAgICAgICAgY3VzdG9tUmVzb3VyY2VzLlBoeXNpY2FsUmVzb3VyY2VJZC5vZihcIm9yZ2FuaXNhdGlvbklkXCIpXG4gICAgICB9LFxuICAgICAgcmVzb3VyY2VUeXBlOiBcIkN1c3RvbTo6RGVzY3JpYmVPcmdhbmlzYXRpb25cIixcbiAgICAgIHBvbGljeTogY3VzdG9tUmVzb3VyY2VzLkF3c0N1c3RvbVJlc291cmNlUG9saWN5LmZyb21TdGF0ZW1lbnRzKFtcbiAgICAgICAgbmV3IFBvbGljeVN0YXRlbWVudCh7XG4gICAgICAgICAgYWN0aW9uczogW1wib3JnYW5pemF0aW9uczpEZXNjcmliZU9yZ2FuaXphdGlvblwiXSxcbiAgICAgICAgICByZXNvdXJjZXM6IFtcIipcIl0sXG4gICAgICAgICAgZWZmZWN0OiBFZmZlY3QuQUxMT1dcbiAgICAgICAgfSlcbiAgICAgIF0pXG4gICAgfSk7XG5cbiAgICB0aGlzLm9yZ2FuaXNhdGlvbklkID0gbmV3IENmbk91dHB1dCh0aGlzLCBcIk9yZ2FuaXNhdGlvbklkT3V0cHV0XCIsIHtcbiAgICAgIGtleTogXCJPcmdhbmlzYXRpb25JZFwiLFxuICAgICAgdmFsdWU6IG9yZ2FuaXNhdGlvbi5nZXRSZXNwb25zZUZpZWxkKFwiT3JnYW5pemF0aW9uLklkXCIpLFxuICAgICAgZXhwb3J0TmFtZTogXCJPcmdhbmlzYXRpb25JZFwiXG4gICAgfSk7XG5cbiAgICB0aGlzLm9yZ2FuaXNhdGlvbkFjY291bnRBcm4gPSBuZXcgQ2ZuT3V0cHV0KFxuICAgICAgdGhpcyxcbiAgICAgIFwiT3JnYW5pc2F0aW9uQWNjb3VudEFybk91dHB1dFwiLFxuICAgICAge1xuICAgICAgICBrZXk6IFwiT3JnYW5pc2F0aW9uQWNjb3VudEFyblwiLFxuICAgICAgICB2YWx1ZTogb3JnYW5pc2F0aW9uLmdldFJlc3BvbnNlRmllbGQoXCJPcmdhbml6YXRpb24uTWFzdGVyQWNjb3VudEFyblwiKSxcbiAgICAgICAgZXhwb3J0TmFtZTogXCJPcmdhbmlzYXRpb25BY2NvdW50QXJuXCJcbiAgICAgIH1cbiAgICApO1xuICB9XG59XG4iXX0=
@@ -0,0 +1,10 @@
1
+ import { Construct } from "constructs";
2
+ import { type StackProps } from "aws-cdk-lib";
3
+ interface ActivateOrganisationsAccessProps extends StackProps {
4
+ account: string;
5
+ region: string;
6
+ }
7
+ export declare class ActivateOrganisationsAccess extends Construct {
8
+ constructor(scope: Construct, id: string, props: ActivateOrganisationsAccessProps);
9
+ }
10
+ export {};
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ActivateOrganisationsAccess = void 0;
4
+ const customResources = require("aws-cdk-lib/custom-resources");
5
+ const constructs_1 = require("constructs");
6
+ const awsCustomResource_1 = require("../../resources/aws/utilities/awsCustomResource");
7
+ const aws_iam_1 = require("aws-cdk-lib/aws-iam");
8
+ class ActivateOrganisationsAccess extends constructs_1.Construct {
9
+ constructor(scope, id, props) {
10
+ super(scope, id);
11
+ new awsCustomResource_1.AwsCustomResource(this, "activateOrganisationsAccess", {
12
+ functionName: "activateOrganisationsAccess",
13
+ onCreate: {
14
+ service: "cloudformation",
15
+ action: "ActivateOrganizationsAccessCommand", // https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/cloudformation/command/ActivateOrganizationsAccessCommand/
16
+ parameters: {},
17
+ physicalResourceId: customResources.PhysicalResourceId.of("activateOrganisationsAccess")
18
+ },
19
+ resourceType: "Custom::ActivateOrganisationsAccess",
20
+ policy: customResources.AwsCustomResourcePolicy.fromStatements([
21
+ new aws_iam_1.PolicyStatement({
22
+ actions: [
23
+ "cloudformation:ActivateOrganizationsAccess",
24
+ "cloudformation:DescribeOrganizationsAccess",
25
+ "organizations:EnableAWSServiceAccess",
26
+ "organizations:DescribeOrganization",
27
+ "organizations:ListRoots",
28
+ "organizations:ListAWSServiceAccessForOrganization",
29
+ "organizations:DisableAWSServiceAccess",
30
+ "iam:CreateServiceLinkedRole",
31
+ "iam:GetRole"
32
+ ],
33
+ resources: [
34
+ "*" // TODO: Figure out what access is required here
35
+ ],
36
+ effect: aws_iam_1.Effect.ALLOW
37
+ }),
38
+ new aws_iam_1.PolicyStatement({
39
+ actions: ["cloudformation:ActivateOrganizationsAccess"],
40
+ resources: [
41
+ `arn:aws:cloudformation:${props.region}:${props.account}:stackset/*`
42
+ ],
43
+ effect: aws_iam_1.Effect.ALLOW
44
+ })
45
+ ])
46
+ });
47
+ }
48
+ }
49
+ exports.ActivateOrganisationsAccess = ActivateOrganisationsAccess;
50
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3JnYW5pc2F0aW9uc0FjY2Vzcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL2xpYi9jb25maWcvYXdzL29yZ2FuaXNhdGlvbnNBY2Nlc3MudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsZ0VBQWdFO0FBQ2hFLDJDQUF1QztBQUV2Qyx1RkFBb0Y7QUFDcEYsaURBQThEO0FBUTlELE1BQWEsMkJBQTRCLFNBQVEsc0JBQVM7SUFDeEQsWUFDRSxLQUFnQixFQUNoQixFQUFVLEVBQ1YsS0FBdUM7UUFFdkMsS0FBSyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQztRQUVqQixJQUFJLHFDQUFpQixDQUFDLElBQUksRUFBRSw2QkFBNkIsRUFBRTtZQUN6RCxZQUFZLEVBQUUsNkJBQTZCO1lBQzNDLFFBQVEsRUFBRTtnQkFDUixPQUFPLEVBQUUsZ0JBQWdCO2dCQUN6QixNQUFNLEVBQUUsb0NBQW9DLEVBQUUsMkhBQTJIO2dCQUN6SyxVQUFVLEVBQUUsRUFBRTtnQkFDZCxrQkFBa0IsRUFBRSxlQUFlLENBQUMsa0JBQWtCLENBQUMsRUFBRSxDQUN2RCw2QkFBNkIsQ0FDOUI7YUFDRjtZQUNELFlBQVksRUFBRSxxQ0FBcUM7WUFDbkQsTUFBTSxFQUFFLGVBQWUsQ0FBQyx1QkFBdUIsQ0FBQyxjQUFjLENBQUM7Z0JBQzdELElBQUkseUJBQWUsQ0FBQztvQkFDbEIsT0FBTyxFQUFFO3dCQUNQLDRDQUE0Qzt3QkFDNUMsNENBQTRDO3dCQUM1QyxzQ0FBc0M7d0JBQ3RDLG9DQUFvQzt3QkFDcEMseUJBQXlCO3dCQUN6QixtREFBbUQ7d0JBQ25ELHVDQUF1Qzt3QkFDdkMsNkJBQTZCO3dCQUM3QixhQUFhO3FCQUNkO29CQUNELFNBQVMsRUFBRTt3QkFDVCxHQUFHLENBQUMsZ0RBQWdEO3FCQUNyRDtvQkFDRCxNQUFNLEVBQUUsZ0JBQU0sQ0FBQyxLQUFLO2lCQUNyQixDQUFDO2dCQUNGLElBQUkseUJBQWUsQ0FBQztvQkFDbEIsT0FBTyxFQUFFLENBQUMsNENBQTRDLENBQUM7b0JBQ3ZELFNBQVMsRUFBRTt3QkFDVCwwQkFBMEIsS0FBSyxDQUFDLE1BQU0sSUFBSSxLQUFLLENBQUMsT0FBTyxhQUFhO3FCQUNyRTtvQkFDRCxNQUFNLEVBQUUsZ0JBQU0sQ0FBQyxLQUFLO2lCQUNyQixDQUFDO2FBQ0gsQ0FBQztTQUNILENBQUMsQ0FBQztJQUNMLENBQUM7Q0FDRjtBQS9DRCxrRUErQ0MiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBjdXN0b21SZXNvdXJjZXMgZnJvbSBcImF3cy1jZGstbGliL2N1c3RvbS1yZXNvdXJjZXNcIjtcbmltcG9ydCB7IENvbnN0cnVjdCB9IGZyb20gXCJjb25zdHJ1Y3RzXCI7XG5cbmltcG9ydCB7IEF3c0N1c3RvbVJlc291cmNlIH0gZnJvbSBcIi4uLy4uL3Jlc291cmNlcy9hd3MvdXRpbGl0aWVzL2F3c0N1c3RvbVJlc291cmNlXCI7XG5pbXBvcnQgeyBFZmZlY3QsIFBvbGljeVN0YXRlbWVudCB9IGZyb20gXCJhd3MtY2RrLWxpYi9hd3MtaWFtXCI7XG5pbXBvcnQgeyB0eXBlIFN0YWNrUHJvcHMgfSBmcm9tIFwiYXdzLWNkay1saWJcIjtcblxuaW50ZXJmYWNlIEFjdGl2YXRlT3JnYW5pc2F0aW9uc0FjY2Vzc1Byb3BzIGV4dGVuZHMgU3RhY2tQcm9wcyB7XG4gIGFjY291bnQ6IHN0cmluZztcbiAgcmVnaW9uOiBzdHJpbmc7XG59XG5cbmV4cG9ydCBjbGFzcyBBY3RpdmF0ZU9yZ2FuaXNhdGlvbnNBY2Nlc3MgZXh0ZW5kcyBDb25zdHJ1Y3Qge1xuICBjb25zdHJ1Y3RvcihcbiAgICBzY29wZTogQ29uc3RydWN0LFxuICAgIGlkOiBzdHJpbmcsXG4gICAgcHJvcHM6IEFjdGl2YXRlT3JnYW5pc2F0aW9uc0FjY2Vzc1Byb3BzXG4gICkge1xuICAgIHN1cGVyKHNjb3BlLCBpZCk7XG5cbiAgICBuZXcgQXdzQ3VzdG9tUmVzb3VyY2UodGhpcywgXCJhY3RpdmF0ZU9yZ2FuaXNhdGlvbnNBY2Nlc3NcIiwge1xuICAgICAgZnVuY3Rpb25OYW1lOiBcImFjdGl2YXRlT3JnYW5pc2F0aW9uc0FjY2Vzc1wiLFxuICAgICAgb25DcmVhdGU6IHtcbiAgICAgICAgc2VydmljZTogXCJjbG91ZGZvcm1hdGlvblwiLFxuICAgICAgICBhY3Rpb246IFwiQWN0aXZhdGVPcmdhbml6YXRpb25zQWNjZXNzQ29tbWFuZFwiLCAvLyBodHRwczovL2RvY3MuYXdzLmFtYXpvbi5jb20vQVdTSmF2YVNjcmlwdFNESy92My9sYXRlc3QvY2xpZW50L2Nsb3VkZm9ybWF0aW9uL2NvbW1hbmQvQWN0aXZhdGVPcmdhbml6YXRpb25zQWNjZXNzQ29tbWFuZC9cbiAgICAgICAgcGFyYW1ldGVyczoge30sXG4gICAgICAgIHBoeXNpY2FsUmVzb3VyY2VJZDogY3VzdG9tUmVzb3VyY2VzLlBoeXNpY2FsUmVzb3VyY2VJZC5vZihcbiAgICAgICAgICBcImFjdGl2YXRlT3JnYW5pc2F0aW9uc0FjY2Vzc1wiXG4gICAgICAgIClcbiAgICAgIH0sXG4gICAgICByZXNvdXJjZVR5cGU6IFwiQ3VzdG9tOjpBY3RpdmF0ZU9yZ2FuaXNhdGlvbnNBY2Nlc3NcIixcbiAgICAgIHBvbGljeTogY3VzdG9tUmVzb3VyY2VzLkF3c0N1c3RvbVJlc291cmNlUG9saWN5LmZyb21TdGF0ZW1lbnRzKFtcbiAgICAgICAgbmV3IFBvbGljeVN0YXRlbWVudCh7XG4gICAgICAgICAgYWN0aW9uczogW1xuICAgICAgICAgICAgXCJjbG91ZGZvcm1hdGlvbjpBY3RpdmF0ZU9yZ2FuaXphdGlvbnNBY2Nlc3NcIixcbiAgICAgICAgICAgIFwiY2xvdWRmb3JtYXRpb246RGVzY3JpYmVPcmdhbml6YXRpb25zQWNjZXNzXCIsXG4gICAgICAgICAgICBcIm9yZ2FuaXphdGlvbnM6RW5hYmxlQVdTU2VydmljZUFjY2Vzc1wiLFxuICAgICAgICAgICAgXCJvcmdhbml6YXRpb25zOkRlc2NyaWJlT3JnYW5pemF0aW9uXCIsXG4gICAgICAgICAgICBcIm9yZ2FuaXphdGlvbnM6TGlzdFJvb3RzXCIsXG4gICAgICAgICAgICBcIm9yZ2FuaXphdGlvbnM6TGlzdEFXU1NlcnZpY2VBY2Nlc3NGb3JPcmdhbml6YXRpb25cIixcbiAgICAgICAgICAgIFwib3JnYW5pemF0aW9uczpEaXNhYmxlQVdTU2VydmljZUFjY2Vzc1wiLFxuICAgICAgICAgICAgXCJpYW06Q3JlYXRlU2VydmljZUxpbmtlZFJvbGVcIixcbiAgICAgICAgICAgIFwiaWFtOkdldFJvbGVcIlxuICAgICAgICAgIF0sXG4gICAgICAgICAgcmVzb3VyY2VzOiBbXG4gICAgICAgICAgICBcIipcIiAvLyBUT0RPOiBGaWd1cmUgb3V0IHdoYXQgYWNjZXNzIGlzIHJlcXVpcmVkIGhlcmVcbiAgICAgICAgICBdLFxuICAgICAgICAgIGVmZmVjdDogRWZmZWN0LkFMTE9XXG4gICAgICAgIH0pLFxuICAgICAgICBuZXcgUG9saWN5U3RhdGVtZW50KHtcbiAgICAgICAgICBhY3Rpb25zOiBbXCJjbG91ZGZvcm1hdGlvbjpBY3RpdmF0ZU9yZ2FuaXphdGlvbnNBY2Nlc3NcIl0sXG4gICAgICAgICAgcmVzb3VyY2VzOiBbXG4gICAgICAgICAgICBgYXJuOmF3czpjbG91ZGZvcm1hdGlvbjoke3Byb3BzLnJlZ2lvbn06JHtwcm9wcy5hY2NvdW50fTpzdGFja3NldC8qYFxuICAgICAgICAgIF0sXG4gICAgICAgICAgZWZmZWN0OiBFZmZlY3QuQUxMT1dcbiAgICAgICAgfSlcbiAgICAgIF0pXG4gICAgfSk7XG4gIH1cbn1cbiJdfQ==
@@ -0,0 +1,4 @@
1
+ import { Construct } from "constructs";
2
+ export declare class RamSharing extends Construct {
3
+ constructor(scope: Construct, id: string);
4
+ }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RamSharing = void 0;
4
+ const customResources = require("aws-cdk-lib/custom-resources");
5
+ const aws_iam_1 = require("aws-cdk-lib/aws-iam");
6
+ const constructs_1 = require("constructs");
7
+ const awsCustomResource_1 = require("../../resources/aws/utilities/awsCustomResource");
8
+ class RamSharing extends constructs_1.Construct {
9
+ constructor(scope, id) {
10
+ super(scope, id);
11
+ new awsCustomResource_1.AwsCustomResource(this, "enableRamSharing", {
12
+ functionName: "enableRamSharing",
13
+ onCreate: {
14
+ service: "RAM",
15
+ action: "enableSharingWithAwsOrganization", // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/RAM.html#enableSharingWithAwsOrganization-property
16
+ parameters: {},
17
+ physicalResourceId: customResources.PhysicalResourceId.of("enableRamSharing")
18
+ },
19
+ resourceType: "Custom::ram",
20
+ policy: customResources.AwsCustomResourcePolicy.fromStatements([
21
+ new aws_iam_1.PolicyStatement({
22
+ actions: [
23
+ "ram:EnableSharingWithAwsOrganization",
24
+ "iam:CreateServiceLinkedRole",
25
+ "organizations:enableAWSServiceAccess",
26
+ "organizations:DescribeOrganization"
27
+ ],
28
+ resources: ["*"] // TODO: Find out the exact pricipal for this
29
+ })
30
+ ])
31
+ });
32
+ }
33
+ }
34
+ exports.RamSharing = RamSharing;
35
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmFtU2hhcmluZy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL2xpYi9jb25maWcvYXdzL3JhbVNoYXJpbmcudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsZ0VBQWdFO0FBQ2hFLGlEQUFzRDtBQUN0RCwyQ0FBdUM7QUFFdkMsdUZBQW9GO0FBRXBGLE1BQWEsVUFBVyxTQUFRLHNCQUFTO0lBQ3ZDLFlBQVksS0FBZ0IsRUFBRSxFQUFVO1FBQ3RDLEtBQUssQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFFakIsSUFBSSxxQ0FBaUIsQ0FBQyxJQUFJLEVBQUUsa0JBQWtCLEVBQUU7WUFDOUMsWUFBWSxFQUFFLGtCQUFrQjtZQUNoQyxRQUFRLEVBQUU7Z0JBQ1IsT0FBTyxFQUFFLEtBQUs7Z0JBQ2QsTUFBTSxFQUFFLGtDQUFrQyxFQUFFLDZHQUE2RztnQkFDekosVUFBVSxFQUFFLEVBQUU7Z0JBQ2Qsa0JBQWtCLEVBQ2hCLGVBQWUsQ0FBQyxrQkFBa0IsQ0FBQyxFQUFFLENBQUMsa0JBQWtCLENBQUM7YUFDNUQ7WUFDRCxZQUFZLEVBQUUsYUFBYTtZQUMzQixNQUFNLEVBQUUsZUFBZSxDQUFDLHVCQUF1QixDQUFDLGNBQWMsQ0FBQztnQkFDN0QsSUFBSSx5QkFBZSxDQUFDO29CQUNsQixPQUFPLEVBQUU7d0JBQ1Asc0NBQXNDO3dCQUN0Qyw2QkFBNkI7d0JBQzdCLHNDQUFzQzt3QkFDdEMsb0NBQW9DO3FCQUNyQztvQkFDRCxTQUFTLEVBQUUsQ0FBQyxHQUFHLENBQUMsQ0FBQyw2Q0FBNkM7aUJBQy9ELENBQUM7YUFDSCxDQUFDO1NBQ0gsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztDQUNGO0FBM0JELGdDQTJCQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCAqIGFzIGN1c3RvbVJlc291cmNlcyBmcm9tIFwiYXdzLWNkay1saWIvY3VzdG9tLXJlc291cmNlc1wiO1xuaW1wb3J0IHsgUG9saWN5U3RhdGVtZW50IH0gZnJvbSBcImF3cy1jZGstbGliL2F3cy1pYW1cIjtcbmltcG9ydCB7IENvbnN0cnVjdCB9IGZyb20gXCJjb25zdHJ1Y3RzXCI7XG5cbmltcG9ydCB7IEF3c0N1c3RvbVJlc291cmNlIH0gZnJvbSBcIi4uLy4uL3Jlc291cmNlcy9hd3MvdXRpbGl0aWVzL2F3c0N1c3RvbVJlc291cmNlXCI7XG5cbmV4cG9ydCBjbGFzcyBSYW1TaGFyaW5nIGV4dGVuZHMgQ29uc3RydWN0IHtcbiAgY29uc3RydWN0b3Ioc2NvcGU6IENvbnN0cnVjdCwgaWQ6IHN0cmluZykge1xuICAgIHN1cGVyKHNjb3BlLCBpZCk7XG5cbiAgICBuZXcgQXdzQ3VzdG9tUmVzb3VyY2UodGhpcywgXCJlbmFibGVSYW1TaGFyaW5nXCIsIHtcbiAgICAgIGZ1bmN0aW9uTmFtZTogXCJlbmFibGVSYW1TaGFyaW5nXCIsXG4gICAgICBvbkNyZWF0ZToge1xuICAgICAgICBzZXJ2aWNlOiBcIlJBTVwiLFxuICAgICAgICBhY3Rpb246IFwiZW5hYmxlU2hhcmluZ1dpdGhBd3NPcmdhbml6YXRpb25cIiwgLy8gaHR0cHM6Ly9kb2NzLmF3cy5hbWF6b24uY29tL0FXU0phdmFTY3JpcHRTREsvbGF0ZXN0L0FXUy9SQU0uaHRtbCNlbmFibGVTaGFyaW5nV2l0aEF3c09yZ2FuaXphdGlvbi1wcm9wZXJ0eVxuICAgICAgICBwYXJhbWV0ZXJzOiB7fSxcbiAgICAgICAgcGh5c2ljYWxSZXNvdXJjZUlkOlxuICAgICAgICAgIGN1c3RvbVJlc291cmNlcy5QaHlzaWNhbFJlc291cmNlSWQub2YoXCJlbmFibGVSYW1TaGFyaW5nXCIpXG4gICAgICB9LFxuICAgICAgcmVzb3VyY2VUeXBlOiBcIkN1c3RvbTo6cmFtXCIsXG4gICAgICBwb2xpY3k6IGN1c3RvbVJlc291cmNlcy5Bd3NDdXN0b21SZXNvdXJjZVBvbGljeS5mcm9tU3RhdGVtZW50cyhbXG4gICAgICAgIG5ldyBQb2xpY3lTdGF0ZW1lbnQoe1xuICAgICAgICAgIGFjdGlvbnM6IFtcbiAgICAgICAgICAgIFwicmFtOkVuYWJsZVNoYXJpbmdXaXRoQXdzT3JnYW5pemF0aW9uXCIsXG4gICAgICAgICAgICBcImlhbTpDcmVhdGVTZXJ2aWNlTGlua2VkUm9sZVwiLFxuICAgICAgICAgICAgXCJvcmdhbml6YXRpb25zOmVuYWJsZUFXU1NlcnZpY2VBY2Nlc3NcIixcbiAgICAgICAgICAgIFwib3JnYW5pemF0aW9uczpEZXNjcmliZU9yZ2FuaXphdGlvblwiXG4gICAgICAgICAgXSxcbiAgICAgICAgICByZXNvdXJjZXM6IFtcIipcIl0gLy8gVE9ETzogRmluZCBvdXQgdGhlIGV4YWN0IHByaWNpcGFsIGZvciB0aGlzXG4gICAgICAgIH0pXG4gICAgICBdKVxuICAgIH0pO1xuICB9XG59XG4iXX0=
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Monitoring configuration for cross-account access
3
+ */
4
+ export declare const FJALL_MONITORING_CONFIG: {
5
+ /**
6
+ * Fjall webapp AWS account ID
7
+ * This account will be granted permission to assume monitoring roles
8
+ */
9
+ readonly webappAwsAccountId: "985539798308";
10
+ /**
11
+ * IAM role naming convention
12
+ */
13
+ readonly roleNamePrefix: "FjallMonitoring";
14
+ /**
15
+ * IAM role path (for organizational structure)
16
+ */
17
+ readonly rolePath: "/";
18
+ };
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FJALL_MONITORING_CONFIG = void 0;
4
+ /**
5
+ * Monitoring configuration for cross-account access
6
+ */
7
+ exports.FJALL_MONITORING_CONFIG = {
8
+ /**
9
+ * Fjall webapp AWS account ID
10
+ * This account will be granted permission to assume monitoring roles
11
+ */
12
+ webappAwsAccountId: "985539798308", // Fjall platform account
13
+ /**
14
+ * IAM role naming convention
15
+ */
16
+ roleNamePrefix: "FjallMonitoring",
17
+ /**
18
+ * IAM role path (for organizational structure)
19
+ */
20
+ rolePath: "/"
21
+ };
22
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibW9uaXRvcmluZy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL2xpYi9jb25maWcvbW9uaXRvcmluZy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQTs7R0FFRztBQUNVLFFBQUEsdUJBQXVCLEdBQUc7SUFDckM7OztPQUdHO0lBQ0gsa0JBQWtCLEVBQUUsY0FBYyxFQUFFLHlCQUF5QjtJQUU3RDs7T0FFRztJQUNILGNBQWMsRUFBRSxpQkFBaUI7SUFFakM7O09BRUc7SUFDSCxRQUFRLEVBQUUsR0FBRztDQUNMLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIE1vbml0b3JpbmcgY29uZmlndXJhdGlvbiBmb3IgY3Jvc3MtYWNjb3VudCBhY2Nlc3NcbiAqL1xuZXhwb3J0IGNvbnN0IEZKQUxMX01PTklUT1JJTkdfQ09ORklHID0ge1xuICAvKipcbiAgICogRmphbGwgd2ViYXBwIEFXUyBhY2NvdW50IElEXG4gICAqIFRoaXMgYWNjb3VudCB3aWxsIGJlIGdyYW50ZWQgcGVybWlzc2lvbiB0byBhc3N1bWUgbW9uaXRvcmluZyByb2xlc1xuICAgKi9cbiAgd2ViYXBwQXdzQWNjb3VudElkOiBcIjk4NTUzOTc5ODMwOFwiLCAvLyBGamFsbCBwbGF0Zm9ybSBhY2NvdW50XG5cbiAgLyoqXG4gICAqIElBTSByb2xlIG5hbWluZyBjb252ZW50aW9uXG4gICAqL1xuICByb2xlTmFtZVByZWZpeDogXCJGamFsbE1vbml0b3JpbmdcIixcblxuICAvKipcbiAgICogSUFNIHJvbGUgcGF0aCAoZm9yIG9yZ2FuaXphdGlvbmFsIHN0cnVjdHVyZSlcbiAgICovXG4gIHJvbGVQYXRoOiBcIi9cIlxufSBhcyBjb25zdDtcbiJdfQ==
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Static-site contact-form handler (Lambda Function URL → SES).
3
+ *
4
+ * Authored directly under asset/ as CJS — the only dependency,
5
+ * @aws-sdk/client-sesv2, ships in the Lambda Node 22 runtime, so there is no
6
+ * build step. asset/package.json pins CJS resolution.
7
+ *
8
+ * Security posture (Function URL authType NONE — see staticSite.ts). The
9
+ * endpoint is public and unauthenticated. Be precise about what each control
10
+ * actually buys, because it is easy to mistake this for authentication:
11
+ * - The Origin/Referer check stops a browser on another site from POSTing here
12
+ * (it cannot forge those headers). It does NOT stop a non-browser client,
13
+ * which sets them freely. It is a nuisance filter, not an auth gate.
14
+ * - A populated honeypot field (_gotcha) marks a bot → silent 200, no send.
15
+ * - What actually bounds abuse: the function's reserved concurrency (capped in
16
+ * staticSite.ts, so a flood cannot drain account-wide Lambda concurrency),
17
+ * SES account send limits, and the fact that both sender and recipient are
18
+ * fixed at deploy time — whatever a caller sends, the mail can only ever go
19
+ * from noreply@<domain> to the site owner.
20
+ *
21
+ * Logging discipline: status + requestId + SES error NAME only — never the
22
+ * parsed body or the recipient address (both are submitter PII).
23
+ */
24
+ "use strict";
25
+
26
+ const { SESv2Client, SendEmailCommand } = require("@aws-sdk/client-sesv2");
27
+
28
+ const client = new SESv2Client({});
29
+
30
+ const CONTACT_TO = process.env.CONTACT_TO;
31
+ const CONTACT_FROM = process.env.CONTACT_FROM;
32
+ const ALLOWED_ORIGIN = process.env.ALLOWED_ORIGIN;
33
+
34
+ const MAX_NAME = 200;
35
+ const MAX_EMAIL = 320;
36
+ const MAX_MESSAGE = 5000;
37
+
38
+ exports.handler = async (event) => {
39
+ const requestId = event?.requestContext?.requestId ?? "unknown";
40
+ const method = event?.requestContext?.http?.method ?? "";
41
+
42
+ if (method !== "POST") {
43
+ return respond(405, { error: "Method not allowed" });
44
+ }
45
+
46
+ const headers = normaliseHeaders(event?.headers);
47
+ if (!originAllowed(headers)) {
48
+ log("warn", "origin rejected", requestId);
49
+ return respond(403, { error: "Forbidden" });
50
+ }
51
+
52
+ let fields;
53
+ try {
54
+ fields = parseBody(event);
55
+ } catch {
56
+ log("warn", "invalid body", requestId);
57
+ return respond(400, { error: "Invalid request body" });
58
+ }
59
+
60
+ // Populated honeypot ⇒ bot: pretend success so it does not probe further.
61
+ if (typeof fields._gotcha === "string" && fields._gotcha.trim() !== "") {
62
+ return respond(200, { ok: true });
63
+ }
64
+
65
+ const name = String(fields.name ?? "").slice(0, MAX_NAME);
66
+ const email = String(fields.email ?? "").slice(0, MAX_EMAIL);
67
+ const message = String(fields.message ?? "").slice(0, MAX_MESSAGE);
68
+
69
+ if (message.trim() === "") {
70
+ return respond(400, { error: "Message is required" });
71
+ }
72
+
73
+ try {
74
+ await client.send(
75
+ new SendEmailCommand({
76
+ FromEmailAddress: CONTACT_FROM,
77
+ Destination: { ToAddresses: [CONTACT_TO] },
78
+ ...(isValidEmail(email) && { ReplyToAddresses: [email] }),
79
+ Content: {
80
+ Simple: {
81
+ Subject: {
82
+ Data: `Contact form: ${name || "(no name)"}`.slice(0, MAX_NAME)
83
+ },
84
+ Body: { Text: { Data: buildBody(name, email, message) } }
85
+ }
86
+ }
87
+ })
88
+ );
89
+ log("info", "sent", requestId);
90
+ return respond(200, { ok: true });
91
+ } catch (err) {
92
+ log("error", "ses send failed", requestId, err?.name);
93
+ return respond(502, { error: "Failed to send message" });
94
+ }
95
+ };
96
+
97
+ function normaliseHeaders(headers) {
98
+ const out = {};
99
+ if (headers && typeof headers === "object") {
100
+ for (const [key, value] of Object.entries(headers)) {
101
+ out[key.toLowerCase()] = value;
102
+ }
103
+ }
104
+ return out;
105
+ }
106
+
107
+ function originAllowed(headers) {
108
+ if (!ALLOWED_ORIGIN) return false;
109
+ const origin = headers.origin;
110
+ if (origin) return origin === ALLOWED_ORIGIN;
111
+ // Some clients omit Origin on same-site POSTs — fall back to the Referer's
112
+ // origin. Compare whole origins: a prefix test would accept a Referer of
113
+ // https://example.com.attacker.test/ for ALLOWED_ORIGIN https://example.com.
114
+ const referer = headers.referer;
115
+ if (referer) return originOf(referer) === ALLOWED_ORIGIN;
116
+ return false;
117
+ }
118
+
119
+ function originOf(url) {
120
+ try {
121
+ return new URL(url).origin;
122
+ } catch {
123
+ return undefined;
124
+ }
125
+ }
126
+
127
+ function parseBody(event) {
128
+ const raw =
129
+ event?.isBase64Encoded && typeof event.body === "string"
130
+ ? Buffer.from(event.body, "base64").toString("utf8")
131
+ : (event?.body ?? "");
132
+ const trimmed = raw.trim();
133
+ if (trimmed.startsWith("{")) {
134
+ return JSON.parse(trimmed);
135
+ }
136
+ // application/x-www-form-urlencoded fallback
137
+ const params = new URLSearchParams(trimmed);
138
+ const fields = {};
139
+ for (const [key, value] of params.entries()) {
140
+ fields[key] = value;
141
+ }
142
+ return fields;
143
+ }
144
+
145
+ function isValidEmail(value) {
146
+ return /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value);
147
+ }
148
+
149
+ function buildBody(name, email, message) {
150
+ return [
151
+ `Name: ${name || "(not provided)"}`,
152
+ `Email: ${email || "(not provided)"}`,
153
+ "",
154
+ message
155
+ ].join("\n");
156
+ }
157
+
158
+ function respond(statusCode, body) {
159
+ return {
160
+ statusCode,
161
+ headers: { "content-type": "application/json" },
162
+ body: JSON.stringify(body)
163
+ };
164
+ }
165
+
166
+ function log(level, msg, requestId, errorName) {
167
+ console.log(
168
+ JSON.stringify({
169
+ level,
170
+ msg,
171
+ requestId,
172
+ ...(errorName && { error: errorName })
173
+ })
174
+ );
175
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "type": "commonjs",
3
+ "private": true
4
+ }
@@ -2,10 +2,11 @@ import { type Construct } from "constructs";
2
2
  import { type IBucket } from "aws-cdk-lib/aws-s3";
3
3
  import { type IApplicationLoadBalancer } from "aws-cdk-lib/aws-elasticloadbalancingv2";
4
4
  import { type ICertificate } from "aws-cdk-lib/aws-certificatemanager";
5
- import { type ICachePolicy } from "aws-cdk-lib/aws-cloudfront";
5
+ import { type ICachePolicy, type IResponseHeadersPolicy } from "aws-cdk-lib/aws-cloudfront";
6
6
  import type App from "../../app.js";
7
7
  import { CloudFrontDistribution, type CachePolicyPreset, type AccessGateConfig } from "../../resources/aws/cdn/index.js";
8
8
  import { type ICdn } from "./interfaces/cdn.js";
9
+ import type { StaticSiteRouting } from "@fjall/util";
9
10
  import { type Storage } from "./storage.js";
10
11
  import { type AnyCompute } from "./compute.js";
11
12
  /**
@@ -32,7 +33,24 @@ export interface S3CdnProps extends BaseCdnProps {
32
33
  originType: "s3";
33
34
  bucket: IBucket | Storage;
34
35
  originPath?: string;
36
+ /** OAI (default) or OAC (single-stack only — see cloudFront.ts). */
37
+ originAccess?: "oai" | "oac";
38
+ /** Object served for `/` (static sites: "index.html"). */
39
+ defaultRootObject?: string;
40
+ /** "multipage" → clean-URL `.html` rewriting; "spa" → 403/404 fallback to index. */
41
+ routing?: StaticSiteRouting;
42
+ /** Security-headers policy (built by the caller, e.g. SecurityHeadersPolicy). */
43
+ responseHeadersPolicy?: IResponseHeadersPolicy;
44
+ /**
45
+ * Origin-request policy for the default behaviour. Defaults to forwarding
46
+ * viewer headers, as every distribution has since before the static-site
47
+ * pattern. `none` suits an origin that ignores them — S3 serves objects by
48
+ * key — but it is opt-in: flipping the default would strip
49
+ * `OriginRequestPolicyId` from live distributions on their next deploy.
50
+ */
51
+ originRequestPolicy?: "all-viewer-except-host" | "none";
35
52
  }
53
+ export type { StaticSiteRouting };
36
54
  /**
37
55
  * ALB origin CDN props - use with Application Load Balancer
38
56
  */
@@ -135,4 +153,3 @@ export declare class CdnFactory {
135
153
  */
136
154
  static build(id: string, props: ICdnProps): (_app: App, scope: Construct) => Cdn;
137
155
  }
138
- export {};
@@ -71,6 +71,15 @@ export class Cdn extends CloudFrontDistribution {
71
71
  (props.certificateArn
72
72
  ? Certificate.fromCertificateArn(scope, `${id}Certificate`, props.certificateArn)
73
73
  : undefined);
74
+ const s3Routing = props.originType === "s3"
75
+ ? {
76
+ defaultRootObject: props.defaultRootObject,
77
+ cleanUrls: props.routing === "multipage",
78
+ spaFallback: props.routing === "spa",
79
+ responseHeadersPolicy: props.responseHeadersPolicy,
80
+ originRequestPolicy: props.originRequestPolicy
81
+ }
82
+ : {};
74
83
  return {
75
84
  defaultOrigin,
76
85
  defaultCachePolicy: props.cachePolicy,
@@ -83,7 +92,8 @@ export class Cdn extends CloudFrontDistribution {
83
92
  logBucket: props.logBucket,
84
93
  priceClass: props.priceClass,
85
94
  forwardHostHeader: props.forwardHostHeader,
86
- accessGate: props.accessGate
95
+ accessGate: props.accessGate,
96
+ ...s3Routing
87
97
  };
88
98
  }
89
99
  /**
@@ -112,7 +122,8 @@ export class Cdn extends CloudFrontDistribution {
112
122
  return {
113
123
  type: "s3",
114
124
  bucket,
115
- originPath: props.originPath
125
+ originPath: props.originPath,
126
+ originAccess: props.originAccess
116
127
  };
117
128
  }
118
129
  case "alb": {