基于 EdgeOne Pages 和 KV 存储 的 TOTP 双因素验证应用,无需自建服务器即可部署。
- 应用密码:密码存储在 KV 中(加密存储,不存明文)。首次使用访问任意页面会跳转到 设置页(/setup.html) 完成密码设置;未设置时无法使用应用。
- 密码规则:须大于 6 位,且包含大小写字母、数字和特殊字符(如
!@#$%^&*等)。 - 解锁与记住登录:设置完成后,在首页输入应用密码解锁;同一浏览器中,解锁成功后会在本地安全缓存密码,在 7 天内重新访问可自动解锁(仍需通过后端校验),超过 7 天需重新输入。
- 更改密码:右上角收纳菜单提供「更改密码」,会重新校验旧密码,并用新密码重新加密并保存所有账户数据。
- 账户列表:展示已添加的 2FA 账户及当前动态码(30 秒刷新)
- 添加/编辑/删除账户:支持服务名称、Base32 密钥、账户名称与图标(可选)
- 批量导入:支持从 Google Authenticator(迁移二维码/otpauth 链接)、Aegis、2FAS、Bitwarden 等导出的备份;支持 JSON、TXT、CSV 及每行一个
otpauth://的文本。 - 多格式导出:JSON、TXT、CSV、HTML 备份文件,以及 Google 迁移二维码(可用 Google Authenticator 扫码导入)。
数据全部存储在 KV 中,密钥前缀为 2fa:(含 2fa:config 密码配置、2fa:accounts 或加密的 2fa:accounts_enc),与其它用途可共存于同一 KV 命名空间。
- 数据加密:自首次设置起,账户列表(含 TOTP 密钥)使用 AES-256-GCM 加密后存入
2fa:accounts_enc,加密密钥由应用密码经 PBKDF2 派生,不存明文密钥。此前已部署且未重做设置的项目仍使用明文2fa:accounts,兼容旧数据。
.
├── functions/
│ ├── api/
│ │ ├── auth.js # POST /api/auth - 应用密码校验
│ │ ├── accounts.js # GET/POST /api/accounts - 列表与添加
│ │ ├── accounts/
│ │ │ └── [id].js # GET/PUT/DELETE /api/accounts/:id - 取码、编辑、删除
│ │ ├── setup.js # POST /api/setup - 首次设置应用密码
│ │ ├── setup/
│ │ │ └── status.js # GET /api/setup/status - 是否已设置密码
│ │ └── verify.js # POST /api/verify - 验证 TOTP 码
│ ├── _auth_kv.js # 从请求与 KV 校验应用密码
│ ├── _config.js # 说明(密码已迁至 KV)
│ ├── _kv2fa.js # KV 键名与 getKV
│ ├── _password.js # 密码强度校验与哈希
│ ├── _totp.js # TOTP 生成与校验(Web Crypto)
│ └── _middleware.js # 可选中间件
├── index.html # 前端单页(未设置密码时跳转 setup.html)
├── setup.html # 首次设置应用密码页
├── wrangler.toml # 配置(含 KV 绑定)
├── package.json
├── _routes.json
└── README.md
首次部署后访问站点会跳转到 /setup.html,在此设置应用密码。密码经哈希后存入 KV 键 2fa:config,不可重复初始化;满足:大于 6 位,且含大小写字母、数字、特殊字符。
在 wrangler.toml 中配置 KV 命名空间(或在 EdgeOne 控制台绑定):
[[kv_namespaces]]
binding = "EOFAUTH"
id = "你的_KV_命名空间_ID"2FA 数据使用键:2fa:config(密码配置,JSON)、2fa:accounts / 2fa:accounts_enc(账户列表,明文或加密 JSON 数组)。
CLI 部署:
npm install -g @edgeone/pages-cli
eop auth login
npm run deploy控制台部署:
- 在 EdgeOne Pages 创建项目并绑定 KV(变量名:
EOFAUTH) - 上传或关联本仓库并部署
-
GET /api/setup/status
无需鉴权。返回{ "configured": true|false },前端据此决定是否跳转 /setup.html。 -
POST /api/setup
仅当未设置密码时可用。Body:{ "password", "confirmPassword" },须满足密码强度规则。成功返回{ "success": true }。 -
POST /api/auth
Body:{ "password": "应用密码" },与 KV 中存储的密码哈希比对。成功返回{ "success": true }。 -
GET /api/accounts
需请求头:X-App-Password: 应用密码或Authorization: Bearer 应用密码
返回{ "success": true, "accounts": [{ "id", "name", "issuer", "createdAt" }] }(不含 secret)。 -
POST /api/accounts
同上请求头。Body:{ "name", "secret", "issuer?" },secret为 Base32 密钥。 -
GET /api/accounts/:id
同上请求头。返回当前 TOTP 码:{ "success", "code", "expiresIn", "name", "issuer" }。 -
DELETE /api/accounts/:id
同上请求头。删除该账户。 -
POST /api/verify
同上请求头。Body:{ "accountId", "code" }。返回{ "success": true/false, "message" }。 -
GET /api/accounts/export?format=json|txt|csv|html|google-qr
同上请求头。format默认json:下载备份文件;google-qr返回{ "success", "migrationUrls": ["otpauth-migration://..."] }供前端展示二维码。 -
POST /api/accounts/import
同上请求头。Body:{ "accounts": [{ "name", "secret", "issuer?", "iconUrl?" }, ...] }。将账户追加到现有列表。 -
POST /api/password
同上请求头。Body:{ "oldPassword"?, "newPassword", "confirmPassword" }。在已解锁状态下更改应用密码,并使用新密码重新加密账户数据(含 TOTP 密钥)。
- 应用密码以盐+哈希形式存于 KV(
2fa:config),不存明文。 - 新安装:账户数据(含 TOTP 密钥)经 AES-256-GCM 加密后存于
2fa:accounts_enc,密钥由应用密码派生,KV 中不出现明文密钥。 - 旧安装:未重做设置时仍使用明文
2fa:accounts,依赖 HTTPS 与访问控制。 - 为实现「7 天内免输密码」,应用会在浏览器本地(
localStorage)缓存加密前的应用密码与过期时间,仅在同一浏览器使用;不建议在公共电脑或不受信任环境中勾留该缓存,可通过浏览器清除站点数据或在页面上点击更改密码/关闭浏览器来清除。
MIT