@broberg/apikey

中文文档
0.3.1发布于 2026/08/07MITTypeScriptESM + CJS零依赖构建来源已验证近 30 天下载 7472

Broberg.ai车队的框架不可知的 API- key 原始: 薄荷前缀密钥, 时间安全验证( hashed 或 plaintext), 滑动窗率限制于可插件商店, Cloudflare 式的授权级联( permission × 资源过滤器)

中文使用文档
文档目录9 节
  1. 核心项目 @broberg/apikey (中文(简体) )
  2. 速率限制——可插件存储
  3. 授权级联 @broberg/apikey/authorize (中文(简体) )
  4. 租户选择者(铁路选择者-非赠与者)
  5. 适配器
  6. 你的错误合同,不是我们的(v0.2.0)
  7. 每条路线的上限不同,1个限制(v0.3.0)
  8. 您的处理器( v0. 3. 0)
  9. 边界(它故意不做的事)

@broberg/ apikey (英语)

框架不可知性 输入 API- key 原始 为了兄弟会的舰队 它拥有危险到错误的位点——薄荷,常时核查,限速,和云浮式的授权级联——并离开 存储、租赁和请求文本解决方案带上你自己的 lookup。 。 。 .

由9-repo分舰队勘测(trail ^ carmem → cms → upmethers → vn)所设计:包裹从不强迫散发,租借模式,固定前缀,或限速后端.

npm i @broberg/apikey      # exact-pin for prod-auth deps

核心项目@broberg/apikey(中文(简体) )

import { generateKey, hashKey, verifyKey, makeKeyPreview, hasScope } from "@broberg/apikey";

const raw = generateKey("trail");           // "trail_<64 hex>"  — show ONCE
const stored = hashKey(raw);                // sha256 — store this (hash-at-rest)
const preview = makeKeyPreview(raw);         // "trail_0a1b2c3d" — display/grep anchor

// On each request — YOU do the DB read; the package does the constant-time compare:
verifyKey(presented, stored);               // hashed (default): timingSafeEqual(sha256(presented), stored)
verifyKey(presented, stored, { hashed: false }); // plaintext-revealable (upmetrics-style)

hasScope(["content:*"], ["content:write"]); // true — exact / `*` / `area:*`

timingSafeEqual(a, b) 也导出 a !== b 代币支票.

速率限制——可插件存储

默认内相(单机). 对a 无国籍的多机 车队,通过一个共享商店,这样窗口就不会每台机器漏出:

import { SlidingWindowRateLimiter, type RateLimitStore } from "@broberg/apikey";

const limiter = new SlidingWindowRateLimiter({ windowMs: 60_000, max: 100 });
const { allowed, remaining, resetAt } = await limiter.check(clientKey);

// One limiter, per-key caps — pass a per-check `max` override (v0.1.1):
await limiter.check(clientKey, { max: keyRecord.rateLimitPerHour });

// Shared backend (Turso/Redis): implement one method.
const turso: RateLimitStore = {
  async hit(key, now, windowMs) { /* … */ return { count, oldest }; },
};
new SlidingWindowRateLimiter({ windowMs: 60_000, max: 100, store: turso });

授权级联@broberg/apikey/authorize(中文(简体) )

可选的丰富级——允许相×资源-过滤相×CIDR相×TTL(型号为cms F134). 简单领养者跳过此选项并使用 hasScope。 。 。 .

import { evaluateToken, type TokenGrant } from "@broberg/apikey/authorize";

const grant: TokenGrant = {
  permissions: ["deploy:trigger"],
  resources: [{ scope: "site", effect: "include", targets: ["fysiodk"] }],
  ipFilters: [{ mode: "in", cidrs: ["203.0.113.0/24"] }],
  notBefore: Date.parse("2026-01-01"),
  notAfter: Date.parse("2027-01-01"),
};

const decision = evaluateToken(grant, {
  permission: "deploy:trigger",
  resource: { scope: "site", target: "fysiodk" },
  ip: "203.0.113.5",
});
// → { allowed: true } | { allowed: false, reason: "expired" | "permission_denied" | "resource_denied" | "ip_denied" }

连带命令: TTL ~ 权限 ~ 资源 (排除胜负+ CIDR(IPv4 + IPv6,0-dep). 没有过滤器的瞄准镜不受限制 .

租户选择者(铁路选择者-非赠与者)

import { selectTenant, TenantAccessError } from "@broberg/apikey/authorize";

// A `spansAll` key lets the owner pick any tenant they belong to via a header.
// A non-member slug is a HARD refuse — never a silent fall-back to home.
try {
  const tenant = selectTenant({ requestedSlug, homeTenant, spansAll: true, isMember });
} catch (e) {
  if (e instanceof TenantAccessError) return new Response(null, { status: 401 });
}

适配器

// Stack B — Hono
import { honoApiKeyMiddleware, honoRateLimit } from "@broberg/apikey/hono";
app.use("/api/*", honoApiKeyMiddleware({ lookup, authorize }));   // 401/403; c.get("apiKey")
app.use("/api/*", honoRateLimit(limiter));                         // 429 + Retry-After

// Stack A — Next.js (Web-standard Request/Response, edge-safe, no `next` dep)
import { withApiKeyAuth, nextRateLimit } from "@broberg/apikey/next";
export const POST = withApiKeyAuth(async (req, record) => Response.json({ ok: true }), { lookup });

你的错误合同,不是我们的(v0.2.0)

默认机构是 { error: "missing_api_key" } ——a. 国家 字符串时 API回答其他问题(如:. { error: { code, message } },该机器 客户端可以切换, 让它自己而不是重写中间软件 :

app.use("/api/*", honoApiKeyMiddleware({
  lookup,
  authorize,
  onUnauthorized: (c, reason) =>            // reason: "missing" | "invalid"
    c.json({ error: { code: `unauthorized_${reason}` } }, 401),
  onForbidden: (c, record) =>
    c.json({ error: { code: "forbidden", at: record.id } }, 403),
}));

app.use("/api/*", honoRateLimit(limiter, keyFn, {
  onLimited: (c, r) => c.json({ error: { code: "rate_limited", retryAt: r.resetAt } }, 429),
}));

中间的软件仍然决定 什麽 发生了;只有钩子决定它是如何 写下来。 状态代码和 X-RateLimit-Remaining 编号 : Retry-After 标题不变。 解开钩子和反应 字节相同 (原始内容存档于2013-10-01). v. 1.1.

reason 值得采取行动的是: 401 (中文(简体) ) 表示 "我不知道你是谁" (无) 指使,未知指使,刚被撤销),通过取出指使来固定; 403 (韩语) 表示 "我知道你到底是谁, 这不是你的" 由 请求不同的角色。 反应是唯一能告诉 打电话的.

限速桶键. 没有 keyFn,在 x-forwarded-for后退为 "unknown" 当信头缺席时。 在一个 在前面没有代理的回放服务意味着 每一个呼叫者份额 一个桶 - 通过一个 keyFn 代之以代号/记录id上的密钥.

每条路线的上限不同,1个限制(v0.3.0)

keyFn 可能返回 { key, max } 而不是一个赤裸的字符串,所以一个单一的限定器 每个路由执行不同的上限——您不需要每个上限的限制实例 :

app.use("/admin/*", honoRateLimit(limiter, (c) => ({ key: tokenId(c), max: 600 })));
app.use("/write/*", honoRateLimit(limiter, (c) => ({ key: tokenId(c), max: 300 })));

两套适配器 X-RateLimit-Limit 以及 X-RateLimit-Remaining —— 说 Limit 报告 有效 该请求的上限(每条路线) max 何时 ),因为你无法解释的剩余数并不多用. RateLimitResult.limit 直接驱动限制器时带相同的号码.

您的处理器( v0. 3. 0)

默认情况下 整体 查询记录是什么 c.set(contextKey, …) 存储 (Hono)和作为处理器的第二论点(Next)——包括: 无论你的存储行携带,例如 hash 栏。 大麻不是一个 可用证书,所以这不是证书泄露,但不止是 处理器需要,一个 c.json(caller) 后来它成为一个反应机构.

过 project 选择自己形状的呼叫者:

honoApiKeyMiddleware({ lookup, project: (r) => ({ id: r.id, role: r.role }) });
withApiKeyAuth(handler, { lookup, project: (r) => ({ id: r.id, role: r.role }) });

适配器不对称,明确声明: 页:1 onUnauthorized 编号 : onForbidden 编号 : onLimited 上面有钩子 浩诺 仅限适配器. 下一个适配器 仍然释放内置的字符串体。 将问题归档是正确之举,如果 你需要他们在那里.

如果你把自己的429个尸体 引用同样的话 result。 。 。 . 信头来了 从中间的软件 和身体来自你 onLimited,所以他们是 在两个地方生产。 打电话的人说 "每60个1" 在体内 X-RateLimit-Limit 说 600 无法判断该相信什么。 使用 result.limit 编号 : result.resetAt 在身体中,而不是一个常数 你持有 - 包不能为您断言这一点,所以值得测试 你那边.

Retry-After 下层为 1 (自v.3.1起):与一个共享远程商店,该商店 绕行超过剩余窗口, 原始计算可能为负数 发射 Retry-After: 0 - 让客户重新尝试 马上 还在 有限.

lookup(presented) => record | null 是您的: 散列 + DB/ 文件系统读取, 您的存储, 您的租赁 。 包裹没看到你的店.

边界(它故意不做的事)

  • 无存储 – 没有 DB/ CRUD 层; 您拥有 schema( Drizzle / libSQL / JSON) .
  • 无要求-租户解决 - 这是你的代理/路由器;将结果输入 selectTenant。 。 。 .
  • 无捆绑的 Redis/ Turso - 船舶 RateLimitStore 接口 + 仅内存.
  • 核心加密为节点/ Bun (单位:千美元)node:crypto) (中文(简体) ). 在边缘处,通过网络加密的散列 lookup;适配器本身是边缘安全.

麻省理工学院的一部分 broberg.ai 共享库存。 。 。 .

版本与发布记录

@broberg/apikey 共发布 5 个版本,首次发布于 2026/06/16;过去 12 个月发布了 5 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.3.1latest2026/08/073 个月前
0.3.02026/08/073 个月前
0.2.02026/08/073 个月前
0.1.12026/06/164 个月前
0.1.02026/06/164 个月前

关于 @broberg/apikey 的常见问题

@broberg/apikey 是什么?

Broberg.ai车队的框架不可知的 API- key 原始: 薄荷前缀密钥, 时间安全验证( hashed 或 plaintext), 滑动窗率限制于可插件商店, Cloudflare 式的授权级联( permission × 资源过滤器)

如何安装 @broberg/apikey?

在项目目录运行 npm install @broberg/apikey,或使用 pnpm add / yarn add / bun add。

@broberg/apikey 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@broberg/apikey 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@broberg/apikey 可以商用吗?

@broberg/apikey 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@broberg/apikey 的下载量有多少?

根据 npm 官方下载统计,@broberg/apikey 在 2026/10/09 – 2026/11/07 这 30 天内被下载 7,472 次,日均约 249 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@broberg/apikey 还在维护吗?

最新版本 0.3.1 发布于 2026/08/07(3 个月前);至今共发布 5 个版本,其中过去 12 个月发布了 5 个;首次发布于 2026/06/16。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。