Skip to content

feat(auth): shared cross-node rate-limit + session store via cache service (ADR-0069 D2) - #2572

Merged
os-zhuang merged 3 commits into
mainfrom
claude/adr-0069-p2-shared-ratelimit
Jul 4, 2026
Merged

feat(auth): shared cross-node rate-limit + session store via cache service (ADR-0069 D2)#2572
os-zhuang merged 3 commits into
mainfrom
claude/adr-0069-p2-shared-ratelimit

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

概述

补上 ADR-0069 D2 的多节点缺口(P2)。这是你选定的部署形态(多节点 + 应用层限流)对应的那一项。

问题:better-auth 默认 rateLimit store 是进程内内存。多节点部署下每个节点各算各的计数,攻击者轮流打不同节点就能绕过限流——一个真实的多节点安全洞。

设计选择(评估后)

评估发现:runtime 里那套同步 RateLimiter(security/rate-limit.ts)根本没有调用点(死代码),实际 auth 限流走的是 better-auth 原生 rateLimit。better-auth 支持 storage: 'secondary-storage' + secondaryStorage(KV)。框架 cache service 已有 Redis adapter

选择:把 better-auth secondaryStorage 接到框架 cache service(而非新增 DB 对象/表或硬接 Redis):

  • 复用已有 cache service + Redis adapter,不新增依赖/对象/schema。
  • 多节点配 Redis cache(你的形态)→ 限流计数器 + session cache 自动跨节点共享
  • 单机内存 cache → 行为不变,无回归。
  • 无 cache service 时打 warn(ADR-0049 诚实:不把「进程内限流」伪装成全局)。

变更

  • 新增 cacheSecondaryStorage(cache) —— ICacheService → better-auth SecondaryStorage(getundefinednull;set 转发 TTL;delete)。
  • AuthManager:接受 secondaryStorage;存在时注入 betterAuth config 并把 rateLimit.storage 翻成 'secondary-storage'(有无显式 rateLimit 块都工作)。
  • AuthPlugin:cache service 存在时绑为 secondaryStorage;不存在时 warn。

原子性说明(代码 + changeset 已诚实标注):ICacheService 无原子自增,高并发下 get→set 计数路径可能轻微超计——对限流可接受,且严格优于「各节点独立计数」;未来 cache adapter 暴露 INCR 可加 increment 方法做精确计数。

验证

  • 6 个单测:adapter round-trip / miss→null / TTL / delete;auth-manager 在有/无显式 rateLimit 块时都把 storage 翻成 secondary-storage。
  • plugin-auth 6 文件全绿;全量 build 通过。

文档协调

本 PR 刻意不碰 ADR-0069(状态矩阵在评审中的 #2570 里)以避免冲突;两者合并后,ADR-0069 实现矩阵里「remaining P2: shared-store rate limiting」这条应移除——我会在后合并的那个 PR rebase 时顺手改掉。

剩余 P2(已开 issue,不在本 PR)

关联

🤖 Generated with Claude Code

https://claude.ai/code/session_014y5kiH3aPLWtRRRGcVrXcT


Generated by Claude Code

@vercel

vercelBot commented Jul 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJul 4, 2026 10:30am

Request Review

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling size/m labels Jul 4, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-auth.

10 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/concepts/implementation-status.mdx(via @objectstack/plugin-auth)
  • content/docs/concepts/packages.mdx(via @objectstack/plugin-auth)
  • content/docs/getting-started/cli.mdx(via @objectstack/plugin-auth)
  • content/docs/guides/auth-sso.mdx(via @objectstack/plugin-auth)
  • content/docs/guides/authentication.mdx(via @objectstack/plugin-auth)
  • content/docs/guides/kernel-services.mdx(via @objectstack/plugin-auth)
  • content/docs/guides/packages.mdx(via @objectstack/plugin-auth)
  • content/docs/guides/plugins.mdx(via @objectstack/plugin-auth)
  • content/docs/guides/production-readiness.mdx(via @objectstack/plugin-auth)
  • content/docs/releases/v9.mdx(via @objectstack/plugin-auth)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

claude added 3 commits July 4, 2026 10:25
…rvice (ADR-0069 D2)
Multi-node deployments rate-limited per-process — better-auth's default
rateLimit store is in-memory, so each node counted independently and an
attacker could rotate nodes to bypass the limit.
- new cacheSecondaryStorage(cache): ICacheService → better-auth
SecondaryStorage (get maps undefined→null; set forwards TTL; delete).
- AuthManager: accept secondaryStorage; when present, wire it into the
betterAuth config AND flip rateLimit.storage to 'secondary-storage' so
counters go to the shared store (works with or without an explicit
rateLimit config block).
- AuthPlugin: bind the kernel `cache` service as secondaryStorage when
registered (shared across nodes iff the cache is — Redis adapter in a
cluster, memory single-node). Logs a warning when no cache service is
present so a multi-node deploy isn't silently per-process (ADR-0049
honesty).
Atomicity note (in code + changeset): ICacheService has no atomic
increment, so the get→set counter path can slightly over-count under high
concurrency — acceptable for a rate limiter and strictly better than
independent per-node counters; a future cache adapter exposing INCR can
add an increment method for exact counting.
Tests: adapter round-trip / miss→null / TTL / delete; auth-manager flips
rateLimit.storage to secondary-storage (with and without an explicit
rateLimit block). plugin-auth 6 files green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014y5kiH3aPLWtRRRGcVrXcT
…tered)
The kernel registers `cache` as an ASYNC service, so the sync
`ctx.getService('cache')` throws "Service 'cache' is async - use await"
at AuthPlugin.init — breaking bootstrap (caught by the dogfood gate,
which boots the real kernel; unit tests don't). Resolve via
`getServiceAsync` wrapped in try/catch so an absent/not-ready cache
falls to the per-process warning path instead of crashing boot.
Verified: dogfood 36 files / 181 tests green; plugin-auth units green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014y5kiH3aPLWtRRRGcVrXcT
)
Reconcile the ADR-0069 implementation matrix now that the shared
secondaryStorage-backed rate-limit store has landed in this PR — moves it
from 'Remaining P2' to 'landed', leaving only per-org IP ranges (#2571)
as the remaining P2 gap.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014y5kiH3aPLWtRRRGcVrXcT
@os-zhuang
os-zhuangforce-pushed the claude/adr-0069-p2-shared-ratelimit branch from 07bfd0b to a8f57a4CompareJuly 4, 2026 10:27
@os-zhuang
os-zhuang marked this pull request as ready for review July 4, 2026 10:39
@os-zhuang
os-zhuang merged commit 1b1b34e into mainJul 4, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the claude/adr-0069-p2-shared-ratelimit branch July 4, 2026 10:39
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-zhuang@claude