项目地址:项目地址:https://github.com/nap0o/2fauth-worker
看完了 features/auth/providers/* 和前端主要几块(登录、回调、vault 列表/新增、request 工具等),帮你把“明显能看出来的坑”列一下。
1. OAuth provider 代码:整体设计是靠谱的
流程大致是:
- 前端
/login:- 请求
GET /api/oauth/providers拿到 provider 列表(里边有 id/name/icon/color)。 - 点击按钮 →
GET /api/oauth/authorize/:provider拿回{ authUrl, state, codeVerifier }。 - 前端把
state/provider/codeVerifier存到localStorage,然后window.location.href = authUrl。
- 请求
- 回调页
/oauth/callback:- 从 URL 取
code/state/hash等。 - 根据路由参数/
hash/缓存判断 providerId。 - 前端自己做一遍 state 校验:
localStorage.oauth_state === route.query.state,否则报“State 校验失败,请求可能被篡改”。
- 然后把
{ code, state, codeVerifier }或 Telegram 的 query 全量 JSON POST 到/api/oauth/callback/:provider。
- 从 URL 取
- 后端
AuthService.handleOAuthCallback:- 通过
getOAuthProvider()找到对应 provider。 - provider.handleCallback 里:
- GitHub / Google / Gitee / NodeLoc:标准 OAuth2 Code → Token → UserInfo;
- CloudflareAccess:使用 PKCE(code_challenge=S256 + code_verifier);
- Telegram:严格按官方规范校验
hash(HMAC-SHA256(bot token))和auth_date(24h 内)。
- 做一次白名单检查(前面已经说过)。
- 用
generateSecureJWT签发自己的 JWT,放进 httpOnly Cookieauth_token。 - 生成
deviceKey(基于userId + "device_salt_offline_key"的 HMAC),返回给前端给离线 vault 加密用。
- 通过
这些 provider 本身没有明显漏洞:
- 都是固定 URL + 后端 env 里的 clientId / secret / redirectUri;
- 没有把任何用户输入直接拼到 token/用户信息请求 URL 里;
- Telegram 的签名+时间窗口验证是按官方推荐做的;
- CloudflareAccess 用了 PKCE,codeVerifier 从前端带回来。
需要注意/改进的点:OAuth state 只在前端校验
- 现在 “state” 只在前端做校验,后端完全没看 state:
/api/oauth/authorize只是返回 state;/api/oauth/callback/:provider里用的是body.code,没有任何 state 验证逻辑。
这在当前架构下不算立刻致命:
/api/oauth/callback/*只能从同源 JS 发 JSON 请求(fetch+Content-Type: application/json),- 恶意站点跑在别的域名上时,浏览器会挡住跨域 fetch;
- 纯表单 POST 没法伪造 JSON body,所以 CSP + CORS 配合下,“第三方网站替你调用 callback 去登陆别人的账号”比较难。
但从安全审计角度,这是一个比较明显的“缺口”:
- 推荐在后端也把 state 串起来做一次验证,例如:
- 把 state 放在 httpOnly Cookie 里(或 server-side KV store);
- callback 时检查
cookie_state == body.state; - 确保就算前端被绕过,后端也不会接受没有配对 state 的授权码。
结论:不是立即爆炸级漏洞,但属于“安全 posture 不完美”,建议作者后续补。
2. Provider 里是否有明显坑?
逐个看了一遍:
- GitHubProvider / GoogleProvider / GiteeProvider / NodeLocProvider:
- 使用固定的 token / userinfo endpoint;
- 用的是 POST + x-www-form-urlencoded,没把 code/secret 拼到 URL 里暴露;
- token 不写入任何日志,只在内存里用一次;
- userInfo 中只取 id / username / email / avatar。
- 没有用到 provider 返回的任何 HTML 片段,也没有做
v-html之类。
- CloudflareAccessProvider:
getAuthorizeUrl加了 PKCE(code_challenge=S256);- 回调时必须带
code_verifier,否则 CF 按理会拒绝; - 错误时会
console.errortokenResponse 文本,但没有把 accessToken 打到日志; - 用户字段直接用 OIDC userinfo,映射到 id/username/email,没额外拼接。
- TelegramProvider:
- 严格按官方 Login Widget 标准:
dataCheckString用固定allowedKeys排序;- secret key = SHA256(bot token);
- 用 HMAC-SHA256 验证 hash;
- 检查
auth_date不超过 24 小时;
- 没有对 Telegram 参数做任何
eval/HTML 注入,直接映射为 id/username/email。
- 严格按官方 Login Widget 标准:
总的来说:providers 这块没有找到明显逻辑漏洞或者注入点,主要问题还是前面说的“state 只在前端校验”。
3. 前端安全点:有没有明显 XSS / CSRF / 信息泄露
我重点看了这些文件:
frontend/src/main.jsfrontend/src/app/app.vuefrontend/src/app/router.jsfrontend/src/features/auth/views/login.vuefrontend/src/features/auth/views/oauthCallback.vuefrontend/src/features/vault/views/home.vuefrontend/src/features/vault/components/vaultList.vuefrontend/src/features/vault/components/addVaultManual.vuefrontend/src/shared/utils/request.jsfrontend/src/shared/utils/crypto.jsfrontend/src/shared/utils/totp.jsfrontend/src/features/auth/store/userStore.jsfrontend/src/features/vault/store/vaultStore.js
3.1 XSS 相关
唯一使用 v-html 的地方:
<!-- login.vue -->
<el-icon v-if="provider.icon">
<span v-html="provider.icon" style="display: flex;"></span>
</el-icon>
provider.icon来源:GET /api/oauth/providers;- 后端
getAvailableProviders里 icon 全是硬编码在 provider 类里的 SVG 字符串,不是从 env 或用户输入来的; - 只要你自己不改代码去从配置读 SVG,这个
v-html就不会成为 XSS 入口。
其它用户可控内容(服务名称、账号、分类、防备份文件名字等)在 UI 全部是 {{ ... }} 文本插值,没有找到 v-html、innerHTML 之类。
登录/回调页的错误消息:
oauthCallback.vue:<el-result v-else icon="error" title="授权失败" :sub-title="errorMsg" >errorMsg来自 URL 的error/error_description或自定义文字,但这里只是普通绑定,不是 v-html,不会执行脚本。
综合看下来:
- 没有明显的 XSS 点;
- 真正的风险还是那句:一旦前端未来改版时引入新的
v-html或第三方脚本,整套系统因为掌握所有 TOTP secret,会马上变成高危。
3.2 CSRF 相关
- 所有 API 请求通过
request(url, options):- 会从 cookie 读
csrf_token,自动在 header 加X-CSRF-Token; credentials: 'include',带上同站 cookie;- 后端
authMiddleware要求:auth_token存在;csrf_cookie === X-CSRF-Token,否则 403;
- 敏感路由(vault、backup 等)都挂了
authMiddleware。
- 会从 cookie 读
从前后端配合来看:CSRF 防护是到位的,除非你在别的地方再开不带 authMiddleware 的危险写接口。
3.3 离线 vault、本地加密
vaultStore.js:- 从
IndexedDB拿device_salt(就是后端返回的 deviceKey); secure_vault存在 localStorage,但内容通过encryptDataWithPassword()加密:- PBKDF2-SHA256 + 100000 iterations;
- AES-GCM,salt + iv + ciphertext 打包;
- 你不拿到 deviceKey(它正常保存在浏览器 IDB 里)的话,dump localStorage 也只能得到密文。
- 从
这个算是设计上的加分项,不是漏洞。
4. 这里再帮你收个“明显风险点清单”
结合前后端,现在比较「值得你在意」的点是:
- OAuth state 只在前端校验(后端没校验)
- 当前环境下不至于轻易被利用,但从 best practice 来看,是个缺口;
- 建议:给作者提 issue,把 state 也在后端绑定到 cookie 或 KV,然后 callback 时验证。
- 白名单配置的“魔法值”逻辑(之前说过)
OAUTH_ALLOWED_USERS === JWT_SECRET时白名单失效;- 你自己部署时,记得:
OAUTH_ALLOWED_USERS写明确的 email/username 列表;- 不要乱用“等于 JWT_SECRET”这类隐式开关。
- ENCRYPTION_KEY 和 JWT_SECRET 的复用
- 现在 ENCRYPTION_KEY 不配就回退到 JWT_SECRET;
- 意味着:
- 一把钥匙可以既解密 TOTP secret,又伪造 JWT;
- 部署时务必单独配置一个高熵 ENCRYPTION_KEY(与 JWT_SECRET 不同)。
- OAuth redirectUri 的配置安全性
- GitHub/Google/Gitee/NodeLoc/Cloudflare 的 redirectUri 都来自 env:
- 如果你把 redirectUri 配成了攻击者控制的域名,授权码和 accessToken 会直接被对方拿走;
- 所以部署时要确认:
- redirectUri = 你自己 Worker 的 https 地址;
- 不要给任何“测试域”“外部服务”共享。
- GitHub/Google/Gitee/NodeLoc/Cloudflare 的 redirectUri 都来自 env:
- 整个系统的“攻击价值”非常高
- 这一点也不是代码 bug,而是定位问题:
- 只要 Cloudflare 账号、Worker 环境变量、前端出现 XSS 中任何一处被攻破,你所有 TOTP secret 都要视为泄露;
- 对标密码管理器:使用/运维时要拉到那个标准去看待。
- 这一点也不是代码 bug,而是定位问题:
总体评价
features/auth/providers/*和前端登录/回调/Vault 相关代码,没有看到那种“一眼决定不要用”的明显漏洞。- 最大的可以挑出来的点:
- OAuth state 没在 server 端验证(建议改进);
- 几个配置项的“魔法用法”(白名单关闭条件、ENCRYPTION_KEY 回退 JWT_SECRET)容易被误配置踩坑。
正文完