(function () { 'use strict';
const currentHost = location.hostname;
const isAliyun = currentHost.endsWith('.aliyun.com') || currentHost === 'aliyun.com'; const isAliCloud = currentHost.endsWith('.alibabacloud.com') || currentHost === 'alibabacloud.com'; if (!isAliyun && !isAliCloud) return;
const domainKey = isAliyun ? 'aliyun.com' : 'alibabacloud.com'; const STORAGE_KEY = 'mfa_secret_' + domainKey;
function initSiteSwitcher() { let targetHost, label; if (isAliyun) { targetHost = currentHost.replace(/\.?aliyun\.com$/, currentHost.startsWith('aliyun.com') ? 'alibabacloud.com' : '.alibabacloud.com'); label = '切换国际站'; } else { targetHost = currentHost.replace(/\.?alibabacloud\.com$/, currentHost.startsWith('alibabacloud.com') ? 'aliyun.com' : '.aliyun.com'); label = '切换国内站'; }
const targetUrl = location.protocol + '//' + targetHost + location.pathname + location.search + location.hash;
const btn = document.createElement('div'); btn.textContent = label; btn.style.cssText = 'position:fixed;right:12px;bottom:120px;z-index:99999;padding:8px 14px;background:rgba(255,106,0,0.85);color:#fff;font-size:13px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;border-radius:6px;cursor:pointer;box-shadow:0 2px 8px rgba(0,0,0,.15);transition:background 0.2s,transform 0.15s;user-select:none;white-space:nowrap;'; btn.addEventListener('mouseenter', function () { this.style.background = 'rgba(255,106,0,1)'; this.style.transform = 'scale(1.05)'; }); btn.addEventListener('mouseleave', function () { this.style.background = 'rgba(255,106,0,0.85)'; this.style.transform = 'scale(1)'; }); btn.addEventListener('click', function () { if (confirm('将跳转到: ' + targetUrl)) { location.href = targetUrl; } }); document.body.appendChild(btn); }
function base32ToBytes(base32) { const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; base32 = base32.replace(/=+$/, '').toUpperCase(); const bits = base32.length * 5; const bytes = new Uint8Array(Math.floor(bits / 8)); let byteIdx = 0, buf = 0, bufBits = 0; for (let i = 0; i < base32.length; i++) { const v = alphabet.indexOf(base32[i]); if (v === -1) continue; buf = (buf << 5) | v; bufBits += 5; if (bufBits >= 8) { bufBits -= 8; bytes[byteIdx++] = (buf >> bufBits) & 0xff; } } return bytes; }
async function generateTOTP(secret) { const keyBytes = base32ToBytes(secret); const counter = Math.floor(Date.now() / 1000 / 30); const counterBuf = new ArrayBuffer(8); new DataView(counterBuf).setBigUint64(0, BigInt(counter));
const cryptoKey = await crypto.subtle.importKey( 'raw', keyBytes, { name: 'HMAC', hash: 'SHA-1' }, false, ['sign'] ); const hmac = await crypto.subtle.sign('HMAC', cryptoKey, counterBuf); const hmacBytes = new Uint8Array(hmac); const offset = hmacBytes[19] & 0xf; const code = ((hmacBytes[offset] & 0x7f) << 24 | (hmacBytes[offset + 1] & 0xff) << 16 | (hmacBytes[offset + 2] & 0xff) << 8 | (hmacBytes[offset + 3] & 0xff)) % 1000000; return code.toString().padStart(6, '0'); }
function decodeQRFromImage(img) { if (!img.complete || img.naturalWidth === 0) return null;
const canvas = document.createElement('canvas'); canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const result = jsQR(imageData.data, canvas.width, canvas.height);
if (result && result.data) { return result.data; } return null; }
function extractSecretFromOtpAuth(otpauthUrl) { const match = otpauthUrl.match(/[?&]secret=([A-Za-z2-7]+)/); return match ? match[1] : null; }
function isLikelyQRCode(img) { const w = img.naturalWidth; const h = img.naturalHeight; if (w < 100 || h < 100 || w > 600 || h > 600) return false; const ratio = Math.max(w, h) / Math.min(w, h); return ratio < 1.3; }
function scanForQRCodes() { const imgs = document.querySelectorAll('img'); let found = false; for (const img of imgs) { if (!isLikelyQRCode(img)) continue; const data = decodeQRFromImage(img); if (data && data.startsWith('otpauth://')) { const secret = extractSecretFromOtpAuth(data); if (secret) { saveSecret(secret); found = true; break; } } } return found; }
function decodeQRFromBlob(blob) { return new Promise(function (resolve) { const img = new Image(); img.onload = function () { const canvas = document.createElement('canvas'); canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const result = jsQR(imageData.data, canvas.width, canvas.height); URL.revokeObjectURL(img.src); resolve(result ? result.data : null); }; img.onerror = function () { URL.revokeObjectURL(img.src); resolve(null); }; img.src = URL.createObjectURL(blob); }); }
function showPasteDialog(onResult) { const overlay = document.createElement('div'); overlay.style.cssText = 'position:fixed;inset:0;z-index:100001;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;';
const dialog = document.createElement('div'); dialog.style.cssText = 'background:#fff;border-radius:12px;padding:32px;text-align:center;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;box-shadow:0 8px 32px rgba(0,0,0,.3);min-width:320px;'; dialog.innerHTML = '<div style="font-size:16px;font-weight:600;margin-bottom:12px;">粘贴二维码图片</div>' + '<div style="font-size:13px;color:#666;margin-bottom:16px;">先截图/复制二维码,然后在下方按 Ctrl+V</div>' + '<div id="ali-mfa-paste-zone" contenteditable="true" style="border:2px dashed #ccc;border-radius:8px;min-height:120px;display:flex;align-items:center;justify-content:center;color:#999;font-size:14px;outline:none;cursor:text;">点击此处后按 Ctrl+V 粘贴</div>' + '<div style="margin-top:16px;"><button id="ali-mfa-paste-close" style="padding:6px 20px;border:1px solid #ddd;border-radius:6px;background:#f5f5f5;cursor:pointer;font-size:13px;">关闭</button></div>';
overlay.appendChild(dialog); document.body.appendChild(overlay);
const zone = document.getElementById('ali-mfa-paste-zone'); const closeBtn = document.getElementById('ali-mfa-paste-close');
zone.focus();
function cleanup() { overlay.remove(); }
closeBtn.addEventListener('click', function () { cleanup(); onResult(false); }); overlay.addEventListener('click', function (e) { if (e.target === overlay) { cleanup(); onResult(false); } });
zone.addEventListener('paste', async function (e) { e.preventDefault(); const files = e.clipboardData && e.clipboardData.files; if (!files || files.length === 0) { zone.textContent = '未检测到图片,请重试'; return; } const file = Array.from(files).find(f => f.type.startsWith('image/')); if (!file) { zone.textContent = '剪贴板中没有图片'; return; } zone.textContent = '识别中...'; const data = await decodeQRFromBlob(file); if (data && data.startsWith('otpauth://')) { const secret = extractSecretFromOtpAuth(data); if (secret) { saveSecret(secret); cleanup(); onResult(true); return; } } if (data) { zone.textContent = '二维码非OTP格式: ' + data.substring(0, 60); } else { zone.textContent = '未识别到二维码,请确认图片清晰完整后重试'; } }); }
function getSecret() { return GM_getValue(STORAGE_KEY, null); }
function saveSecret(secret) { GM_setValue(STORAGE_KEY, secret); showToast('MFA密钥已保存 (' + domainKey + ')'); }
function deleteSecret() { GM_deleteValue(STORAGE_KEY); showToast('MFA密钥已删除 (' + domainKey + ')'); }
function showToast(msg) { const existing = document.querySelector('.ali-mfa-toast'); if (existing) existing.remove();
const toast = document.createElement('div'); toast.className = 'ali-mfa-toast'; toast.textContent = msg; toast.style.cssText = 'position:fixed;top:16px;left:50%;transform:translateX(-50%);z-index:100000;padding:10px 24px;background:#1a1a1a;color:#fff;font-size:14px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;border-radius:8px;box-shadow:0 4px 16px rgba(0,0,0,.25);pointer-events:none;transition:opacity 0.4s;'; document.body.appendChild(toast);
setTimeout(function () { toast.style.opacity = '0'; setTimeout(function () { toast.remove(); }, 400); }, 2500); }
function injectButtonStyle() { if (document.getElementById('ali-mfa-btn-style')) return; const style = document.createElement('style'); style.id = 'ali-mfa-btn-style'; style.textContent = [ '.ali-mfa-fill-btn {', ' display:inline-flex;align-items:center;justify-content:center;', ' margin-left:8px;padding:0 12px;height:32px;', ' background:#ff6a00;color:#fff;border:none;border-radius:4px;', ' font-size:13px;cursor:pointer;white-space:nowrap;', ' transition:background 0.15s;', '}', '.ali-mfa-fill-btn:hover { background:#e85d00; }', '.ali-mfa-fill-btn.has-secret { background:#0077cc; }', '.ali-mfa-fill-btn.has-secret:hover { background:#0066b3; }', '.ali-mfa-actions {', ' display:inline-flex;align-items:center;margin-left:8px;gap:6px;', '}', ].join('\n'); document.head.appendChild(style); }
function findMFAInputs() { const candidates = []; const inputs = document.querySelectorAll('input[type="text"]:not([readonly]):not([disabled]), input[type="number"]:not([readonly]):not([disabled]), input:not([type]):not([readonly]):not([disabled])');
for (const inp of inputs) { const attrs = (inp.outerHTML + ' ' + (inp.placeholder || '') + ' ' + (inp.name || '') + ' ' + (inp.id || '') + ' ' + (inp.getAttribute('aria-label') || '')).toLowerCase();
const mfaKeywords = ['mfa', 'totp', 'code', 'captcha', 'otp', 'verify', 'verification', 'token', 'passcode', 'auth', '验证', '认证', '动态', '口令', '两步', '安全码'];
const hasMFASignal = mfaKeywords.some(function (kw) { return attrs.indexOf(kw) !== -1; }); const isSixDigitField = (inp.getAttribute('maxlength') === '6' || inp.getAttribute('size') === '6');
if (hasMFASignal || isSixDigitField) { candidates.push(inp); } } return candidates; }
function createFillButton(inputEl, secret) { const parent = inputEl.parentElement; if (!parent) return null;
const existing = parent.querySelector('.ali-mfa-actions'); if (existing) return null;
const wrapper = document.createElement('span'); wrapper.className = 'ali-mfa-actions';
const fillBtn = document.createElement('button'); fillBtn.className = 'ali-mfa-fill-btn'; fillBtn.textContent = '复制MFA'; fillBtn.title = '生成验证码并复制到剪贴板,然后粘贴到输入框';
fillBtn.addEventListener('click', async function () { fillBtn.textContent = '...'; fillBtn.disabled = true; try { const code = await generateTOTP(secret); await navigator.clipboard.writeText(code); fillBtn.textContent = '已复制 ' + code; showToast('验证码 ' + code + ' 已复制,请粘贴到输入框'); setTimeout(function () { fillBtn.textContent = '复制MFA'; }, 3000); } catch (e) { fillBtn.textContent = '失败'; setTimeout(function () { fillBtn.textContent = '复制MFA'; }, 2000); } fillBtn.disabled = false; });
wrapper.appendChild(fillBtn); parent.appendChild(wrapper); return wrapper; }
function processMFA() { scanForQRCodes();
const secret = getSecret(); if (!secret) return;
const inputs = findMFAInputs(); for (const inp of inputs) { createFillButton(inp, secret); } }
function initMFA() { injectButtonStyle();
processMFA();
const observer = new MutationObserver(function () { processMFA(); }); observer.observe(document.body, { childList: true, subtree: true });
setInterval(processMFA, 2000); }
function initMFAIndicator() { const secret = getSecret(); const indicator = document.createElement('div'); indicator.style.cssText = 'position:fixed;left:12px;bottom:120px;z-index:99998;padding:6px 12px;background:' + (secret ? 'rgba(0,136,68,0.85)' : 'rgba(136,136,136,0.75)') + ';color:#fff;font-size:12px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;border-radius:6px;cursor:pointer;box-shadow:0 2px 8px rgba(0,0,0,.15);transition:background 0.2s;user-select:none;white-space:nowrap;'; indicator.textContent = secret ? 'MFA: ' + domainKey + ' ✓' : 'MFA: 未设置';
indicator.addEventListener('click', function () { const currentSecret = getSecret(); if (currentSecret) { if (confirm('删除 ' + domainKey + ' 的MFA密钥?\n\n当前密钥: ' + currentSecret)) { deleteSecret(); indicator.textContent = 'MFA: 未设置'; indicator.style.background = 'rgba(136,136,136,0.75)'; } } else { var input = prompt('请粘贴 ' + domainKey + ' 的TOTP密钥或 otpauth:// 链接:\n\n可从 Authenticator App 导出二维码内容,或从初始绑定页面获取'); if (input) { input = input.replace(/\s+/g, '');
if (input.startsWith('otpauth://')) { var secretMatch = input.match(/[?&]secret=([A-Za-z2-7]+)/); if (secretMatch) { input = secretMatch[1].toUpperCase(); } else { showToast('无法从链接中提取密钥,请检查链接是否完整'); return; } } else { input = input.toUpperCase(); }
if (/^[A-Z2-7]+=*$/.test(input) && input.length >= 16) { saveSecret(input); indicator.textContent = 'MFA: ' + domainKey + ' ✓'; indicator.style.background = 'rgba(0,136,68,0.85)'; } else { showToast('密钥格式无效,请输入有效的Base32密钥'); } } } });
document.body.appendChild(indicator);
const clipBtn = document.createElement('div'); clipBtn.textContent = '剪贴板识别'; clipBtn.style.cssText = 'position:fixed;left:12px;bottom:80px;z-index:99998;padding:6px 12px;background:rgba(0,119,204,0.85);color:#fff;font-size:12px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;border-radius:6px;cursor:pointer;box-shadow:0 2px 8px rgba(0,0,0,.15);transition:background 0.2s;user-select:none;white-space:nowrap;'; clipBtn.addEventListener('mouseenter', function () { this.style.background = 'rgba(0,119,204,1)'; }); clipBtn.addEventListener('mouseleave', function () { this.style.background = 'rgba(0,119,204,0.85)'; }); clipBtn.addEventListener('click', function () { showPasteDialog(function (ok) { if (ok) { indicator.textContent = 'MFA: ' + domainKey + ' ✓'; indicator.style.background = 'rgba(0,136,68,0.85)'; } }); }); document.body.appendChild(clipBtn); }
initSiteSwitcher(); initMFA(); initMFAIndicator(); })();
|