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

vercel Bot commented Jul 4, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 4, 2026 10:30am

Request Review

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

github-actions Bot commented Jul 4, 2026

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-zhuang force-pushed the claude/adr-0069-p2-shared-ratelimit branch from 07bfd0b to a8f57a4 Compare July 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 main Jul 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 free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants