@objectstack/service-settings 10.2.0 → 11.0.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.js CHANGED
@@ -852,6 +852,7 @@ import { dirname, join } from "path";
852
852
  var SECRET_KEY_ENV = "OS_SECRET_KEY";
853
853
  var DEV_KEY_ENV = "OS_DEV_CRYPTO_KEY";
854
854
  var DEV_KEY_LEGACY_ENV = "OBJECTSTACK_DEV_CRYPTO_KEY";
855
+ var AUTOKEY_ENV = "OS_CRYPTO_AUTOKEY";
855
856
  var processEnv = () => globalThis.process?.env ?? {};
856
857
  var detectMode = (env) => {
857
858
  if (env.VITEST || env.NODE_ENV === "test") return "test";
@@ -875,6 +876,10 @@ var parseKey = (raw) => {
875
876
  }
876
877
  return void 0;
877
878
  };
879
+ var parseBool = (raw) => {
880
+ const v = raw?.trim().toLowerCase();
881
+ return v === "1" || v === "true" || v === "yes";
882
+ };
878
883
  var loadExistingKey = (path) => {
879
884
  try {
880
885
  if (!existsSync(path)) return void 0;
@@ -948,6 +953,17 @@ function resolveDataKey(opts) {
948
953
  );
949
954
  return { key: existing, source: "file" };
950
955
  }
956
+ if (parseBool(env[AUTOKEY_ENV])) {
957
+ const persisted2 = loadOrCreateKey(path);
958
+ if (persisted2) {
959
+ if (persisted2.generated) {
960
+ warn(
961
+ `[LocalCryptoProvider] No ${SECRET_KEY_ENV} set \u2014 minted a new AES-256-GCM key and persisted it to ${path} (mode 0600). Restarts on this host reuse it automatically. For containers, CI, or multi-node, set ${SECRET_KEY_ENV} so every node shares one key.`
962
+ );
963
+ }
964
+ return { key: persisted2.key, source: persisted2.generated ? "generated-file" : "file" };
965
+ }
966
+ }
951
967
  throw new Error(MISSING_PROD_KEY_MSG(path));
952
968
  }
953
969
  const persisted = loadOrCreateKey(path);
@@ -1112,7 +1128,15 @@ function registerSettingsRoutes(http, service, opts = {}) {
1112
1128
  }));
1113
1129
  http.put(`${base}/:namespace`, (async (req, res) => {
1114
1130
  const ns = req.params.namespace;
1115
- const body = req.body ?? {};
1131
+ let body = req.body ?? {};
1132
+ if (Object.keys(body).length === 1 && body.values && typeof body.values === "object" && !Array.isArray(body.values)) {
1133
+ const inner = body.values;
1134
+ body = Object.fromEntries(
1135
+ Object.entries(inner).map(
1136
+ ([k, v]) => v && typeof v === "object" && !Array.isArray(v) && "value" in v ? [k, v.value] : [k, v]
1137
+ )
1138
+ );
1139
+ }
1116
1140
  try {
1117
1141
  const ctx = ctxOf(req);
1118
1142
  const result = await service.setMany(ns, body, ctx);
@@ -1241,6 +1265,95 @@ var manifest = {
1241
1265
  description: "Upper bound guards against denial-of-service via very long password hashing.",
1242
1266
  visible: "${data.email_password_enabled !== false}"
1243
1267
  },
1268
+ {
1269
+ type: "toggle",
1270
+ key: "password_reject_breached",
1271
+ label: "Reject breached passwords",
1272
+ required: false,
1273
+ default: false,
1274
+ description: "Block passwords found in public breach corpora via Have I Been Pwned (k-anonymity range check; the password is never sent in full).",
1275
+ visible: "${data.email_password_enabled !== false}"
1276
+ },
1277
+ {
1278
+ type: "toggle",
1279
+ key: "password_require_complexity",
1280
+ label: "Require complex passwords",
1281
+ required: false,
1282
+ default: false,
1283
+ description: "Require passwords to mix character classes (uppercase, lowercase, digits, symbols) on sign-up and password change/reset.",
1284
+ visible: "${data.email_password_enabled !== false}"
1285
+ },
1286
+ {
1287
+ type: "number",
1288
+ key: "password_min_classes",
1289
+ label: "Minimum character classes",
1290
+ required: false,
1291
+ default: 3,
1292
+ min: 1,
1293
+ max: 4,
1294
+ description: "How many of the four classes (upper / lower / digit / symbol) a password must include.",
1295
+ visible: "${data.email_password_enabled !== false && data.password_require_complexity === true}"
1296
+ },
1297
+ {
1298
+ type: "number",
1299
+ key: "password_history_count",
1300
+ label: "Password history (no reuse)",
1301
+ required: false,
1302
+ default: 0,
1303
+ min: 0,
1304
+ max: 24,
1305
+ description: "Block reusing this many previous passwords on change/reset. 0 disables the check.",
1306
+ visible: "${data.email_password_enabled !== false}"
1307
+ },
1308
+ {
1309
+ type: "group",
1310
+ id: "anti_abuse",
1311
+ label: "Anti-abuse",
1312
+ required: false,
1313
+ description: "Brute-force protection: per-identity account lockout and per-IP rate limiting on auth endpoints."
1314
+ },
1315
+ {
1316
+ type: "number",
1317
+ key: "lockout_threshold",
1318
+ label: "Account lockout threshold",
1319
+ required: false,
1320
+ default: 0,
1321
+ min: 0,
1322
+ max: 20,
1323
+ description: "Lock an account after this many consecutive failed sign-ins. 0 disables lockout. While locked, sign-in is rejected even with the correct password.",
1324
+ visible: "${data.email_password_enabled !== false}"
1325
+ },
1326
+ {
1327
+ type: "number",
1328
+ key: "lockout_duration_minutes",
1329
+ label: "Lockout duration (minutes)",
1330
+ required: false,
1331
+ default: 15,
1332
+ min: 1,
1333
+ max: 1440,
1334
+ description: "How long an account stays locked once the threshold is crossed.",
1335
+ visible: "${data.email_password_enabled !== false && data.lockout_threshold > 0}"
1336
+ },
1337
+ {
1338
+ type: "number",
1339
+ key: "rate_limit_max",
1340
+ label: "Auth rate-limit: max requests",
1341
+ required: false,
1342
+ default: 10,
1343
+ min: 1,
1344
+ max: 1e3,
1345
+ description: "Maximum requests per IP, per window, to the sign-in / sign-up / password-reset endpoints."
1346
+ },
1347
+ {
1348
+ type: "number",
1349
+ key: "rate_limit_window_seconds",
1350
+ label: "Auth rate-limit: window (seconds)",
1351
+ required: false,
1352
+ default: 60,
1353
+ min: 1,
1354
+ max: 3600,
1355
+ description: "Sliding window over which the request cap above is counted."
1356
+ },
1244
1357
  {
1245
1358
  type: "group",
1246
1359
  id: "sessions",
@@ -3189,10 +3302,10 @@ var zhCN = {
3189
3302
  default_country: { label: "\u9ED8\u8BA4\u56FD\u5BB6/\u5730\u533A", help: "ISO 3166-1 \u4E8C\u4F4D\u4EE3\u7801(\u5982 US\u3001GB\u3001CN)\u3002" },
3190
3303
  date_format: { label: "\u65E5\u671F\u683C\u5F0F" },
3191
3304
  time_format: { label: "\u65F6\u95F4\u683C\u5F0F", options: { "24h": "24 \u5C0F\u65F6\u5236(14:30)", "12h": "12 \u5C0F\u65F6\u5236(2:30 PM)" } },
3192
- number_format: { label: "\u6570\u5B57\u683C\u5F0F" },
3193
- first_day_of_week: { label: "\u6BCF\u5468\u8D77\u59CB\u65E5", options: { monday: "\u5468\u4E00(ISO)", sunday: "\u5468\u65E5", saturday: "\u5468\u516D" } },
3194
- currency: { label: "\u9ED8\u8BA4\u8D27\u5E01" },
3195
- fiscal_year_start: { label: "\u8D22\u5E74\u8D77\u59CB\u6708" }
3305
+ number_format: { label: "\u6570\u5B57\u683C\u5F0F", help: "\u7528\u4E8E\u663E\u793A\u6570\u5B57\u7684\u5343\u5206\u4F4D\u4E0E\u5C0F\u6570\u5206\u9694\u7B26\u3002" },
3306
+ first_day_of_week: { label: "\u6BCF\u5468\u8D77\u59CB\u65E5", help: "\u7528\u4F5C\u5468\u5EA6\u5206\u6790\u5206\u6876\u4E0E\u65E5\u5386\u7F51\u683C\u7684\u8D77\u59CB\u57FA\u51C6\u3002", options: { monday: "\u5468\u4E00(ISO)", sunday: "\u5468\u65E5", saturday: "\u5468\u516D" } },
3307
+ currency: { label: "\u9ED8\u8BA4\u8D27\u5E01", help: "\u5F53\u8D27\u5E01\u5B57\u6BB5\u672A\u6307\u5B9A\u5E01\u79CD\u65F6\u5957\u7528\u7684 ISO 4217 \u4EE3\u7801\u3002" },
3308
+ fiscal_year_start: { label: "\u8D22\u5E74\u8D77\u59CB\u6708", help: '\u8D22\u5E74\u7684\u8D77\u59CB\u6708\u4EFD\u2014\u2014\u51B3\u5B9A\u62A5\u8868\u4E2D\u7684"\u672C\u5B63\u5EA6/\u672C\u8D22\u5E74"\u3002' }
3196
3309
  }
3197
3310
  },
3198
3311
  auth: {