# @activescott/auth 中文文档

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

版本：5\.9\.0
npm 元数据更新时间：2026-10-19T01:39:11.556Z
中文译文更新时间：2026-11-05T23:37:33.455Z
软件包许可：MIT

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

原始 npm 页面：https://www.npmjs.com/package/%40activescott/auth
原文对照：https://npmcn.com/package/%40activescott/auth/?lang=original
项目主页：https://github.com/activescott/auth#readme
源码仓库：https://github.com/activescott/auth

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

- 包体积（解压后）：321 kB，91 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- 运行依赖：1 个
- 首次发布：2026-03-16T19:01:16.521Z
- 最新版本发布：2026-10-19T01:39:11.220Z
- 近 30 天下载量：9347（2026-10-05 至 2026-11-03，npm 官方下载统计）
- 已发布版本数：22（近 12 个月 22 个）

## 中文简介

框架不可知性认证, 提供方模式用于魔法链接, OAuth, 以及更多

## 安装

```sh
npm install @activescott/auth
```

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

## README

# @ activescott/ auth (英语)

[![npm version](https://img.shields.io/npm/v/@activescott/auth.svg)](https://www.npmjs.com/package/@activescott/auth) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

框架不可知的直接认证,刻意小:单用魔法链接和一次性代码通过电子邮件和短信,以及密码(WebAuthn). 没有第三方身份提供者。 在节点和边缘运行时间(如Cloudflare Workers)上运行.

这个包是核心: `Auth` 类,JWT-cookie会话管理,以及提供者/存储界面. 它不单独处理任何具体的认证方法——将其与一个或多个提供者软件包和(可选的)框架适配器配对:

-   [`@activescott/auth-provider-email`](https://www.npmjs.com/package/@activescott/auth-provider-email) ——电子邮件魔法链接+一次性代码
-   [`@activescott/auth-provider-sms`](https://www.npmjs.com/package/@activescott/auth-provider-sms) ——短信一次性代码([`@activescott/auth-sms-twilio`](https://www.npmjs.com/package/@activescott/auth-sms-twilio) 提供Twilio运输: 通讯和验证)
-   [`@activescott/auth-provider-passkey`](https://www.npmjs.com/package/@activescott/auth-provider-passkey) ——密码(WebAuthn)
-   [`@activescott/auth-adapter-react-router`](https://www.npmjs.com/package/@activescott/auth-adapter-react-router) ——反应路由器 v8 适配器

用于生产 [frumblefeed.com (英语)](https://ramblefeed.com/) 财务报告和审定财务报表 [(原始内容存档于2018-10-21). dinkerbellbot.com](https://tinkerbellbot.com/)。 。 。 .

## 为什么直接的,没有密码的认证?

每个人都有一个电子邮件地址或电话号码。 没有人想要另一个密码。 而许多用户对"与Google/Apple/Microsoft签约"犹豫不决,因为它与第三方分享他们的签入活动. 这个库主要研究一个人验证的方法 **直接** 用你的应用程序:

-   **用户的摩擦度最低.** 没有创建、忘记或重置的密码, 也没有需要第三方的账户 。 现代平台自动填充我们发送的代码,所以签名是: 输入您的电子邮件,输入您的操作系统提供的代码.
-   **对你最轻松.** 没有OAuth应用程序注册,没有身份提供仪表板,没有额外服务. SMTP 服务器和您的数据库是唯一的依赖性 .
-   **私人设计.** 循环中没有任何第三方身份提供商——大技术不会在用户在你的应用上签名时得知(或那).
-   **故意地小取来.** 这不是一个与Outh-professor auuth 库合作的作品——像这样的项目很好地服务于这一优势 [更好的认证](https://www.better-auth.com/)限制范围 使得它很容易掉入一个新的应用.

Passkeys更进一步地推开同一想法:防打钓鱼,没有共同的秘密,仍然没有第三方.

## 特征

-   ✅ **电子邮件魔法链接** ——单用,服务器后置的登录链接,并有一个确认步骤,即电子邮件安全扫描仪无法消耗(参见 [财务问题](https://github.com/activescott/auth#faq)) (中文(简体) ). 生产中.
-   ✅ **电子邮件一次性代码** ——每个登录的电子邮件中也包含一个带有iOS/macOS AutoFill支持的数字代码,因此用户可以输入代码而不是切换到收件箱分页.
-   ✅ **带上自己的数据库** 3个小商店接口(`IdentityStore`, (中文(简体) ), `UserStore`, (中文(简体) ), `ChallengeStore`;用Prisma, Drizzle,生生SQL,Redis,任意使用.
-   ✅ **边缘准备, [冬季TC兼容](https://wintertc.org/faq) 核心项目** ——标准获取 `Request`编号 :`Response`,网络晶体,以及 [`jose`](https://github.com/panva/jose) 用于会话 JWTs; 无节点的 API, 因此它运行在 Cloudflare Workers, Deno, Bun, 以及 WinterTC 相通的运行时间 .
-   ✅ **反应路由器 v8 适配器** —— 说 `createAuthHandlers`, (中文(简体) ), `requireAuth`, (中文(简体) ), `optionalAuth`, (中文(简体) ), `getSession`, (中文(简体) ), `logout`。 。 。 .
-   ✅ **短消息一次性代码** - 供应商中立的供应商,配有Twilio通讯运输(RCS-ready)和 [网络OTP](https://developer.mozilla.org/docs/Web/API/WebOTP_API) 自动填充支持.
-   ✅ **主机核查(无美国A2P 10DLC)** - 同一个短信提供商接受 `VerificationTransport` 由供应商生成、发送和检查密码. `TwilioVerifyTransport` twilio套装中的船舶:没有可购买的号码,没有品牌或竞选注册——每个签入的费用为~4–6x.
-   ✅ **侵权保护,默认** ——每个IP和每个接收者的费率限额,最小的填充时间检查,被屏蔽的尝试被记录,被屏蔽的呼叫者得到的响应与成功发送会产生的相同. 可选包添加主机检查( Q)[转弯](https://www.npmjs.com/package/@activescott/auth-botcheck-turnstile)) (中文(简体) ).
-   ✅ **密码( WebAuthn)** ——在签入时添加一个密码,然后在无用户名下签名,使用触摸ID,Face ID,Windows Hello, 1Password, iCloud keychain,或安全密钥;有条件的UI(passkey自動填充)支持. 通过 [`@simplewebauthn/server`](https://simplewebauthn.dev/);包含零依赖性浏览器客户端.

提供者接口( E)`AuthProvider`)是扩展点。 实施一个新的供应商不需要修改这个核心包.

## 文档实例( E)

Full docs——快速起步,架构图,自定义提供方指南,e2e测试模式,FAQ——和一个可运行的React Router框架-mode实例,其中Playwright测试在单曲中直播:

→ **[https://github.com/活塞斯科特/奥斯\*](https://github.com/activescott/auth*)\* 联合国**

其余的README涵盖这个核心包本身出口和预期的内容.

## 安装

```bash
npm install @activescott/auth
```

## 盒子里的东西

| 导出 | 目的 |
| --- | --- |
| `Auth` | 圆相相. 给提供者的路由认证请求, 管理会话 cookie . |
| `SessionManager` | 独立的JWT会话签名人/验证人(很少直接需要). |
| `AuthProvider` | 接口每个提供者工具( E)`initiate`, (中文(简体) ), `verify`, (中文(简体) ), `canHandle`选项 `handleAction` 页:1. |
| `IdentityStore`, (中文(简体) ), `UserStore` | 用于插入数据库的接口. |
| `ChallengeStore`, (中文(简体) ), `InMemoryChallengeStore` | 为短期单一用途挑战而储存(见下文). |
| `AbuseConfig`, (中文(简体) ), `RateLimitStore`, (中文(简体) ), `InMemoryRateLimitStore` | 对起始终点的滥用保护——默认(见下文). |
| `InitiateGate` | 有关谁可以开始登录或链接的您自己的政策(见下文). |
| `createWaitlist`, (中文(简体) ), `waitlistNotificationEmail` | 等待名单,经管理员批准,建在大门上 `onVerified` (见下文). |
| `BotCheckProvider`, (中文(简体) ), `createFormToken`, (中文(简体) ), `FORM_TOKEN_FIELD` | Bot检查接口和登录表的反机器人字段. |
| `generateOtpCode`, (中文(简体) ), `hashOtpCode`, (中文(简体) ), `verifyOtpCode` | 检察官办公室有能力提供商使用的一次性编码公用事业. |
| `AuthUser`, (中文(简体) ), `Identity`, (中文(简体) ), `Session`, (中文(简体) ), `AuthResult`, (中文(简体) ), `AuthInitResult` | 核心数据类型. |
| `AuthErrors`, (中文(简体) ), `getAuthErrorMessage`, (中文(简体) ), `AUTH_ERROR_CODES` | 结构化错误帮助. |

再加上 `@activescott/auth/admin` 子路径: 管理员仪表板数据和管理员许可列表检查(参见 [管理子路径](https://npmcn.com/package/%40activescott/auth/#readme-admin-subpath)) (中文(简体) ).

## 数据模型

你带了三个适配器 `IdentityStore`, (中文(简体) ), `UserStore`,以及 `ChallengeStore` - 读出/写出你的数据库。 库处理挑战,饼干,提供者的路由,以及会话验证.

一个 `Identity` 是一个 `(provider, identifier)` 配对(例如:. `("email", "alice@example.com")`联系到你的一个 `User` 记录。 一个用户可以拥有多个身份——电子邮件,电话,和密码都使用同一个表格.

`Identity.metadata` 实值 **供应商拥有的国家**,与您的申请不透明:坚持未修改的(JSON/JSONB一栏),并如所储存的那样返回。 拥有每个身份状态的供应商保存在那里——密码提供者存储每个证书的公用钥匙和签名柜台——和无国籍供应商存储 `{}`。它可能包含敏感材料,因此保护它,如证书数据(休息时加密是一种合理的默认). `IdentityStore.update(id, {metadata, verifiedAt})` 替换存储的元数据批发; 提供者依赖它, 因此它是一种必需的方法.

## 最小形状

```ts
import { Auth, InMemoryChallengeStore } from "@activescott/auth"
import { EmailProvider } from "@activescott/auth-provider-email"

const auth = new Auth({
  session: {
    secret: process.env.JWT_SECRET!,
    maxAge: "30d",
    cookieName: "session",
    cookie: { secure: true, sameSite: "lax", path: "/" },
  },
  identityStore, // your impl
  userStore, // your impl
  challengeStore: new InMemoryChallengeStore(), // DB-backed in production
  providers: [new EmailProvider({ ... })],
})
```

那就打电话 `auth.handleRequest(request)` 从您框架的路由层( 或使用框架适配器),以及 `auth.verifySession(request)` 检查受保护路线上的会话饼干.

## 会话缓存

`verifySession` 每个已校验的会话在内存中保存了两分钟,所以一个页面的加载器会每页询问谁在成本上签名一对商店读取,而不是每个加载器读取一对. 你为此付出的代价是沉闷:在你阻断或删除某人后最多两分钟内,他们的请求仍然得到证实.

`session.cacheTtlMs` 那个窗口是毫秒,和 `0` 关闭缓存,所以每个请求读取您的商店:

```ts
session: {
  secret: process.env.JWT_SECRET!,
  maxAge: "30d",
  cookieName: "session",
  cookie: { secure: true, sameSite: "lax", path: "/" },
  cacheTtlMs: 0, // block a user, and their next request is signed out
}
```

随着缓存的被打开,它最多维持了一万个会话,并驱逐了最古老的过去,因此,在扫荡之间爆发的签注无法无限制地增长. 任何事物都无法在实例之间共享:一个条目只会重复程序商店刚才说的话.

## 挑战斯托尔

每一次登录尝试都以服务器侧面的挑战为后盾:魔法链接和一次性代码存储被散列的秘密,一个尝试计数器,以及一个过期;密码仪式记录了WebAuthn的挑战,因此可以完全赎回一次. 这个州就住在 `challengeStore`,这就是为什么它必须成为 `Auth` 相会.

`InMemoryChallengeStore` 用于单一服务器进程(和dev/例)的正确性。 \* 重新启动后失去挑战,没有在各种情况下分担挑战——多机构部署采用四方法 `ChallengeStore` 界面对共享存储。 SQL的执行大致如下:

```sql
CREATE TABLE challenges (
  id TEXT PRIMARY KEY,
  type TEXT NOT NULL,
  identifier TEXT NOT NULL,
  hashed_code TEXT,
  data JSONB,
  attempts INT NOT NULL DEFAULT 0,
  max_attempts INT NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  expires_at TIMESTAMPTZ NOT NULL
);
```

与 `incrementAttempts` 作为 `UPDATE challenges SET attempts = attempts + 1 WHERE id = $1 RETURNING attempts` (递增必须是原子——它执行猜想限制),周期性 `DELETE ... WHERE expires_at < now()`。 。 。 .

## 保护虐待

这个 `initiate` 端点会把邮件和短信发送到任何来电者提交的地址,这使得它们成为了一种吸引人的方式,可以将邮件轰炸第三方,或者在弹出时烧掉您发送的名声. 保护是 **默认** ——您不需要配置或执行任何东西:

| 层 | 默认 |
| --- | --- |
| 每个客户端IP | 每分钟3分,然后每小时10分 |
| 每个收件人 | 每小时3个,然后每天10个 |
| 最少填表时间 | 2 秒( 只当表格显示一个符号时执行, 下面) |
| 柜台存储 | `InMemoryRateLimitStore` |
| 阻断反应 | 与成功发送完全相同 |

被阻断的尝试总是被记录下来(`console.warn`)与原因,提供者,IP,请求的标识符,和规则,所以滥用爆发是可见的:

```
[auth] blocked initiate: reason=identifier_rate_limited provider=email ip=203.0.113.7 identifier=victim@example.com rule=3/3600s retryAfter=2841s
```

### 一个被封锁的呼叫者看到成功

通过设计一个被压抑或被打上花旗的请求,得到一个真正的发送将产生的反应——同样的302回 `?sent=1`,或同为 `{success: true, message}` -减去挑战饼干。 无所寄也无所存也. 这使得机器人无法绘制 地址或IP被挤压 如果你愿意回来 `429 RATE_LIMITED` (仅用于部署API的合理),设置 `abuse.respondWith: "rateLimited"`。 。 。 .

一个块在两种模式中都有不同的回答: 机器人检查报告 `incomplete` (一个在提交表格时尚未发出其符号的主机部件)获得 `BOT_CHECK_INCOMPLETE`,作为302的背面 `?error=BOT_CHECK_INCOMPLETE` 或四百个 这个判决仅出自请求,从未出自标识,所以它告诉呼叫者他们没有发送的任何东西,它让一个在缓慢的连接上的真正用户知道再次尝试. 它仍然被记录 并传递给 `onBlocked` 作为 `bot_check_failed`。 。 。 .

### 调试

```ts
const auth = new Auth({
  // ...
  abuse: {
    perIp: [
      { windowSeconds: 60, max: 3 },
      { windowSeconds: 3600, max: 10 },
    ],
    perIdentifier: [{ windowSeconds: 3600, max: 3 }],
    store: myRedisRateLimitStore, // multi-instance: share the counters
    onBlocked: (event) => logger.warn(event, "auth abuse blocked"),
  },
})
```

`abuse: { enabled: false }` 关掉一切.

`InMemoryRateLimitStore` 每个过程的计数,所以多进制部署可以有效地将每个限制乘以例数. 实施单一方法 `RateLimitStore` 界面对 Redis (`INCR` + 键 `EXPIRE`)或您要共享计数器的数据库.

客户IP来自 `cf-connecting-ip`,则 `x-forwarded-for` (最右侧跳;设置) `abuse.clientIp.trustedProxyHops` 如果一个以上的代理附件),那么 `x-real-ip`。这些信头是可以伪造的,除非在您的应用程序前有一个代理重写它们——供应 `abuse.clientIp.getClientIp` 当您的运行时间显示对等地址时 。 当无法确定IP时,会跳过每个IP的限制,仍然适用每个接受者的限制.

### 窗体符号

最小形式填充时间检查需要在您的登录格式中隐藏一个字段 :

```tsx
import { createFormToken, FORM_TOKEN_FIELD } from "@activescott/auth"

// in the route/loader that renders the form:
const formToken = await createFormToken(process.env.JWT_SECRET!)
```

```html
<input type="hidden" name="authFormToken" value="{formToken}" />
```

令牌是已签名的渲染时间戳; 服务器拒绝更快到达的提交 `minFormFillSeconds`必须签字——没有签字的时间戳只是要伪造的另一个领域。 与 **没有** 指示符是允许的,所以添加字段是可选的,可以在稍后推出. 一个比一日更古老的标志也被允许,而不是被拒绝——在标签中留下的登录页是人类.

### 托管的机器人检查

Cloudflare Turnstile, h Captcha, 朋友住在自己的包里, 所以你只安装你使用的供应商:

```ts
import { TurnstileBotCheck } from "@activescott/auth-botcheck-turnstile"

abuse: {
  botChecks: [new TurnstileBotCheck({ secretKey: process.env.TURNSTILE_SECRET_KEY! })],
}
```

执行 `BotCheckProvider` (单位:千美元)`{ id, verify({ request, body, ip, providerId }) }`)来加入自己.

## 启动闸门

只邀请的β、允许列表或被封禁的域名是一条关于 _谁_ 可能会发出签名信息。 放进去 `gate.onInitiate` 而不是在您的认证路径的序言中:

```ts
const auth = new Auth({
  // ...
  gate: {
    async onInitiate({ provider, identifier, mode, request }) {
      if (mode === "link") return "allow" // already signed in
      if (await invites.has(provider, identifier)) return "allow"
      return { redirect: "/waitlist" }
    },
  },
})
```

门开进去了 `handleRequest`,在提供者解析、正常化并验证标识符之后(`User@Example.com` 以 `user@example.com`,在创建或发送任何信息之前,电话号码为E.164。 一个畸形的标识符首先被提供商拒绝,所以大门从不对其中一个采取行动. `mode` 实值 `"signin"` 或者说 `"link"` (见 [连接身份](https://npmcn.com/package/README.md#linking-identities--account-merge)(三); `request` 是启动请求的复制件,用于信头或饼干.

返回一个:

-   `"allow"` ——照常发送.
-   `{ redirect: "/waitlist" }` ——不发送任何信息,用302个地址回答,每个呼叫者.
-   `{ error: AuthErrors.invalidCredentials({ reason: "Invite only" }) }` – 不发送, 像其它启动一样失败 : 浏览器表单帖会回到提交页面 `?error=<code>`,一个接线员得到的错误作为JSON.

发射失败的门 Per-IP和受虐者滥用限制在出道门前运行,因此被节制的请求仍然得到沉默"同意"的回答.

任何人都可以开始一个他们不拥有的地址的登录,所以一个写东西或告诉别人标识符的规则属于可选的 `gate.onVerified` 换句话说 它在提供者验证了标识符之后运行,在创建会话之前,使用相同的 `provider`, (中文(简体) ), `identifier`, (中文(简体) ), `mode`,以及 `request` (核查请求)加 `user` 财务报告和审定财务报表 `identity` 已解决的核查和 `isNewUser`它返回同样的决定: `"allow"` 创建会话, `{ redirect }` 以302的回答,没有会话, `{ error }` 无法像错误代码一样进行校验 。 它看到电子邮件和短信被验证;密码签入则跳过.

内置的电子邮件和短信提供商查询大门。 与 `gate` 设定, `new Auth` 如果为启动路由服务的任何提供者没有声明 `consultsInitiateGate: true` ——旧的供应商包否则会跳过您的政策而无踪迹. 自定义提供者选择通过调用 `context.gate?.check({ provider, identifier, mode })` 一旦其标识符有效,在有结果时返回结果,并设置 `consultsInitiateGate = true`。 。 。 .

## 管理子路径

`@activescott/auth/admin` 管理仪表板是用来建造的,没有框架:

| 导出 | 目的 |
| --- | --- |
| `createAdminData(auth)` | `{ listUsers, describeConfig }` 在你的店铺上, 作为简单的系列行. |
| `createAdminPredicate(admins)` | 从已划定的字符串、数组或你自己的上游构建允许列表检查. |
| `isAdminUser(auth, user)` | 检查一个用户的回答 `AUTH_ADMIN_IDENTIFIERS`。 。 。 . |

许可列表为电子邮件地址和E.164电话号码,以逗号或白空分隔,与 **每个** 用户身份。 因此,一个被允许的上市地址即使通过短信系统签字也承认其拥有者。 空的或缺失的许可名单是不允许任何人进入的.

```ts
import { isAdminUser } from "@activescott/auth/admin"

const session = await auth.verifySession(request)
if (!session || !(await isAdminUser(auth, session.user))) {
  return new Response("Not Found", { status: 404 })
}
```

`isAdminUser` 读取环境允许列表,并在每次呼叫中加载用户的身份。 当列表来自别处时,用 `createAdminPredicate(admins)` 并亲自将用户的身份交给用户(`identityStore.findByUserId(user.id)`) (中文(简体) ). 反应路由器适配器的仪表板使用这些,所以一个你自己打开的页面,仪表板同意谁是管理员.

## 日志

重新定向目的地从 `?redirectTo=`字段和 `Referer` 头曰. 指定另一个来源或另一个方案的名称被拒绝, 并使用一个配置的路径取而代之, 否则无法为您的应用程序所识别: stale link 或代理重写 `Referer` 仅当用户在出乎意料的地方降落时才出现. 给图书馆找个地方说:

```ts
const auth = new Auth({
  // ...
  logger: console, // or { warn: (message, context) => log.warn(context, message) }
})
```

每个衰落的目的地记录一个WARN,命名它来自的参数和该值的出处. 数值本身从未被记录,因为一个神奇链接的URL在其查询中带有一键:

```
[auth] redirect destination declined, using fallback { source: 'redirectTo', fallback: '/', reason: 'other-origin', origin: 'https://other.example' }
```

`logger` 是可选的,当它不存在时,没有记录。 供应商作为 `AuthContext.logger`,框架适配器读取 `Auth` 实例`auth.getLogger()`),所以在这里配置它覆盖了整个流量. 虐待街区是分开的: `console.warn`附加时 `abuse.onBlocked` 如果你设置它.

## 等待名单

`createWaitlist` 是手批准新用户的应用程序的门。 每个人都得到一个代码。 一旦他们核实,一个没有核定账户的用户就会变成PENDING,并登入你的等待列表页面,没有会话;管理员通过它听到 `notify`;一个管理员批准或挡住仪表板。 经批准的用户照常签入.

状态在你的数据库里 隐藏在 `ApprovalStore`它的价值, `"PENDING" | "APPROVED" | "BLOCKED"`,匹配 enum 应用程序通常已经具备:

```ts
import {
  createWaitlist,
  waitlistApprovalEmail,
  waitlistNotificationEmail,
} from "@activescott/auth"

const emailOptions = {
  appName: "Fernfiles",
  domain: "fernfiles.com",
  from: "noreply@fernfiles.com",
}

export const waitlist = createWaitlist({
  // Only needed so onApproved below can look up an address to email
  identityStore,
  approvalStore: {
    getApprovalStatus: async (userId) =>
      (await db.user.findUnique({ where: { id: userId } }))?.approvalStatus ??
      null,
    setApprovalStatus: async (userId, approvalStatus) => {
      await db.user.update({ where: { id: userId }, data: { approvalStatus } })
    },
  },
  waitlistUrl: "/waitlist",
  blockedUrl: "/login?error=blocked", // defaults to waitlistUrl
  // App rules that skip the waitlist. Never consulted for BLOCKED users.
  autoApprove: ({ identifier }) => autoApproved.has(identifier),
  notify: (notice) =>
    transporter.sendMail({
      ...waitlistNotificationEmail(notice, emailOptions),
      to: adminEmails,
    }),
  // Tell the user when an admin approves them
  onApproved: ({ identities }) => {
    const to = identities.find((i) => i.provider === "email")?.identifier
    if (to) {
      return transporter.sendMail({
        ...waitlistApprovalEmail(emailOptions),
        to,
      })
    }
  },
  logger: console,
})

const auth = new Auth({ /* ... */ gate: waitlist })
```

等待列表仅在核实后进行操作,因此开始为他人地址签名不会产生用户,发送管理员什么也没有. 校验步骤创建了用户,因为它没有等待列表;然后等待列表记录他们的状态. `notify` 当用户加入等待列表时, 点火一次( w)`reason: "waitlisted"`以及何时 `autoApprove` 让某人进入( l)`reason: "auto-approved"`); 被记录的投出,不会出错。 电子邮件是简单的和通用的,目的:app名称、域名、发送者和链接 `/admin/users` (单位:千美元)`adminPath` 改变它。 发送它是你的,所以核心 不依赖邮件.

`onApproved` 当管理者批准尚未批准的用户时,通过 `approve` 或者说 `handleAdminAction`,带有用户身份,可以选择地址(仅短消息用户没有电子邮件身份)。 它不会开火 `autoApprove`,其用户已签名. `waitlistApprovalEmail` 创建信件,链接到 `/login` (单位:千美元)`signInPath` 改变它。 投球被记录 批准站.

无记录状态的用户计数为未获批准. 在打开等待列表前标出已存在的用户 .

大门只看到电子邮件和短信的签注,而且只有在他们核实时才会看到. Passkey 签入 和用户在他们签署 需要每个要求检查后被屏蔽; `waitlist.redirectFor(userId)` 返回给已核准用户和 URL 发送给其他用户的无效。 使用 React 路由器适配器 :

```ts
createAuthHandlers(auth, {
  // ...
  onSessionVerified: async ({ user }, request) => {
    const to = await waitlist.redirectFor(user.id)
    if (to) return logout(request, to)
  },
})
```

对于仪表板, 提供批准和屏蔽按钮 `AdminUsersPage`是因为 `rowActions` 作为表格张贴 `userId` 财务报告和审定财务报表 `intent` (单位:千美元)`"approve"` 或者说 `"block"`)到您的管理路线, 在这条路线的动作呼吁 `waitlist.handleAdminAction(await request.formData())` 在检查了拨打者之后,他是一个管理员. `waitlist.approve(userId)` 财务报告和审定财务报表 `waitlist.block(userId)` 从你自己的代码做同样的。 表格本身没有CSRF标志;它依赖于会话饼干是 `SameSite=Lax` 或更严格,如示例配置。 这个 [示例应用程序](https://npmcn.com/package/examples/react-router/app/routes/admin.users.tsx) 有整个流量.

## 许可证

麻省理工学院

## 常见问题

### @activescott/auth 是什么？

框架不可知性认证, 提供方模式用于魔法链接, OAuth, 以及更多

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

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

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

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

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

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

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

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

### @activescott/auth 的下载量有多少？

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

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

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

