oclaw/uds-auth/lib/client.js.bak
2026-09-08 00:28:58 +08:00

904 lines
44 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* uds-auth Client Plugin - User info display + QR code login
* Includes embedded QR code library
*/
(function() {
// QR Code Library - Minimal but functional implementation
var QRCodeLib = (function() {
var ALPHANUMERIC = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';
function QRCode(data) {
this.data = data;
this.modules = null;
this.moduleCount = 0;
this.version = 1;
}
QRCode.prototype.getMinimumVersion = function() {
var len = this.data.length;
if (len <= 25) return 1;
if (len <= 47) return 2;
if (len <= 77) return 3;
if (len <= 114) return 4;
if (len <= 154) return 5;
if (len <= 195) return 6;
if (len <= 224) return 7;
return 8;
};
QRCode.prototype.make = function() {
this.version = this.getMinimumVersion();
this.moduleCount = this.version * 4 + 17;
this.modules = [];
for (var r = 0; r < this.moduleCount; r++) {
this.modules[r] = new Array(this.moduleCount);
for (var c = 0; c < this.moduleCount; c++) {
this.modules[r][c] = null;
}
}
// Add finder patterns with separator
this.addFinderPatternWithSeparator(0, 0);
this.addFinderPatternWithSeparator(this.moduleCount - 7, 0);
this.addFinderPatternWithSeparator(0, this.moduleCount - 7);
// Timing patterns
for (var i = 8; i < this.moduleCount - 8; i++) {
if (this.modules[6][i] === null) this.modules[6][i] = (i % 2 === 0);
if (this.modules[i][6] === null) this.modules[i][6] = (i % 2 === 0);
}
// Alignment patterns
var alignPos = this.getAlignmentPositions();
for (var ai = 0; ai < alignPos.length; ai++) {
for (var aj = 0; aj < alignPos.length; aj++) {
var row = alignPos[ai], col = alignPos[aj];
if (this.modules[row][col] === null) {
this.addAlignmentPattern(row, col);
}
}
}
// Dark module
this.modules[this.moduleCount - 8][8] = true;
// Reserve format info areas
this.reserveFormatAreas();
// Place data
var bits = this.createData();
this.placeData(bits, 0);
};
QRCode.prototype.addFinderPatternWithSeparator = function(row, col) {
for (var r = -1; r <= 8; r++) {
for (var c = -1; c <= 8; c++) {
var tr = row + r, tc = col + c;
if (tr < 0 || tr >= this.moduleCount || tc < 0 || tc >= this.moduleCount) continue;
if (r === -1 || r === 8 || c === -1 || c === 8) {
this.modules[tr][tc] = false; // separator
} else if (r >= 0 && r < 7 && c >= 0 && c < 7) {
var inside = (r >= 1 && r <= 5 && (c === 1 || c === 5)) ||
(c >= 1 && c <= 5 && (r === 1 || r === 5)) ||
(r >= 2 && r <= 4 && c >= 2 && c <= 4);
this.modules[tr][tc] = inside;
} else {
this.modules[tr][tc] = true;
}
}
}
};
QRCode.prototype.addAlignmentPattern = function(row, col) {
for (var r = -2; r <= 2; r++) {
for (var c = -2; c <= 2; c++) {
var tr = row + r, tc = col + c;
if (tr < 0 || tr >= this.moduleCount || tc < 0 || tc >= this.moduleCount) continue;
if (r === -2 || r === 2 || c === -2 || c === 2) {
this.modules[tr][tc] = true;
} else if (r === 0 && c === 0) {
this.modules[tr][tc] = true;
} else {
this.modules[tr][tc] = false;
}
}
}
};
QRCode.prototype.getAlignmentPositions = function() {
if (this.version === 1) return [];
var positions = [];
var step = Math.ceil((this.version * 4 + 10) / (Math.floor(this.version / 7) + 1));
step = Math.ceil(step / 2) * 2;
var last = this.moduleCount - 7;
for (var pos = last; pos >= 0; pos -= step) {
positions.unshift(pos);
}
if (positions[0] !== 6) positions.unshift(6);
return positions;
};
QRCode.prototype.reserveFormatAreas = function() {
// Reserve format info around finder patterns
for (var i = 0; i < 9; i++) {
if (this.modules[8][i] === null) this.modules[8][i] = null; // reserved
if (this.modules[i][8] === null) this.modules[i][8] = null; // reserved
}
for (var i = 0; i < 8; i++) {
if (this.modules[this.moduleCount - 1 - i][8] === null) this.modules[this.moduleCount - 1 - i][8] = null;
if (this.modules[8][this.moduleCount - 1 - i] === null) this.modules[8][this.moduleCount - 1 - i] = null;
}
};
QRCode.prototype.createData = function() {
var bits = [];
// Mode: alphanumeric
bits.push(0, 0, 0, 1, 0);
// Length
var len = this.data.length;
var lenBits = this.version <= 9 ? 8 : 16;
for (var i = lenBits - 1; i >= 0; i--) {
bits.push((len >> i) & 1);
}
// Data
for (var i = 0; i < this.data.length; i += 2) {
var c1 = this.data.charAt(i);
var c2 = this.data.charAt(i + 1) || '';
var v1 = ALPHANUMERIC.indexOf(c1);
if (v1 < 0) v1 = 0;
var v2 = ALPHANUMERIC.indexOf(c2);
if (v2 < 0) v2 = 0;
var val = v1 * 45 + v2;
var bin = val.toString(2);
while (bin.length < 11) bin = '0' + bin;
for (var j = 0; j < 11; j++) {
bits.push(bin.charAt(j) === '1' ? 1 : 0);
}
}
// Terminator
var capacity = this.getDataCapacity();
bits.push(0, 0, 0, 0);
// Pad to byte boundary
while (bits.length % 8 !== 0) bits.push(0);
// Pad codewords
var pad1 = [1, 1, 0, 0, 0, 1, 0, 0];
var pad2 = [0, 0, 0, 1, 0, 0, 0, 1];
while (bits.length < capacity) {
for (var i = 0; i < 8 && bits.length < capacity; i++) bits.push(pad1[i]);
for (var i = 0; i < 8 && bits.length < capacity; i++) bits.push(pad2[i]);
}
return bits.slice(0, capacity);
};
QRCode.prototype.getDataCapacity = function() {
var capacities = [0, 152, 272, 440, 640, 864, 1088, 1248, 1552, 1856, 2192];
return capacities[this.version] || 272;
};
QRCode.prototype.placeData = function(bits, maskPattern) {
var upward = true;
var col = this.moduleCount - 1;
var bitIndex = 0;
while (col > 0) {
if (col === 6) col = 5; // skip timing column
for (var row = upward ? this.moduleCount - 1 : 0;
upward ? row >= 0 : row < this.moduleCount;
row += upward ? -1 : 1) {
for (var c = 0; c < 2; c++) {
var currentCol = col - c;
if (this.modules[row][currentCol] === null) {
var dark = bitIndex < bits.length ? bits[bitIndex++] === 1 : false;
if (this.getMask(maskPattern, row, currentCol)) dark = !dark;
this.modules[row][currentCol] = dark;
}
}
}
col -= 2;
upward = !upward;
}
};
QRCode.prototype.getMask = function(pattern, row, col) {
switch (pattern) {
case 0: return (row + col) % 2 === 0;
case 1: return row % 2 === 0;
case 2: return col % 3 === 0;
case 3: return (row + col) % 3 === 0;
case 4: return (Math.floor(row / 2) + Math.floor(col / 3)) % 2 === 0;
case 5: return ((row * col) % 2 + (row * col) % 3) === 0;
case 6: return (((row * col) % 2 + (row * col) % 3) % 2) === 0;
case 7: return (((row * col) % 3 + (row + col) % 2) % 2) === 0;
default: return false;
}
};
QRCode.prototype.isDark = function(row, col) {
if (row < 0 || row >= this.moduleCount || col < 0 || col >= this.moduleCount) return false;
return this.modules[row][col] === true;
};
QRCode.prototype.getModuleCount = function() {
return this.moduleCount;
};
return function(data) {
return new QRCode(data);
};
})();
function createQR(data, canvas, size) {
size = size || 160;
var qr = QRCodeLib(data);
qr.make();
var ctx = canvas.getContext('2d');
var count = qr.getModuleCount();
var tileSize = size / count;
canvas.width = size;
canvas.height = size;
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, size, size);
ctx.fillStyle = '#000000';
for (var row = 0; row < count; row++) {
for (var col = 0; col < count; col++) {
if (qr.isDark(row, col)) {
ctx.fillRect(Math.floor(col * tileSize), Math.floor(row * tileSize), Math.ceil(tileSize), Math.ceil(tileSize));
}
}
}
return true;
}
window.__ModuleLoader__.load({
id: 'uds-auth',
factory: function(require) {
var module = { exports: {} };
var exports = module.exports;
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
var name = 'uds-auth';
var COOKIE_EMP_NO = 'PORTALSSOUser';
var COOKIE_AUTH_VALUE = 'PORTALSSOCookie';
var ALT_COOKIE_EMP_NO = 'ZTEDPGSSOUser';
var ALT_COOKIE_AUTH_VALUE = 'ZTEDPGSSOCookie';
var UAC_VERIFY_URL = 'https://uac.zte.com.cn/uacqr/auth/qrcode/verify.serv';
var LOGIN_SYSTEM_CODE = '100000455558';
var CSS = '.uds-auth-badge{display:flex;align-items:center;gap:6px;padding:4px 10px;border-radius:4px;background:var(--dsw-alias-bg-module-platform,rgba(242,243,245,1));border:1px solid var(--dsw-alias-border-l2,#dfe1e5);color:var(--dsw-alias-label-primary,#1f2329);font-size:13px;cursor:pointer;font-weight:500;transition:all .15s}.uds-auth-badge:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(247,248,250,1))}.uds-auth-badge-unauth{color:var(--dsw-alias-label-tertiary,#8f959e)}.uds-auth-badge-unauth:hover{color:var(--dsw-alias-label-secondary,#646a73)}.uds-auth-avatar{width:22px;height:22px;border-radius:50%;background:linear-gradient(135deg,var(--dsw-alias-state-business-primary,#3370ff),var(--dsw-alias-state-success-primary,#20a162));display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;color:#fff}.uds-auth-badge-unauth .uds-auth-avatar{background:var(--dsw-alias-bg-module-platform,rgba(242,243,245,1));color:var(--dsw-alias-label-tertiary,#8f959e)}.uds-auth-role-badge{font-size:10px;padding:1px 6px;border-radius:10px;font-weight:600}.uds-auth-role-super_admin{background:rgba(213,73,65,.12);color:var(--dsw-alias-state-error-primary,#d54941)}.uds-auth-role-admin{background:rgba(217,119,6,.12);color:var(--dsw-alias-state-warn-primary,#d97706)}.uds-auth-role-fallback_admin{background:rgba(51,112,255,.12);color:var(--dsw-alias-state-business-primary,#3370ff)}.uds-auth-dropdown{position:absolute;top:100%;right:0;margin-top:8px;background:var(--dsw-alias-bg-layer-1,#fff);border-radius:8px;border:1px solid var(--dsw-alias-border-l2,#dee0e3);box-shadow:0 8px 28px rgba(0,0,0,.08);min-width:220px;z-index:1000;overflow:hidden}.uds-auth-info{padding:14px 16px;border-bottom:1px solid var(--dsw-alias-border-l1,#eef0f3)}.uds-auth-info-name{font-weight:600;margin-bottom:4px;color:var(--dsw-alias-label-primary,#1f2329);font-size:14px}.uds-auth-info-detail{font-size:12px;color:var(--dsw-alias-label-secondary,#646a73);margin-top:2px}.uds-auth-info-detail-label{color:var(--dsw-alias-label-tertiary,#8f959e);margin-right:4px}.uds-auth-settings-btn{width:100%;padding:8px 16px;border:none;background:transparent;color:var(--dsw-alias-state-business-primary,#3370ff);font-size:13px;cursor:pointer;transition:background .15s;text-align:left}.uds-auth-settings-btn:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(247,248,250,1))}.uds-auth-logout{width:100%;padding:10px 16px;border:none;background:transparent;color:var(--dsw-alias-state-error-primary,#d54941);font-size:13px;cursor:pointer;transition:background .15s}.uds-auth-logout:hover{background:rgba(213,73,65,.08)}.uds-auth-qr-container{text-align:center;padding:16px}.uds-auth-qr-canvas{margin:0 auto 12px;display:block}.uds-auth-qr-status{font-size:12px;color:var(--dsw-alias-label-secondary,#646a73);margin-top:8px}.uds-auth-qr-refresh{background:none;border:none;color:var(--dsw-alias-state-business-primary,#3370ff);cursor:pointer;font-size:12px;margin-top:8px;padding:4px 8px}.uds-auth-qr-refresh:hover{text-decoration:underline}.uds-auth-modal{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.4);z-index:9999;display:flex;align-items:center;justify-content:center}.uds-auth-modal-content{background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l2,#dee0e3);border-radius:8px;padding:20px;max-width:600px;width:90%;max-height:80vh;overflow:auto}.uds-auth-user-table{width:100%;border-collapse:collapse;font-size:13px}.uds-auth-user-table th,.uds-auth-user-table td{padding:8px;border-bottom:1px solid var(--dsw-alias-border-l1,#eef0f3);text-align:left}.uds-auth-user-table th{color:var(--dsw-alias-label-tertiary,#8f959e);font-weight:500}.uds-auth-del-btn{background:rgba(213,73,65,.1);color:var(--dsw-alias-state-error-primary,#d54941);border:none;padding:4px 10px;border-radius:4px;cursor:pointer;font-size:12px;margin-left:8px}.uds-auth-del-btn:hover{background:rgba(213,73,65,.18)}.uds-auth-add-user{margin-top:16px;display:flex;gap:8px}.uds-auth-add-user input,.uds-auth-add-user select{background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l2,#dfe1e5);color:var(--dsw-alias-label-primary,#1f2329);padding:6px 10px;border-radius:4px;font-size:13px}.uds-auth-add-user button{background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-state-business-primary,#3370ff);border:1px solid var(--dsw-alias-border-l2,#dfe1e5);padding:6px 14px;border-radius:4px;cursor:pointer;font-size:13px}.uds-auth-add-user button:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(247,248,250,1))}';
function getCookie(name) {
var cookieName = encodeURIComponent(name) + '=';
var cookieStart = document.cookie.indexOf(cookieName);
if (cookieStart === -1) return null;
var cookieEnd = document.cookie.indexOf(';', cookieStart);
if (cookieEnd === -1) cookieEnd = document.cookie.length;
return decodeURIComponent(document.cookie.substring(cookieStart + cookieName.length, cookieEnd));
}
function setCookie(name, value, days) {
var expires = '';
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = '; expires=' + date.toUTCString();
}
document.cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value) + expires + '; path=/';
}
function getEmpNo() {
return getCookie(COOKIE_EMP_NO) || getCookie(ALT_COOKIE_EMP_NO) || null;
}
function injectCss() {
if (!document.getElementById('uds-auth-styles')) {
var style = document.createElement('style');
style.id = 'uds-auth-styles';
style.textContent = CSS;
document.head.appendChild(style);
}
}
function findHeaderArea() {
var selectors = ['[class*="headerUtilities"]', '[class*="header-actions"]', '[class*="sessionHeader"]'];
for (var i = 0; i < selectors.length; i++) {
var el = document.querySelector(selectors[i]);
if (el) return el;
}
return null;
}
function updateBadge(user) {
var badge = document.querySelector('#uds-auth-badge .uds-auth-badge');
var dropdown = document.getElementById('uds-auth-dropdown');
if (!badge || !dropdown) return;
if (!user) {
badge.innerHTML = '<span class="uds-auth-avatar">?</span><span>未登录</span>';
badge.className = 'uds-auth-badge uds-auth-badge-unauth';
dropdown.innerHTML = '<div class="uds-auth-info"><div class="uds-auth-info-name">未登录</div><div class="uds-auth-info-detail">请扫码登录</div></div>' +
'<div class="uds-auth-qr-container"><canvas id="qr-canvas" class="uds-auth-qr-canvas" width="160" height="160"></canvas><div id="qr-status" class="uds-auth-qr-status">加载中...</div><button id="qr-refresh" class="uds-auth-qr-refresh">刷新二维码</button></div>';
initQRLogin();
return;
}
var displayName = user.userName || user.name || '用户' + user.empNo;
var initials = displayName.slice(0, 2).toUpperCase();
var role = user.role || 'user';
var perms = user.permissions || {};
// 角色标签
var roleLabel = { super_admin: '超管', admin: '管理员', fallback_admin: '兜底', user: '' }[role] || '';
var roleBadge = roleLabel ? '<span class="uds-auth-role-badge uds-auth-role-' + role + '">' + roleLabel + '</span>' : '';
badge.innerHTML = '<span class="uds-auth-avatar">' + initials + '</span><span>' + displayName + '</span>' + roleBadge;
badge.className = 'uds-auth-badge';
var info = '<div class="uds-auth-info"><div class="uds-auth-info-name">' + displayName + '</div>';
info += '<div class="uds-auth-info-detail"><span class="uds-auth-info-detail-label">工号</span>' + (user.empNo || '-') + '</div>';
info += '<div class="uds-auth-info-detail"><span class="uds-auth-info-detail-label">角色</span>' + roleLabel + '</div>';
if (user.department) info += '<div class="uds-auth-info-detail"><span class="uds-auth-info-detail-label">部门</span>' + user.department + '</div>';
if (user.email) info += '<div class="uds-auth-info-detail"><span class="uds-auth-info-detail-label">邮箱</span>' + user.email + '</div>';
if (user.phone) info += '<div class="uds-auth-info-detail"><span class="uds-auth-info-detail-label">手机</span>' + user.phone + '</div>';
info += '</div>';
// 用户管理入口 (super_admin only)
if (perms.canManageUsers) {
info += '<button id="uds-user-mgmt-btn" class="uds-auth-settings-btn">👥 用户管理</button>';
}
// 设置入口 (admin+ only)
if (perms.canAccessSettings) {
info += '<button id="uds-goto-settings-btn" class="uds-auth-settings-btn">⚙️ 系统设置</button>';
}
info += '<button id="uds-logout-btn" class="uds-auth-logout">退出登录</button>';
dropdown.innerHTML = info;
var settingsBtn = document.getElementById('uds-goto-settings-btn');
if (settingsBtn) settingsBtn.onclick = function() { window.open('/settings', '_self'); };
var mgmtBtn = document.getElementById('uds-user-mgmt-btn');
if (mgmtBtn) mgmtBtn.onclick = function() { openUserManagement(); };
var logoutBtn = document.getElementById('uds-logout-btn');
if (logoutBtn) {
logoutBtn.onclick = function() {
setCookie(COOKIE_EMP_NO, '', -1);
setCookie(COOKIE_AUTH_VALUE, '', -1);
setCookie(ALT_COOKIE_EMP_NO, '', -1);
setCookie(ALT_COOKIE_AUTH_VALUE, '', -1);
setCookie('UDS_FALLBACK_USER', '', -1);
location.reload();
};
}
}
function openUserManagement() {
fetch('/uds-auth/api/users')
.then(function(r) { return r.json(); })
.then(function(data) {
var users = data.users || [];
var html = '<div class="uds-auth-user-mgmt"><h4>用户管理</h4>';
html += '<table class="uds-auth-user-table"><thead><tr><th>工号</th><th>角色</th><th>操作</th></tr></thead><tbody>';
users.forEach(function(u) {
var roleLabel = { super_admin: '超级管理员', admin: '管理员', user: '普通用户', fallback_admin: '兜底管理员' }[u.role] || u.role;
var rolesOpts = '<option value="user"' + (u.role === 'user' ? ' selected' : '') + '>普通用户</option>' +
'<option value="admin"' + (u.role === 'admin' ? ' selected' : '') + '>管理员</option>' +
'<option value="super_admin"' + (u.role === 'super_admin' ? ' selected' : '') + '>超级管理员</option>';
html += '<tr><td>' + u.empNo + '</td><td>' + roleLabel + '</td><td>' +
'<select onchange="udsChangeRole(\'' + u.empNo + '\', this.value)">' + rolesOpts + '</select> ' +
'<button onclick="udsDeleteUser(\'' + u.empNo + '\')" class="uds-auth-del-btn">删除</button></td></tr>';
});
html += '</tbody></table>';
html += '<div class="uds-auth-add-user"><input id="uds-new-empno" placeholder="工号"><select id="uds-new-role"><option value="user">普通用户</option><option value="admin">管理员</option></select><button onclick="udsAddUser()">添加</button></div>';
html += '</div>';
var panel = document.createElement('div');
panel.className = 'uds-auth-modal';
panel.innerHTML = '<div class="uds-auth-modal-content" id="uds-user-mgmt-panel">' + html + '</div>';
document.body.appendChild(panel);
panel.onclick = function(e) { if (e.target === panel) panel.remove(); };
})
.catch(function(err) { alert('加载用户列表失败: ' + err.message); });
}
// 全局 API 供 onclick 用
window.udsChangeRole = function(empNo, role) {
fetch('/uds-auth/api/users/role', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ empNo: empNo, role: role })
}).then(function(r) { return r.json(); })
.then(function(d) { if (d.error) alert(d.error); else alert(d.message); openUserManagement(); })
.catch(function(err) { alert('操作失败: ' + err.message); });
};
window.udsDeleteUser = function(empNo) {
if (!confirm('确认删除用户 ' + empNo + '?')) return;
fetch('/uds-auth/api/users/delete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ empNo: empNo })
}).then(function(r) { return r.json(); })
.then(function(d) { if (d.error) alert(d.error); else alert(d.message); openUserManagement(); })
.catch(function(err) { alert('操作失败: ' + err.message); });
};
window.udsAddUser = function() {
var empNo = document.getElementById('uds-new-empno').value.trim();
var role = document.getElementById('uds-new-role').value;
if (!empNo) return;
fetch('/uds-auth/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ empNo: empNo, role: role })
}).then(function(r) { return r.json(); })
.then(function(d) { if (d.error) alert(d.error); else openUserManagement(); })
.catch(function(err) { alert('操作失败: ' + err.message); });
};
var currentQRKey = null;
var currentQRValue = null;
var qrPollingTimer = null;
function generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0;
var v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
function md5(str) {
function md5cycle(x, k) {
var a = x[0], b = x[1], c = x[2], d = x[3];
a = ff(a, b, c, d, k[0], 7, -680876936); d = ff(d, a, b, c, k[1], 12, -389564586);
c = ff(c, d, a, b, k[2], 17, 606105819); b = ff(b, c, d, a, k[3], 22, -1044525330);
a = ff(a, b, c, d, k[4], 7, -176418897); d = ff(d, a, b, c, k[5], 12, 1200080426);
c = ff(c, d, a, b, k[6], 17, -1473231341); b = ff(b, c, d, a, k[7], 22, -45705983);
a = ff(a, b, c, d, k[8], 7, 1770035416); d = ff(d, a, b, c, k[9], 12, -1958414417);
c = ff(c, d, a, b, k[10], 17, -42063); b = ff(b, c, d, a, k[11], 22, -1990404162);
a = ff(a, b, c, d, k[12], 7, 1804603682); d = ff(d, a, b, c, k[13], 12, -40341101);
c = ff(c, d, a, b, k[14], 17, -1502002290); b = ff(b, c, d, a, k[15], 22, 1236535329);
a = gg(a, b, c, d, k[1], 5, -165796510); d = gg(d, a, b, c, k[6], 9, -1069501632);
c = gg(c, d, a, b, k[11], 14, 643717713); b = gg(b, c, d, a, k[0], 20, -373897302);
a = gg(a, b, c, d, k[5], 5, -701558691); d = gg(d, a, b, c, k[10], 9, 38016083);
c = gg(c, d, a, b, k[15], 14, -660478335); b = gg(b, c, d, a, k[4], 20, -405537848);
a = gg(a, b, c, d, k[9], 5, 568446438); d = gg(d, a, b, c, k[14], 9, -1019803690);
c = gg(c, d, a, b, k[3], 14, -187363961); b = gg(b, c, d, a, k[8], 20, 1163531501);
a = gg(a, b, c, d, k[13], 5, -1444681467); d = gg(d, a, b, c, k[2], 9, -51403784);
c = gg(c, d, a, b, k[7], 14, 1735328473); b = gg(b, c, d, a, k[12], 20, -1926607734);
a = hh(a, b, c, d, k[5], 4, -378558); d = hh(d, a, b, c, k[8], 11, -2022574463);
c = hh(c, d, a, b, k[11], 16, 1839030562); b = hh(b, c, d, a, k[14], 23, -35309556);
a = hh(a, b, c, d, k[1], 4, -1530992060); d = hh(d, a, b, c, k[4], 11, 1272893353);
c = hh(c, d, a, b, k[7], 16, -155497632); b = hh(b, c, d, a, k[10], 23, -1094730640);
a = hh(a, b, c, d, k[13], 4, 681279174); d = hh(d, a, b, c, k[0], 11, -358537222);
c = hh(c, d, a, b, k[3], 16, -722521979); b = hh(b, c, d, a, k[6], 23, 76029189);
a = hh(a, b, c, d, k[9], 4, -640364487); d = hh(d, a, b, c, k[12], 11, -421815835);
c = hh(c, d, a, b, k[15], 16, 530742520); b = hh(b, c, d, a, k[2], 23, -995338651);
a = ii(a, b, c, d, k[0], 6, -198630844); d = ii(d, a, b, c, k[7], 10, 1126891415);
c = ii(c, d, a, b, k[14], 15, -1416354905); b = ii(b, c, d, a, k[3], 21, -57434055);
a = ii(a, b, c, d, k[10], 6, 1700485571); d = ii(d, a, b, c, k[1], 10, -1894986606);
c = ii(c, d, a, b, k[8], 15, -1051523); b = ii(b, c, d, a, k[15], 21, -2054922799);
a = ii(a, b, c, d, k[6], 6, 1873313359); d = ii(d, a, b, c, k[13], 10, -30611744);
c = ii(c, d, a, b, k[4], 15, -1560198380); b = ii(b, c, d, a, k[11], 21, 1309151649);
a = ii(a, b, c, d, k[2], 6, -145523070); d = ii(d, a, b, c, k[9], 10, -1120210379);
c = ii(c, d, a, b, k[0], 15, 718787259); b = ii(b, c, d, a, k[7], 21, -343485551);
x[0] = add32(a, x[0]); x[1] = add32(b, x[1]); x[2] = add32(c, x[2]); x[3] = add32(d, x[3]);
}
function cmn(q, a, b, x, s, t) { a = add32(add32(a, q), add32(x, t)); return add32((a << s) | (a >>> (32 - s)), b); }
function ff(a, b, c, d, x, s, t) { return cmn((b & c) | ((~b) & d), a, b, x, s, t); }
function gg(a, b, c, d, x, s, t) { return cmn((b & d) | (c & (~d)), a, b, x, s, t); }
function hh(a, b, c, d, x, s, t) { return cmn(b ^ c ^ d, a, b, x, s, t); }
function ii(a, b, c, d, x, s, t) { return cmn(c ^ (b | (~d)), a, b, x, s, t); }
function md51(s) {
var n = s.length, state = [1732584193, -271733879, -1732584194, 271733878], i;
for (i = 64; i <= s.length; i += 64) md5cycle(state, md5blk(s.substring(i - 64, i)));
s = s.substring(i - 64);
var tail = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
for (i = 0; i < s.length; i++) tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3);
tail[i >> 2] |= 0x80 << ((i % 4) << 3);
if (i > 55) { md5cycle(state, tail); tail = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; }
tail[14] = n * 8; md5cycle(state, tail);
return state;
}
function md5blk(s) {
var md5blks = [], i;
for (i = 0; i < 64; i += 4) md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24);
return md5blks;
}
var hex_chr = '0123456789abcdef'.split('');
function rhex(n) { var s = '', j = 0; for (; j < 4; j++) s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F]; return s; }
function hex(x) { for (var i = 0; i < x.length; i++) x[i] = rhex(x[i]); return x.join(''); }
function add32(a, b) { return (a + b) & 0xFFFFFFFF; }
return hex(md51(str));
}
// SHA256 implementation for verifyCode
function sha256(str) {
function rotl(n, s) { return (n << s) | (n >>> (32 - s)); }
function sha256_0(x, y, z) { return (x & y) ^ (~x & z); }
function sha256_1(x, y, z) { return (x & y) ^ (x & z) ^ (y & z); }
function sha256_2(x) { return (x >>> 2 | x << 30) ^ (x >>> 13 | x << 19) ^ (x >>> 22 | x << 10); }
function sha256_3(x) { return (x >>> 6 | x << 26) ^ (x >>> 11 | x << 21) ^ (x >>> 25 | x << 7); }
function sha256_4(x) { return (x >>> 2 | x << 30) ^ (x >>> 13 | x << 19) ^ (x >>> 22 | x << 10); }
function sha256_5(x) { return (x >>> 6 | x << 26) ^ (x >>> 11 | x << 21) ^ (x >>> 25 | x << 7); }
function sha256_6(x) { return (x >>> 2 | x << 30) ^ (x >>> 17 | x << 15) ^ (x >>> 19 | x << 13); }
function sha256_7(x) { return (x >>> 7 | x << 25) ^ (x >>> 18 | x << 14) ^ (x >>> 3); }
var K = [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2];
var h = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19];
var msg = [];
for (var i = 0; i < str.length; i++) {
msg.push(str.charCodeAt(i));
}
var msgLen = msg.length;
var bitLen = msgLen * 8;
msg.push(0x80);
while ((msg.length % 64) !== 56) msg.push(0);
for (var i = 7; i >= 0; i--) msg.push((bitLen >>> (i * 8)) & 0xff);
for (var i = 0; i < msg.length; i += 64) {
var w = [];
for (var j = 0; j < 16; j++) {
w[j] = (msg[i + j * 4] << 24) | (msg[i + j * 4 + 1] << 16) | (msg[i + j * 4 + 2] << 8) | msg[i + j * 4 + 3];
}
for (var j = 16; j < 64; j++) {
var s0 = sha256_7(w[j - 15]);
var s1 = sha256_6(w[j - 2]);
w[j] = (w[j - 16] + s0 + w[j - 7] + s1) & 0xffffffff;
}
var a = h[0], b = h[1], c = h[2], d = h[3], e = h[4], f = h[5], g = h[6], hh = h[7];
for (var j = 0; j < 64; j++) {
var s1 = sha256_3(e);
var ch = sha256_0(e, f, g);
var temp1 = (hh + s1 + ch + K[j] + w[j]) & 0xffffffff;
var s0 = sha256_2(a);
var maj = sha256_1(a, b, c);
var temp2 = (s0 + maj) & 0xffffffff;
hh = g; g = f; f = e; e = (d + temp1) & 0xffffffff;
d = c; c = b; b = a; a = (temp1 + temp2) & 0xffffffff;
}
h[0] = (h[0] + a) & 0xffffffff;
h[1] = (h[1] + b) & 0xffffffff;
h[2] = (h[2] + c) & 0xffffffff;
h[3] = (h[3] + d) & 0xffffffff;
h[4] = (h[4] + e) & 0xffffffff;
h[5] = (h[5] + f) & 0xffffffff;
h[6] = (h[6] + g) & 0xffffffff;
h[7] = (h[7] + hh) & 0xffffffff;
}
var hex = '0123456789abcdef';
var result = '';
for (var i = 0; i < 8; i++) {
result += hex[(h[i] >>> 28) & 0xf];
result += hex[(h[i] >>> 24) & 0xf];
result += hex[(h[i] >>> 20) & 0xf];
result += hex[(h[i] >>> 16) & 0xf];
result += hex[(h[i] >>> 12) & 0xf];
result += hex[(h[i] >>> 8) & 0xf];
result += hex[(h[i] >>> 4) & 0xf];
result += hex[h[i] & 0xf];
}
return result;
}
function initQRLogin() {
var canvas = document.getElementById('qr-canvas');
var qrStatus = document.getElementById('qr-status');
var refreshBtn = document.getElementById('qr-refresh');
if (!canvas || !qrStatus) { setTimeout(initQRLogin, 500); return; }
if (refreshBtn) refreshBtn.onclick = startQRLogin;
startQRLogin();
}
// 从本地 vendor 加载 jQuery 和 UAC md5 插件
function loadUACMd5Plugin() {
return new Promise(function(resolve, reject) {
function ensureJQuery(callback) {
if (window.jQuery) { callback(window.jQuery); return; }
var script = document.createElement('script');
script.src = '/uds-auth/vendor/jquery.min.js';
script.onload = function() { callback(window.jQuery); };
script.onerror = function() { reject(new Error('Failed to load jQuery')); };
document.head.appendChild(script);
}
ensureJQuery(function($) {
if ($.md5QrCode) { resolve($); return; }
var script = document.createElement('script');
script.src = '/uds-auth/vendor/jquery.md5.min.js';
script.onload = function() { resolve($); };
script.onerror = function() { reject(new Error('Failed to load UAC md5 plugin')); };
document.head.appendChild(script);
});
});
}
function startQRLogin() {
var canvas = document.getElementById('qr-canvas');
var qrStatus = document.getElementById('qr-status');
if (!canvas || !qrStatus) return;
if (qrPollingTimer) { clearInterval(qrPollingTimer); qrPollingTimer = null; }
qrStatus.textContent = '正在生成二维码...';
loadUACMd5Plugin()
.then(function($) {
var qrCodeStr = $.md5QrCode('TwoDIMAuth');
var parts = qrCodeStr.split(':');
currentQRKey = parts[1];
currentQRValue = parts[2];
qrStatus.textContent = '请使用iCenter扫码登录...';
var container = canvas.parentElement;
if (container) {
canvas.style.display = 'none';
var img = container.querySelector('.uds-auth-qr-img');
if (!img) {
img = document.createElement('img');
img.className = 'uds-auth-qr-img';
img.style.width = '160px';
img.style.height = '160px';
img.style.margin = '0 auto';
img.style.display = 'block';
container.insertBefore(img, qrStatus);
}
img.src = '/uds-auth/qr?data=' + encodeURIComponent(qrCodeStr);
qrPollingTimer = setInterval(pollQRStatus, 2000);
}
})
.catch(function(err) {
qrStatus.textContent = '加载加密插件失败';
});
}
function pollQRStatus() {
var qrStatus = document.getElementById('qr-status');
if (!qrStatus || !currentQRKey) return;
var source = currentQRKey + currentQRValue + '127.0.0.1' + LOGIN_SYSTEM_CODE + '';
var verifyCode = window.jQuery ? window.jQuery.verfiyCode(source) : null;
var bodyPromise;
if (verifyCode) {
bodyPromise = Promise.resolve(JSON.stringify({
qrCodeKey: currentQRKey,
qrCodeValue: currentQRValue,
loginClientIp: '127.0.0.1',
originSystemCode: '',
loginSystemCode: LOGIN_SYSTEM_CODE,
verifyCode: verifyCode
}));
} else {
var verifyUrl = '/uds-auth/verify-code?qrCodeKey=' + encodeURIComponent(currentQRKey) + '&qrCodeValue=' + encodeURIComponent(currentQRValue) + '&loginClientIp=127.0.0.1&loginSystemCode=' + LOGIN_SYSTEM_CODE;
bodyPromise = fetch(verifyUrl)
.then(function(r) { return r.json(); })
.then(function(data) {
return JSON.stringify({
qrCodeKey: currentQRKey,
qrCodeValue: currentQRValue,
loginClientIp: '127.0.0.1',
originSystemCode: '',
loginSystemCode: LOGIN_SYSTEM_CODE,
verifyCode: data.verifyCode
});
});
}
bodyPromise.then(function(body) {
return fetch('/uds-auth/qr-proxy', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: body });
})
.then(function(r) { return r.json(); })
.then(handleQRResult)
.catch(function(err) {
if (qrStatus) qrStatus.textContent = '网络错误,重试中...';
});
}
function handleQRResult(result) {
var qrStatus = document.getElementById('qr-status');
if (!qrStatus) return;
var code = result.code || {};
var codeCode = code.code || '';
var bo = result.bo || {};
var boCode = bo.code || '';
var boMsg = bo.msg || '';
// 成功
if (codeCode === '0000' && boCode === '0000') {
clearInterval(qrPollingTimer); qrPollingTimer = null;
currentQRKey = null; currentQRValue = null;
var other = result.other || {};
var empNo = other.account || other.empNo || '';
var token = other.token || other.authValue || '';
if (empNo) {
setCookie(COOKIE_EMP_NO, empNo, 7);
if (token) setCookie(COOKIE_AUTH_VALUE, token, 7);
qrStatus.textContent = '登录成功!';
fetchUserAndPermissions(function(user) { updateBadge(user); });
}
return;
}
// 等待扫码
if (codeCode === '0000' && boCode === '4002') {
qrStatus.textContent = '等待扫码...';
return;
}
// 二维码已过期
if (codeCode === '0000' && boCode === '1002') {
clearInterval(qrPollingTimer); qrPollingTimer = null;
qrStatus.textContent = '二维码已过期,请点击刷新';
currentQRKey = null; currentQRValue = null;
return;
}
// 其他可恢复错误:继续轮询
if (codeCode !== '0000') {
return;
}
// 其他 bo.code 错误:停止轮询
clearInterval(qrPollingTimer); qrPollingTimer = null;
qrStatus.textContent = '⚠️ ' + (boMsg || boCode || '登录失败') + ',请刷新重试';
}
function getAuthToken() {
return getCookie(COOKIE_AUTH_VALUE) || getCookie(ALT_COOKIE_AUTH_VALUE) || null;
}
function fetchUserAndPermissions(cb) {
var empNo = getEmpNo();
if (!empNo) { updateBadge(null); return; }
// 先拉 /api/me 拿 role/permissions
fetch('/uds-auth/api/me')
.then(function(r) { return r.json(); })
.then(function(me) {
var permissions = me?.data?.permissions || {};
var role = me?.data?.role || 'user';
cb({ empNo: empNo, userName: null, role: role, permissions: permissions });
// 再拉详细用户信息
fetchUserInfo(function(user) {
cb(Object.assign({ role: role, permissions: permissions }, user));
});
})
.catch(function() { fetchUserInfo(cb); });
}
function fetchUserInfo(cb) {
var empNo = getEmpNo();
if (!empNo) { updateBadge(null); return; }
var token = getAuthToken();
var url = '/uds-auth/user-info?empNo=' + encodeURIComponent(empNo);
if (token) url += '&token=' + encodeURIComponent(token);
fetch(url)
.then(function(r) { return r.json(); })
.then(function(result) {
if (result.code && result.code.code === '0000' && result.bo && result.bo.length > 0) {
var emp = result.bo[0];
// icenterapi 返回字段名可能不一致,兼容多种常见命名
var dept = emp.deptName || emp.deptShortName || emp.deptFullName
|| emp.orgName || emp.orgShortName || emp.orgNamePath || emp.deptNamePath
|| emp.departmentName || emp.department || '';
var name = emp.name || emp.empName || emp.userName || emp.displayName || '用户' + empNo;
var user = {
empNo: emp.employeeShortId || emp.employeeNO || emp.employeeId || empNo,
userName: name,
department: dept,
email: emp.email || emp.mail || '',
phone: emp.mobile || emp.phone || emp.telephone || ''
};
if (cb) cb(user); else updateBadge(user);
} else {
var fallback = { empNo: empNo, userName: '用户' + empNo };
if (cb) cb(fallback); else updateBadge(fallback);
}
})
.catch(function() {
var fallback = { empNo: empNo, userName: '用户' + empNo };
if (cb) cb(fallback); else updateBadge(fallback);
});
}
function injectBadge() {
if (document.getElementById('uds-auth-badge')) return true;
var headerArea = findHeaderArea();
if (!headerArea) return false;
var container = document.createElement('div');
container.id = 'uds-auth-badge';
container.style.cssText = 'position:relative;display:flex;align-items:center;margin-left:8px';
var badge = document.createElement('button');
badge.className = 'uds-auth-badge uds-auth-badge-unauth';
badge.innerHTML = '<span class="uds-auth-avatar">...</span><span>加载中</span>';
var dropdown = document.createElement('div');
dropdown.id = 'uds-auth-dropdown';
dropdown.className = 'uds-auth-dropdown';
dropdown.style.display = 'none';
dropdown.innerHTML = '<div class="uds-auth-info"><div class="uds-auth-info-name">加载中...</div></div>';
badge.onclick = function() { dropdown.style.display = dropdown.style.display === 'none' ? 'block' : 'none'; };
container.appendChild(badge);
container.appendChild(dropdown);
headerArea.appendChild(container);
fetchUserAndPermissions(function(user) { updateBadge(user); });
return true;
}
function init() {
injectCss();
var timer = setInterval(function() { if (injectBadge()) clearInterval(timer); }, 500);
}
function apply(ctx) {
// 兜底:无论 ctx.inject / connection 是否可用,都保证 init 被调用
var inited = false;
function safeInit() {
if (inited) return;
inited = true;
try { init(); } catch (e) { console.error('[uds-auth] init error:', e); }
}
// 立即兜底:如果 connection 注入迟迟不到,3 秒后自行初始化
var fallbackTimer = setTimeout(safeInit, 3000);
try {
if (ctx && typeof ctx.inject === 'function') {
ctx.inject(['connection'], function(connCtx) {
clearTimeout(fallbackTimer);
try {
var call = connCtx && connCtx.connection && connCtx.connection.rpc && connCtx.connection.rpc.call;
function loadConfigAndInit() {
if (typeof call !== 'function') { safeInit(); return; }
call('/uds-auth', 'config.get').then(function(resp) {
if (resp && resp.ok && resp.value) {
var c = resp.value;
if (c.loginSystemCode) LOGIN_SYSTEM_CODE = c.loginSystemCode;
if (c.uacBaseUrl) UAC_VERIFY_URL = c.uacBaseUrl.replace(/\/$/, '') + '/uacqr/auth/qrcode/verify.serv';
}
safeInit();
}).catch(function() { safeInit(); });
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', loadConfigAndInit);
else loadConfigAndInit();
if (connCtx && typeof connCtx.effect === 'function') {
connCtx.effect(function() {}, 'uds-auth client: rpc effect');
}
} catch (e) {
console.error('[uds-auth] apply inject error:', e);
safeInit();
}
});
} else {
clearTimeout(fallbackTimer);
safeInit();
}
} catch (e) {
console.error('[uds-auth] apply error:', e);
clearTimeout(fallbackTimer);
safeInit();
}
}
exports.name = 'uds-auth';
exports.inject = ['connection'];
exports.apply = apply;
return module.exports;
}
});
})();