canopycms-cdk 0.0.67 → 0.0.68-int.94

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 CHANGED
@@ -14,6 +14,7 @@ AWS CDK constructs and EC2 worker for deploying CanopyCMS.
14
14
  - Lambda Function URL (for CloudFront origin)
15
15
  - EC2 Worker (t4g.nano spot in ASG, public subnet, EFS mount)
16
16
  - Security groups and IAM roles (least-privilege)
17
+ - `attachTo(distribution)`: adds the editor's `/edit`, `/edit/*` and `/api/canopycms/*` behaviors (and, optionally, the CMS build's `assetPrefix` and the preview route) to a CloudFront distribution you already own
17
18
 
18
19
  **`CanopyCmsDistribution`** — CloudFront + DNS (optional):
19
20
 
@@ -21,8 +22,9 @@ AWS CDK constructs and EC2 worker for deploying CanopyCMS.
21
22
  - CloudFront distribution with Lambda Function URL origin
22
23
  - Route53 A/AAAA alias records
23
24
  - Cache policies: no-cache for API/editor, long-cache for static assets
25
+ - Response headers: framing protection (`frame-ancestors 'self'`), `nosniff`, HSTS, `X-Robots-Tag: noindex`
24
26
 
25
- Use `CanopyCmsDistribution` if you don't have existing CloudFront infrastructure. Otherwise, use the `functionUrl` output from `CanopyCmsService` and wire it into your own CloudFront setup.
27
+ Use `CanopyCmsDistribution` when the CMS gets its own domain. To serve the editor from an existing distribution, call `cmsService.attachTo(distribution)` instead of wiring the `functionUrl` by hand; see [Serving the editor from a distribution you already own](../../docs/deploying-to-aws.md#serving-the-editor-from-a-distribution-you-already-own).
26
28
 
27
29
  ### EC2 Worker
28
30
 
@@ -98,17 +98,18 @@ export interface CanopyCmsDistributionProps {
98
98
  /**
99
99
  * Optional CDK construct for CanopyCMS CloudFront distribution.
100
100
  *
101
- * Use this if you don't have existing CloudFront infrastructure.
102
- * If you do, use the functionUrl output from CanopyCmsService
103
- * and wire it into your own CloudFront setup.
101
+ * Use this when the CMS gets a domain of its own. To serve the editor from a
102
+ * distribution you already own, use `CanopyCmsService.attachTo` instead.
104
103
  *
105
104
  * Creates:
106
105
  * - ACM certificate (DNS validated) — unless provided
107
106
  * - CloudFront distribution with Function URL origin
108
107
  * - Route53 A/AAAA alias records
109
- * - Cache policies: no-cache for /api/* and /edit*, cache /_next/static/*
108
+ * - Cache policies: no-cache default behavior, cache /_next/static/*
110
109
  * - Viewer-request CloudFront Function setting x-forwarded-host (redirect-URL
111
110
  * derivation behind the Host-stripping OAC origin)
111
+ * - The editor's response headers policy (framing protection, `noindex`) on
112
+ * both of its own behaviors
112
113
  * - `AssetSupport`'s `/assets/*` and `/assets/t/*` behaviors, in the required
113
114
  * order, when the `assetSupport` prop is passed
114
115
  */
@@ -1,6 +1,7 @@
1
1
  import { Construct } from 'constructs';
2
- import { Duration, Stack, Token, aws_cloudfront as cloudfront, aws_cloudfront_origins as origins, aws_certificatemanager as acm, aws_route53 as route53, aws_route53_targets as targets, } from 'aws-cdk-lib';
3
- import { DEFAULT_CMS_LAMBDA_TIMEOUT, MAX_CLOUDFRONT_ORIGIN_READ_TIMEOUT } from './cms-service.js';
2
+ import { Stack, Token, aws_cloudfront as cloudfront, aws_cloudfront_origins as origins, aws_certificatemanager as acm, aws_route53 as route53, aws_route53_targets as targets, } from 'aws-cdk-lib';
3
+ import { DEFAULT_CMS_LAMBDA_TIMEOUT } from './cms-service.js';
4
+ import { assertOriginReadTimeout, createEditorResponseHeadersPolicy, createForwardedHostFunction, createStaticCachePolicy, lambdaBehaviorOptions, } from './editor-routing.js';
4
5
  import { ASSETS_PATH_PATTERN, ASSETS_TRANSFORM_PATH_PATTERN, ASSET_BEHAVIOR_SPREAD_MISTAKE_KEYS, } from './asset-support.js';
5
6
  /**
6
7
  * Merge caller behaviors over this construct's own, preserving THE CALLER'S
@@ -128,17 +129,18 @@ function assertNoAssetBehaviorOrderingHazards(merged, attachingAssetSupport) {
128
129
  /**
129
130
  * Optional CDK construct for CanopyCMS CloudFront distribution.
130
131
  *
131
- * Use this if you don't have existing CloudFront infrastructure.
132
- * If you do, use the functionUrl output from CanopyCmsService
133
- * and wire it into your own CloudFront setup.
132
+ * Use this when the CMS gets a domain of its own. To serve the editor from a
133
+ * distribution you already own, use `CanopyCmsService.attachTo` instead.
134
134
  *
135
135
  * Creates:
136
136
  * - ACM certificate (DNS validated) — unless provided
137
137
  * - CloudFront distribution with Function URL origin
138
138
  * - Route53 A/AAAA alias records
139
- * - Cache policies: no-cache for /api/* and /edit*, cache /_next/static/*
139
+ * - Cache policies: no-cache default behavior, cache /_next/static/*
140
140
  * - Viewer-request CloudFront Function setting x-forwarded-host (redirect-URL
141
141
  * derivation behind the Host-stripping OAC origin)
142
+ * - The editor's response headers policy (framing protection, `noindex`) on
143
+ * both of its own behaviors
142
144
  * - `AssetSupport`'s `/assets/*` and `/assets/t/*` behaviors, in the required
143
145
  * order, when the `assetSupport` prop is passed
144
146
  */
@@ -193,75 +195,26 @@ export class CanopyCmsDistribution extends Construct {
193
195
  // entirely when unset, so CloudFront's 30s service default applies and
194
196
  // silently halves the CMS Lambda's 60s budget. See the prop's doc comment.
195
197
  const readTimeout = props.originReadTimeout ?? DEFAULT_CMS_LAMBDA_TIMEOUT;
196
- if (readTimeout.toSeconds() > MAX_CLOUDFRONT_ORIGIN_READ_TIMEOUT.toSeconds()) {
197
- throw new Error(`CanopyCmsDistribution: originReadTimeout is ${readTimeout.toSeconds()}s, but CloudFront ` +
198
- `allows at most ${MAX_CLOUDFRONT_ORIGIN_READ_TIMEOUT.toSeconds()}s without a service-quota ` +
199
- `increase. Either lower the CMS Lambda's timeout to match, or request a quota increase for ` +
200
- `"Origin response timeout" and pass the higher value explicitly. Deploying with a shorter ` +
201
- `origin timeout than the Lambda's would 504 at the edge on requests that actually succeed.`);
202
- }
198
+ assertOriginReadTimeout(readTimeout, 'CanopyCmsDistribution');
203
199
  const origin = origins.FunctionUrlOrigin.withOriginAccessControl(props.functionUrl, {
204
200
  readTimeout,
205
201
  });
206
- // API/editor routes take AWS's managed CACHING_DISABLED policy as it is:
207
- // CloudFront rejects ANY non-none cache-key setting on a caching-disabled
208
- // policy - `Authorization` in a header allowlist, and equally
209
- // cookieBehavior/queryStringBehavior `all()` ("The parameter CookieBehavior
210
- // is invalid for policy with caching disabled"). With TTL 0 the cache key is
211
- // meaningless anyway, and the origin still receives the full viewer request
212
- // (headers/cookies/query string, minus Host - forwarding Host would break
213
- // the OAC-signed Function URL) via the ALL_VIEWER_EXCEPT_HOST_HEADER origin
214
- // request policy on the behavior.
215
- const noCachePolicy = cloudfront.CachePolicy.CACHING_DISABLED;
216
- const staticCachePolicy = new cloudfront.CachePolicy(this, 'StaticCachePolicy', {
217
- defaultTtl: Duration.days(365),
218
- maxTtl: Duration.days(365),
219
- minTtl: Duration.days(365),
220
- headerBehavior: cloudfront.CacheHeaderBehavior.none(),
221
- queryStringBehavior: cloudfront.CacheQueryStringBehavior.none(),
222
- cookieBehavior: cloudfront.CacheCookieBehavior.none(),
223
- });
224
- // CloudFront gives the origin the Function URL's own Host (forwarding the
225
- // viewer Host would break the OAC SigV4 signature), and Lambda Web Adapter
226
- // forwards no `x-forwarded-*` headers of its own - so without this function,
227
- // Clerk/Next derive sign-in redirect URLs from the IAM-authed Function URL
228
- // host instead of the public domain, and the redirect 403s (direct Function
229
- // URL hits are rejected).
230
- //
231
- // ONLY x-forwarded-host: x-forwarded-proto is on CloudFront Functions'
232
- // DISALLOWED header list and setting it fails every request with 502
233
- // FunctionValidationError. Proto is unambiguous anyway - viewer-facing
234
- // CloudFront is HTTPS-only via REDIRECT_TO_HTTPS.
235
- const forwardedHostFunction = new cloudfront.Function(this, 'ForwardedHostFunction', {
236
- code: cloudfront.FunctionCode.fromInline([
237
- 'function handler(event) {',
238
- ' var request = event.request;',
239
- " request.headers['x-forwarded-host'] = { value: request.headers.host.value };",
240
- ' return request;',
241
- '}',
242
- ].join('\n')),
243
- });
202
+ const staticCachePolicy = createStaticCachePolicy(this, 'StaticCachePolicy');
203
+ const forwardedHostFunction = createForwardedHostFunction(this, 'ForwardedHostFunction');
204
+ const responseHeadersPolicy = createEditorResponseHeadersPolicy(this, 'EditorResponseHeadersPolicy');
244
205
  this.distribution = new cloudfront.Distribution(this, 'Distribution', {
245
206
  domainNames: [props.domainName],
246
207
  certificate,
247
208
  defaultBehavior: {
248
209
  origin,
249
- cachePolicy: noCachePolicy,
250
- viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
251
- allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
252
- originRequestPolicy: cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER,
253
- functionAssociations: [
254
- {
255
- function: forwardedHostFunction,
256
- eventType: cloudfront.FunctionEventType.VIEWER_REQUEST,
257
- },
258
- ],
210
+ ...lambdaBehaviorOptions(forwardedHostFunction, responseHeadersPolicy),
259
211
  },
260
212
  additionalBehaviors: mergeBehaviors({
261
213
  '/_next/static/*': {
262
214
  origin,
263
215
  cachePolicy: staticCachePolicy,
264
216
  viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
217
+ responseHeadersPolicy,
265
218
  },
266
219
  }, props.additionalBehaviors, props.assetSupport !== undefined),
267
220
  });
@@ -1,6 +1,7 @@
1
1
  import { Construct } from 'constructs';
2
- import { Duration, RemovalPolicy, aws_ec2 as ec2, aws_efs as efs, aws_iam as iam, aws_lambda as lambda, aws_autoscaling as autoscaling, aws_logs as logs } from 'aws-cdk-lib';
2
+ import { Duration, RemovalPolicy, aws_ec2 as ec2, aws_efs as efs, aws_cloudfront as cloudfront, aws_iam as iam, aws_lambda as lambda, aws_autoscaling as autoscaling, aws_logs as logs } from 'aws-cdk-lib';
3
3
  import type { IBucket } from 'aws-cdk-lib/aws-s3';
4
+ import type { CanopyCmsAttachOptions } from './editor-routing.js';
4
5
  /**
5
6
  * Default CMS Lambda timeout.
6
7
  *
@@ -20,8 +21,6 @@ import type { IBucket } from 'aws-cdk-lib/aws-s3';
20
21
  * than deploying a configuration that would 504.
21
22
  */
22
23
  export declare const DEFAULT_CMS_LAMBDA_TIMEOUT: Duration;
23
- /** CloudFront's maximum origin read timeout without a service-quota increase. */
24
- export declare const MAX_CLOUDFRONT_ORIGIN_READ_TIMEOUT: Duration;
25
24
  export interface CanopyCmsServiceProps {
26
25
  /** Docker image for the CMS Lambda function */
27
26
  cmsDockerImage: lambda.DockerImageCode;
@@ -360,4 +359,25 @@ export declare class CanopyCmsService extends Construct {
360
359
  /** The EC2 worker's CloudWatch log group (worker stdout/stderr) */
361
360
  readonly workerLogGroup: logs.LogGroup;
362
361
  constructor(scope: Construct, id: string, props: CanopyCmsServiceProps);
362
+ /**
363
+ * Serve the editor from a CloudFront distribution you already own, such as
364
+ * the site's: adds `/edit`, `/edit/*` and `/api/canopycms/*` behaviors
365
+ * (plus any `previewPrefix` and `editorAssetPrefix` routes) to this
366
+ * service's Function URL, behind OAC, with
367
+ * an origin read timeout equal to {@link timeout}, no caching, the whole
368
+ * viewer request forwarded, an `x-forwarded-host` viewer-request function and
369
+ * the editor's response headers policy (framing protection, `noindex`).
370
+ *
371
+ * The behaviors are appended. Synth fails if a behavior listed before them
372
+ * matches an editor route, since CloudFront would never reach the editor's.
373
+ * It warns when the distribution has custom error responses, which also
374
+ * rewrite the API's errors.
375
+ *
376
+ * Pass `editorAssetPrefix` with the CMS build's Next `assetPrefix`, so the
377
+ * editor's chunks stay out of the site's `/_next/static/*`, and
378
+ * `previewPrefix` with a static-export site's preview route.
379
+ * `AssetSupport.attachTo` adds `/assets/*`. Use `CanopyCmsDistribution`
380
+ * instead when the CMS gets a domain of its own.
381
+ */
382
+ attachTo(distribution: cloudfront.Distribution, options?: CanopyCmsAttachOptions): void;
363
383
  }
@@ -3,6 +3,7 @@ import { fileURLToPath } from 'node:url';
3
3
  import { Construct } from 'constructs';
4
4
  import { Duration, RemovalPolicy, Stack, Token, aws_ec2 as ec2, aws_efs as efs, aws_iam as iam, aws_lambda as lambda, aws_autoscaling as autoscaling, aws_s3_assets as s3assets, aws_logs as logs, } from 'aws-cdk-lib';
5
5
  import { attachLambdaExecutionPolicies } from './lambda-execution-role.js';
6
+ import { attachEditorBehaviors } from './editor-routing.js';
6
7
  // This package (`canopycms-cdk`) is `"type": "module"`, so its compiled output
7
8
  // is real ESM and `__dirname` is not a global there - the worker asset path
8
9
  // below throws `__dirname is not defined` under a real ESM runtime (e.g. `tsx`)
@@ -455,8 +456,6 @@ function assertGitHubAuthProps(props) {
455
456
  * than deploying a configuration that would 504.
456
457
  */
457
458
  export const DEFAULT_CMS_LAMBDA_TIMEOUT = Duration.seconds(60);
458
- /** CloudFront's maximum origin read timeout without a service-quota increase. */
459
- export const MAX_CLOUDFRONT_ORIGIN_READ_TIMEOUT = Duration.seconds(60);
460
459
  /**
461
460
  * Core CDK construct for CanopyCMS deployment.
462
461
  *
@@ -986,4 +985,27 @@ export class CanopyCmsService extends Construct {
986
985
  // EC2-health-checked ASG never notices.
987
986
  this.workerAsg.node.addDependency(this.fileSystem.mountTargetsAvailable);
988
987
  }
988
+ /**
989
+ * Serve the editor from a CloudFront distribution you already own, such as
990
+ * the site's: adds `/edit`, `/edit/*` and `/api/canopycms/*` behaviors
991
+ * (plus any `previewPrefix` and `editorAssetPrefix` routes) to this
992
+ * service's Function URL, behind OAC, with
993
+ * an origin read timeout equal to {@link timeout}, no caching, the whole
994
+ * viewer request forwarded, an `x-forwarded-host` viewer-request function and
995
+ * the editor's response headers policy (framing protection, `noindex`).
996
+ *
997
+ * The behaviors are appended. Synth fails if a behavior listed before them
998
+ * matches an editor route, since CloudFront would never reach the editor's.
999
+ * It warns when the distribution has custom error responses, which also
1000
+ * rewrite the API's errors.
1001
+ *
1002
+ * Pass `editorAssetPrefix` with the CMS build's Next `assetPrefix`, so the
1003
+ * editor's chunks stay out of the site's `/_next/static/*`, and
1004
+ * `previewPrefix` with a static-export site's preview route.
1005
+ * `AssetSupport.attachTo` adds `/assets/*`. Use `CanopyCmsDistribution`
1006
+ * instead when the CMS gets a domain of its own.
1007
+ */
1008
+ attachTo(distribution, options) {
1009
+ attachEditorBehaviors(distribution, this.functionUrl, this.timeout, options);
1010
+ }
989
1011
  }
@@ -0,0 +1,131 @@
1
+ import { Construct } from 'constructs';
2
+ import { Duration, aws_cloudfront as cloudfront, aws_lambda as lambda } from 'aws-cdk-lib';
3
+ /** CloudFront's maximum origin read timeout without a service-quota increase. */
4
+ export declare const MAX_CLOUDFRONT_ORIGIN_READ_TIMEOUT: Duration;
5
+ /**
6
+ * The routes the editor needs from the CMS Lambda on a distribution it shares
7
+ * with a site: the editor page and the catch-all API route `canopycms init`
8
+ * scaffolds. `/edit` and `/edit/*` rather than `/edit*`, which also matches
9
+ * `/editorial` and `/edit-assets/…` and so takes site pages away from the site.
10
+ *
11
+ * Not included, because on a shared distribution they belong to the site:
12
+ * `/_next/static/*` (the CMS build moves its chunks out of it with Next's
13
+ * `assetPrefix`; see `editorAssetPrefix`) and `/assets/*`
14
+ * (`AssetSupport.attachTo` owns it).
15
+ */
16
+ export declare const EDITOR_PATH_PATTERNS: readonly ["/edit", "/edit/*", "/api/canopycms/*"];
17
+ /**
18
+ * Whether a CloudFront path pattern matches a request path: `*` is any run of
19
+ * characters, `?` exactly one, matching is case-sensitive, and the pattern's
20
+ * leading `/` is optional.
21
+ */
22
+ export declare function cloudFrontPathPatternMatches(pattern: string, path: string): boolean;
23
+ /**
24
+ * Throws when the origin read timeout exceeds the 60s CloudFront accepts
25
+ * without a quota increase, which it would reject at deploy. Neither construct
26
+ * takes a higher, quota-raised value.
27
+ */
28
+ export declare function assertOriginReadTimeout(readTimeout: Duration, owner: string): void;
29
+ /**
30
+ * The viewer-request function every Lambda behavior needs. CloudFront gives
31
+ * the origin the Function URL's own Host (forwarding the viewer Host would
32
+ * break the OAC SigV4 signature), and Lambda Web Adapter adds no
33
+ * `x-forwarded-*` headers, so without this Clerk/Next derive sign-in redirect
34
+ * URLs from the IAM-authed Function URL host and the redirect 403s.
35
+ *
36
+ * Only `x-forwarded-host`: `x-forwarded-proto` is on CloudFront Functions'
37
+ * disallowed list and setting it fails every request with a 502. The viewer
38
+ * side is HTTPS-only, so proto is unambiguous anyway.
39
+ */
40
+ export declare function createForwardedHostFunction(scope: Construct, id: string): cloudfront.Function;
41
+ /**
42
+ * Response headers for everything the CMS Lambda serves.
43
+ *
44
+ * - `frame-ancestors 'self'` (plus `X-Frame-Options: SAMEORIGIN` for browsers
45
+ * that ignore it) stops other sites framing the editor. `'self'` keeps the
46
+ * editor's own preview iframe working, which frames same-origin site pages.
47
+ * - Both framing headers have `override: false`, so an origin that sends its
48
+ * own CSP keeps it whole; when that CSP has no `frame-ancestors` and the
49
+ * origin sends no `X-Frame-Options`, this one applies.
50
+ * - `X-Robots-Tag: noindex` overrides: nothing the Lambda serves is meant to
51
+ * be indexed.
52
+ * - No `Cross-Origin-Opener-Policy`: it can cut the `window.opener` link a
53
+ * popup-based OAuth sign-in reports back through.
54
+ */
55
+ export declare function createEditorResponseHeadersPolicy(scope: Construct, id: string): cloudfront.ResponseHeadersPolicy;
56
+ /**
57
+ * A year-long cache for content-hashed build output. The cache key carries no
58
+ * header, cookie or query string, so every viewer shares one copy.
59
+ */
60
+ export declare function createStaticCachePolicy(scope: Construct, id: string): cloudfront.CachePolicy;
61
+ /**
62
+ * Behavior options for a route the CMS Lambda serves. The managed
63
+ * CACHING_DISABLED policy as it is: CloudFront rejects any non-none cache-key
64
+ * setting on a caching-disabled policy, and the origin still gets the whole
65
+ * viewer request (headers, cookies, query string, minus Host, which would
66
+ * break the OAC signature) through ALL_VIEWER_EXCEPT_HOST_HEADER.
67
+ */
68
+ export declare function lambdaBehaviorOptions(viewerRequestFunction: cloudfront.FunctionAssociation['function'] | undefined, responseHeadersPolicy: NonNullable<cloudfront.AddBehaviorOptions['responseHeadersPolicy']>): cloudfront.AddBehaviorOptions;
69
+ /** Options for `CanopyCmsService.attachTo`. */
70
+ export interface CanopyCmsAttachOptions {
71
+ /**
72
+ * Viewer-request function for the editor behaviors, in place of the
73
+ * `x-forwarded-host` function `attachTo` otherwise creates. CloudFront allows
74
+ * one per behavior, so pass this when the distribution's own function must
75
+ * run on every route.
76
+ *
77
+ * It must set `x-forwarded-host` from the viewer's Host, or sign-in
78
+ * redirects point at the Function URL and 403. It must not gate on HTTP
79
+ * Basic auth: the API answers 401 without `WWW-Authenticate`, and a browser
80
+ * that sees that drops its cached Basic credential and prompts again.
81
+ *
82
+ * @default - a function that sets `x-forwarded-host` and nothing else
83
+ */
84
+ viewerRequestFunction?: cloudfront.IFunction;
85
+ /**
86
+ * Options merged into the editor and preview behaviors, after this construct's
87
+ * own; keys whose value is `undefined` are ignored. A `responseHeadersPolicy`
88
+ * here replaces the editor's framing protection, so it must carry its own.
89
+ * `functionAssociations` here replaces the viewer-request function as well,
90
+ * which is why it cannot be combined with `viewerRequestFunction`.
91
+ *
92
+ * @default - no overrides
93
+ */
94
+ behaviorOverrides?: Partial<cloudfront.AddBehaviorOptions>;
95
+ /**
96
+ * The CMS build's Next `assetPrefix`, such as `'/edit-assets'`: adds a
97
+ * `/<prefix>/*` behavior to the Lambda with a year-long cache, since the
98
+ * chunks under it are content-hashed.
99
+ *
100
+ * A CMS build that shares a distribution with a static site needs one.
101
+ * Without it the editor page loads its chunks from `/_next/static/*`, which
102
+ * the site serves, and they 404. Set `assetPrefix` in the CMS build's
103
+ * `next.config` only (not the static export's) and pass the same value here.
104
+ *
105
+ * A path starting with `/`, without a trailing `/` or wildcards, and
106
+ * overlapping neither the editor's routes, `/_next/*` nor AssetSupport's
107
+ * `/assets/*`. Of `behaviorOverrides`, only `responseHeadersPolicy` applies
108
+ * to this behavior.
109
+ *
110
+ * @default - no asset-prefix behavior
111
+ */
112
+ editorAssetPrefix?: string;
113
+ /**
114
+ * The path of the CMS-only preview route, the same value as
115
+ * `editor.previewPrefix` in the CanopyCMS config (e.g. `'/preview'`): adds
116
+ * `<prefix>` and `<prefix>/*` behaviors with the editor routes' options, so
117
+ * the editor's preview pane reaches the CMS rather than the site.
118
+ *
119
+ * Validated like `editorAssetPrefix`, and must not overlap it either. An
120
+ * absolute `https://` `previewPrefix` is another origin and needs nothing
121
+ * here.
122
+ *
123
+ * @default - no preview behaviors
124
+ */
125
+ previewPrefix?: string;
126
+ }
127
+ /**
128
+ * Attach the editor's behaviors to a distribution. See
129
+ * `CanopyCmsService.attachTo`, which is the public entry point.
130
+ */
131
+ export declare function attachEditorBehaviors(distribution: cloudfront.Distribution, functionUrl: lambda.IFunctionUrl, readTimeout: Duration, options?: CanopyCmsAttachOptions): void;