修复商品规则适配与安全随机密码生成
This commit is contained in:
1 parent
c1c8bf2696
commit
51a496822e
3 files changed
+448
-58
No files matched your search
+16
-5
@@ -1,10 +1,20 @@
|
||||
/**
|
||||
* 生成便于辨认的随机密码建议:至少含小写、大写和数字各一位,排除易混字符。
|
||||
* 现有实现使用 Math.random(非密码学随机源),不能当作高强度密钥/令牌生成器;
|
||||
* 即使 e 小于 3,三个必选字符也会保留。此处注释如实说明现有行为,不改变协议。
|
||||
* 使用浏览器安全随机源生成密码:至少含小写、大写和数字各一位,排除易混字符。
|
||||
* 即使要求长度小于 3,三个必选字符也会保留;安全随机源不可用时明确失败,不降级。
|
||||
*/
|
||||
function e(e = 12) {
|
||||
let t = (e) => e[Math.floor(Math.random() * e.length)],
|
||||
const source = globalThis.crypto;
|
||||
if (typeof source?.getRandomValues !== `function`)
|
||||
throw Error(`当前环境不支持安全随机密码生成`);
|
||||
const buffer = new Uint32Array(1);
|
||||
// 只接受能均分为 upperBound 份的区间,避免直接取模让某些字符/洗牌位置概率偏高。
|
||||
function randomIndex(upperBound) {
|
||||
const limit = 2 ** 32 - (2 ** 32 % upperBound);
|
||||
do source.getRandomValues(buffer);
|
||||
while (buffer[0] >= limit);
|
||||
return buffer[0] % upperBound;
|
||||
}
|
||||
let t = (e) => e[randomIndex(e.length)],
|
||||
n = [
|
||||
t(`abcdefghjkmnpqrstuvwxyz`),
|
||||
t(`ABCDEFGHJKMNPQRSTUVWXYZ`),
|
||||
@@ -18,7 +28,8 @@ function e(e = 12) {
|
||||
),
|
||||
i = [...n, ...r];
|
||||
for (let e = i.length - 1; e > 0; e--) {
|
||||
let t = Math.floor(Math.random() * (e + 1));
|
||||
// Fisher–Yates 与字符选取共用无偏采样,不能在打乱阶段重新引入非安全随机源。
|
||||
let t = randomIndex(e + 1);
|
||||
[i[e], i[t]] = [i[t], i[e]];
|
||||
}
|
||||
return i.join(``);
|
||||
|
||||
+39
-53
@@ -36,8 +36,8 @@ function r(n) {
|
||||
.filter((e) => e.value !== ``);
|
||||
}
|
||||
/**
|
||||
* 将后端 PCRE 风格 regexpr 的可表达部分转换为 JS RegExp。
|
||||
* 不支持的高级构造标记 patternServerOnly,不能假装浏览器已经完成等价验证。
|
||||
* 只转换能确认等价的 ASCII PCRE 子集,不能把“JS 能编译”当作跨引擎语义相同。
|
||||
* Unicode、换行模式、未知 escape、POSIX/补集及高级分组均交给后端最终校验。
|
||||
*/
|
||||
function i(e) {
|
||||
let n = t(e).trim();
|
||||
@@ -74,70 +74,56 @@ function i(e) {
|
||||
}
|
||||
if (!/^[imsuxADSUXJnr]*$/.test(r))
|
||||
throw Error(`自定义字段验证规则修饰符无效`);
|
||||
let o = /^\(\?([imsx]+)\)/.exec(n);
|
||||
o && ((r += o[1]), (n = n.slice(o[0].length)));
|
||||
let s = [...new Set(r.replace(/[^imsu]/g, ``))].join(``);
|
||||
if (
|
||||
/\(\?(?:[>('|&R0-9]|P[=>]|[imsx-]+:)|\(\*|\\[KCG]|(?:[+*?]|\{\d+(?:,\d*)?\})\+/.test(
|
||||
n,
|
||||
) ||
|
||||
r.includes(`r`) ||
|
||||
(r.includes(`J`) && /\(\?</.test(n))
|
||||
)
|
||||
return {
|
||||
pattern: n,
|
||||
patternFlags: s || void 0,
|
||||
patternServerOnly: !0,
|
||||
};
|
||||
// 逐字符翻译锚点、扩展空白模式和非捕获/非贪婪约定;字符组内不执行这些改写。
|
||||
let c = ``,
|
||||
const serverOnly = { pattern: n, patternServerOnly: !0 };
|
||||
// 除 i 外的修饰符涉及字符/换行/量词等引擎差异;宁可多一次后端验证,也不误拒合法值。
|
||||
if (/[^i]/.test(r)) return serverOnly;
|
||||
let s = r ? `i` : ``,
|
||||
c = ``,
|
||||
l = !1;
|
||||
for (let e = 0; e < n.length; e++) {
|
||||
let t = n[e];
|
||||
if (t.charCodeAt(0) > 127) return serverOnly;
|
||||
if (t === `\\`) {
|
||||
let t = n[++e],
|
||||
r = {
|
||||
A: `(?<![\\s\\S])`,
|
||||
z: `(?![\\s\\S])`,
|
||||
Z: `(?=\\n?(?![\\s\\S]))`,
|
||||
};
|
||||
c += !l && t && r[t] ? r[t] : `\\${t ?? ``}`;
|
||||
let t = n[++e];
|
||||
if (t === `d`) c += l ? `0-9` : `[0-9]`;
|
||||
else if (!l && t === `A`) c += `(?<![\\s\\S])`;
|
||||
else if (!l && t === `z`) c += `(?![\\s\\S])`;
|
||||
else if (!l && t === `Z`) c += `(?=\\n?(?![\\s\\S]))`;
|
||||
else if (t && (`nrtf`.includes(t) || `\\^$.*+?()[]{}|/-`.includes(t)))
|
||||
c += `\\${t}`;
|
||||
else return serverOnly;
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
(t === `[` && !l ? (l = !0) : t === `]` && l && (l = !1),
|
||||
!l && r.includes(`x`))
|
||||
) {
|
||||
if (/\s/.test(t)) continue;
|
||||
if (t === `#`) {
|
||||
for (
|
||||
;
|
||||
e < n.length &&
|
||||
n[e] !==
|
||||
`
|
||||
`;
|
||||
)
|
||||
e++;
|
||||
continue;
|
||||
}
|
||||
if (t === `[`) {
|
||||
// 嵌套方括号可能是 POSIX 类;否定字符组还涉及非 ASCII 编码宽度,不作猜测。
|
||||
if (l || n[e + 1] === `^`) return serverOnly;
|
||||
l = !0;
|
||||
} else if (t === `]`) {
|
||||
if (!l) return serverOnly;
|
||||
l = !1;
|
||||
}
|
||||
if (!l && t === `$` && r.includes(`D`) && !s.includes(`m`)) {
|
||||
c += `(?![\\s\\S])`;
|
||||
if (l) {
|
||||
c += t;
|
||||
continue;
|
||||
}
|
||||
if (!l && t === `(` && r.includes(`n`) && n[e + 1] !== `?`) {
|
||||
if (t === `.` || (t === `(` && n[e + 1] === `*`)) return serverOnly;
|
||||
if (t === `(` && n[e + 1] === `?`) {
|
||||
if (n[e + 2] !== `:`) return serverOnly;
|
||||
c += `(?:`;
|
||||
e += 2;
|
||||
continue;
|
||||
}
|
||||
((c += t),
|
||||
!l &&
|
||||
r.includes(`U`) &&
|
||||
(`*+`.includes(t) ||
|
||||
(t === `?` && e > 0 && n[e - 1] !== `(`) ||
|
||||
(t === `}` && /\{\d+(?:,\d*)?\}$/.test(c))) &&
|
||||
(n[e + 1] === `?` ? e++ : (c += `?`)));
|
||||
if (t === `{`) {
|
||||
const quantifier = /^\{\d+(?:,\d*)?\}/.exec(n.slice(e));
|
||||
if (!quantifier || n[e + quantifier[0].length] === `+`) return serverOnly;
|
||||
c += quantifier[0];
|
||||
e += quantifier[0].length - 1;
|
||||
continue;
|
||||
}
|
||||
if (t === `}` || (`*+?`.includes(t) && n[e + 1] === `+`)) return serverOnly;
|
||||
// PCRE 默认 $ 只允许最终 LF 前;JS 原生 $ 还会放行 CR/Unicode 行分隔符。
|
||||
c += t === `$` ? `(?=\\n?(?![\\s\\S]))` : t;
|
||||
}
|
||||
r.includes(`A`) && (c = `(?<![\\s\\S])(?:${c})`);
|
||||
try {
|
||||
new RegExp(c, s);
|
||||
} catch {
|
||||
|
||||
Reference in new issue
Block a user