feat: sign auth cookies with expiry, opt-in login rate limiting, login UX fixes - #221
Open
libertydragonn wants to merge 4 commits into
Open
feat: sign auth cookies with expiry, opt-in login rate limiting, login UX fixes#221libertydragonn wants to merge 4 commits into
libertydragonn wants to merge 4 commits into
Conversation
- Add src/lib/password.ts: scrypt hashing (N=16384,r=8,p=1), constant-time verification, strict stored-hash detection, and normalize-on-write. - redis/kvrocks, upstash, and memory stores now hash on register/change and re-hash legacy plaintext on the next successful login. - Add password storage format tests. Migration note: verifying a legacy plaintext password rewrites it to a scrypt hash in place. This is a one-way upgrade — if the deployment is later rolled back to a build that compares plaintext, those already- migrated users can no longer log in until their password is reset. See the PR description for details.
…x login UX Auth cookie signing: - Shared auth-signature module signs username:role:iat:exp, preferring a dedicated AUTH_SECRET (NEXTAUTH_SECRET fallback) over PASSWORD; expired or tampered cookies are rejected server-side and rotating the secret invalidates all sessions. - verifyApiAuth and middleware verify the signed expiry; legacy cookies without iat/exp are rejected (one-time re-login after deploy). - Split the readable auth_meta cookie from the HttpOnly auth cookie. Login rate limiting (hardened against self-lockout): - Per client-IP+account (5/15min) and per-IP (15/15min) only. Buckets are keyed on the real client IP so a limited client can never lock out the operator on the same account. - When no client IP is forwarded (direct LAN/Docker exposure) rate limiting is disabled for that request instead of collapsing every client into one bucket — this avoids the 429 lockout that looked like a broken login in the earlier merge. Login UX fixes (root causes of the earlier "login stuck / wrong password" reports): - Login page now distinguishes 429 (too many attempts, shows wait time) and surfaces the server's specific 401 reason instead of always saying the password is wrong. - NavbarGate always hides the navbar on /login and /register; it no longer renders a logged-in navbar when a stale readable cookie lingers after an upgrade. - await all now-async verifyApiAuth callers. Depends on the password-hashing PR. Verified with a real Docker kvrocks deployment: owner and member login, legacy-cookie re-login, logout and re-login, and page refresh all behave correctly.
Behind Docker/reverse proxies every request is observed with the same gateway/proxy IP, so an always-on limiter collapses all clients into one bucket and a few failed attempts (including those a stale post-upgrade session produces) lock the whole instance out with 429s — a self-inflicted login outage. Rate limiting is now disabled unless LOGIN_RATE_LIMIT=true, so login can never be throttled out of the box; operators whose proxy forwards the real client IP can opt in. When enabled, buckets stay keyed on the client IP so a limited client cannot lock out the operator.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景
重做系列的核心 auth 加固,依赖 #217(密码哈希),在其合并前 diff 会包含那一支的 commit。这个 PR 同时修复了上次合并→回退过程中「登录页状态异常 / 正确密码报错」的三个根因(见下)。
上次登录回归的根因分析(本地已复现并验证修复)
LOGIN_RATE_LIMIT=true显式开启,且仅在反代转发真实客户端 IP 时建议开启);开启时按客户端 IP 分桶,被限的客户端永远不会连累站长。改动
auth-signature模块:HMAC 签名内容为username:role:iat:exp,优先使用独立AUTH_SECRET(NEXTAUTH_SECRET次之,未配置回退PASSWORD,零配置升级)。过期或被篡改的 cookie 服务端直接拒绝;轮换密钥可一次性注销所有会话。authcookie 改为 HttpOnly,新增可读的auth_meta(仅 username/role)供前端展示。verifyApiAuth与 middleware 验证签名过期;旧格式 cookie 会被拒绝——升级后所有用户需重新登录一次(一次性,验证过恢复流程正常)。Retry-After。AUTH_SECRET与LOGIN_RATE_LIMIT。验证(真实 Docker 部署,kvrocks)
用本分支镜像
docker build+docker run(kvrocks compose 等价配置)实测:/api/favorites等受保护 API 200Set-Cookie: auth=...; HttpOnly; SameSite=lax,auth_meta可读LOGIN_RATE_LIMIT=true时:同一转发 IP 5 次失败后 429(含 Retry-After),换一个客户端 IP 用正确密码立即 200(站长不会被攻击者连累)exp的 cookie 被 middleware 拒绝(307 → /login)jest --runInBand22 套件 / 122 测试通过;pnpm typecheck通过升级须知
合并部署后,所有已登录用户的旧 cookie 失效,需要重新登录一次(预期行为,登录页会正常引导)。建议同时设置
AUTH_SECRET(openssl rand -base64 32)。