From 5c2c6cfb6ca67413641e0cc43ef3b6a11ad1061e Mon Sep 17 00:00:00 2001 From: shuaiplus <2327005759@qq.com> Date: Sun, 8 Mar 2026 02:31:36 +0800 Subject: [PATCH] feat: add TOTP codes page and related components for displaying verification codes --- src/router.ts | 14 ++ webapp/src/App.tsx | 12 +- webapp/src/components/TotpCodesPage.tsx | 204 ++++++++++++++++++++++++ webapp/src/components/VaultPage.tsx | 2 +- webapp/src/lib/i18n.ts | 6 + webapp/src/styles.css | 98 +++++++++++- 6 files changed, 328 insertions(+), 8 deletions(-) create mode 100644 webapp/src/components/TotpCodesPage.tsx diff --git a/src/router.ts b/src/router.ts index 14a4df7..84b056a 100644 --- a/src/router.ts +++ b/src/router.ts @@ -142,6 +142,17 @@ function handleNwFavicon(): Response { }); } +const BITWARDEN_DEFAULT_ICON_SHA256 = 'aaa64871332ad5b7d28fe8874efb19c2d9cc2f1e6de75d52b080b438225a0783'; + +function bytesToHex(bytes: Uint8Array): string { + return Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join(''); +} + +async function sha256Hex(buffer: ArrayBuffer): Promise { + const digest = await crypto.subtle.digest('SHA-256', buffer); + return bytesToHex(new Uint8Array(digest)); +} + function isValidIconHostname(hostname: string): boolean { if (!hostname) return false; if (hostname.length > 253) return false; @@ -192,6 +203,9 @@ async function handleGetIcon(request: Request, env: Env, hostname: string): Prom if (resp.ok) { const body = await resp.arrayBuffer(); + if (body.byteLength === 500 && (await sha256Hex(body)) === BITWARDEN_DEFAULT_ICON_SHA256) { + return new Response(null, { status: 204 }); + } const iconResponse = new Response(body, { status: 200, headers: { diff --git a/webapp/src/App.tsx b/webapp/src/App.tsx index 3e59733..2ea3e93 100644 --- a/webapp/src/App.tsx +++ b/webapp/src/App.tsx @@ -1,7 +1,7 @@ import { useEffect, useMemo, useRef, useState } from 'preact/hooks'; import { Link, Route, Switch, useLocation } from 'wouter'; import { useQuery } from '@tanstack/react-query'; -import { ArrowUpDown, Cloud, Lock, LogOut, Send as SendIcon, Settings as SettingsIcon, Shield, ShieldUser, Vault } from 'lucide-preact'; +import { ArrowUpDown, Cloud, Clock3, KeyRound, Lock, LogOut, Send as SendIcon, Settings as SettingsIcon, Shield, ShieldUser } from 'lucide-preact'; import AuthViews from '@/components/AuthViews'; import ConfirmDialog from '@/components/ConfirmDialog'; import ToastHost from '@/components/ToastHost'; @@ -15,6 +15,7 @@ import SecurityDevicesPage from '@/components/SecurityDevicesPage'; import AdminPage from '@/components/AdminPage'; import HelpPage from '@/components/HelpPage'; import ImportPage from '@/components/ImportPage'; +import TotpCodesPage from '@/components/TotpCodesPage'; import type { ImportAttachmentFile, ImportResultSummary } from '@/components/ImportPage'; import { changeMasterPassword, @@ -1669,9 +1670,13 @@ export default function App() {