@fjall/components-infrastructure 2.29.0 → 2.30.0

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.
@@ -0,0 +1,92 @@
1
+ import { Duration } from "aws-cdk-lib";
2
+ import { ResponseHeadersPolicy as CdkResponseHeadersPolicy, HeadersFrameOption, HeadersReferrerPolicy } from "aws-cdk-lib/aws-cloudfront";
3
+ const REFERRER_POLICY_MAP = {
4
+ "no-referrer": HeadersReferrerPolicy.NO_REFERRER,
5
+ "no-referrer-when-downgrade": HeadersReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE,
6
+ origin: HeadersReferrerPolicy.ORIGIN,
7
+ "origin-when-cross-origin": HeadersReferrerPolicy.ORIGIN_WHEN_CROSS_ORIGIN,
8
+ "same-origin": HeadersReferrerPolicy.SAME_ORIGIN,
9
+ "strict-origin": HeadersReferrerPolicy.STRICT_ORIGIN,
10
+ "strict-origin-when-cross-origin": HeadersReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN,
11
+ "unsafe-url": HeadersReferrerPolicy.UNSAFE_URL
12
+ };
13
+ const DEFAULT_HSTS_MAX_AGE_DAYS = 730;
14
+ const DEFAULT_COMMENT = "Fjall security headers policy";
15
+ /**
16
+ * Fjall wrapper for `aws-cdk-lib/aws-cloudfront.ResponseHeadersPolicy`.
17
+ *
18
+ * Wrapped on the description + SOC2-defaults trigger (NOT taggability — the
19
+ * resource is not taggable): it owns Fjall's uniform security-header defaults
20
+ * so every consumer inherits the same posture in one edit. Per
21
+ * `.claude/rules/generator-standards.md § Wrapper Routing Discipline`,
22
+ * `lib/{config,patterns}/` instantiate this class, never the raw L2.
23
+ *
24
+ * Defaults (all `override:true`): HSTS 730d+includeSubdomains+preload,
25
+ * X-Content-Type-Options: nosniff, X-Frame-Options: DENY,
26
+ * Referrer-Policy: strict-origin-when-cross-origin. CSP is opt-in only (§5.5).
27
+ */
28
+ export class SecurityHeadersPolicy extends CdkResponseHeadersPolicy {
29
+ constructor(scope, id, props) {
30
+ const securityHeadersBehavior = SecurityHeadersPolicy.buildSecurityHeaders(props);
31
+ const customHeaders = SecurityHeadersPolicy.buildCustomHeaders(props.customHeaders);
32
+ super(scope, id, {
33
+ responseHeadersPolicyName: props.policyName,
34
+ comment: props.comment ?? DEFAULT_COMMENT,
35
+ securityHeadersBehavior,
36
+ ...(customHeaders.length > 0 && {
37
+ customHeadersBehavior: { customHeaders }
38
+ })
39
+ });
40
+ }
41
+ static buildSecurityHeaders(props) {
42
+ const behaviour = {};
43
+ if (props.hsts !== false) {
44
+ const hsts = props.hsts ?? {};
45
+ behaviour.strictTransportSecurity = {
46
+ accessControlMaxAge: Duration.days(hsts.maxAgeDays ?? DEFAULT_HSTS_MAX_AGE_DAYS),
47
+ includeSubdomains: hsts.includeSubdomains ?? true,
48
+ preload: hsts.preload ?? true,
49
+ override: true
50
+ };
51
+ }
52
+ if (props.contentTypeOptions !== false) {
53
+ behaviour.contentTypeOptions = { override: true };
54
+ }
55
+ if (props.frameOptions !== false) {
56
+ behaviour.frameOptions = {
57
+ frameOption: props.frameOptions === "SAMEORIGIN"
58
+ ? HeadersFrameOption.SAMEORIGIN
59
+ : HeadersFrameOption.DENY,
60
+ override: true
61
+ };
62
+ }
63
+ if (props.referrerPolicy !== false) {
64
+ behaviour.referrerPolicy = {
65
+ referrerPolicy: REFERRER_POLICY_MAP[props.referrerPolicy ?? "strict-origin-when-cross-origin"],
66
+ override: true
67
+ };
68
+ }
69
+ // CSP is opt-in — only emitted when a policy string is supplied (§5.5).
70
+ if (props.contentSecurityPolicy !== undefined) {
71
+ behaviour.contentSecurityPolicy = {
72
+ contentSecurityPolicy: props.contentSecurityPolicy,
73
+ override: true
74
+ };
75
+ }
76
+ return behaviour;
77
+ }
78
+ static buildCustomHeaders(customHeaders) {
79
+ if (customHeaders === undefined) {
80
+ return [];
81
+ }
82
+ return Object.entries(customHeaders).map(([header, value]) => ({
83
+ header,
84
+ value,
85
+ override: true
86
+ }));
87
+ }
88
+ /** The underlying policy, for wiring into a distribution behaviour. */
89
+ getPolicy() {
90
+ return this;
91
+ }
92
+ }
@@ -32,6 +32,13 @@ export interface LambdaFunctionProps {
32
32
  vpc?: IVpc;
33
33
  timeout?: number;
34
34
  memorySize?: number;
35
+ /**
36
+ * Cap on concurrent executions. Set this on any Lambda reachable without
37
+ * authentication (a public Function URL): it bounds both the function's own
38
+ * spend and the share of account-wide concurrency it can take from every
39
+ * other function.
40
+ */
41
+ reservedConcurrentExecutions?: number;
35
42
  /** Ephemeral storage size in MiB */
36
43
  ephemeralStorageSize?: number;
37
44
  /**
@@ -9,6 +9,13 @@ export interface DomainCertificateProps {
9
9
  readonly transparencyLogging?: boolean;
10
10
  readonly costAllocationEnvironment?: string;
11
11
  readonly costAllocationDomain?: string;
12
+ /**
13
+ * Publish the `${domain}-certificate-arn` CloudFormation export. Default true.
14
+ * Set false when the cert is consumed in-stack (single-stack patterns) to
15
+ * avoid an export collision with a managed-domain/apex stack that also owns
16
+ * the domain's exports.
17
+ */
18
+ readonly exportCertificateArn?: boolean;
12
19
  }
13
20
  export declare class DomainCertificate extends Construct {
14
21
  readonly certificate: ICertificate;
@@ -26,12 +26,14 @@ export class DomainCertificate extends Construct {
26
26
  domain: props.costAllocationDomain ?? props.domainName,
27
27
  environment: props.costAllocationEnvironment
28
28
  });
29
- const safeKey = toPascalCase(props.domainName.split(".").join(""));
30
- const exports = getDomainExportNames(props.domainName);
31
- new CfnOutput(this, `${safeKey}CertificateArn`, {
32
- key: `${safeKey}CertificateArn`,
33
- value: this.certificateArn,
34
- exportName: exports.certificateArn
35
- });
29
+ if (props.exportCertificateArn !== false) {
30
+ const safeKey = toPascalCase(props.domainName.split(".").join(""));
31
+ const exports = getDomainExportNames(props.domainName);
32
+ new CfnOutput(this, `${safeKey}CertificateArn`, {
33
+ key: `${safeKey}CertificateArn`,
34
+ value: this.certificateArn,
35
+ exportName: exports.certificateArn
36
+ });
37
+ }
36
38
  }
37
39
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fjall/components-infrastructure",
3
- "version": "2.29.0",
3
+ "version": "2.30.0",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "type": "module",
6
6
  "bin": {
@@ -34,7 +34,8 @@
34
34
  "clean": "rm -rf ./dist",
35
35
  "clean:node": "rm -rf ./node_modules",
36
36
  "build:cert-gen-lambda": "node lib/lambda-assets/cert-generator/src/build.mjs",
37
- "build": "npm run build:cert-gen-lambda && tsc && cp -r lib/layers dist/lib/layers && mkdir -p dist/lib/lambda-assets/cert-generator/asset && cp lib/lambda-assets/cert-generator/asset/index.js dist/lib/lambda-assets/cert-generator/asset/index.js && cp lib/lambda-assets/cert-generator/asset/package.json dist/lib/lambda-assets/cert-generator/asset/package.json && mkdir -p dist/lib/lambda-assets/identity-store-user/asset && cp lib/lambda-assets/identity-store-user/asset/index.js dist/lib/lambda-assets/identity-store-user/asset/index.js && cp lib/lambda-assets/identity-store-user/asset/package.json dist/lib/lambda-assets/identity-store-user/asset/package.json && cp lib/resources/aws/compute/lifecycleHookLambda.source.cjs dist/lib/resources/aws/compute/lifecycleHookLambda.source.cjs && cp lib/resources/aws/compute/ec2GracefulTerminationLambda.source.cjs dist/lib/resources/aws/compute/ec2GracefulTerminationLambda.source.cjs && cp lib/resources/aws/compute/persistentDataVolumeLambda.source.cjs dist/lib/resources/aws/compute/persistentDataVolumeLambda.source.cjs",
37
+ "build": "npm run build:cert-gen-lambda && tsc && cp -r lib/layers dist/lib/layers && mkdir -p dist/lib/lambda-assets/cert-generator/asset && cp lib/lambda-assets/cert-generator/asset/index.js dist/lib/lambda-assets/cert-generator/asset/index.js && cp lib/lambda-assets/cert-generator/asset/package.json dist/lib/lambda-assets/cert-generator/asset/package.json && mkdir -p dist/lib/lambda-assets/identity-store-user/asset && cp lib/lambda-assets/identity-store-user/asset/index.js dist/lib/lambda-assets/identity-store-user/asset/index.js && cp lib/lambda-assets/identity-store-user/asset/package.json dist/lib/lambda-assets/identity-store-user/asset/package.json && mkdir -p dist/lib/lambda-assets/static-site-forms/asset && cp lib/lambda-assets/static-site-forms/asset/index.js dist/lib/lambda-assets/static-site-forms/asset/index.js && cp lib/lambda-assets/static-site-forms/asset/package.json dist/lib/lambda-assets/static-site-forms/asset/package.json && cp lib/resources/aws/compute/lifecycleHookLambda.source.cjs dist/lib/resources/aws/compute/lifecycleHookLambda.source.cjs && cp lib/resources/aws/compute/ec2GracefulTerminationLambda.source.cjs dist/lib/resources/aws/compute/ec2GracefulTerminationLambda.source.cjs && cp lib/resources/aws/compute/persistentDataVolumeLambda.source.cjs dist/lib/resources/aws/compute/persistentDataVolumeLambda.source.cjs",
38
+ "prepack": "node ../../scripts/check-dist-freshness.mjs",
38
39
  "watch": "tsc -w",
39
40
  "watch:only": "tsc -w",
40
41
  "test": "vitest run",
@@ -52,6 +53,7 @@
52
53
  "@aws-sdk/client-elastic-load-balancing-v2": "^3.1045.0",
53
54
  "@aws-sdk/client-iam": "^3.1038.0",
54
55
  "@aws-sdk/client-identitystore": "^3.1038.0",
56
+ "@aws-sdk/client-sesv2": "^3.1087.0",
55
57
  "@peculiar/x509": "2.0.0",
56
58
  "@types/aws-lambda": "^8.10.161",
57
59
  "@types/node": "^26.0.0",
@@ -65,8 +67,8 @@
65
67
  },
66
68
  "dependencies": {
67
69
  "@aws-sdk/client-organizations": "^3.1038.0",
68
- "@fjall/generator": "^2.29.0",
69
- "@fjall/util": "^2.29.0",
70
+ "@fjall/generator": "^2.30.0",
71
+ "@fjall/util": "^2.30.0",
70
72
  "constructs": "^10.6.0"
71
73
  },
72
74
  "overrides": {
@@ -80,5 +82,5 @@
80
82
  "engines": {
81
83
  "node": ">=18.0.0"
82
84
  },
83
- "gitHead": "c40ea0e1435400f3f42faaad5bcbf50e4400c1d4"
85
+ "gitHead": "ec35b16db566dfa5feb714c16f132c55b4663c86"
84
86
  }