@flun/html-template 5.0.0 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/f-CHANGELOG.md CHANGED
@@ -1,4 +1,12 @@
1
1
  # 变更日志
2
+ ## [5.0.1] - 2026-06-10 11:59
3
+ ### 紧急修复
4
+ - 修复二次验证报错问题:
5
+ - 修复前端脚本语法错误(可选链非法赋值、逗号表达式歧义)。
6
+ - 修正二次验证接口请求字段名(`input` → `token`),使其与后端匹配。
7
+ - 优化验证逻辑,使用统一输入框/按钮引用,避免重复代码。
8
+ - 二次验证(TOTP、备份码、WebAuthn)功能恢复正常。
9
+ > **请快更新和替换该文件(`2fa.html`)**
2
10
  ## [5.0.0] - 2026-06-10 10:05
3
11
  ### 重大更新
4
12
  - 对templates/account目录中的页面结构进行了大面积优化,如果你是老用户请注意对比迁移!!!;
@@ -10,7 +18,4 @@
10
18
  - customize\account.js文件为配合上述功能实现也做了相应适配;
11
19
  ## [4.4.3] - 2026-06-05 22:16
12
20
  ### 优化
13
- - 模板中用户页面的移动端样式适配;
14
- ## [4.4.2] - 2026-05-31 15:08
15
- ### 修复
16
- - 修复全局中间件中 `getCurrentUser(req)` 返回 `null` 时解构报错导致服务崩溃的问题,增加空值判断并自动清理无效登录态;
21
+ - 模板中用户页面的移动端样式适配;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flun/html-template",
3
- "version": "5.0.0",
3
+ "version": "5.0.1",
4
4
  "description": "一个HTML模板工具包,提供开发服务器和模板编译功能,支持自定义标签和快捷输入,变量定义,包含文件引用,帮助开发者模块化处理HTML;",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -141,10 +141,10 @@
141
141
  <h2>双因素验证</h2>
142
142
 
143
143
  <!-- TOTP 模式区域 -->
144
- <div id="totpSection">
144
+ <div id="totpSection" hidden>
145
145
  <p id="promptText">请输入身份验证器中的6位数字验证码</p>
146
146
  <div class="form-group">
147
- <input type="text" id="tokenInput" placeholder="6位验证码" maxlength="6" autofocus
147
+ <input type="text" id="totpInput" placeholder="6位验证码" maxlength="6" autofocus
148
148
  autocomplete="one-time-code" class="totp-input">
149
149
  </div>
150
150
  <button type="button" class="btn" id="verifyTotpBtn">验证</button>
@@ -176,8 +176,8 @@
176
176
  <script src="/static/topImg.js" defer></script>
177
177
  <script src="/static/utils/browser.js"></script>
178
178
  <script>
179
- const [totpSection, webauthnSection, backupPanel, messageDiv, tokenInput, verifyTotpBtn, backupCodeInput, submitBackupBtn,
180
- retryHardwareBtn] = ['totpSection', 'webauthnSection', 'backupPanel', 'message', 'tokenInput',
179
+ const [totpSection, webauthnSection, backupPanel, messageDiv, totpInput, verifyTotpBtn, backupCodeInput, submitBackupBtn,
180
+ retryHardwareBtn] = ['totpSection', 'webauthnSection', 'backupPanel', 'message', 'totpInput',
181
181
  'verifyTotpBtn', 'backupCodeInput', 'submitBackupBtn', 'retryHardwareBtn']
182
182
  .map(id => document.getElementById(id)),
183
183
  urlParams = new URLSearchParams(window.location.search), method = urlParams.get('method'),
@@ -187,7 +187,7 @@
187
187
  const setMessage = msg => messageDiv.textContent = msg, clearMessage = () => messageDiv.textContent = '',
188
188
  // 禁用/启用表单控件
189
189
  setFormDisabled = disabled => {
190
- const inputs = [tokenInput, backupCodeInput, verifyTotpBtn, submitBackupBtn, retryHardwareBtn];
190
+ const inputs = [totpInput, backupCodeInput, verifyTotpBtn, submitBackupBtn, retryHardwareBtn];
191
191
  inputs.forEach(el => { if (el) el.disabled = disabled; });
192
192
  },
193
193
  // 显示备份码面板
@@ -197,25 +197,25 @@
197
197
  backupCodeInput.value = '', backupCodeInput.focus();
198
198
  },
199
199
  // 统一处理验证响应(TOTP 和备份码共用)
200
- handleVerifyResponse = async input => {
201
- if (!input) {
200
+ handleVerifyResponse = async token => {
201
+ if (!token) {
202
202
  setMessage('请输入验证码/备用码');
203
203
  return false;
204
204
  }
205
- const isToken = input.length === 6, isBackup = input.length === 10;
206
- if (!isToken && !isBackup) {
205
+ const totp = token.length === 6, isBackup = token.length === 10;
206
+ if (!totp && !isBackup) {
207
207
  setMessage('验证码必须为6位数字,备用码为10位');
208
208
  return false;
209
209
  }
210
210
 
211
211
  // 临时禁用提交按钮防止重复请求
212
- const activeBtn = isToken ? verifyTotpBtn : submitBackupBtn;
212
+ const [activeInput, activeBtn] = totp ? [totpInput, verifyTotpBtn] : [backupCodeInput, submitBackupBtn];
213
213
  activeBtn.disabled = true, clearMessage();
214
214
 
215
215
  try {
216
216
  const response = await fetch('/api/verify-2fa', {
217
217
  method: 'POST', headers: { 'Content-Type': 'application/json' },
218
- body: JSON.stringify({ input })
218
+ body: JSON.stringify({ token })
219
219
  }), data = await response.json();
220
220
 
221
221
  if (response.ok) {
@@ -233,16 +233,10 @@
233
233
  if (data.remainingAttempts !== undefined) {
234
234
  const failuresSoFar = 9 - data.remainingAttempts;
235
235
  setMessage(`${data.message} (已失败 ${failuresSoFar} 次)`);
236
- if (failuresSoFar >= maxNum && isToken) showBackupPanel(`连续失败 ${failuresSoFar} 次,请使用备用码登录`);
236
+ if (failuresSoFar >= maxNum && totp) showBackupPanel(`连续失败 ${failuresSoFar} 次,请使用备用码登录`);
237
237
  }
238
238
  else setMessage(data.message);
239
-
240
- // 清空输入框,准备下次尝试
241
- tokenInput?.value = '', backupCodeInput?.value = '', activeBtn.disabled = false;
242
-
243
- // 判断焦点归属
244
- if (isBackup) backupCodeInput.focus();
245
- else if (isToken) tokenInput.focus();
239
+ activeInput.value = '', activeInput.focus(), activeBtn.disabled = false;
246
240
  return false;
247
241
  } catch (err) {
248
242
  setMessage(err.message), activeBtn.disabled = false;
@@ -250,7 +244,7 @@
250
244
  }
251
245
  },
252
246
  // TOTP验证和备份码提交入口
253
- performTotpVerification = () => handleVerifyResponse(tokenInput.value.trim()),
247
+ performTotpVerification = () => handleVerifyResponse(totpInput.value.trim()),
254
248
  submitBackupCode = () => handleVerifyResponse(backupCodeInput.value.trim()),
255
249
  // 硬件验证核心
256
250
  performHardwareVerification = async () => {
@@ -305,7 +299,7 @@
305
299
  backupPanel.hidden = true;
306
300
  if (method === 'webauthn')
307
301
  totpSection.hidden = true, webauthnSection.hidden = false, performHardwareVerification().catch(e => console.warn(e));
308
- else totpSection.hidden = false, webauthnSection.hidden = true, tokenInput.maxLength = 6, tokenInput.focus();
302
+ else totpSection.hidden = false, webauthnSection.hidden = true, totpInput.focus();
309
303
 
310
304
  // 事件监听
311
305
  submitBackupBtn.addEventListener('click', submitBackupCode);
@@ -313,10 +307,10 @@
313
307
  backupCodeInput.addEventListener('keypress', e => {
314
308
  if (e.key === 'Enter') submitBackupCode();
315
309
  });
316
- tokenInput.addEventListener('keypress', e => {
310
+ totpInput.addEventListener('keypress', e => {
317
311
  if (e.key === 'Enter') performTotpVerification();
318
312
  });
319
- [backupCodeInput, tokenInput].forEach(el => el.addEventListener('input', clearMessage));
313
+ [backupCodeInput, totpInput].forEach(el => el.addEventListener('input', clearMessage));
320
314
  };
321
315
 
322
316
  initPage();