@easy-cms/core 0.1.1 → 0.2.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.
- package/dist/index.d.ts +8 -1
- package/dist/index.js +56 -2
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -412,6 +412,12 @@ interface Config {
|
|
|
412
412
|
* so frontends on other origins can use them. Default: relative URLs.
|
|
413
413
|
*/
|
|
414
414
|
readonly serverURL?: string;
|
|
415
|
+
/**
|
|
416
|
+
* Origins whose browser code may call the REST API (CORS), e.g. a Vite or React app on
|
|
417
|
+
* another origin. `'*'` allows any origin for anonymous requests. Origins in
|
|
418
|
+
* `auth.trustedOrigins` are always allowed, with cookies. Default: none.
|
|
419
|
+
*/
|
|
420
|
+
readonly cors?: readonly string[] | '*';
|
|
415
421
|
readonly routes?: RoutesConfig;
|
|
416
422
|
readonly admin?: AdminConfig;
|
|
417
423
|
readonly upload?: UploadConfig;
|
|
@@ -425,7 +431,8 @@ interface Config {
|
|
|
425
431
|
readonly plugins?: readonly Plugin[];
|
|
426
432
|
}
|
|
427
433
|
/** The config after plugins ran, validation passed and defaults were applied. */
|
|
428
|
-
interface ResolvedConfig extends Omit<Config, 'routes' | 'admin' | 'upload' | 'auth' | 'collections' | 'globals' | 'plugins'> {
|
|
434
|
+
interface ResolvedConfig extends Omit<Config, 'cors' | 'routes' | 'admin' | 'upload' | 'auth' | 'collections' | 'globals' | 'plugins'> {
|
|
435
|
+
readonly cors: readonly string[] | '*';
|
|
429
436
|
readonly routes: Required<RoutesConfig>;
|
|
430
437
|
readonly admin: Required<AdminConfig>;
|
|
431
438
|
readonly upload: Required<Omit<UploadConfig, 'storage'>> & Pick<UploadConfig, 'storage'>;
|
package/dist/index.js
CHANGED
|
@@ -867,6 +867,7 @@ function validateConfig(config) {
|
|
|
867
867
|
validateAdmin(config, add);
|
|
868
868
|
validateUpload(config, add);
|
|
869
869
|
validateAuth(config, add);
|
|
870
|
+
validateCors(config.cors, add);
|
|
870
871
|
const collections = asArray(config.collections, "collections", add);
|
|
871
872
|
const globals = asArray(config.globals, "globals", add);
|
|
872
873
|
const collectionSlugs = /* @__PURE__ */ new Set([...BUILTIN_COLLECTIONS]);
|
|
@@ -943,6 +944,21 @@ function validateAuth(config, add) {
|
|
|
943
944
|
}
|
|
944
945
|
}
|
|
945
946
|
}
|
|
947
|
+
function validateCors(cors, add) {
|
|
948
|
+
if (cors === void 0 || cors === "*") return;
|
|
949
|
+
if (!Array.isArray(cors)) {
|
|
950
|
+
add("cors", 'must be a list of origins or "*"', 'cors: ["https://my-site.com"]');
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
for (const [i, origin] of cors.entries()) {
|
|
954
|
+
if (typeof origin !== "string" || !/^https?:\/\/[^/]+$/.test(origin)) {
|
|
955
|
+
add(
|
|
956
|
+
`cors[${i}]`,
|
|
957
|
+
`must be an origin like "https://example.com" (got ${JSON.stringify(origin)})`
|
|
958
|
+
);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
946
962
|
function validateUpload(config, add) {
|
|
947
963
|
const upload = config.upload;
|
|
948
964
|
if (upload === void 0) return;
|
|
@@ -1125,6 +1141,7 @@ async function resolveConfig(input) {
|
|
|
1125
1141
|
const { plugins: _plugins, ...rest } = config;
|
|
1126
1142
|
const result = {
|
|
1127
1143
|
...rest,
|
|
1144
|
+
cors: config.cors ?? [],
|
|
1128
1145
|
routes: { api: `/${(config.routes?.api ?? DEFAULT_API_PATH).replace(/^\/+|\/+$/g, "")}` },
|
|
1129
1146
|
admin: {
|
|
1130
1147
|
path: config.admin?.path ?? DEFAULT_ADMIN_PATH,
|
|
@@ -2228,6 +2245,13 @@ function createRestHandler(instance, options = {}) {
|
|
|
2228
2245
|
const basePath = (options.basePath ?? cms.config.routes.api).replace(/\/+$/, "");
|
|
2229
2246
|
const production = process.env.NODE_ENV === "production";
|
|
2230
2247
|
return async (request) => {
|
|
2248
|
+
const cors = corsHeaders(cms, request);
|
|
2249
|
+
if (request.method.toUpperCase() === "OPTIONS") {
|
|
2250
|
+
return new Response(null, { status: 204, headers: cors });
|
|
2251
|
+
}
|
|
2252
|
+
return withHeaders(await handle(request), cors);
|
|
2253
|
+
};
|
|
2254
|
+
async function handle(request) {
|
|
2231
2255
|
const headers = new Headers({
|
|
2232
2256
|
"content-type": "application/json; charset=utf-8",
|
|
2233
2257
|
"cache-control": "no-store",
|
|
@@ -2243,14 +2267,44 @@ function createRestHandler(instance, options = {}) {
|
|
|
2243
2267
|
const user = token ? await cms.auth.verify(token) : null;
|
|
2244
2268
|
const ctx = { request, url, user, via: user ? via : null, token, headers };
|
|
2245
2269
|
const method = request.method.toUpperCase();
|
|
2246
|
-
if (method !== "GET" && method !== "HEAD"
|
|
2270
|
+
if (method !== "GET" && method !== "HEAD") checkCsrf(cms, ctx);
|
|
2247
2271
|
const result = await route(cms, ctx, method, segments, options);
|
|
2248
2272
|
if (result.body instanceof Response) return result.body;
|
|
2249
2273
|
return new Response(JSON.stringify(result.body), { status: result.status ?? 200, headers });
|
|
2250
2274
|
} catch (error) {
|
|
2251
2275
|
return errorResponse(cms, error, headers, production);
|
|
2252
2276
|
}
|
|
2253
|
-
}
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
var CORS_METHODS = "GET, HEAD, POST, PATCH, PUT, DELETE";
|
|
2280
|
+
var CORS_HEADERS = `authorization, content-type, ${CSRF_HEADER}`;
|
|
2281
|
+
function corsHeaders(cms, request) {
|
|
2282
|
+
const headers = new Headers({ vary: "Origin" });
|
|
2283
|
+
const origin = request.headers.get("origin");
|
|
2284
|
+
if (!origin) return headers;
|
|
2285
|
+
const { cors, auth } = cms.config;
|
|
2286
|
+
const trusted = auth.trustedOrigins.includes(origin);
|
|
2287
|
+
if (!trusted && cors !== "*" && !cors.includes(origin)) return headers;
|
|
2288
|
+
headers.set("access-control-allow-origin", origin);
|
|
2289
|
+
if (trusted) headers.set("access-control-allow-credentials", "true");
|
|
2290
|
+
if (request.method.toUpperCase() === "OPTIONS") {
|
|
2291
|
+
headers.set("access-control-allow-methods", CORS_METHODS);
|
|
2292
|
+
headers.set("access-control-allow-headers", CORS_HEADERS);
|
|
2293
|
+
headers.set("access-control-max-age", "600");
|
|
2294
|
+
}
|
|
2295
|
+
return headers;
|
|
2296
|
+
}
|
|
2297
|
+
function withHeaders(response, extra) {
|
|
2298
|
+
const headers = new Headers(response.headers);
|
|
2299
|
+
extra.forEach((value, key2) => {
|
|
2300
|
+
if (key2 === "vary") headers.append("vary", value);
|
|
2301
|
+
else headers.set(key2, value);
|
|
2302
|
+
});
|
|
2303
|
+
return new Response(response.body, {
|
|
2304
|
+
status: response.status,
|
|
2305
|
+
statusText: response.statusText,
|
|
2306
|
+
headers
|
|
2307
|
+
});
|
|
2254
2308
|
}
|
|
2255
2309
|
async function route(cms, ctx, method, segments, options) {
|
|
2256
2310
|
const [first, second, third] = segments;
|