/** * 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 += ''; }); html += '
工号角色操作
' + u.empNo + '' + roleLabel + '' + ' ' + '
'; 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; } }); })();