diff --git a/README.md b/README.md index 624452f..106d909 100644 --- a/README.md +++ b/README.md @@ -1,59 +1,81 @@ -# Frontend 2FA Tool +# 2FA Authenticator / 两步验证器 -这是一个安全、离线优先的前端双因素认证 (2FA) 工具,基于 Next.js 构建。 +一个纯前端的 TOTP 两步验证工具,支持多种添加令牌方式,数据完全存储在本地浏览器中。 -## 功能特性 +A pure frontend TOTP two-factor authentication tool with multiple token import methods. All data is stored locally in your browser. -- 🔒 **安全**: 所有数据存储在本地,不上传服务器 -- 📱 **QR 扫码**: 支持直接扫描 QR 码添加令牌 (使用 jsQR) -- ⌨️ **手动录入**: 支持手动输入密钥添加 -- 🌓 **深色模式**: 内置明亮/深色主题切换 -- 📤 **导入/导出**: 支持令牌数据的备份与恢复 -- 🌐 **多语言**: 支持国际化 +## Features / 功能特性 -## 技术栈 +- **多种添加方式** - 手动输入密钥、扫描二维码、上传二维码图片 +- **高级设置** - 支持 SHA-1/256/512 算法、6/8位验证码、30/60秒刷新周期 +- **完整编辑** - 可编辑令牌的所有信息(名称、发行者、密钥、算法等) +- **一键复制** - 点击验证码即可复制,带视觉反馈 +- **数据管理** - 支持导入/导出备份(JSON格式) +- **去重检测** - 自动检测重复令牌 +- **主题切换** - 支持亮色/暗色/跟随系统三种主题 +- **多语言** - 支持中文/英文切换 +- **纯前端** - 无需后端,数据存储在浏览器 localStorage +- **静态部署** - 支持导出为静态文件部署到任意平台 -- **框架**: Next.js 14 -- **UI 组件**: Radix UI -- **样式**: Tailwind CSS -- **工具库**: jsQR, date-fns +## Tech Stack / 技术栈 -## 环境变量 +- Next.js 15 +- React 19 +- TypeScript +- Tailwind CSS +- shadcn/ui +- jsQR (二维码识别) -可以在部署时设置以下环境变量来配置页脚信息: +## Environment Variables / 环境变量 -- `NEXT_PUBLIC_SHOW_FOOTER`: 是否显示页脚 (默认: true, 设置为 "false" 隐藏) -- `NEXT_PUBLIC_FILING_ICP`: ICP 备案号 (例如: 滇ICP备xxxxxxxx号) -- `NEXT_PUBLIC_FILING_SECURITY`: 公安联网备案号 (例如: 滇公网安备xxxxxxxxxxxxxx号) +所有环境变量均为可选配置: -## 声明 +| 变量名 | 说明 | 示例值 | +|--------|------|--------| +| `NEXT_PUBLIC_SHOW_FOOTER` | 是否显示页脚(默认隐藏) | `true` | +| `NEXT_PUBLIC_FOOTER_COPYRIGHT` | 版权所有者名称 | `Your Name` | +| `NEXT_PUBLIC_ICP_NUMBER` | ICP备案号(完整文本) | `京ICP备xxxxxxxx号` | +| `NEXT_PUBLIC_PSB_NUMBER` | 公安备案号(完整文本) | `京公网安备xxxxxxxxxxxxxx号` | -本项目由阿里云ESA提供加速、计算和保护 +## Deployment / 部署 -![阿里云ESA Pages](public/images/aliyun-esa.png) +### Vercel -## 开始使用 +直接导入 GitHub 仓库即可,无需额外配置。 -1. 安装依赖: +### 静态部署(阿里云 ESA / Cloudflare Pages 等) -```bash -pnpm install +项目已配置为静态导出模式,构建后会生成 `out` 目录。 + +**构建配置:** + +``` +安装命令:npm install +构建命令:npm run build +静态资源目录:out +Node.js 版本:20.x 或 22.x ``` -2. 启动开发服务器: +### 本地开发 ```bash -pnpm dev +# 安装依赖 +npm install + +# 启动开发服务器 +npm run dev + +# 构建 +npm run build ``` -3. 访问 [http://localhost:3000](http://localhost:3000) +## Security / 安全说明 -## 构建 +- 所有令牌数据仅存储在浏览器本地 localStorage 中 +- 不会向任何服务器发送数据 +- 建议定期导出备份以防数据丢失 +- 导出的 JSON 文件包含敏感密钥信息,请妥善保管 -```bash -pnpm build -``` +## License / 许可证 -## 部署 - -本项目可以直接部署在阿里云 ESA Pages 上。 +MIT diff --git a/app/globals.css b/app/globals.css index ab1567c..dc2aea1 100644 --- a/app/globals.css +++ b/app/globals.css @@ -19,7 +19,7 @@ --accent: oklch(0.97 0 0); --accent-foreground: oklch(0.205 0 0); --destructive: oklch(0.577 0.245 27.325); - --destructive-foreground: oklch(0.985 0 0); + --destructive-foreground: oklch(0.577 0.245 27.325); --border: oklch(0.922 0 0); --input: oklch(0.922 0 0); --ring: oklch(0.708 0 0); diff --git a/app/page.tsx b/app/page.tsx index cfe1293..b84b913 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -27,7 +27,6 @@ import { Monitor, Languages, Check, - Github, } from "lucide-react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" @@ -51,6 +50,7 @@ import { Toaster } from "@/components/ui/toaster" import { useToast } from "@/hooks/use-toast" import { useTheme } from "@/components/theme-provider" import { useLanguage } from "@/lib/i18n" +import { TechBackground } from "@/components/tech-background" interface TOTPToken { id: string @@ -163,7 +163,6 @@ export default function TwoFactorAuth() { const [isSettingsOpen, setIsSettingsOpen] = useState(false) const [editingToken, setEditingToken] = useState(null) const [showAdvanced, setShowAdvanced] = useState(false) - const [mounted, setMounted] = useState(false) const videoRef = useRef(null) const canvasRef = useRef(null) const fileInputRef = useRef(null) @@ -197,7 +196,6 @@ export default function TwoFactorAuth() { if (savedSettings) { setSettings(JSON.parse(savedSettings)) } - setMounted(true) }, []) // Save tokens to localStorage @@ -643,9 +641,10 @@ export default function TwoFactorAuth() { } return ( -
+
+ {/* Header */} -
+
@@ -658,33 +657,15 @@ export default function TwoFactorAuth() {
- - @@ -768,7 +749,7 @@ export default function TwoFactorAuth() {
{/* Main Content */} -
+
{/* Timer Progress */}
@@ -1017,7 +998,7 @@ export default function TwoFactorAuth() { {/* Edit Dialog */} !open && setEditingToken(null)}> - + {t.editToken} @@ -1037,6 +1018,68 @@ export default function TwoFactorAuth() { onChange={(e) => setEditingToken({ ...editingToken, issuer: e.target.value })} />
+
+ + setEditingToken({ ...editingToken, secret: e.target.value.toUpperCase().replace(/\s/g, "") })} + className="font-mono" + /> +
+
+
+ + +
+
+ + +
+
+ + +
+
)} @@ -1049,47 +1092,46 @@ export default function TwoFactorAuth() { {/* Footer */} - {/* Footer */} - {(process.env.NEXT_PUBLIC_SHOW_FOOTER !== "false") && ( -