@broberg/ 连
做任何应用 符合卡通要求 几行:暴露出舰队标准 薄荷终点 这样一来Lens就能登录 通过认证墙 并截图 真实 认证表面 ——包括生产中——而不是登录页.
npm i @broberg/lens
这是什么
纸牌 镜头 验证表面用户实际看到, 这几乎总是 登录后镜头不能硬编码每个应用程序的认证,所以每个应用程序都暴露一个 薄荷a的终点 寿命短,只读 按需开会; Lens 称之为 在捕捉之前,使用会话,然后丢弃它.
合同在每次回购中都是相同的——只有 如何薄荷会话 不一样.
所以这个包裹 装上制服,安全敏感 ~80% 作为无头核;
您提供了特定认证 20% 联合国 createLensSession 钩上薄荷和标志
自己的会话饼干.
执行机队F098.1 薄荷标准(卡通)
docs/LENS-MINT-ENDPOINT.md) (中文(简体) ).components拥有 + 发布; cardem 拥有 spec.
合同
POST /api/lens-session标题 Authorization: Bearer <LENS_MINT_SECRET> →
200个 有剧作家 存储状态 JSON Lens守护进程逐字注射:
{ "cookies": [ { "name": "<session-cookie>", "value": "<signed>", "domain": "<host>",
"path": "/", "httpOnly": true, "secure": true, "sameSite": "Lax",
"expires": 1733430000 } ],
"origins": [] }
expires 实值 unix 秒。核心将填补每个字段,但 name编号 :value。 。 。 .
Next.js 16 (Stack A) (英语)
app/api/lens-session/route.ts数字 :
import { createLensRoute } from "@broberg/lens/next";
import { signLensCookie } from "@/lib/auth"; // your app's signing
export const { POST } = createLensRoute({
principal: "[email protected]", // dedicated, read-only — NEVER [email protected]
async createSession({ principal, expiresAt }) {
const value = await signLensCookie(principal, expiresAt);
return { name: "myapp.session_token", value };
},
});
霍诺(B级)
import { Hono } from "hono";
import { lensSessionHandler } from "@broberg/lens/hono";
const app = new Hono();
app.post("/api/lens-session", lensSessionHandler({
principal: "[email protected]",
async createSession({ principal, expiresAt }) {
return { name: "myapp_session", value: await mintSession(principal, expiresAt) };
},
}));
任何其他框架
直接用普通请求调用核心处理器 :
import { createLensMintHandler } from "@broberg/lens";
const handle = createLensMintHandler({ principal, createSession });
const res = await handle({ authorization, host, secure }); // → { status, body }
计算会话( 您写的部分)
⚠️ 发布一个真实的会话 cookie , 您自己接受 - 同一个你 SPA的认证门检查( P)
getSession()/你的JWT中间软件。 合成符号 可以认证 API 路由( 因此)curl似乎绿色),但SPA仍然反弹到 登录,而Lens抓取一个登录墙. 通过您框架的真实会话薄荷 机制.
- 更好的认证 : 饼干是签名的(
<token>.<sig>) (中文(简体) ). 通过auth.$context → internalAdapter.createSession(userId, ctx)时,将其过期限制为expiresAt,然后序列化签名的饼干。 饼干名=<prefix>.session_token。 。 。 . - 苏帕克: 薄荷服务器侧面, 有专用镜头的服务作用键
用户; 返回
sb-<ref>-auth-token饼干@supabase/ssr读取. - 自定义JWT(吉斯/HS256): 为镜头签出短寿命只读 JWT 校长; 还原为您的会话饼干 .
核心保障
- 暗舰:
503直至LENS_MINT_SECRET设置( 逐项阅读 —— 翻转) 打开而不重新启动. 401+ 常数 无记名比较( e)crypto.timingSafeEqual超过SHA-256 文摘——长度独立,从不掷出.- 从不cb@ : 以
principal: "[email protected]"(或空白) 校长) 掷出 ——镜头身份必须是专用的只读用户. - TTL 夹口 改为
[60s, 10min](违约10min);expiresAt交给你 钩子,所以你夹住你的会话行 到相同的TTL. - 基本费率限额 (违约30分)
429。 。 。 . - Cookie 域名 =:
cookieDomain ?? LENS_COOKIE_DOMAIN ?? request host(开口) 剥了,所以localhost:3000dev 构建生成一个有效的localhost(域名)—— 绝不是绑定的套接字地址( n)0.0.0.0在飞行/代用主机上),从不 端口范围值。 在一个 网页 解决当地宿主家庭的要求 没有明确覆盖的主机,核心console.warn大声点 - 后面一个 反向代理是一个沉默的假绿色( set)LENS_COOKIE_DOMAIN) (中文(简体) ). 真实 http localhost dev stay system, 所以一个Docker dev-building sandbox只是工作.
写会话——仅在要求时,明确(0.2.1,F036.6)
有些表面需要Lens来ACT,而不只是看(helpdesk:Lens playing the 接待员和现场聊天时写作). 卡梅的标准:薄荷门给 (a) 默认的READ本金 每张薄荷纸都要经过审核.
| 请求正文 | 结果 |
|---|---|
无/ 为空 / {} 编号 : {"mode":"read"} | 改为本金,完全为0.1.x |
{"mode":"write","writes":true} | 写入主笔, 如果应用程序允许写入 |
{"mode":"write"} • 妇女 {"writes":true} 未知模式 ^ 坏 JSON | 400,没有薄荷 |
写入请求, 应用程序没有 writePrincipal | 403,没有薄荷 |
两种密钥, 目的: 一个字段是 一种字段, 或者默认不写 访问.
createLensRoute({
principal: "[email protected]", // read
writePrincipal: "[email protected]", // write: a SEPARATE identity
async onWriteMint({ principal, host, expiresAt, at }) {
await audit.insert({ kind: "lens.write-mint", principal, host, expiresAt, at });
},
async createSession({ principal, mode, expiresAt }) {
// mint a write-capable role ONLY when mode === "write"
const value = await signMySessionCookie(principal, expiresAt, { role: mode === "write" ? "reception" : "readonly" });
return { name: "myapp_session", value };
},
});
- 写字关了 直至
writePrincipal已经设定。 它必须与principal(审计必须告诉阅读 从写),它永远不会 [email protected] 页面存档备份,存于互联网档案馆。设置它不onWriteMint启动时抛出. - 审计未能结束.
onWriteMint运行后createSession财务报告和审定财务报表 在饼干还原之前 如果抛出,反应是500没有 cookie left; 薄荷行仅与 TTL (最大为 10 min) 相通而过期. ctx.mode(单位:千美元)"read"|"write")是新开的LensSessionContext密码 只有 READS 上下文不受影响; 代码为 BUILDS 1( 测试固定) 必须添加mode。 。 。 .
只读由您的应用程序执行
此包薄荷表示会话; 它确实 没有 从内部执行只读
终点 添加服务器侧 书写护卫: 如果经认证的本金是
镜头用户,拒绝每个突变请求(POST编号 :PUT编号 :PATCH编号 :DELETE + 写入
(RPC/工具)与 403。给镜头本金足够 读取 获取
目标表面(常读取行政级别)。 PII 表面, 捕获 no_diff
烟 -- -- 从未有存储像素基线.
运行在 节点运行时间 (核心用途)
node:crypto)——不是"下一个边缘" 运行时间。 Mint 端点击中一个 DB 来创建会话,所以节点是正确的.
测试-gaps——CI的F086检查(0.3.0,F036.7)
每个互动要素必须包含 data-testid (舰队的F086规则). 这个
Cardmem 守护进程在本地检查(POST /lens/testid-gaps); CI 无法达到
守护进程,所以同样的扫描仪船在这里:
npx -p @broberg/lens testid-gaps src # or: testid-gaps src, with the package installed
| 退出 | 表示 |
|---|---|
0 | 已扫描文件, 没有空白—— 打印它读取的文件数 |
1 | 差距——1个 file:line <tag> without data-testid … 每个差距 |
2 | 使用错误,或 无源文件扫描 (一条错误的道路不能通过一扇门) |
文件扫描仪携带处理器时无法查看(hyperscript,
h('button', { onClick }))在 stderr 上命名为 未审查他们不是
空白,它们也不干净.
在代码中 : import { inventoryInteractiveGaps, gapsInFile } from "@broberg/lens/testid-gaps" (只有节点).
规矩是卡梅伦的,逐字移动 manifest.ts @ cc3ad9e) 与
他们的测试。 何谓互动是在那里决定的;一个变化经过
所以守护进程和CI从来不会有分歧.
API 密码
interface LensCookie { name: string; value: string; domain?: string; path?: string;
httpOnly?: boolean; secure?: boolean; sameSite?: "Lax" | "Strict" | "None"; expires?: number; }
interface LensSessionContext { principal: string; mode: "read" | "write"; host: string; secure: boolean; ttlMs: number; expiresAt: number; }
type CreateLensSession = (ctx: LensSessionContext) => Promise<LensCookie | LensCookie[]> | LensCookie | LensCookie[];
interface LensMintOptions { secret?: string; createSession: CreateLensSession; principal: string;
ttlMs?: number; cookieDomain?: string; maxPerMinute?: number;
writePrincipal?: string; onWriteMint?: (e: { principal: string; host: string; expiresAt: number; at: number }) => void | Promise<void>; }
function createLensMintHandler(opts: LensMintOptions): (req: { authorization: string | null; host: string; secure: boolean; body?: string | null }) => Promise<{ status: number; body: unknown }>;
// @broberg/lens/next → createLensRoute(opts): { POST(req: Request): Promise<Response> }
// @broberg/lens/hono → lensSessionHandler(opts): (c: Context) => Promise<Response>
// @broberg/lens/next-auth → nextAuthLensSession(opts): (ctx: LensSessionContext) => Promise<LensCookie>
下一个 Auth 应用程序 - @broberg/lens/next-auth
一个现成的 createSession 钩子 下一个 Auth( v5) 应用,所以你不用手卷
jWT 编码 + 微妙的饼干 得到了每个下Auth-behind-login回波:
import { createLensMintHandler } from "@broberg/lens";
import { nextAuthLensSession } from "@broberg/lens/next-auth";
const handler = createLensMintHandler({
principal: "[email protected]",
createSession: nextAuthLensSession({
authSecret: process.env.AUTH_SECRET!,
claims: { id: "lens-user-id", email: "[email protected]", name: "Lens" },
// `secure` defaults to NODE_ENV === "production" (NextAuth's useSecureCookies)
}),
});
next-auth 是一个 可选同伴依赖 (进口懒散;通过习惯)
encode 为了避免它。 帮手抓了3个经过战斗测试的人 所以你不能:
salt必须等于饼干名称 下一个 AuthgetToken生成 JWE 键 与salt = cookie name; 不匹配的无声解码为null。 。 。 .__Secure-前缀 qqsecure: true安全模式驱动 cookie 名称 (单位:千美元)__Secure-authjs.session-token对authjs.session-token盐,和 安全旗帜,一起.- 铬拒绝安全饼干
{domain}一个人 ——卡德明连锁店 守护进程合成一个 https source- URL 这样{domain, path}全舰作业 非守护天洞,设置url而不是饼干.
麻省理工学院的一部分 @broberg/* 共享-图书馆家庭.