diff --git a/web/auth/user_store.js b/web/auth/user_store.js index b66fdb8..1dc864d 100644 --- a/web/auth/user_store.js +++ b/web/auth/user_store.js @@ -29,12 +29,71 @@ var UserStore = (function () { crypto.getRandomValues(buf); return Array.from(buf).map(function (b) { return b.toString(16).padStart(2, "0"); }).join(""); } + /* 纯 JS SHA-256(geraintluff 实现,公有领域):crypto.subtle 仅在 + * 安全上下文(https / localhost)可用,纯静态 http 部署(如 :8090) + * 必须降级到此实现,否则本地账号无法播种/校验。 */ + function sha256Js(ascii) { + function rr(v, a) { return (v >>> a) | (v << (32 - a)); } + var mp = Math.pow, mw = mp(2, 32), result = "", words = []; + var bitLen = ascii.length * 8; + var hash = (sha256Js.h = sha256Js.h || []); + var k = (sha256Js.k = sha256Js.k || []); + var pc = k.length, comp = {}; + for (var c = 2; pc < 64; c++) { + if (!comp[c]) { + for (var i = 0; i < 313; i += c) comp[i] = c; + hash[pc] = (mp(c, 0.5) * mw) | 0; + k[pc++] = (mp(c, 1 / 3) * mw) | 0; + } + } + ascii += "\x80"; + while ((ascii.length % 64) - 56) ascii += "\x00"; + for (var i = 0; i < ascii.length; i++) { + var j = ascii.charCodeAt(i); + if (j >> 8) throw new Error("sha256Js: 仅接受字节串"); + words[i >> 2] |= j << (((3 - i) % 4) * 8); + } + words[words.length] = (bitLen / mw) | 0; + words[words.length] = bitLen; + for (var j = 0; j < words.length;) { + var w = words.slice(j, (j += 16)); + var old = hash; + hash = hash.slice(0, 8); + for (var i = 0; i < 64; i++) { + var w15 = w[i - 15], w2 = w[i - 2]; + var a = hash[0], e = hash[4]; + var t1 = hash[7] + (rr(e, 6) ^ rr(e, 11) ^ rr(e, 25)) + + ((e & hash[5]) ^ (~e & hash[6])) + k[i] + + (w[i] = i < 16 ? w[i] : (w[i - 16] + + (rr(w15, 7) ^ rr(w15, 18) ^ (w15 >>> 3)) + w[i - 7] + + (rr(w2, 17) ^ rr(w2, 19) ^ (w2 >>> 10))) | 0); + var t2 = (rr(a, 2) ^ rr(a, 13) ^ rr(a, 22)) + + ((a & hash[1]) ^ (a & hash[2]) ^ (hash[1] & hash[2])); + hash = [(t1 + t2) | 0].concat(hash); + hash[4] = (hash[4] + t1) | 0; + } + for (var i = 0; i < 8; i++) hash[i] = (hash[i] + old[i]) | 0; + } + for (var i = 0; i < 8; i++) { + for (var j = 3; j + 1; j--) { + var b = (hash[i] >> (j * 8)) & 255; + result += (b < 16 ? "0" : "") + b.toString(16); + } + } + return result; + } + function utf8Bytes(str) { return unescape(encodeURIComponent(str)); } function hashPassword(password, salt) { - var data = new TextEncoder().encode(salt + ":" + password); - return crypto.subtle.digest("SHA-256", data).then(function (buf) { - return Array.from(new Uint8Array(buf)) - .map(function (b) { return b.toString(16).padStart(2, "0"); }).join(""); - }); + var data = salt + ":" + password; + // 优先 WebCrypto(安全上下文);不可用时降级纯 JS SHA-256(http 部署) + if (window.crypto && crypto.subtle && crypto.subtle.digest) { + var encoded = new TextEncoder().encode(data); + return crypto.subtle.digest("SHA-256", encoded).then(function (buf) { + return Array.from(new Uint8Array(buf)) + .map(function (b) { return b.toString(16).padStart(2, "0"); }).join(""); + }).catch(function () { return sha256Js(utf8Bytes(data)); }); + } + return Promise.resolve(sha256Js(utf8Bytes(data))); } /* 默认演示账号(Ant Design Pro 风格重构新增):纯静态部署(无 core/auth diff --git a/web/shared/session.js b/web/shared/session.js index aa1a64b..9e7657f 100644 --- a/web/shared/session.js +++ b/web/shared/session.js @@ -124,18 +124,20 @@ var IAOP_SESSION = (function () { saveSession(d.user); return d.user; }); - // 后端在线但拒绝(401 等):是真实鉴权失败,不降级 - return r.json().then(function (d) { - throw new Error((d && d.error) || "用户名或密码错误"); - }); - }).catch(function (e) { - if (e && e.message && e.message !== "timeout" && - e.message !== "no backend session" && - e.message !== "Failed to fetch" && - e.name !== "TypeError" && !/fetch/i.test(e.message || "")) { - throw e; // 后端鉴权失败,直接抛 + // 4xx = 后端在线且拒绝(真实鉴权失败);5xx/网关错误 = 后端不可用,降级本地 + if (r.status >= 400 && r.status < 500) { + return r.text().then(function (t) { + var msg = "用户名或密码错误"; + try { msg = (JSON.parse(t) || {}).error || msg; } catch (e) {} + var err = new Error(msg); + err.authReject = true; + throw err; + }); } - // 后端不可达 → 本地账号校验 + throw new Error("backend-http-" + r.status); + }).catch(function (e) { + if (e && e.authReject) throw e; // 后端鉴权失败,不降级 + // 超时 / 网络错误 / 5xx / 响应非 JSON 等一切后端不可用情形 → 本地账号校验 if (typeof UserStore === "undefined") { throw new Error("认证服务不可用,且本地账号存储未加载"); }