feat: register Shareit IDs with QR

This commit is contained in:
王源
2026-10-20 17:03:12 +08:00
parent 58f33f94cc
commit bc575812cb
9 changed files with 140 additions and 81 deletions
+1 -1
View File
@@ -14,7 +14,7 @@
let activeResult = null;
let inputTimer;
$('currentUser').innerHTML = `<b>${escapeHtml(current.username)}</b><small>${current.role === 'admin' ? '管理员' : '普通用户'}</small>`;
$('currentUser').textContent = current.username;
$('templateCount').textContent = `${templates.length} 个平台模板`;
templates.forEach((template, index) => platformSelect.add(new Option(template.name, index)));
+2 -2
View File
@@ -68,8 +68,8 @@
clearTimeout(showToast.timer); showToast.timer = setTimeout(() => toast.classList.remove('show'), 1800);
}
$('currentUser').innerHTML = `<b>${escapeHtml(current.username)}</b><small>${current.role === 'admin' ? '管理员' : '普通用户'}</small>`;
$('historyScope').textContent = current.role === 'admin' ? '管理员可查看当前浏览器内全部用户的记录。' : '普通用户仅可查看当前浏览器内自己的记录。';
$('currentUser').textContent = current.username;
$('historyScope').textContent = '查看当前浏览器内你有权限访问的链接记录。';
$('logoutBtn').addEventListener('click', () => { auth.logout(); location.href = 'login.html'; });
$('historySearch').addEventListener('input', render);
$('clearHistory').addEventListener('click', () => {
+84 -44
View File
@@ -2,9 +2,7 @@
const auth = window.TrafficAuth;
const $ = (id) => document.getElementById(id);
const enrollmentByUser = new Map();
const userSelect = $('loginUser');
auth.users.forEach((user) => userSelect.add(new Option(`${user.username} · ${user.role === 'admin' ? '管理员' : '普通用户'}`, user.username)));
let selectedUsername = '';
function nextPage() {
const next = new URLSearchParams(location.search).get('next');
@@ -18,36 +16,86 @@
}
function currentEnrollment() {
const username = userSelect.value;
if (!enrollmentByUser.has(username)) enrollmentByUser.set(username, auth.createEnrollment(username));
return enrollmentByUser.get(username);
if (!enrollmentByUser.has(selectedUsername)) enrollmentByUser.set(selectedUsername, auth.createEnrollment(selectedUsername));
return enrollmentByUser.get(selectedUsername);
}
function renderMode() {
const username = userSelect.value;
const user = auth.findUser(username);
const enrolled = auth.isEnrolled(username);
$('selectedRole').innerHTML = `<span class="user-role ${user.role}">${user.role === 'admin' ? '管理员' : '普通用户'}</span><small>${enrolled ? '已在当前浏览器绑定' : '首次使用,需要完成本机设置'}</small>`;
$('signinForm').classList.toggle('hidden', !enrolled);
$('enrollForm').classList.toggle('hidden', enrolled);
$('loginTitle').textContent = enrolled ? '用户登录' : '首次绑定';
$('loginSubtitle').textContent = enrolled ? '密码和动态验证码双重验证' : '设置密码并绑定 Google Authenticator';
$('signinMessage').textContent = '';
$('enrollMessage').textContent = '';
if (!enrolled) {
const enrollment = currentEnrollment();
$('setupKey').textContent = enrollment.secret.match(/.{1,4}/g).join(' ');
$('authAccountName').textContent = `Traffic Intelligence (${username})`;
function renderQr(otpauthUrl) {
const container = $('qrCode');
container.innerHTML = '';
if (typeof window.qrcode !== 'function') {
container.innerHTML = '<span class="qr-error">二维码加载失败,请刷新页面重试</span>';
return false;
}
try {
const qr = window.qrcode(0, 'M');
qr.addData(otpauthUrl);
qr.make();
container.innerHTML = qr.createSvgTag(5, 4);
return true;
} catch (_) {
container.innerHTML = '<span class="qr-error">二维码生成失败,请刷新页面重试</span>';
return false;
}
}
function resetForms() {
['signinPassword','signinCode','newPassword','confirmPassword','setupCode'].forEach((id) => { $(id).value = ''; });
$('accountMessage').textContent = '';
$('signinMessage').textContent = '';
$('enrollMessage').textContent = '';
}
function openAccount(username) {
selectedUsername = username;
const enrolled = auth.isEnrolled(username);
$('accountForm').classList.add('hidden');
$('confirmedAccount').classList.remove('hidden');
$('confirmedUser').textContent = username;
$('signinForm').classList.toggle('hidden', !enrolled);
$('enrollForm').classList.toggle('hidden', enrolled);
$('loginTitle').textContent = enrolled ? '欢迎回来' : '首次注册';
$('loginSubtitle').textContent = enrolled ? '输入密码和动态验证码登录' : '扫描二维码并设置本机密码';
if (!enrolled) {
const ready = renderQr(currentEnrollment().otpauthUrl);
$('enrollMessage').textContent = ready ? '' : '二维码组件加载失败,暂时无法完成注册';
}
}
function changeAccount() {
selectedUsername = '';
resetForms();
$('confirmedAccount').classList.add('hidden');
$('signinForm').classList.add('hidden');
$('enrollForm').classList.add('hidden');
$('accountForm').classList.remove('hidden');
$('loginTitle').textContent = 'Shareit ID 登录';
$('loginSubtitle').textContent = '输入你的 Shareit ID 继续';
$('loginUser').focus();
}
$('accountForm').addEventListener('submit', (event) => {
event.preventDefault();
const username = $('loginUser').value.trim().toLowerCase();
if (!username) {
$('accountMessage').textContent = '请输入 Shareit ID';
return;
}
if (!auth.findUser(username)) {
$('accountMessage').textContent = '该 Shareit ID 不在授权名单中';
return;
}
resetForms();
openAccount(username);
});
$('signinForm').addEventListener('submit', async (event) => {
event.preventDefault();
const button = event.submitter;
button.disabled = true;
$('signinMessage').textContent = '正在验证…';
try {
await auth.login(userSelect.value, $('signinPassword').value, $('signinCode').value);
await auth.login(selectedUsername, $('signinPassword').value, $('signinCode').value);
location.href = nextPage();
} catch (error) {
$('signinMessage').textContent = error.message;
@@ -62,11 +110,15 @@
$('enrollMessage').textContent = '两次输入的密码不一致';
return;
}
if (typeof window.qrcode !== 'function') {
$('enrollMessage').textContent = '二维码组件尚未加载,请刷新页面重试';
return;
}
const button = event.submitter;
button.disabled = true;
$('enrollMessage').textContent = '正在绑定…';
$('enrollMessage').textContent = '正在注册…';
try {
await auth.enroll(userSelect.value, password, currentEnrollment().secret, $('setupCode').value);
await auth.enroll(selectedUsername, password, currentEnrollment().secret, $('setupCode').value);
location.href = nextPage();
} catch (error) {
$('enrollMessage').textContent = error.message;
@@ -74,32 +126,20 @@
}
});
$('copySetupKey').addEventListener('click', async () => {
const secret = currentEnrollment().secret;
try { await navigator.clipboard.writeText(secret); }
catch (_) {
const area = document.createElement('textarea'); area.value = secret;
document.body.appendChild(area); area.select(); document.execCommand('copy'); area.remove();
}
showToast('设置密钥已复制');
});
$('changeAccount').addEventListener('click', changeAccount);
$('resetEnrollment').addEventListener('click', () => {
const username = userSelect.value;
if (!window.confirm(`确定清除 ${username} 在当前浏览器的密码和 Authenticator 绑定吗?`)) return;
if (!window.confirm(`确定清除 ${selectedUsername} 在当前浏览器的密码和 Authenticator 绑定吗?`)) return;
try {
auth.clearEnrollment(username);
enrollmentByUser.delete(username);
$('signinPassword').value = '';
$('signinCode').value = '';
renderMode();
showToast('本机绑定已清除,请重新设置');
auth.clearEnrollment(selectedUsername);
enrollmentByUser.delete(selectedUsername);
resetForms();
openAccount(selectedUsername);
showToast('本机绑定已清除,请重新注册');
} catch (error) {
$('signinMessage').textContent = error.message;
}
});
userSelect.addEventListener('change', renderMode);
if (auth.currentUser()) location.replace(nextPage());
else renderMode();
else $('loginUser').focus();
})();
+4
View File
@@ -16,3 +16,7 @@
.history-shortcut{min-height:72px;padding:15px 21px;display:flex;align-items:center;gap:13px;transition:border-color .2s,transform .2s}.history-shortcut:hover{border-color:#9eaff4;transform:translateY(-2px)}.history-shortcut-icon{width:37px;height:37px;border-radius:50%;background:#e5eaff;color:var(--blue-dark);display:grid;place-items:center;font-size:19px}.history-shortcut p{display:flex;flex-direction:column;margin:0}.history-shortcut strong{font-size:12px}.history-shortcut small{font-size:9px;color:var(--muted)}.history-shortcut>b{margin-left:auto;min-width:26px;height:23px;padding:0 7px;border-radius:99px;background:#eaf7c9;color:#58790a;display:grid;place-items:center;font:9px var(--mono)}.history-shortcut>i{font-style:normal;font-size:17px}.history-page-panel{min-height:440px}.history-heading-actions{display:flex;align-items:center;gap:8px}.link-button{display:inline-flex;align-items:center;justify-content:center}.history-toolbar{display:flex;align-items:flex-end;gap:16px;padding:17px 0}.history-toolbar label{width:min(420px,100%)}.history-toolbar label>span{display:block;font-size:9px;font-weight:700;margin-bottom:5px}.history-toolbar>span{margin-left:auto;color:var(--muted);font:9px var(--mono);padding-bottom:12px}.history-empty{height:245px}.history-empty h3{font-size:13px;color:var(--ink);margin:9px 0 1px}.history-empty a{color:var(--blue-dark);font-size:10px}.auth-history-table{min-width:1030px}.auth-history-table th:first-child{width:145px}.auth-history-table th:nth-child(2){width:115px}.auth-history-table th:nth-child(3){width:95px}.auth-history-table th:nth-child(4){width:190px}.record-user{display:inline-block;border-radius:99px;background:#eef0ed;color:#42504b;padding:4px 7px;font:8px var(--mono)}
@media(max-width:900px){.login-main{grid-template-columns:1fr;gap:34px;padding-block:45px}.login-intro h1{font-size:48px}.login-intro>p:not(.eyebrow){max-width:620px}.login-card{width:min(100%,520px)}.header-user span small{display:none}}
@media(max-width:620px){.header-user>span{display:none}.header-user{gap:4px}.main-nav{gap:9px}.main-nav a{font-size:9px}.login-main{padding-block:38px}.login-intro h1{font-size:39px}.login-card{padding:22px}.setup-steps{grid-template-columns:1fr}.history-shortcut{padding:13px 15px}.history-shortcut small{display:none}.history-heading-actions{align-items:stretch;flex-direction:column}.history-toolbar{align-items:stretch;flex-direction:column}.history-toolbar>span{margin:0;padding:0}.auth-history-table{min-width:940px}}
/* Shareit ID entry and local QR enrollment */
.account-form{margin-top:20px}.confirmed-account{display:flex;align-items:center;gap:10px;margin-top:18px;padding:11px 13px;border:1px solid var(--line);border-radius:10px;background:#f6f6f2}.confirmed-account>span{font:8px var(--mono);color:var(--muted)}.confirmed-account>strong{font:12px var(--mono)}.confirmed-account>button{margin-left:auto;border:0;background:transparent;color:var(--blue-dark);font-size:9px}.qr-setup{display:grid;grid-template-columns:190px 1fr;gap:18px;align-items:center;margin:18px 0 22px;padding:17px;border:1px solid var(--line);border-radius:13px;background:#fafaf7}.qr-code{width:190px;min-height:190px;display:grid;place-items:center;background:#fff;border:1px solid #e8eae6;border-radius:10px;overflow:hidden}.qr-code svg{display:block;width:180px;height:180px}.qr-code>span{padding:15px;text-align:center;font-size:9px;color:var(--muted)}.qr-code .qr-error{color:#b74b39}.qr-instructions>b{display:inline-block;border-radius:99px;background:#e5eaff;color:#2d47b3;padding:4px 8px;font-size:8px}.qr-instructions ol{margin:13px 0 0;padding-left:20px}.qr-instructions li{margin:8px 0;font-size:10px;color:#58635f}.header-user>span{font:10px var(--mono);color:var(--ink)}
@media(max-width:620px){.qr-setup{grid-template-columns:1fr}.qr-code{margin:auto}.confirmed-account{flex-wrap:wrap}}