befly 3.34.0 → 3.36.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.
@@ -14,7 +14,7 @@ export default {
14
14
  required: [],
15
15
  handler: async (befly, ctx) => {
16
16
  try {
17
- const maxFileSizeMb = Number(befly.config?.uploadMaxSize || 20);
17
+ const maxFileSizeMb = Number(befly.config.upload.maxSize);
18
18
  const maxFileSize = maxFileSizeMb * 1024 * 1024;
19
19
  const requestContentType = ctx.req.headers.get("content-type") || "";
20
20
 
@@ -22,6 +22,11 @@ export default {
22
22
  return befly.tool.No("请使用 FormData 上传文件");
23
23
  }
24
24
 
25
+ const requestContentLength = Number(ctx.req.headers.get("content-length") || 0);
26
+ if (Number.isFinite(requestContentLength) && requestContentLength > maxFileSize) {
27
+ return befly.tool.No(`文件不能超过${maxFileSizeMb}MB`);
28
+ }
29
+
25
30
  if (!ctx.userId) {
26
31
  return befly.tool.No("用户未登录");
27
32
  }
@@ -33,6 +38,22 @@ export default {
33
38
  return befly.tool.No("缺少上传文件");
34
39
  }
35
40
 
41
+ const originalFileName = file.name || "未命名文件";
42
+ const extension = extname(originalFileName).toLowerCase();
43
+ const mimeType = String(file.type || "").toLowerCase();
44
+
45
+ if (!extension) {
46
+ return befly.tool.No("上传文件缺少扩展名");
47
+ }
48
+
49
+ if (!befly.config.upload.allowedExtensions.split(",").includes(extension)) {
50
+ return befly.tool.No(`不允许上传 ${extension} 类型文件`);
51
+ }
52
+
53
+ if (!befly.config.upload.allowedMimeTypes.split(",").includes(mimeType)) {
54
+ return befly.tool.No("不允许上传该文件类型");
55
+ }
56
+
36
57
  const rawBuffer = await file.arrayBuffer();
37
58
 
38
59
  if (!rawBuffer || rawBuffer.byteLength <= 0) {
@@ -43,9 +64,6 @@ export default {
43
64
  return befly.tool.No(`文件不能超过${maxFileSizeMb}MB`);
44
65
  }
45
66
 
46
- const originalFileName = file.name || "未命名文件";
47
- const extension = extname(originalFileName).toLowerCase();
48
- const mimeType = String(file.type || "").toLowerCase();
49
67
  const mimeTypeGroup = mimeType.split("/")[0];
50
68
  const fileType = ["image", "video", "audio"].includes(mimeTypeGroup) ? mimeTypeGroup : "file";
51
69
 
@@ -54,9 +72,9 @@ export default {
54
72
  const monthDir = getMonthDir(now, befly.config?.tz);
55
73
  const fileKey = Bun.randomUUIDv7();
56
74
  const savedFileName = extension ? `${fileKey}${extension}` : fileKey;
57
- const uploadDir = join(getAppPublicDir(befly.config?.publicDir || "./public"), categoryDir, monthDir);
75
+ const uploadDir = join(getAppPublicDir(befly.config.upload.publicDir), categoryDir, monthDir);
58
76
  const relativeFilePath = `/public/${categoryDir}/${monthDir}/${savedFileName}`;
59
- const absoluteFileUrl = `${befly.config.publicHost}${relativeFilePath}`;
77
+ const absoluteFileUrl = `${befly.config.upload.publicHost}${relativeFilePath}`;
60
78
  const isImage = fileType === "image" ? 1 : 0;
61
79
 
62
80
  mkdirSync(uploadDir, { recursive: true });
package/checks/api.js CHANGED
@@ -93,7 +93,18 @@ const apiSchema = z
93
93
  fields: fieldsSchema,
94
94
  required: z.array(noTrimString)
95
95
  })
96
- .strict();
96
+ .strict()
97
+ .superRefine((value, context) => {
98
+ if (value.source !== "app") {
99
+ return;
100
+ }
101
+
102
+ if (value.relativePath.split("/")[0] !== "core") {
103
+ return;
104
+ }
105
+
106
+ addIssue(context, ["relativePath"], "app API 不能使用 core 作为一级目录,避免占用 /api/core 命名空间");
107
+ });
97
108
 
98
109
  const apiListSchema = z.array(apiSchema);
99
110
 
package/checks/config.js CHANGED
@@ -10,6 +10,8 @@ z.config(z.locales.zhCN());
10
10
  const boolIntSchema = z.union([z.literal(0), z.literal(1), z.literal(true), z.literal(false)]);
11
11
  const noTrimString = z.string().refine(isNoTrimStringAllowEmpty, "不允许首尾空格");
12
12
  const beflyModeSchema = z.union([z.literal("manual"), z.literal("auto")]);
13
+ const uploadExtensionListSchema = noTrimString.regex(/^\.[a-z0-9]+(?:,\.[a-z0-9]+)*$/);
14
+ const uploadMimeTypeListSchema = noTrimString.regex(/^[a-z0-9][a-z0-9.+-]*\/[a-z0-9][a-z0-9.+-]*(?:,[a-z0-9][a-z0-9.+-]*\/[a-z0-9][a-z0-9.+-]*)*$/);
13
15
  const projectListCodeSchema = z.string().regex(/^[a-z][a-zA-Z0-9]*$/, "必须是小驼峰命名");
14
16
  const projectListItemSchema = z
15
17
  .object({
@@ -30,7 +32,7 @@ const projectListsSchema = z.array(projectListItemSchema).superRefine((projectLi
30
32
 
31
33
  if (codeSet.has(code)) {
32
34
  ctx.addIssue({
33
- code: z.ZodIssueCode.custom,
35
+ code: "custom",
34
36
  message: `projectLists[${index}].code 重复`,
35
37
  path: [index, "code"]
36
38
  });
@@ -41,7 +43,7 @@ const projectListsSchema = z.array(projectListItemSchema).superRefine((projectLi
41
43
 
42
44
  if (productCodeSet.has(productCode)) {
43
45
  ctx.addIssue({
44
- code: z.ZodIssueCode.custom,
46
+ code: "custom",
45
47
  message: `projectLists[${index}].productCode 重复`,
46
48
  path: [index, "productCode"]
47
49
  });
@@ -59,13 +61,20 @@ const configSchema = z
59
61
  appPort: z.int().min(1).max(65535),
60
62
  appHost: noTrimString,
61
63
  apiHost: noTrimString,
62
- publicHost: noTrimString,
63
64
  devEmail: z.union([z.literal(""), z.email()]),
64
65
  devPassword: z.string().min(6),
65
66
  bodyLimit: z.int().min(1),
66
- uploadMaxSize: z.int().min(1),
67
+ upload: z
68
+ .object({
69
+ maxSize: z.int().min(1),
70
+ publicDir: noTrimString.min(1),
71
+ publicHost: noTrimString,
72
+ allowedExtensions: uploadExtensionListSchema,
73
+ allowedMimeTypes: uploadMimeTypeListSchema,
74
+ forceDownloadExtensions: z.union([z.literal(""), uploadExtensionListSchema])
75
+ })
76
+ .strict(),
67
77
  tz: z.string().refine((value) => isValidTimeZone(value), "无效的时区"),
68
- publicDir: noTrimString.min(1),
69
78
  excludeApisLog: z.array(noTrimString),
70
79
 
71
80
  logger: z
@@ -128,7 +137,18 @@ const configSchema = z
128
137
  maxAge: z.int().min(0),
129
138
  credentials: z.union([z.literal("true"), z.literal("false"), z.literal(true), z.literal(false)])
130
139
  })
131
- .strict(),
140
+ .strict()
141
+ .superRefine((cors, ctx) => {
142
+ if (cors.origin !== "*" || (cors.credentials !== true && cors.credentials !== "true")) {
143
+ return;
144
+ }
145
+
146
+ ctx.addIssue({
147
+ code: "custom",
148
+ message: "cors.credentials 为 true 时 cors.origin 不能为 *",
149
+ path: ["credentials"]
150
+ });
151
+ }),
132
152
 
133
153
  rateLimit: z
134
154
  .object({
package/checks/menu.js CHANGED
@@ -36,7 +36,7 @@ const menuListSchema = z.array(menuSchema).superRefine((menuList, refineCtx) =>
36
36
  const childFullPath = `${menu.path}${child.path}`;
37
37
  if (!fullMenuPathRegex.test(childFullPath)) {
38
38
  refineCtx.addIssue({
39
- code: z.ZodIssueCode.custom,
39
+ code: "custom",
40
40
  message: "菜单完整路径必须是 /core/xxx 或无前缀路径",
41
41
  path: [menuIndex, "children", childIndex, "path"]
42
42
  });
@@ -6,11 +6,16 @@
6
6
  "devEmail": "dev@qq.com",
7
7
  "devPassword": "111111",
8
8
  "bodyLimit": 1048576,
9
- "uploadMaxSize": 20,
9
+ "upload": {
10
+ "maxSize": 20,
11
+ "publicDir": "./public",
12
+ "publicHost": "http://127.0.0.1:3000",
13
+ "allowedExtensions": ".jpg,.jpeg,.png,.gif,.webp,.bmp,.mp4,.webm,.mp3,.wav,.pdf,.txt,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.zip",
14
+ "allowedMimeTypes": "image/jpeg,image/png,image/gif,image/webp,image/bmp,video/mp4,video/webm,audio/mpeg,audio/wav,audio/x-wav,application/pdf,text/plain,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation,application/zip,application/x-zip-compressed",
15
+ "forceDownloadExtensions": ".html,.htm,.js,.mjs,.css,.svg,.xml,.xhtml,.wasm,.json,.map"
16
+ },
10
17
  "tz": "Asia/Shanghai",
11
18
  "apiHost": "http://127.0.0.1",
12
- "publicDir": "./public",
13
- "publicHost": "http://127.0.0.1:3000",
14
19
  "excludeApisLog": ["/api/core/tongJi/*Report"],
15
20
  "logger": {
16
21
  "debug": 1,
@@ -58,7 +63,7 @@
58
63
  "allowedHeaders": "Content-Type,Authorization",
59
64
  "exposedHeaders": "",
60
65
  "maxAge": 86400,
61
- "credentials": "true"
66
+ "credentials": "false"
62
67
  },
63
68
 
64
69
  "rateLimit": {
package/index.js CHANGED
@@ -214,7 +214,7 @@ export class Befly {
214
214
 
215
215
  // 启动 HTTP服务器
216
216
  const apiFetch = apiHandler(this.apis, this.hooks, this.context);
217
- const staticFetch = staticHandler(this.context.config.cors, this.context.config.publicDir);
217
+ const staticFetch = staticHandler(this.context.config.cors, this.context.config.upload);
218
218
 
219
219
  const server = Bun.serve({
220
220
  port: this.context.config.appPort || 3000,
package/lib/dbHelper.js CHANGED
@@ -154,6 +154,42 @@ function assertWriteDataHasFields(data, message, table) {
154
154
  });
155
155
  }
156
156
 
157
+ function getTableFieldNames(tableInfo) {
158
+ return Object.keys(tableInfo)
159
+ .map((fieldName) => snakeCase(fieldName))
160
+ .toSorted();
161
+ }
162
+
163
+ function assertWriteFieldNamesDefined(tableInfo, fieldNames, table, label) {
164
+ const allowedFields = getTableFieldNames(tableInfo);
165
+ const allowedFieldSet = new Set(allowedFields);
166
+ const invalidFields = [];
167
+
168
+ for (const fieldName of fieldNames) {
169
+ const snakeFieldName = snakeCase(fieldName);
170
+
171
+ if (!allowedFieldSet.has(snakeFieldName)) {
172
+ invalidFields.push(snakeFieldName);
173
+ }
174
+ }
175
+
176
+ if (invalidFields.length < 1) {
177
+ return;
178
+ }
179
+
180
+ throw new Error(`${label} 包含表 ${table} 未定义字段: ${invalidFields.join(", ")}`, {
181
+ cause: null,
182
+ code: "validation",
183
+ table: table,
184
+ invalidFields: invalidFields,
185
+ allowedFields: allowedFields
186
+ });
187
+ }
188
+
189
+ function assertWriteFieldsDefined(tableInfo, data, table, label) {
190
+ assertWriteFieldNamesDefined(tableInfo, Object.keys(data), table, label);
191
+ }
192
+
157
193
  function assertNoUndefinedInRecord(row, label) {
158
194
  for (const [key, value] of Object.entries(row)) {
159
195
  if (value === undefined) {
@@ -744,6 +780,7 @@ class DbHelper {
744
780
  async insData(options) {
745
781
  const parsed = this.createDbParse().parseInsert(options);
746
782
  const { table, snakeTable, data } = parsed;
783
+ const tableInfo = this.getTableDef(table);
747
784
  const inputData = this.prepareWriteInputData(data);
748
785
  assertWriteDataHasFields(inputData, "插入数据必须至少有一个字段", snakeTable);
749
786
  const now = Date.now();
@@ -751,6 +788,7 @@ class DbHelper {
751
788
  const processed = insertRows.processedList[0];
752
789
 
753
790
  assertWriteDataHasFields(processed, "插入数据必须至少有一个字段", snakeTable);
791
+ assertWriteFieldsDefined(tableInfo, processed, table, "insData.data");
754
792
 
755
793
  assertNoUndefinedInRecord(processed, `insData 插入数据 (table: ${snakeTable})`);
756
794
 
@@ -779,11 +817,13 @@ class DbHelper {
779
817
  assertInsertBatchSize(dataList.length, MAX_BATCH_SIZE);
780
818
 
781
819
  const snakeTable = parsed.snakeTable;
820
+ const tableInfo = this.getTableDef(table);
782
821
  const now = Date.now();
783
822
  const insertRows = await this.createInsertRows(table, snakeTable, dataList, now);
784
823
  const processedList = insertRows.processedList;
785
824
 
786
825
  const insertFields = assertBatchInsertRowsConsistent(processedList, { table: snakeTable });
826
+ assertWriteFieldNamesDefined(tableInfo, insertFields, table, "insBatch.dataList");
787
827
  const builder = this.createSqlBuilder();
788
828
  const { sql, params } = builder.toInsertSql(snakeTable, processedList);
789
829
 
@@ -851,6 +891,7 @@ class DbHelper {
851
891
  }
852
892
 
853
893
  const snakeTable = parsed.snakeTable;
894
+ const tableInfo = this.getTableDef(table);
854
895
  const now = Date.now();
855
896
 
856
897
  const processedList = [];
@@ -873,6 +914,11 @@ class DbHelper {
873
914
  sql: { sql: "", params: [], duration: 0 }
874
915
  };
875
916
  }
917
+ const writeFields = fields.slice();
918
+ if (this.beflyMode === "auto") {
919
+ writeFields.push("updated_at");
920
+ }
921
+ assertWriteFieldNamesDefined(tableInfo, writeFields, table, "updBatch.dataList");
876
922
 
877
923
  const query = SqlBuilder.toUpdateCaseByIdSql({
878
924
  table: snakeTable,
@@ -896,6 +942,7 @@ class DbHelper {
896
942
 
897
943
  async updData(options) {
898
944
  const parsed = this.createDbParse().parseUpdate(options);
945
+ const tableInfo = this.getTableDef(parsed.table);
899
946
  const inputData = this.prepareWriteInputData(parsed.data);
900
947
  assertWriteDataHasFields(inputData, "更新数据必须至少有一个字段", parsed.snakeTable);
901
948
 
@@ -906,6 +953,7 @@ class DbHelper {
906
953
  beflyMode: this.beflyMode
907
954
  });
908
955
  assertWriteDataHasFields(processed, "更新数据必须至少有一个字段", parsed.snakeTable);
956
+ assertWriteFieldsDefined(tableInfo, processed, parsed.table, "updData.data");
909
957
  const builder = this.createSqlBuilder().where(parsed.where);
910
958
  const { sql, params } = builder.toUpdateSql(parsed.snakeTable, processed);
911
959
 
@@ -919,6 +967,7 @@ class DbHelper {
919
967
 
920
968
  async delData(options) {
921
969
  const parsed = this.createDbParse().parseDelete(options, false);
970
+ const tableInfo = this.getTableDef(parsed.table);
922
971
  let processed;
923
972
 
924
973
  if (parsed.deleteMode === "manual") {
@@ -932,6 +981,7 @@ class DbHelper {
932
981
  updated_at: now
933
982
  };
934
983
  }
984
+ assertWriteFieldsDefined(tableInfo, processed, parsed.table, "delData.data");
935
985
 
936
986
  const builder = this.createSqlBuilder().where(parsed.where);
937
987
  const { sql, params } = builder.toUpdateSql(parsed.snakeTable, processed);
@@ -960,6 +1010,8 @@ class DbHelper {
960
1010
 
961
1011
  async increment(table, field, where, value = 1) {
962
1012
  const parsed = this.createDbParse().parseIncrement(table, field, where, value, "increment");
1013
+ const tableInfo = this.getTableDef(table);
1014
+ assertWriteFieldNamesDefined(tableInfo, [parsed.snakeField], table, "increment.field");
963
1015
 
964
1016
  const builder = this.createSqlBuilder().where(parsed.where);
965
1017
  const { sql: whereClause, params: whereParams } = builder.getWhereConditions();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "befly",
3
- "version": "3.34.0",
3
+ "version": "3.36.0",
4
4
  "gitHead": "49c39d36695036e85fc64083cc43c1652fff96cb",
5
5
  "private": false,
6
6
  "description": "Befly - 为 Bun 专属打造的 JavaScript API 接口框架核心引擎",
package/paths.js CHANGED
@@ -113,7 +113,7 @@ export const appTableDir = join(appDir, "tables");
113
113
 
114
114
  /**
115
115
  * 项目上传/公共文件目录
116
- * @description 默认 {appDir}/public,可通过 config.publicDir 覆盖
116
+ * @description 默认 {appDir}/public,可通过 config.upload.publicDir 覆盖
117
117
  * @usage 用于本地上传文件保存目录解析,不承担 HTTP 静态托管职责
118
118
  */
119
119
  export function getAppPublicDir(publicDir = "./public") {
package/router/api.js CHANGED
@@ -9,7 +9,7 @@ import { Logger } from "../lib/logger.js";
9
9
  // 相对导入
10
10
  import { setCorsOptions } from "../utils/cors.js";
11
11
  import { getClientIp } from "../utils/getClientIp.js";
12
- import { FinalResponse } from "../utils/response.js";
12
+ import { ErrorResponse, FinalResponse } from "../utils/response.js";
13
13
  import { genShortId } from "../utils/util.js";
14
14
 
15
15
  function createExcludeApisLogMatchers(excludeApisLog) {
@@ -41,7 +41,7 @@ export function apiHandler(apis, hooks, context) {
41
41
  const requestId = genShortId();
42
42
  const now = Date.now();
43
43
 
44
- const corsHeaders = setCorsOptions(req, context.config?.cors || {});
44
+ const corsHeaders = setCorsOptions(req, context.config.cors);
45
45
  corsHeaders["X-Request-ID"] = requestId;
46
46
 
47
47
  // 2. OPTIONS 预检请求:直接返回,不走 hooks,不打日志
@@ -95,6 +95,21 @@ export function apiHandler(apis, hooks, context) {
95
95
  apiFile: apiData.filePath
96
96
  };
97
97
 
98
+ if (ctx.apiMethod !== "ALL" && ctx.method !== ctx.apiMethod) {
99
+ return ErrorResponse(
100
+ ctx,
101
+ `请求方法不允许,请使用 ${ctx.apiMethod}`,
102
+ 1,
103
+ null,
104
+ {
105
+ method: ctx.method,
106
+ allowMethod: ctx.apiMethod,
107
+ apiPath: ctx.apiPath
108
+ },
109
+ "method"
110
+ );
111
+ }
112
+
98
113
  try {
99
114
  // 4. 串联执行所有钩子
100
115
  for (const hook of hooks) {
package/router/static.js CHANGED
@@ -4,24 +4,40 @@
4
4
  */
5
5
 
6
6
  // 外部依赖
7
- import { join } from "pathe";
7
+ import { basename, extname, isAbsolute, normalize, relative, resolve } from "pathe";
8
8
 
9
9
  import { Logger } from "../lib/logger.js";
10
10
  // 相对导入
11
11
  import { getAppPublicDir } from "../paths.js";
12
12
  import { setCorsOptions } from "../utils/cors.js";
13
13
 
14
+ function getStaticFilePath(url, baseDir) {
15
+ try {
16
+ const relativePath = normalize(decodeURIComponent(url.pathname.slice("/public/".length)));
17
+ const filePath = resolve(baseDir, relativePath);
18
+ const fileRelativePath = relative(baseDir, filePath);
19
+
20
+ if (fileRelativePath.startsWith("..") || isAbsolute(fileRelativePath)) {
21
+ return "";
22
+ }
23
+
24
+ return filePath;
25
+ } catch {
26
+ return "";
27
+ }
28
+ }
29
+
14
30
  /**
15
31
  * 静态文件处理器工厂
16
32
  */
17
- export function staticHandler(corsConfig = undefined, publicDir = "./public") {
33
+ export function staticHandler(corsConfig, uploadConfig) {
18
34
  return async (req) => {
19
35
  // 设置 CORS 响应头
20
36
  const corsHeaders = setCorsOptions(req, corsConfig);
21
37
 
22
38
  const url = new URL(req.url);
23
- const publicPath = url.pathname.replace(/^\/public/, "") || "/";
24
- const filePath = join(getAppPublicDir(publicDir), publicPath);
39
+ const baseDir = resolve(getAppPublicDir(uploadConfig.publicDir));
40
+ const filePath = getStaticFilePath(url, baseDir);
25
41
 
26
42
  try {
27
43
  // OPTIONS预检请求
@@ -32,6 +48,18 @@ export function staticHandler(corsConfig = undefined, publicDir = "./public") {
32
48
  });
33
49
  }
34
50
 
51
+ if (!filePath) {
52
+ return Response.json(
53
+ {
54
+ code: 1,
55
+ msg: "文件未找到"
56
+ },
57
+ {
58
+ headers: corsHeaders
59
+ }
60
+ );
61
+ }
62
+
35
63
  const file = Bun.file(filePath);
36
64
  if (await file.exists()) {
37
65
  const headers = {};
@@ -41,6 +69,10 @@ export function staticHandler(corsConfig = undefined, publicDir = "./public") {
41
69
  }
42
70
  }
43
71
  headers["Content-Type"] = file.type || "application/octet-stream";
72
+ headers["X-Content-Type-Options"] = "nosniff";
73
+ if (uploadConfig.forceDownloadExtensions.split(",").includes(extname(filePath).toLowerCase())) {
74
+ headers["Content-Disposition"] = `attachment; filename="${basename(filePath).replaceAll('"', "")}"`;
75
+ }
44
76
  return new Response(file, {
45
77
  headers: headers
46
78
  });
package/utils/cors.js CHANGED
@@ -1,21 +1,12 @@
1
1
  /**
2
2
  * 设置 CORS 响应头
3
3
  * @param req - 请求对象
4
- * @param config - CORS 配置(可选)
4
+ * @param config - CORS 配置
5
5
  * @returns CORS 响应头对象
6
6
  */
7
- export function setCorsOptions(req, config = {}) {
8
- const defaultConfig = {
9
- origin: "*",
10
- methods: "GET, POST, OPTIONS",
11
- allowedHeaders: "Content-Type, Authorization, authorization, token",
12
- exposedHeaders: "Content-Range, X-Content-Range, Authorization, authorization, token",
13
- maxAge: 86400,
14
- credentials: "true"
15
- };
16
-
17
- const merged = Object.assign({}, defaultConfig, config || {});
18
- const origin = merged.origin;
7
+ export function setCorsOptions(req, config) {
8
+ const origin = config.origin;
9
+ const allowCredentials = config.credentials === true || config.credentials === "true" ? "true" : "false";
19
10
  const requestedHeaders = req.headers.get("Access-Control-Request-Headers") || "";
20
11
 
21
12
  const allowedHeaderItems = [];
@@ -40,7 +31,7 @@ export function setCorsOptions(req, config = {}) {
40
31
  allowedHeaderItems.push(value);
41
32
  };
42
33
 
43
- for (const item of String(merged.allowedHeaders || "").split(",")) {
34
+ for (const item of String(config.allowedHeaders || "").split(",")) {
44
35
  pushHeaderIfNeeded(item);
45
36
  }
46
37
 
@@ -51,11 +42,11 @@ export function setCorsOptions(req, config = {}) {
51
42
  const allowHeaders = allowedHeaderItems.join(", ");
52
43
 
53
44
  return {
54
- "Access-Control-Allow-Origin": origin === "*" ? req.headers.get("origin") || "*" : origin,
55
- "Access-Control-Allow-Methods": merged.methods,
45
+ "Access-Control-Allow-Origin": origin === "*" && allowCredentials === "false" ? req.headers.get("origin") || "*" : origin,
46
+ "Access-Control-Allow-Methods": config.methods,
56
47
  "Access-Control-Allow-Headers": allowHeaders,
57
- "Access-Control-Expose-Headers": merged.exposedHeaders,
58
- "Access-Control-Max-Age": String(merged.maxAge),
59
- "Access-Control-Allow-Credentials": merged.credentials
48
+ "Access-Control-Expose-Headers": config.exposedHeaders,
49
+ "Access-Control-Max-Age": String(config.maxAge),
50
+ "Access-Control-Allow-Credentials": allowCredentials
60
51
  };
61
52
  }