@_linked/s3 1.3.1 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/README.md +58 -0
- package/lib/esm/index.d.ts +2 -1
- package/lib/esm/index.js +2 -1
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/ontologies/s3.js +0 -4
- package/lib/esm/ontologies/s3.js.map +1 -1
- package/lib/esm/ontologies/s3.register.d.ts +1 -0
- package/lib/esm/ontologies/s3.register.js +17 -0
- package/lib/esm/ontologies/s3.register.js.map +1 -0
- package/lib/esm/shapes/S3Bucket.d.ts +51 -1
- package/lib/esm/shapes/S3Bucket.js +112 -1
- package/lib/esm/shapes/S3Bucket.js.map +1 -1
- package/lib/esm/shapes/S3FileStore.d.ts +22 -1
- package/lib/esm/shapes/S3FileStore.js +27 -1
- package/lib/esm/shapes/S3FileStore.js.map +1 -1
- package/lib/esm/utils/cors.d.ts +79 -0
- package/lib/esm/utils/cors.js +159 -0
- package/lib/esm/utils/cors.js.map +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# @\_linked/s3
|
|
2
2
|
|
|
3
|
+
## 1.4.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#26](https://github.com/linked-fw/s3/pull/26) [`e4c7ebe`](https://github.com/linked-fw/s3/commit/e4c7ebe7e78533fee0eada1eddab0575ed8fa53d) Thanks [@flyon](https://github.com/flyon)! - The ontology no longer registers by importing itself.
|
|
8
|
+
|
|
9
|
+
It carried `import * as _this from './<prefix>.js'` and passed that namespace to
|
|
10
|
+
`linkedOntology()`. Under `tsc` the self-reference survives; under a bundler it does
|
|
11
|
+
not — Rollup treats it as a circular import and elides it, so the binding is
|
|
12
|
+
`undefined` and a consuming app dies at boot with `_this is not defined`.
|
|
13
|
+
|
|
14
|
+
Registration now lives in a `<prefix>.register.ts` sibling, imported from the package
|
|
15
|
+
entry. Nothing changes for consumers: importing this package still registers the
|
|
16
|
+
ontology.
|
|
17
|
+
|
|
18
|
+
## 1.4.0
|
|
19
|
+
|
|
20
|
+
### Minor Changes
|
|
21
|
+
|
|
22
|
+
- [#17](https://github.com/linked-fw/s3/pull/17) [`f8d5740`](https://github.com/linked-fw/s3/commit/f8d5740b37cb5f77b62b482465ff2777ce8de2f5) Thanks [@flyon](https://github.com/flyon)! - Configure bucket CORS for cross-origin asset hosting.
|
|
23
|
+
|
|
24
|
+
`S3Bucket` gains `getBucketCors()`, `putBucketCors(rules)` and an idempotent
|
|
25
|
+
`ensureCors(origins, options)` that writes the rule a CDN-hosted release needs
|
|
26
|
+
(GET/HEAD, the given origins, exposed ETag and content headers, a max age).
|
|
27
|
+
`getBucketCors()` returns `null` instead of throwing when the bucket has no
|
|
28
|
+
configuration, and `ensureCors` reports `status: 'forbidden'` with an
|
|
29
|
+
actionable message rather than throwing when the credentials may not read or
|
|
30
|
+
set bucket configuration — the normal case on Cloudflare R2 and other providers
|
|
31
|
+
that keep bucket CORS out of reach of object-scoped tokens.
|
|
32
|
+
|
|
33
|
+
For deployments that cannot configure the bucket from code, `checkCorsAccess`
|
|
34
|
+
(and `S3FileStore.checkAssetCors`) verifies from the outside, using only public
|
|
35
|
+
read access, that an asset really is loadable from a given page origin.
|
|
36
|
+
`S3FileStore` also exposes `ensureCors`.
|
|
37
|
+
|
|
3
38
|
## 1.3.1
|
|
4
39
|
|
|
5
40
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -105,6 +105,64 @@ if (head === null) {
|
|
|
105
105
|
`fileExists` is built on it, so checking whether a path is taken no longer
|
|
106
106
|
downloads the object that is about to be overwritten.
|
|
107
107
|
|
|
108
|
+
## CORS for a CDN-hosted release
|
|
109
|
+
|
|
110
|
+
`linked build-app` bakes Vite's `base` to the release URL, so a release served
|
|
111
|
+
from a bucket loads its dynamic-import chunks from that bucket rather than from
|
|
112
|
+
the app origin. Module scripts are **always** fetched in CORS mode, so without
|
|
113
|
+
an `Access-Control-Allow-Origin` header on the bucket's responses those chunks
|
|
114
|
+
simply fail to load — the first route that lazy-loads breaks, while the initial
|
|
115
|
+
HTML looks fine.
|
|
116
|
+
|
|
117
|
+
The rule to set is narrow: `GET` and `HEAD`, your app origins, `ETag` and the
|
|
118
|
+
content headers exposed (range requests and cache validation need them), and a
|
|
119
|
+
`MaxAgeSeconds` so browsers stop re-preflighting.
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
const result = await store.ensureCors(['https://app.example'], {
|
|
123
|
+
maxAgeSeconds: 3600,
|
|
124
|
+
});
|
|
125
|
+
console.log(result.status); // 'updated' | 'unchanged' | 'forbidden'
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`ensureCors` reads the current configuration first and writes nothing when an
|
|
129
|
+
equivalent rule is already in place. It keeps any unrelated rules the bucket
|
|
130
|
+
already carries; pass `{replace: true}` to overwrite them instead. The lower
|
|
131
|
+
level `S3Bucket.putBucketCors(rules)` and `S3Bucket.getBucketCors()` are there
|
|
132
|
+
when you want the raw calls — `getBucketCors()` returns `null` rather than
|
|
133
|
+
throwing when the bucket has no configuration at all.
|
|
134
|
+
|
|
135
|
+
### Many providers will not let you set this from code
|
|
136
|
+
|
|
137
|
+
**Expect `status: 'forbidden'`.** Object-scoped credentials — Cloudflare R2
|
|
138
|
+
tokens in particular — can read and write objects but get a `403` on
|
|
139
|
+
`GetBucketCors` and `PutBucketCors`. `ensureCors` reports that instead of
|
|
140
|
+
throwing, and tells you to set the rule in the provider's dashboard (or with an
|
|
141
|
+
account-level token). Treat it as best effort: on most deployments the CORS
|
|
142
|
+
rule is a one-time dashboard setting, and what you actually want in CI is the
|
|
143
|
+
check below.
|
|
144
|
+
|
|
145
|
+
### Verifying
|
|
146
|
+
|
|
147
|
+
Verification needs only public read access, so it works everywhere:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
curl -sI -H 'Origin: https://app.example' https://cdn.example/releases/1.2.3/assets/app.js
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Look for `access-control-allow-origin` in the response. Programmatically:
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
const check = await store.checkAssetCors('assets/app.js', 'https://app.example');
|
|
157
|
+
if (!check.ok) {
|
|
158
|
+
throw new Error(check.message); // also carries the equivalent curl one-liner
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`checkCorsAccess(url, origin)` from `@_linked/s3/utils/cors.js` does the same
|
|
163
|
+
against any URL, and never rejects — a network failure comes back as
|
|
164
|
+
`{ok: false, error}`.
|
|
165
|
+
|
|
108
166
|
## Registering the store
|
|
109
167
|
|
|
110
168
|
Registering stores by purpose (`LinkedFileStorage.setStore`, `getStore`,
|
package/lib/esm/index.d.ts
CHANGED
package/lib/esm/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import './types.js';
|
|
2
|
-
import './ontologies/s3.js';
|
|
2
|
+
import './ontologies/s3.register.js';
|
|
3
3
|
//SHAPES FIRST
|
|
4
4
|
import './shapes/S3Bucket.js';
|
|
5
5
|
import './shapes/S3FileStore.js';
|
|
@@ -9,4 +9,5 @@ import './shapes/S3FileStore.js';
|
|
|
9
9
|
// import './shapes/S3QuadStore.js';
|
|
10
10
|
//THEN COMPONENTS
|
|
11
11
|
import './utils/accessUrl.js';
|
|
12
|
+
import './utils/cors.js';
|
|
12
13
|
//# sourceMappingURL=index.js.map
|
package/lib/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,CAAC;AACpB,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,CAAC;AACpB,OAAO,6BAA6B,CAAC;AAErC,cAAc;AACd,OAAO,sBAAsB,CAAC;AAC9B,OAAO,yBAAyB,CAAC;AACjC,gFAAgF;AAChF,wCAAwC;AACxC,gDAAgD;AAChD,oCAAoC;AAEpC,iBAAiB;AACjB,OAAO,sBAAsB,CAAC;AAC9B,OAAO,iBAAiB,CAAC"}
|
package/lib/esm/ontologies/s3.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { createNameSpace } from '@_linked/core/utils/NameSpace';
|
|
2
|
-
import { linkedOntology } from '../package.js';
|
|
3
|
-
import * as _this from './s3.js';
|
|
4
2
|
export var loadData = () => {
|
|
5
3
|
//@ts-ignore
|
|
6
4
|
return import('../data/s3.json', { with: { type: 'json' } }).then((data) => data.default);
|
|
@@ -30,6 +28,4 @@ export const s3 = {
|
|
|
30
28
|
key,
|
|
31
29
|
secret,
|
|
32
30
|
};
|
|
33
|
-
//Registers this ontology to LINCD.JS, so that data loading can be automated amongst other things
|
|
34
|
-
linkedOntology(_this, ns, 's3', loadData, '../data/s3.json');
|
|
35
31
|
//# sourceMappingURL=s3.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"s3.js","sourceRoot":"","sources":["../../../src/ontologies/s3.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"s3.js","sourceRoot":"","sources":["../../../src/ontologies/s3.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAEhE,MAAM,CAAC,IAAI,QAAQ,GAAG,GAAG,EAAE;IACzB,YAAY;IACZ,OAAO,MAAM,CAAC,iBAAiB,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,IAAI,CAC/D,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CACvB,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,IAAI,EAAE,GAAG,eAAe,CAAC,0BAA0B,CAAC,CAAC;AAE5D,MAAM,CAAC,IAAI,KAAK,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;AAE1B,UAAU;AACV,MAAM,CAAC,IAAI,MAAM,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC;AACjC,MAAM,CAAC,IAAI,SAAS,GAAG,EAAE,CAAC,WAAW,CAAC,CAAC;AACvC,MAAM,CAAC,IAAI,aAAa,GAAG,EAAE,CAAC,eAAe,CAAC,CAAC;AAC/C,MAAM,CAAC,IAAI,SAAS,GAAG,EAAE,CAAC,WAAW,CAAC,CAAC;AAEvC,aAAa;AACb,MAAM,CAAC,IAAI,MAAM,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC;AACjC,MAAM,CAAC,IAAI,QAAQ,GAAG,EAAE,CAAC,UAAU,CAAC,CAAC;AACrC,MAAM,CAAC,IAAI,GAAG,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;AAC3B,MAAM,CAAC,IAAI,MAAM,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC;AAEjC,mFAAmF;AACnF,MAAM,CAAC,MAAM,EAAE,GAAG;IAChB,UAAU;IACV,MAAM;IACN,SAAS;IACT,aAAa;IACb,SAAS;IAET,aAAa;IACb,MAAM;IACN,QAAQ;IACR,GAAG;IACH,MAAM;CACP,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registers this ontology.
|
|
3
|
+
*
|
|
4
|
+
* Kept out of `s3.ts` because registration needs that module's whole export
|
|
5
|
+
* namespace, and a module cannot import itself once a bundler is involved: Rollup
|
|
6
|
+
* treats a static self-reference as a circular import and elides it, so the binding is
|
|
7
|
+
* undefined at runtime and the consuming app dies at boot with `_this is not
|
|
8
|
+
* defined`. `tsc` preserves it, which is why the pattern survived for as long as
|
|
9
|
+
* packages were built with `tsc` alone.
|
|
10
|
+
*
|
|
11
|
+
* From a sibling module the same import is ordinary and survives.
|
|
12
|
+
*/
|
|
13
|
+
import * as terms from './s3.js';
|
|
14
|
+
import { loadData, ns } from './s3.js';
|
|
15
|
+
import { linkedOntology } from '../package.js';
|
|
16
|
+
linkedOntology(terms, ns, 's3', loadData, '../data/s3.json');
|
|
17
|
+
//# sourceMappingURL=s3.register.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3.register.js","sourceRoot":"","sources":["../../../src/ontologies/s3.register.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC;AACjC,OAAO,EAAC,QAAQ,EAAE,EAAE,EAAC,MAAM,SAAS,CAAC;AACrC,OAAO,EAAC,cAAc,EAAC,MAAM,eAAe,CAAC;AAE7C,cAAc,CAAC,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,iBAAiB,CAAC,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { DeleteObjectCommandInput, HeadObjectCommandOutput, S3Client } from '@aws-sdk/client-s3';
|
|
1
|
+
import { CORSRule, DeleteObjectCommandInput, HeadObjectCommandOutput, S3Client } from '@aws-sdk/client-s3';
|
|
2
|
+
import { type StaticAssetCorsOptions } from '../utils/cors.js';
|
|
2
3
|
import { PutObjectCommandInput, PutObjectCommandOutput } from '@aws-sdk/client-s3/dist-types/commands/PutObjectCommand';
|
|
3
4
|
import { StreamingBlobPayloadInputTypes } from '@smithy/types';
|
|
4
5
|
export interface S3ClientConfigInput {
|
|
@@ -106,4 +107,53 @@ export declare class S3Bucket {
|
|
|
106
107
|
* @returns The promise that will resolve when the bucket exists
|
|
107
108
|
*/
|
|
108
109
|
createBucket(): Promise<import("@aws-sdk/client-s3").CreateBucketCommandOutput>;
|
|
110
|
+
/**
|
|
111
|
+
* Read the bucket's CORS configuration.
|
|
112
|
+
*
|
|
113
|
+
* @returns The rules, or null when the bucket has none configured. A bucket
|
|
114
|
+
* without CORS answers `GetBucketCors` with `NoSuchCORSConfiguration`,
|
|
115
|
+
* which is an ordinary state and not an error worth throwing over.
|
|
116
|
+
* @throws When the credentials may not read bucket configuration, with a
|
|
117
|
+
* message naming what to do about it. Use {@link ensureCors} if you want
|
|
118
|
+
* that case handled for you.
|
|
119
|
+
*/
|
|
120
|
+
getBucketCors(): Promise<CORSRule[] | null>;
|
|
121
|
+
/**
|
|
122
|
+
* Replace the bucket's CORS configuration with the given rules.
|
|
123
|
+
*
|
|
124
|
+
* `PutBucketCors` replaces the whole configuration, so pass every rule the
|
|
125
|
+
* bucket should end up with.
|
|
126
|
+
*
|
|
127
|
+
* @throws When the credentials may not write bucket configuration, with a
|
|
128
|
+
* message naming what to do about it.
|
|
129
|
+
*/
|
|
130
|
+
putBucketCors(rules: CORSRule[]): Promise<CORSRule[]>;
|
|
131
|
+
/**
|
|
132
|
+
* Make sure the bucket serves static assets to the given page origins.
|
|
133
|
+
*
|
|
134
|
+
* Best effort by design: it reads the current configuration first and writes
|
|
135
|
+
* nothing when an equivalent rule is already there, and it reports rather
|
|
136
|
+
* than throws when the credentials are not allowed to touch bucket config —
|
|
137
|
+
* which is the normal case on Cloudflare R2 and other providers that keep
|
|
138
|
+
* bucket CORS out of reach of object-scoped tokens.
|
|
139
|
+
*
|
|
140
|
+
* @param origins The page origins that will load the assets, e.g.
|
|
141
|
+
* `['https://app.example']`.
|
|
142
|
+
* @param options Overrides for the default rule, plus `replace` to drop any
|
|
143
|
+
* rules the bucket already carries instead of keeping them.
|
|
144
|
+
*/
|
|
145
|
+
ensureCors(origins: string[], options?: StaticAssetCorsOptions & {
|
|
146
|
+
replace?: boolean;
|
|
147
|
+
}): Promise<EnsureCorsResult>;
|
|
148
|
+
}
|
|
149
|
+
export interface EnsureCorsResult {
|
|
150
|
+
/**
|
|
151
|
+
* `unchanged` when an equivalent rule was already in place, `updated` when
|
|
152
|
+
* the rule was written, `forbidden` when the credentials may not configure
|
|
153
|
+
* bucket CORS (in which case `message` says what to do instead).
|
|
154
|
+
*/
|
|
155
|
+
status: 'unchanged' | 'updated' | 'forbidden';
|
|
156
|
+
/** The rules now in effect, or null when they could not be read or written. */
|
|
157
|
+
rules: CORSRule[] | null;
|
|
158
|
+
message: string;
|
|
109
159
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { CopyObjectCommand, CreateBucketCommand, DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, ListObjectsCommand, PutObjectCommand, S3Client, } from '@aws-sdk/client-s3';
|
|
1
|
+
import { CopyObjectCommand, CreateBucketCommand, DeleteObjectCommand, GetBucketCorsCommand, GetObjectCommand, HeadObjectCommand, ListObjectsCommand, PutBucketCorsCommand, PutObjectCommand, S3Client, } from '@aws-sdk/client-s3';
|
|
2
|
+
import { corsAccessDeniedMessage, corsRulesEqual, isCorsAccessDenied, isNoSuchCorsConfiguration, staticAssetCorsRule, } from '../utils/cors.js';
|
|
2
3
|
export const endpoint = process.env.S3_BUCKET_ENDPOINT;
|
|
3
4
|
/**
|
|
4
5
|
* Builds an S3 client from explicit config, falling back to legacy env vars.
|
|
@@ -276,5 +277,115 @@ export class S3Bucket {
|
|
|
276
277
|
console.log('Could not create bucket: ', err);
|
|
277
278
|
}
|
|
278
279
|
}
|
|
280
|
+
/**
|
|
281
|
+
* Read the bucket's CORS configuration.
|
|
282
|
+
*
|
|
283
|
+
* @returns The rules, or null when the bucket has none configured. A bucket
|
|
284
|
+
* without CORS answers `GetBucketCors` with `NoSuchCORSConfiguration`,
|
|
285
|
+
* which is an ordinary state and not an error worth throwing over.
|
|
286
|
+
* @throws When the credentials may not read bucket configuration, with a
|
|
287
|
+
* message naming what to do about it. Use {@link ensureCors} if you want
|
|
288
|
+
* that case handled for you.
|
|
289
|
+
*/
|
|
290
|
+
async getBucketCors() {
|
|
291
|
+
var _a;
|
|
292
|
+
try {
|
|
293
|
+
const data = await this._client.send(new GetBucketCorsCommand({ Bucket: this.label }));
|
|
294
|
+
return (_a = data.CORSRules) !== null && _a !== void 0 ? _a : null;
|
|
295
|
+
}
|
|
296
|
+
catch (err) {
|
|
297
|
+
if (isNoSuchCorsConfiguration(err)) {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
if (isCorsAccessDenied(err)) {
|
|
301
|
+
const error = new Error(corsAccessDeniedMessage(this.label, 'read'));
|
|
302
|
+
error.name = 'CorsAccessDenied';
|
|
303
|
+
error.cause = err;
|
|
304
|
+
throw error;
|
|
305
|
+
}
|
|
306
|
+
throw err;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Replace the bucket's CORS configuration with the given rules.
|
|
311
|
+
*
|
|
312
|
+
* `PutBucketCors` replaces the whole configuration, so pass every rule the
|
|
313
|
+
* bucket should end up with.
|
|
314
|
+
*
|
|
315
|
+
* @throws When the credentials may not write bucket configuration, with a
|
|
316
|
+
* message naming what to do about it.
|
|
317
|
+
*/
|
|
318
|
+
async putBucketCors(rules) {
|
|
319
|
+
try {
|
|
320
|
+
await this._client.send(new PutBucketCorsCommand({
|
|
321
|
+
Bucket: this.label,
|
|
322
|
+
CORSConfiguration: { CORSRules: rules },
|
|
323
|
+
}));
|
|
324
|
+
return rules;
|
|
325
|
+
}
|
|
326
|
+
catch (err) {
|
|
327
|
+
if (isCorsAccessDenied(err)) {
|
|
328
|
+
const error = new Error(corsAccessDeniedMessage(this.label, 'set'));
|
|
329
|
+
error.name = 'CorsAccessDenied';
|
|
330
|
+
error.cause = err;
|
|
331
|
+
throw error;
|
|
332
|
+
}
|
|
333
|
+
throw err;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Make sure the bucket serves static assets to the given page origins.
|
|
338
|
+
*
|
|
339
|
+
* Best effort by design: it reads the current configuration first and writes
|
|
340
|
+
* nothing when an equivalent rule is already there, and it reports rather
|
|
341
|
+
* than throws when the credentials are not allowed to touch bucket config —
|
|
342
|
+
* which is the normal case on Cloudflare R2 and other providers that keep
|
|
343
|
+
* bucket CORS out of reach of object-scoped tokens.
|
|
344
|
+
*
|
|
345
|
+
* @param origins The page origins that will load the assets, e.g.
|
|
346
|
+
* `['https://app.example']`.
|
|
347
|
+
* @param options Overrides for the default rule, plus `replace` to drop any
|
|
348
|
+
* rules the bucket already carries instead of keeping them.
|
|
349
|
+
*/
|
|
350
|
+
async ensureCors(origins, options = {}) {
|
|
351
|
+
const { replace, ...ruleOptions } = options;
|
|
352
|
+
const desired = staticAssetCorsRule(origins, ruleOptions);
|
|
353
|
+
let existing;
|
|
354
|
+
try {
|
|
355
|
+
existing = await this.getBucketCors();
|
|
356
|
+
}
|
|
357
|
+
catch (err) {
|
|
358
|
+
if ((err === null || err === void 0 ? void 0 : err.name) === 'CorsAccessDenied') {
|
|
359
|
+
console.warn(err.message);
|
|
360
|
+
return { status: 'forbidden', rules: null, message: err.message };
|
|
361
|
+
}
|
|
362
|
+
throw err;
|
|
363
|
+
}
|
|
364
|
+
if (existing === null || existing === void 0 ? void 0 : existing.some((rule) => corsRulesEqual(rule, desired))) {
|
|
365
|
+
return {
|
|
366
|
+
status: 'unchanged',
|
|
367
|
+
rules: existing,
|
|
368
|
+
message: `Bucket "${this.label}" already allows ${origins.join(', ')}.`,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
// PutBucketCors replaces the entire configuration, so carry any unrelated
|
|
372
|
+
// rules over unless the caller explicitly asked for a clean slate.
|
|
373
|
+
const rules = replace || !existing ? [desired] : [...existing, desired];
|
|
374
|
+
try {
|
|
375
|
+
await this.putBucketCors(rules);
|
|
376
|
+
}
|
|
377
|
+
catch (err) {
|
|
378
|
+
if ((err === null || err === void 0 ? void 0 : err.name) === 'CorsAccessDenied') {
|
|
379
|
+
console.warn(err.message);
|
|
380
|
+
return { status: 'forbidden', rules: null, message: err.message };
|
|
381
|
+
}
|
|
382
|
+
throw err;
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
status: 'updated',
|
|
386
|
+
rules,
|
|
387
|
+
message: `Bucket "${this.label}" now allows ${origins.join(', ')} for GET/HEAD.`,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
279
390
|
}
|
|
280
391
|
//# sourceMappingURL=S3Bucket.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"S3Bucket.js","sourceRoot":"","sources":["../../../src/shapes/S3Bucket.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,
|
|
1
|
+
{"version":3,"file":"S3Bucket.js","sourceRoot":"","sources":["../../../src/shapes/S3Bucket.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FileStat, IFileStore, SaveFileOptions } from '@_linked/core/interfaces/IFileStore';
|
|
2
|
-
import { type S3ClientConfigInput } from './S3Bucket.js';
|
|
2
|
+
import { S3Bucket, type EnsureCorsResult, type S3ClientConfigInput } from './S3Bucket.js';
|
|
3
|
+
import { type CorsCheckOptions, type CorsCheckResult } from '../utils/cors.js';
|
|
3
4
|
import type { Readable } from 'stream';
|
|
4
5
|
export interface S3FileStoreConfig {
|
|
5
6
|
bucketName?: string;
|
|
@@ -78,4 +79,24 @@ export declare class S3FileStore implements IFileStore {
|
|
|
78
79
|
* @returns The stat, or null when the object does not exist.
|
|
79
80
|
*/
|
|
80
81
|
statFile(filePath: string): Promise<FileStat | null>;
|
|
82
|
+
/**
|
|
83
|
+
* Make sure the store's bucket serves its assets to the given page origins.
|
|
84
|
+
*
|
|
85
|
+
* A CDN-hosted release is loaded cross-origin by the browser, so the bucket
|
|
86
|
+
* needs an `Access-Control-Allow-Origin` rule or the dynamic-import chunks
|
|
87
|
+
* never load. Best effort: see {@link S3Bucket.ensureCors}.
|
|
88
|
+
*/
|
|
89
|
+
ensureCors(origins: string[], options?: Parameters<S3Bucket['ensureCors']>[1]): Promise<EnsureCorsResult>;
|
|
90
|
+
/**
|
|
91
|
+
* Check, from the outside, that a stored file really is loadable from the
|
|
92
|
+
* given page origin.
|
|
93
|
+
*
|
|
94
|
+
* Uses only public read access, so this works even where the store's
|
|
95
|
+
* credentials cannot read bucket configuration — which is the usual case.
|
|
96
|
+
*
|
|
97
|
+
* @param filePath The path of the file, relative to the endpoint, exactly as
|
|
98
|
+
* `saveFile` takes it.
|
|
99
|
+
* @param origin The page origin that will load it, e.g. `https://app.example`.
|
|
100
|
+
*/
|
|
101
|
+
checkAssetCors(filePath: string, origin: string, options?: CorsCheckOptions): Promise<CorsCheckResult>;
|
|
81
102
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { normalizeSaveFileOptions, } from '@_linked/core/interfaces/IFileStore';
|
|
2
2
|
import mime from 'mime';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import { S3Bucket } from './S3Bucket.js';
|
|
4
|
+
import { S3Bucket, } from './S3Bucket.js';
|
|
5
|
+
import { checkCorsAccess, } from '../utils/cors.js';
|
|
5
6
|
const trimSlashes = (value = '') => value.replace(/^\/+|\/+$/g, '');
|
|
6
7
|
const trimTrailingSlash = (value = '') => value.replace(/\/+$/g, '');
|
|
7
8
|
/**
|
|
@@ -219,5 +220,30 @@ export class S3FileStore {
|
|
|
219
220
|
etag: head.ETag,
|
|
220
221
|
};
|
|
221
222
|
}
|
|
223
|
+
/**
|
|
224
|
+
* Make sure the store's bucket serves its assets to the given page origins.
|
|
225
|
+
*
|
|
226
|
+
* A CDN-hosted release is loaded cross-origin by the browser, so the bucket
|
|
227
|
+
* needs an `Access-Control-Allow-Origin` rule or the dynamic-import chunks
|
|
228
|
+
* never load. Best effort: see {@link S3Bucket.ensureCors}.
|
|
229
|
+
*/
|
|
230
|
+
async ensureCors(origins, options = {}) {
|
|
231
|
+
return this.bucket.ensureCors(origins, options);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Check, from the outside, that a stored file really is loadable from the
|
|
235
|
+
* given page origin.
|
|
236
|
+
*
|
|
237
|
+
* Uses only public read access, so this works even where the store's
|
|
238
|
+
* credentials cannot read bucket configuration — which is the usual case.
|
|
239
|
+
*
|
|
240
|
+
* @param filePath The path of the file, relative to the endpoint, exactly as
|
|
241
|
+
* `saveFile` takes it.
|
|
242
|
+
* @param origin The page origin that will load it, e.g. `https://app.example`.
|
|
243
|
+
*/
|
|
244
|
+
async checkAssetCors(filePath, origin, options = {}) {
|
|
245
|
+
const key = this.applyPrefix(this.normalizePath(filePath));
|
|
246
|
+
return checkCorsAccess(`${this.accessURL}/${key}`, origin, options);
|
|
247
|
+
}
|
|
222
248
|
}
|
|
223
249
|
//# sourceMappingURL=S3FileStore.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"S3FileStore.js","sourceRoot":"","sources":["../../../src/shapes/S3FileStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,wBAAwB,GACzB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,
|
|
1
|
+
{"version":3,"file":"S3FileStore.js","sourceRoot":"","sources":["../../../src/shapes/S3FileStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { CORSRule } from '@aws-sdk/client-s3';
|
|
2
|
+
/**
|
|
3
|
+
* Options for the default "serve static assets" CORS rule.
|
|
4
|
+
*/
|
|
5
|
+
export interface StaticAssetCorsOptions {
|
|
6
|
+
/** Methods to allow. Static hosting only ever needs GET/HEAD. */
|
|
7
|
+
allowedMethods?: string[];
|
|
8
|
+
/** Request headers the browser may send on the preflight. */
|
|
9
|
+
allowedHeaders?: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Response headers the browser is allowed to read. ETag and the content
|
|
12
|
+
* headers matter for caching and for range requests on large chunks.
|
|
13
|
+
*/
|
|
14
|
+
exposeHeaders?: string[];
|
|
15
|
+
/** How long a preflight may be cached, in seconds. */
|
|
16
|
+
maxAgeSeconds?: number;
|
|
17
|
+
/** Optional rule id, useful when a bucket carries several rules. */
|
|
18
|
+
id?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare const DEFAULT_CORS_MAX_AGE_SECONDS = 3600;
|
|
21
|
+
/**
|
|
22
|
+
* The rule a bucket needs in order to serve a CDN-hosted frontend release.
|
|
23
|
+
*
|
|
24
|
+
* `linked build-app` bakes Vite's `base` to the release URL, so dynamic-import
|
|
25
|
+
* chunks are fetched from the bucket rather than from the app origin. Module
|
|
26
|
+
* scripts are always fetched in CORS mode, so without
|
|
27
|
+
* `Access-Control-Allow-Origin` on the response those chunks fail to load.
|
|
28
|
+
*/
|
|
29
|
+
export declare const staticAssetCorsRule: (origins: string[], options?: StaticAssetCorsOptions) => CORSRule;
|
|
30
|
+
/**
|
|
31
|
+
* Compare two rules by meaning rather than by shape: order within the string
|
|
32
|
+
* lists is irrelevant to S3, and a missing list is the same as an empty one.
|
|
33
|
+
* Used so that `ensureCors` can no-op instead of rewriting an identical rule.
|
|
34
|
+
*/
|
|
35
|
+
export declare const corsRulesEqual: (a?: CORSRule, b?: CORSRule) => boolean;
|
|
36
|
+
/**
|
|
37
|
+
* True when the error means "your credentials may not read or write this
|
|
38
|
+
* bucket's configuration". Object-scoped credentials (Cloudflare R2 tokens in
|
|
39
|
+
* particular) can read and write objects but get a 403 on bucket CORS.
|
|
40
|
+
*/
|
|
41
|
+
export declare const isCorsAccessDenied: (error: any) => boolean;
|
|
42
|
+
/** True when the bucket simply has no CORS configuration yet. */
|
|
43
|
+
export declare const isNoSuchCorsConfiguration: (error: any) => boolean;
|
|
44
|
+
export declare const corsAccessDeniedMessage: (bucket: string, action: string) => string;
|
|
45
|
+
export interface CorsCheckOptions {
|
|
46
|
+
/** Injectable fetch, so this is testable and usable on older runtimes. */
|
|
47
|
+
fetchImpl?: typeof fetch;
|
|
48
|
+
/** HEAD by default: it is enough and downloads nothing. */
|
|
49
|
+
method?: 'HEAD' | 'GET';
|
|
50
|
+
}
|
|
51
|
+
export interface CorsCheckResult {
|
|
52
|
+
/** True only when the asset responded OK *and* the origin is allowed. */
|
|
53
|
+
ok: boolean;
|
|
54
|
+
url: string;
|
|
55
|
+
origin: string;
|
|
56
|
+
/** HTTP status, or null when the request never completed. */
|
|
57
|
+
status: number | null;
|
|
58
|
+
/** The raw Access-Control-Allow-Origin header, or null when absent. */
|
|
59
|
+
allowOrigin: string | null;
|
|
60
|
+
/** Whether that header actually covers the origin we asked about. */
|
|
61
|
+
allowsOrigin: boolean;
|
|
62
|
+
/** The raw Access-Control-Expose-Headers header, or null when absent. */
|
|
63
|
+
exposeHeaders: string | null;
|
|
64
|
+
/** Set when the request itself failed (DNS, TLS, offline). */
|
|
65
|
+
error?: string;
|
|
66
|
+
/** The equivalent one-liner, handy to paste into a runbook or an issue. */
|
|
67
|
+
curl: string;
|
|
68
|
+
/** A human-readable verdict. */
|
|
69
|
+
message: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Verify cross-origin access to a published asset from the outside, the way a
|
|
73
|
+
* browser would.
|
|
74
|
+
*
|
|
75
|
+
* This needs nothing but public read access, so it works on deployments whose
|
|
76
|
+
* credentials cannot read bucket configuration at all — which is the common
|
|
77
|
+
* case. Prefer it as the check that a release actually loads.
|
|
78
|
+
*/
|
|
79
|
+
export declare const checkCorsAccess: (url: string, origin: string, options?: CorsCheckOptions) => Promise<CorsCheckResult>;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
export const DEFAULT_CORS_MAX_AGE_SECONDS = 3600;
|
|
2
|
+
/**
|
|
3
|
+
* The rule a bucket needs in order to serve a CDN-hosted frontend release.
|
|
4
|
+
*
|
|
5
|
+
* `linked build-app` bakes Vite's `base` to the release URL, so dynamic-import
|
|
6
|
+
* chunks are fetched from the bucket rather than from the app origin. Module
|
|
7
|
+
* scripts are always fetched in CORS mode, so without
|
|
8
|
+
* `Access-Control-Allow-Origin` on the response those chunks fail to load.
|
|
9
|
+
*/
|
|
10
|
+
export const staticAssetCorsRule = (origins, options = {}) => {
|
|
11
|
+
var _a, _b, _c, _d;
|
|
12
|
+
const rule = {
|
|
13
|
+
AllowedMethods: (_a = options.allowedMethods) !== null && _a !== void 0 ? _a : ['GET', 'HEAD'],
|
|
14
|
+
AllowedOrigins: origins,
|
|
15
|
+
AllowedHeaders: (_b = options.allowedHeaders) !== null && _b !== void 0 ? _b : ['*'],
|
|
16
|
+
ExposeHeaders: (_c = options.exposeHeaders) !== null && _c !== void 0 ? _c : [
|
|
17
|
+
'ETag',
|
|
18
|
+
'Content-Length',
|
|
19
|
+
'Content-Type',
|
|
20
|
+
],
|
|
21
|
+
MaxAgeSeconds: (_d = options.maxAgeSeconds) !== null && _d !== void 0 ? _d : DEFAULT_CORS_MAX_AGE_SECONDS,
|
|
22
|
+
};
|
|
23
|
+
if (options.id) {
|
|
24
|
+
rule.ID = options.id;
|
|
25
|
+
}
|
|
26
|
+
return rule;
|
|
27
|
+
};
|
|
28
|
+
const normalizeList = (values) => [...(values !== null && values !== void 0 ? values : [])].map((value) => value.trim()).sort();
|
|
29
|
+
/**
|
|
30
|
+
* Compare two rules by meaning rather than by shape: order within the string
|
|
31
|
+
* lists is irrelevant to S3, and a missing list is the same as an empty one.
|
|
32
|
+
* Used so that `ensureCors` can no-op instead of rewriting an identical rule.
|
|
33
|
+
*/
|
|
34
|
+
export const corsRulesEqual = (a, b) => {
|
|
35
|
+
var _a, _b;
|
|
36
|
+
if (!a || !b) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
const sameList = (x, y) => {
|
|
40
|
+
const left = normalizeList(x);
|
|
41
|
+
const right = normalizeList(y);
|
|
42
|
+
return (left.length === right.length &&
|
|
43
|
+
left.every((value, index) => value === right[index]));
|
|
44
|
+
};
|
|
45
|
+
return (sameList(a.AllowedMethods, b.AllowedMethods) &&
|
|
46
|
+
sameList(a.AllowedOrigins, b.AllowedOrigins) &&
|
|
47
|
+
sameList(a.AllowedHeaders, b.AllowedHeaders) &&
|
|
48
|
+
sameList(a.ExposeHeaders, b.ExposeHeaders) &&
|
|
49
|
+
((_a = a.MaxAgeSeconds) !== null && _a !== void 0 ? _a : null) === ((_b = b.MaxAgeSeconds) !== null && _b !== void 0 ? _b : null));
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* True when the error means "your credentials may not read or write this
|
|
53
|
+
* bucket's configuration". Object-scoped credentials (Cloudflare R2 tokens in
|
|
54
|
+
* particular) can read and write objects but get a 403 on bucket CORS.
|
|
55
|
+
*/
|
|
56
|
+
export const isCorsAccessDenied = (error) => {
|
|
57
|
+
var _a, _b;
|
|
58
|
+
const name = (_a = error === null || error === void 0 ? void 0 : error.name) !== null && _a !== void 0 ? _a : error === null || error === void 0 ? void 0 : error.Code;
|
|
59
|
+
return (((_b = error === null || error === void 0 ? void 0 : error.$metadata) === null || _b === void 0 ? void 0 : _b.httpStatusCode) === 403 ||
|
|
60
|
+
name === 'AccessDenied' ||
|
|
61
|
+
name === 'Forbidden' ||
|
|
62
|
+
name === 'NotImplemented');
|
|
63
|
+
};
|
|
64
|
+
/** True when the bucket simply has no CORS configuration yet. */
|
|
65
|
+
export const isNoSuchCorsConfiguration = (error) => {
|
|
66
|
+
var _a;
|
|
67
|
+
const name = (_a = error === null || error === void 0 ? void 0 : error.name) !== null && _a !== void 0 ? _a : error === null || error === void 0 ? void 0 : error.Code;
|
|
68
|
+
return (name === 'NoSuchCORSConfiguration' ||
|
|
69
|
+
name === 'NoSuchCORSConfigurationError');
|
|
70
|
+
};
|
|
71
|
+
export const corsAccessDeniedMessage = (bucket, action) => `These credentials cannot ${action} bucket CORS on "${bucket}". ` +
|
|
72
|
+
`Many providers (Cloudflare R2 among them) never expose bucket CORS to ` +
|
|
73
|
+
`object-scoped credentials, so the rule has to be set in the provider's ` +
|
|
74
|
+
`dashboard or with an account-level token. See the CORS section of the ` +
|
|
75
|
+
`@_linked/s3 README, and verify the result with checkCorsAccess().`;
|
|
76
|
+
/**
|
|
77
|
+
* Verify cross-origin access to a published asset from the outside, the way a
|
|
78
|
+
* browser would.
|
|
79
|
+
*
|
|
80
|
+
* This needs nothing but public read access, so it works on deployments whose
|
|
81
|
+
* credentials cannot read bucket configuration at all — which is the common
|
|
82
|
+
* case. Prefer it as the check that a release actually loads.
|
|
83
|
+
*/
|
|
84
|
+
export const checkCorsAccess = async (url, origin, options = {}) => {
|
|
85
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
86
|
+
const method = (_a = options.method) !== null && _a !== void 0 ? _a : 'HEAD';
|
|
87
|
+
const fetchImpl = (_b = options.fetchImpl) !== null && _b !== void 0 ? _b : globalThis.fetch;
|
|
88
|
+
const curl = `curl -sI -H 'Origin: ${origin}' ${url}`;
|
|
89
|
+
if (!fetchImpl) {
|
|
90
|
+
return {
|
|
91
|
+
ok: false,
|
|
92
|
+
url,
|
|
93
|
+
origin,
|
|
94
|
+
status: null,
|
|
95
|
+
allowOrigin: null,
|
|
96
|
+
allowsOrigin: false,
|
|
97
|
+
exposeHeaders: null,
|
|
98
|
+
error: 'No fetch implementation available',
|
|
99
|
+
curl,
|
|
100
|
+
message: 'Could not check CORS: this runtime has no global fetch. Pass fetchImpl, ' +
|
|
101
|
+
`or run: ${curl}`,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
let response;
|
|
105
|
+
try {
|
|
106
|
+
response = await fetchImpl(url, { method, headers: { Origin: origin } });
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
const reason = (_c = error === null || error === void 0 ? void 0 : error.message) !== null && _c !== void 0 ? _c : String(error);
|
|
110
|
+
return {
|
|
111
|
+
ok: false,
|
|
112
|
+
url,
|
|
113
|
+
origin,
|
|
114
|
+
status: null,
|
|
115
|
+
allowOrigin: null,
|
|
116
|
+
allowsOrigin: false,
|
|
117
|
+
exposeHeaders: null,
|
|
118
|
+
error: reason,
|
|
119
|
+
curl,
|
|
120
|
+
message: `Could not reach ${url}: ${reason}`,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const allowOrigin = (_e = (_d = response.headers) === null || _d === void 0 ? void 0 : _d.get('access-control-allow-origin')) !== null && _e !== void 0 ? _e : null;
|
|
124
|
+
const exposeHeaders = (_g = (_f = response.headers) === null || _f === void 0 ? void 0 : _f.get('access-control-expose-headers')) !== null && _g !== void 0 ? _g : null;
|
|
125
|
+
const allowsOrigin = allowOrigin === '*' ||
|
|
126
|
+
(!!allowOrigin && allowOrigin.toLowerCase() === origin.toLowerCase());
|
|
127
|
+
const status = (_h = response.status) !== null && _h !== void 0 ? _h : null;
|
|
128
|
+
const reachable = status !== null && status >= 200 && status < 400;
|
|
129
|
+
const ok = reachable && allowsOrigin;
|
|
130
|
+
let message;
|
|
131
|
+
if (!reachable) {
|
|
132
|
+
message = `${url} responded ${status}; the asset itself is not publicly readable.`;
|
|
133
|
+
}
|
|
134
|
+
else if (!allowOrigin) {
|
|
135
|
+
message =
|
|
136
|
+
`${url} has no Access-Control-Allow-Origin header, so a page on ${origin} ` +
|
|
137
|
+
`cannot load it as a module script. Configure bucket CORS.`;
|
|
138
|
+
}
|
|
139
|
+
else if (!allowsOrigin) {
|
|
140
|
+
message =
|
|
141
|
+
`${url} allows "${allowOrigin}", which does not cover ${origin}. ` +
|
|
142
|
+
`Add that origin to the bucket CORS rule.`;
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
message = `${url} is loadable from ${origin} (Access-Control-Allow-Origin: ${allowOrigin}).`;
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
ok,
|
|
149
|
+
url,
|
|
150
|
+
origin,
|
|
151
|
+
status,
|
|
152
|
+
allowOrigin,
|
|
153
|
+
allowsOrigin,
|
|
154
|
+
exposeHeaders,
|
|
155
|
+
curl,
|
|
156
|
+
message,
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
//# sourceMappingURL=cors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cors.js","sourceRoot":"","sources":["../../../src/utils/cors.ts"],"names":[],"mappings":"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"}
|