/**
* 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 = '?未登录';
badge.className = 'uds-auth-badge uds-auth-badge-unauth';
dropdown.innerHTML = '
' +
'';
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 ? '' + roleLabel + '' : '';
badge.innerHTML = '' + initials + '' + displayName + '' + roleBadge;
badge.className = 'uds-auth-badge';
var info = '' + displayName + '
';
info += '
工号' + (user.empNo || '-') + '
';
info += '
角色' + roleLabel + '
';
if (user.department) info += '
部门' + user.department + '
';
if (user.email) info += '
邮箱' + user.email + '
';
if (user.phone) info += '
手机' + user.phone + '
';
info += '
';
// 用户管理入口 (super_admin only)
if (perms.canManageUsers) {
info += '';
}
// 设置入口 (admin+ only)
if (perms.canAccessSettings) {
info += '';
}
info += '';
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 = '用户管理
';
html += '
| 工号 | 角色 | 操作 |
';
users.forEach(function(u) {
var roleLabel = { super_admin: '超级管理员', admin: '管理员', user: '普通用户', fallback_admin: '兜底管理员' }[u.role] || u.role;
var rolesOpts = '' +
'' +
'';
html += '| ' + u.empNo + ' | ' + roleLabel + ' | ' +
' ' +
' |
';
});
html += '
';
html += '
';
html += '
';
var panel = document.createElement('div');
panel.className = 'uds-auth-modal';
panel.innerHTML = '' + html + '
';
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 = '...加载中';
var dropdown = document.createElement('div');
dropdown.id = 'uds-auth-dropdown';
dropdown.className = 'uds-auth-dropdown';
dropdown.style.display = 'none';
dropdown.innerHTML = '';
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;
}
});
})();