# @krizaka/auth 中文文档

规范地址：https://npmcn.com/package/%40krizaka/auth/

版本：0\.13\.2
npm 元数据更新时间：2026-11-01T14:49:36.163Z
中文译文更新时间：2026-11-01T16:49:52.924Z
软件包许可：Apache-2\.0

本文由上游 README 机器翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40krizaka/auth
原文对照：https://npmcn.com/package/%40krizaka/auth/?lang=original
项目主页：https://github.com/krizaka/krizaka-ts/tree/main/packages/auth
源码仓库：https://github.com/krizaka/krizaka-ts

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：257 kB，28 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- Node.js 要求：\>=22
- 运行依赖：3 个
- peer 依赖：zod \>=3\.23，vitest \>=4
- 首次发布：2026-10-22T19:07:05.263Z
- 最新版本发布：2026-11-01T14:49:35.772Z
- 已发布版本数：19（近 12 个月 19 个）

## 中文简介

Krizaka产品的帐户-名称，语言，主题，头像，通过带有验证标志的链接更改电子邮件，密码，与其他用户签出的会话，数据导出，删除-在端口后面，使用PostgreSQL适配器及其迁移。每一个名单

## 安装

```sh
npm install @krizaka/auth
```

也可以使用：`pnpm add @krizaka/auth`、`yarn add @krizaka/auth`、`bun add @krizaka/auth`

## README

# @krizaka /身份验证

一个Krizaka产品的账户，在一个端口后面：显示名称、语言、主题和头像种子；电子邮件更改通过链接发送到 **新** 地址（验证标志是由该行为设置的）；密码（在任何敏感内容之前检查，其他会话在更改时退出；通过发送到帐户地址的链接重置-无论该地址是否有帐户，都是相同的答案，默认情况下30分钟内使用一次链接） `passwordResetTtlMinutes`（5至1440，由产品从其设置中读取并在邮件中说-每次会话都签出）；其他人签到的现场会议；数据导出；删除。每种产品都保持其 **自己的** accounts——这个包在产品的PostgreSQL和它需要的迁移上提供了一个适配器，没有什么核心。

```ts
import { emitMigration } from "@krizaka/db";
import { accountsSchema, dbAccounts } from "@krizaka/auth";

emitMigration(accountsSchema({ backfillVerified: true }), 1, { tables: { accounts: "accounts" } }); // commit the SQL once, in your migrations (idempotent)
const accounts = dbAccounts({ query, transaction, mail, baseUrl: "https://www.akinaza.com", product: "Akinaza", tokenPrefix: "ae", passwords: { hash, verify }, tokenHash, passwordResetTtlMinutes: 30 /* optional, 5–1440 */ }); // `transaction`: the work on one connection (BEGIN … COMMIT); the password reset needs it under concurrent requests
await accounts.updateProfile(id, { name: "Éric", theme: "dark" });
await accounts.sessions(id, new Date(), currentSessionId);
await accounts.requestPasswordReset(email, new Date()); // mails `<baseUrl>/<locale>/reset-password?token=…`
await accounts.resetPassword(token, newPassword, new Date()); // "CHANGED" | "INVALID"
```

**模式。** 这些表格是 `@krizaka/db` 模块(`auth`)，版本1字节-与此包之前发布的SQL相同，因此具有它们的产品采用没有DDL更改的SQL： `adoptMigration(accountsSchema(), 1, context)` 只写标记， `emitMigration(…)` 为没有SQL的数据库编写SQL。原来的迁移函数不变， `@deprecated`，为一个未成年人。角色就是模块 `auth_roles` （`rolesSchema(roles, { scoped })`、表 `owner` 和 `scope`)和联邦链接 `auth_identity_links` （`identityLinksSchema`）。 邮件所携带的链接 `mail.link` （`kind`， `url`， `minutes` 或者 `hours`旁边) `subject` 和 `text`：发送带有商标的HTML (`@krizaka/email`： `passwordReset`， `emailChange`)从数据中呈现。

产品保留自己的东西：会话cookie和令牌哈希(`tokenHash`)，它的密码哈希，它的邮件，它的速率限制和它的HTTP应答(`AccountError` 携带状态和稳定代码)。时间在流逝。`now`);这里没有表。

## 每个范围的角色

榜样就是数据： `defineRoles({ roles, permissions, manages })` 说明存在哪些角色，每个角色可以做什么以及谁可以将哪个角色交给谁。 `createRoles` 保留成员关系（一个人、几个范围、每个角色）和邀请（带角色邀请的电子邮件、使用过一次的链接、只保留其散列，从不降级）、拒绝删除或降级最后一个所有者，以及答案 `authorize(account, scope, "invoices")` 与角色或稳定的拒绝(`NOT_A_MEMBER` 404年, `FORBIDDEN` 403)。 `rolesMigration(roles, { scopeTable })` 制作表格。

## 联合登录（b谷歌，苹果，Facebook，原生PKCE）

每个产品都有自己的账户； **krizaka-users** 是端口背后的联合提供商，因此产品没有自己的OAuth应用程序。 `IdentityProvider` 有两个呼叫： `start(provider, returnTo, { codeChallenge? })` 给出收件人的地址， `complete(code, { codeVerifier? })` 交换一次性代码（一次性使用）为一个验证 `ExternalIdentity` （`issuer`， `subject`， `email`， `emailVerified`）. 返回地址保留在产品上（路径）；未知提供者被拒绝。

```ts
import { createFederatedSignIn, dbIdentityLinks, identityLinksSchema, usersIdentityProvider } from "@krizaka/auth";

emitMigration(identityLinksSchema, 1, { tables: { accounts: "accounts" } }); // once, in your migrations (idempotent): (issuer, subject) → account
const federation = usersIdentityProvider({ baseUrl: "https://auth.dev.akinaza.com", appUrl: "https://dev.akinaza.com", fetch });
const signIn = createFederatedSignIn({ links: dbIdentityLinks(query), accounts: { findByEmail, create }, clock });

const { url } = await federation.start("google", "/fr/account"); // redirect there
const result = await signIn.signIn(await federation.complete(code)); // { status: "signed-in", accountId, via } | { status: "refused", code }
```

外部标识附加到现有帐户 **只有在双方都验证过地址的情况下** （由供应商和产品决定）；否则将被拒绝(`EMAIL_NOT_VERIFIED`， `ACCOUNT_EMAIL_UNVERIFIED`)，并且它绝不是第二个用户表。 `create` 是可选的：忽略它，没有账户的人将被拒绝(`NO_ACCOUNT`）. 适配器运行的一致性工具包 `@krizaka/auth/testkit` （`identityProviderContract`， `identityLinkStoreContract`）。

> `postgresAccounts` 名字是 `dbAccounts`;它仍然是 `@deprecated` 别名一个小调，离开下一个大调。

## 签到的访客永远看不到公共的一面

在产品的 `proxy.ts` (网络 `Request`/`Response`，无框架)： `landingRedirect(request, { cookie, landing: ["/", "/fr", "/en"], home, origin })` （`origin`：应用程序声明的公共来源，从不从请求中读取；绝对位置在上面，相对没有)回答307到家里有 `Cache-Control: private, no-store` 和 `Vary: Cookie` 当会话cookie存在于着陆路由上时，则 `null`. `sessionHint(response, request, { cookie, secure })` 保持可读性 `kz-session=1` 与cookie同步标记（而不是令牌），因此静态页面对每个人都保持相同 `NavBarSession` 的 `@krizaka/ui` 在其第一次绘制上绘制已登录占位符。两者都只读取cookie作为提示：页面和API仍然解析会话。

## 会议，密码，地址和政府的锁

Yomizaka和Akinaza各自复制了什么(`password.ts`， `client-ip.ts`， `session.ts`的管理大门 `access.ts`他来过一次。每个部件都是其输入的一个函数：产品读取其环境和框架，并传递值。

```ts
import { allowedIps, clientIp, createSessions, dbSessionStore, hashPassword, requireAdmin, sessionCookie, sessionTokenOf, verifyPassword } from "@krizaka/auth";

const sessions = createSessions({
  store: dbSessionStore(query, { columns: ["id", "email", "role", "locale"], toAccount: (row) => ({ id: String(row.id), email: String(row.email), role: row.role as "USER" | "ADMIN", locale: row.locale as "en" | "fr" }), active: { column: "status", equals: "ACTIVE" } }),
  clock: { now: () => new Date() },
  tokenPrefix: "ys",                       // tokens are `ys1.<43 characters>`
  secret: () => env.AUTH_SESSION_SECRET,   // keys the stored hash (HMAC-SHA256)
  sessionDays: () => settings.get("auth.sessionDays"), // the setting, read for each new session
});

const ip = clientIp(request.headers, { header: env.ABUSE_CLIENT_IP_HEADER, trustedProxyHops: env.ABUSE_TRUSTED_PROXY_HOPS });
const { token, expires } = await sessions.create(account.id, { ip, userAgent: request.headers.get("user-agent") });
cookies().set(sessionCookie(token, expires, { name: "yomizaka_session", secure: production }));

const session = await sessions.resolve(sessionTokenOf(request.headers, { cookie: "yomizaka_session" })); // bearer of a native app, else the cookie
const verdict = requireAdmin({ session, ip, allowedIps: allowedIps(env.AUTH_ALLOWED_IPS, { production }), surface: "api" }); // { ok: true } | { ok: false, status, code }
```

-   **密码:** `hashPassword` 和 `verifyPassword` scrypt (N=2^15, r=8, p=1)，存储 `scrypt$N$r$p$salt$hash`，副本的格式：存储在这里的产品验证一个哈希（一个测试pin）。 `passwordProblem` （12 ~ 256个字符）和 `decoyHash` （当地址没有账户时对其进行验证：相同的工作，相同的时间）。把它们交给 `dbAccounts({ passwords: { hash: hashPassword, verify: verifyPassword } })`.
-   **客户地址:** `clientIp(headers, { header, trustedProxyHops })` 读取边缘设置的标题，否则读取条目 `trustedProxyHops` 从…的右边 `X-Forwarded-For` （从来没有左：客户端写），否则 `X-Real-IP`，否则为null。
-   **届:** 一个 `SessionStore` 港口与 `dbSessionStore` (产品的 `sessions` 表，声明为参数：检查表名和帐户列，挂起的帐户一次不签名)； `memorySessionStore` 用于测试，以及 `sessionsSchema` 对于一个没有表的产品。标记只显示一次；只有它的HMAC被存储，与副本的公式相同，所以已经存储的会话继续工作。一个会话持续 `sessionDays` 24小时的时间。
-   **政府的锁：** `requireAdmin({ session, ip, allowedIps, surface })` 决定三个锁（一个会话、ADMIN角色、列表中的一个地址），并回答API 401或403上的状态和代码 `UNAUTHENTICATED`， `FORBIDDEN` 或者 `IP_NOT_ALLOWED`;在404页上显示每个失败的锁，因为管理没有宣布自己。 `allowedIps(list, { production })` 制作一个空列表，在生产中是“nobody”，在开发中是“anyone”。
-   **`@krizaka/auth/contract`** (网络安全需求 `zod`): `GET /api/me` 答案 **200** `{ signedIn, signupOpen, account? }` 与 `Cache-Control: private, no-store` 和 `Vary: Cookie` (krizaka-ui # 551)。登出不是错误。 `meResponse({ signupOpen, account })` 构建答案并与之核对 `meSchema`;沙洲岛 `@krizaka/ui` 读取相同的文件。
-   **`@krizaka/auth/settings`** (需要 `@krizaka/settings`)：注册表片段 `authSettings`： `auth.sessionDays` （14,1至90）和 `auth.passwordResetMinutes` （30,5至1440）。
-   **忘记密码:** `dbAccounts` 已经是复位端口： `requestPasswordReset(email, now)` （同样的答案，无论地址是否有帐户，链接以该人的语言发送）和 `resetPassword(token, next, now)` (令牌以散列方式存储，使用一次，过期 `auth.passwordResetMinutes`，每个会话结束)。将产品的限位器放在两条路线的前面(`@krizaka/rate-limit`）。

`sessionStoreContract` (从 `@krizaka/auth/testkit`这是合同的全部内容 `SessionStore` 适配器运行，内存和数据库都一样。

## 常见问题

### @krizaka/auth 是什么？

Krizaka产品的帐户-名称，语言，主题，头像，通过带有验证标志的链接更改电子邮件，密码，与其他用户签出的会话，数据导出，删除-在端口后面，使用PostgreSQL适配器及其迁移。每一个名单

### 如何安装 @krizaka/auth？

在项目目录运行 npm install @krizaka/auth，或使用 pnpm add / yarn add / bun add。

### @krizaka/auth 支持 TypeScript 吗？

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

### @krizaka/auth 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### @krizaka/auth 需要什么版本的 Node\.js？

package\.json 的 engines 字段要求 Node\.js \>=22。版本不符时 npm 默认只给出警告，开启 engine-strict 后会拒绝安装。

### @krizaka/auth 可以商用吗？

@krizaka/auth 使用 Apache-2\.0 许可。宽松许可：允许商业使用、修改和再分发，并附带明确的专利授权；分发时需保留许可与 NOTICE 文件，修改过的文件要注明改动。以上为概括，不构成法律意见，请以许可原文为准。

### @krizaka/auth 还在维护吗？

最新版本 0\.13\.2 发布于 2026/11/01（1 天前）；至今共发布 19 个版本，其中过去 12 个月发布了 19 个；首次发布于 2026/10/23。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

