@yss123/cage-http 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,19 +1,69 @@
1
1
  # @yss123/cage-http
2
2
 
3
- 笼子 HTTP 层:Fastify 插件、路由生成、认证、安全基线
3
+ Cage 的 HTTP 与进程装配层。根据 domain 注册表生成业务接口,组合 Cookie 会话、密码/OIDC 认证、CSRF、限流、日志脱敏、系统管理、文件和可选 AI/审批接口。应用只需薄启动入口,业务写操作继续由内核命令执行。
4
4
 
5
- Cage 是通过 TypeScript 声明业务、由框架统一执行权限和数据规则的中后台框架。此包是框架的一部分。
5
+ ## 安装
6
6
 
7
- ## 使用
7
+ 需要 Node.js 24+、已初始化并迁移的 PostgreSQL;框架包保持同版本。在 API 包安装:
8
8
 
9
9
  ```powershell
10
10
  pnpm add @yss123/cage-http
11
11
  ```
12
12
 
13
- 首次使用建议通过脚手架创建完整项目,所需依赖由模板统一配置。需要 Node.js 24 或更新版本。
13
+ 首次使用可执行 `pnpm create @yss123/cage-app my-app --template blank`,模板已配置 API 与独立 worker。
14
14
 
15
- - [完整使用说明](https://github.com/shunshunyue/cage/blob/main/docs/使用说明.md)
16
- - [框架源码与范例](https://github.com/shunshunyue/cage)
17
- - [问题反馈](https://github.com/shunshunyue/cage/issues)
15
+ ## 启动 API 和 worker
18
16
 
19
- 许可证:[MIT](https://github.com/shunshunyue/cage/blob/main/LICENSE)。
17
+ 以下文件放在脚手架的 API 包,`@app/domain` 是本应用导出的声明。进程须从应用根目录加载本地 `.env`,或由部署系统注入环境变量。
18
+
19
+ ```ts
20
+ /** API 入口:按领域声明装配接口,启动时不迁移数据库或创建管理员。 */
21
+ import { createServer } from '@yss123/cage-http';
22
+ import * as domain from '@app/domain';
23
+
24
+ const server = createServer({ domain });
25
+ await server.listen({ port: 3000, host: '127.0.0.1' });
26
+ process.once('SIGINT', () => void server.close());
27
+ process.once('SIGTERM', () => void server.close());
28
+ ```
29
+
30
+ 单独运行 worker 入口,消费 outbox、审批启动、连接器出站和定时任务;API 与 worker 使用同一版本、同一 domain 和数据库:
31
+
32
+ ```ts
33
+ /** 后台入口:框架管理调度、重试与连接,退出时等待在途工作收尾。 */
34
+ import { startWorker } from '@yss123/cage-http/worker';
35
+ import * as domain from '@app/domain';
36
+
37
+ const worker = await startWorker({ domain });
38
+ process.once('SIGINT', () => void worker.stop());
39
+ process.once('SIGTERM', () => void worker.stop());
40
+ ```
41
+
42
+ 标准模板使用 `pnpm dev` 同时启动开发进程。手动部署需配置 `CAGE_APP_DATABASE_URL` 和 `CAGE_READ_DATABASE_URL`,指向同一已有数据库的不同账号;生产环境还须注入至少 32 字节的 `CAGE_COOKIE_SECRET`。真实值仅存在部署配置中。
43
+
44
+ ## 常用配置与生命周期
45
+
46
+ | 入口/选项 | 用途 |
47
+ | --- | --- |
48
+ | `createServer({ domain })` | 自动并入组织模型与声明路由,默认开启 `/admin/` |
49
+ | `server.ready()` / `server.app` | 初始化后用 Fastify `inject` 做 HTTP 集成测试,不必监听端口 |
50
+ | `server.listen()` / `server.close()` | 开始监听及释放 HTTP、扩展、授权监听和连接池 |
51
+ | `server.health()`、`/readyz` | 检查数据库和授权监听是否就绪;`/healthz` 仅表示存活 |
52
+ | `webDir` | 托管已构建业务 SPA;本地开发可由 Vite 提供 Web |
53
+ | `trustProxy` | 配置实际可信代理,默认 `false` |
54
+ | `files` | 私有存储目录、适配器与上传限额 |
55
+ | `adminLabels` | 覆盖系统管理中的中文展示名,不改变真实能力标识 |
56
+ | `routeTable`、`buildOpenApi` | 基于注册表获取路由与接口描述 |
57
+
58
+ 数据库准备顺序是 `cage init` → 审查并执行 `cage migrate` → 显式 `cage bootstrap` → 启动服务;具体初始化参数以模板 README 为准。`createServer` 不承担这些管理操作,`bootstrap: true` 会被拒绝。
59
+
60
+ 不要在 API 入口为普通业务新增手写写路由;修改 domain 并生成契约即可。浏览器使用 `@yss123/cage-client` 和 `@app/contracts`,不导入本包。前端按钮权限、审批选人和连接器回调都不能跳过内核最终授权。
61
+
62
+ ## 文档
63
+
64
+ - [架构、数据库与权限](https://github.com/shunshunyue/cage/blob/main/docs/ai/architecture.md)
65
+ - [路由与 SPA 部署](https://github.com/shunshunyue/cage/blob/main/docs/ai/routing.md)
66
+ - [私有文件](https://github.com/shunshunyue/cage/blob/main/docs/ai/files.md)
67
+ - [原生 AI 接入](https://github.com/shunshunyue/cage/blob/main/docs/ai/intelligence.md)
68
+
69
+ 源码与反馈:[GitHub](https://github.com/shunshunyue/cage)。许可证:[MIT](https://github.com/shunshunyue/cage/blob/main/LICENSE)。
package/dist/auth.d.ts CHANGED
@@ -3,17 +3,22 @@ import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
3
3
  import type { CtxCache } from './ctx.js';
4
4
  declare module 'fastify' {
5
5
  interface FastifyRequest {
6
+ /** 认证钩子总会设置;未登录请求也有带租户/请求号的匿名上下文。 */
6
7
  cageCtx: Ctx;
8
+ /** id 为数据库中的会话摘要,stepUpAt 为毫秒时间戳;没有有效会话时为 null。 */
7
9
  cageSession: {
8
10
  id: string;
9
11
  userId: string;
10
12
  tenantId: string;
11
13
  stepUpAt: number | null;
12
14
  } | null;
15
+ /** JSON 解析前的文本,仅为第三方回调验签保留,不应写入日志。 */
13
16
  rawBody?: string;
14
17
  }
15
18
  }
19
+ /** 浏览器认证 Cookie 的固定名称;不是凭据值,也不保存用户角色。 */
16
20
  export declare const SESSION_COOKIE = "cage_sid";
21
+ /** 服务端认证策略;缺省值面向普通会话,部署层应显式提供 OIDC 和生产 Cookie 配置。 */
17
22
  export interface AuthOptions {
18
23
  /** 会话时长(小时),默认 8 */
19
24
  sessionHours?: number;
@@ -21,6 +26,11 @@ export interface AuthOptions {
21
26
  cookieSecure?: boolean;
22
27
  /** 是否开放账号密码登录,默认开启 */
23
28
  password?: boolean;
29
+ /** 单进程正在执行的密码派生上限,默认 4;排队满后返回 RATE_LIMITED。 */
30
+ passwordConcurrency?: number;
31
+ /** 等待派生的请求上限,默认 32。 */
32
+ passwordQueueLimit?: number;
33
+ /** 可选授权码/PKCE 登录配置;clientSecret 仅保留在服务端,deptClaim 指向部门 code 的 claim 名。 */
24
34
  oidc?: {
25
35
  issuer: string;
26
36
  clientId: string;
@@ -30,29 +40,64 @@ export interface AuthOptions {
30
40
  deptClaim?: string;
31
41
  };
32
42
  }
33
- export declare function verifyPassword(pw: string, stored: string | null | undefined): boolean;
43
+ /** 异步派生仍消耗有限线程和内存,因此执行和等待队列都必须有上限。 */
44
+ export declare class PasswordVerifier {
45
+ private readonly concurrency;
46
+ private readonly queueLimit;
47
+ private active;
48
+ private readonly waiting;
49
+ /** concurrency 是同时派生个数,queueLimit 是额外等待个数;非法配置在启动期抛 CAGE-ENV-002。 */
50
+ constructor(concurrency?: number, queueLimit?: number);
51
+ /** 当前进程预算快照,仅用于观测;不是跨实例的全局限流计数。 */
52
+ get state(): {
53
+ active: number;
54
+ queued: number;
55
+ };
56
+ /** 先校验长度再占用执行槽;排队满抛 RATE_LIMITED,校验不匹配返回 false,派生异常继续上抛。
57
+ * finally 把槽直接移交给下一等待者,保证失败请求也会释放容量。
58
+ */
59
+ verify(pw: string, stored: string | null | undefined): Promise<boolean>;
60
+ }
61
+ /** 使用模块共享的默认有界校验器;HTTP 实例可另建 PasswordVerifier 使用自己的预算配置。 */
62
+ export declare function verifyPassword(pw: string, stored: string | null | undefined): Promise<boolean>;
63
+ /** 服务端会话持久化。调用方传入原始 sid 做验证/销毁,二次认证更新则使用已验证的数据库摘要 ID。 */
34
64
  export declare class Sessions {
35
65
  private readonly k;
36
66
  private readonly hours;
67
+ /** hours 为会话寿命小时数;构造函数不创建会话,也不延长已有会话。 */
37
68
  constructor(k: Kernel, hours: number);
69
+ /** 为已认证用户生成 32 字节随机会话并保存摘要/到期时间;原始 sid 仅返回给 Cookie 写入方。 */
38
70
  create(userId: string, tenantId: string, req: FastifyRequest): Promise<string>;
71
+ /** 只读取仍未过期的会话,未命中返回 null;用户是否禁用和当前授权由后续上下文解析复核。 */
39
72
  verify(sid: string): Promise<{
40
73
  id: string;
41
74
  userId: string;
42
75
  tenantId: string;
43
76
  stepUpAt: number | null;
44
77
  } | null>;
78
+ /** 幂等删除该原始 Cookie 对应的会话;不存在也视为退出完成。 */
45
79
  destroy(sid: string): Promise<void>;
80
+ /** 密码复核成功后记录数据库当前时间,后续命令的 stepUp 约束据此判断认证是否足够新。 */
46
81
  stepUp(sessionId: string): Promise<void>;
47
82
  }
83
+ /** 交付 HttpOnly 会话 Cookie;maxAge 单位秒,与服务端会话小时数对应。CSRF 仍需独立令牌校验。 */
48
84
  export declare function setSessionCookie(reply: FastifyReply, sid: string, opts: AuthOptions): void;
49
85
  /** 认证钩子:每个请求最先执行,把会话变成上下文 */
86
+ /** 先建立匿名上下文,再用有效 Cookie 和当前授权补全身份;健康探针/验签回调不解析用户会话。
87
+ * 无效 Cookie 不在此直接抛错,让各路由按是否需要登录决定拒绝行为;请求元数据不能跨请求缓存。
88
+ */
50
89
  export declare function registerAuthHook(app: FastifyInstance, k: Kernel, sessions: Sessions, cache: CtxCache): void;
90
+ /** 登录专用最小字段查询,包含密码摘要但不返回给前端;租户条件不可省略。 */
51
91
  export declare function findLoginUser(k: Kernel, username: string, tenantId?: string): Promise<{
52
92
  tenant_id: any;
53
93
  id: any;
54
94
  password_hash: any;
55
95
  status: any;
56
96
  } | undefined>;
97
+ /** 提供与 CSRF Cookie 配对的请求令牌;客户端后续写请求将该值放入框架要求的请求头。 */
57
98
  export declare function csrfRoute(app: FastifyInstance): void;
99
+ /** 安装密码登录、二次认证、退出和当前用户接口;由 server 的认证限流作用域承载。
100
+ * 登录失败审计尽力写入但始终拒绝登录;禁用密码登录不会禁用退出或已有会话的 /api/me。
101
+ * /api/me 公开当前能力与范围供 UI 展示,不能代替后端对下一次操作的重新授权。
102
+ */
58
103
  export declare function authRoutes(app: FastifyInstance, k: Kernel, sessions: Sessions, opts: AuthOptions): void;
package/dist/auth.js CHANGED
@@ -1,29 +1,101 @@
1
1
  // 认证:服务端会话 + 会话 cookie。优先对接公司 SSO(OIDC,见 oidc.ts),没有 SSO 时用账号密码加二次认证。
2
2
  // 之后每个请求都由认证钩子把会话变成上下文 ctx。
3
- import { createHash, randomBytes, scryptSync, timingSafeEqual } from 'node:crypto';
3
+ /** HTTP 认证层只确定“谁在请求”和会话的二次认证时间,不决定某条业务命令是否可执行。
4
+ * 密码校验使用有界异步队列;Cookie 保存随机原始会话 ID,数据库仅存其 SHA-256。
5
+ * 业务能力与范围由 CtxCache 当前解析,最终写权限仍在 Kernel.execute 中执行。
6
+ */
7
+ import { createHash, randomBytes, scrypt, timingSafeEqual } from 'node:crypto';
4
8
  import { anonymousCtx, cageError, DEFAULT_TENANT } from '@yss123/cage-kernel';
5
9
  import { sql } from 'kysely';
10
+ /** 浏览器认证 Cookie 的固定名称;不是凭据值,也不保存用户角色。 */
6
11
  export const SESSION_COOKIE = 'cage_sid';
12
+ /** 以摘要查询/删除会话,避免数据库泄露时原始 Cookie 直接成为可用凭据。 */
7
13
  const hashSid = (sid) => createHash('sha256').update(sid).digest('hex');
8
- export function verifyPassword(pw, stored) {
14
+ /** 将 CPU/内存密集的 scrypt 交给异步实现;调用数量仍须由 PasswordVerifier 约束。 */
15
+ const derivePassword = (pw, salt) => new Promise((resolve, reject) => {
16
+ scrypt(pw, salt, 32, (error, key) => error ? reject(error) : resolve(key));
17
+ });
18
+ /** 核对 scrypt$salt$hash 格式;缺少账户时仍做派生以减弱计时枚举,摘要采用恒时比较。 */
19
+ async function comparePassword(pw, stored) {
9
20
  if (!stored) {
10
- scryptSync(pw, 'timing-equalizer', 32); // 用户不存在时也做一次哈希,避免按耗时探测用户名
21
+ await derivePassword(pw, 'timing-equalizer'); // 用户不存在时也做一次哈希,避免按耗时探测用户名
11
22
  return false;
12
23
  }
13
24
  const [alg, salt, hash] = stored.split('$');
14
25
  if (alg !== 'scrypt' || !salt || !hash)
15
26
  return false;
16
- const got = scryptSync(pw, salt, 32);
27
+ const got = await derivePassword(pw, salt);
17
28
  const want = Buffer.from(hash, 'hex');
18
29
  return want.length === got.length && timingSafeEqual(got, want);
19
30
  }
31
+ /** 异步派生仍消耗有限线程和内存,因此执行和等待队列都必须有上限。 */
32
+ export class PasswordVerifier {
33
+ concurrency;
34
+ queueLimit;
35
+ active = 0;
36
+ waiting = [];
37
+ /** concurrency 是同时派生个数,queueLimit 是额外等待个数;非法配置在启动期抛 CAGE-ENV-002。 */
38
+ constructor(concurrency = 4, queueLimit = 32) {
39
+ this.concurrency = concurrency;
40
+ this.queueLimit = queueLimit;
41
+ if (!Number.isSafeInteger(concurrency) || concurrency < 1 || !Number.isSafeInteger(queueLimit) || queueLimit < 0) {
42
+ throw new Error('[CAGE-ENV-002] passwordConcurrency 必须为正整数,passwordQueueLimit 必须为非负整数');
43
+ }
44
+ }
45
+ /** 当前进程预算快照,仅用于观测;不是跨实例的全局限流计数。 */
46
+ get state() { return { active: this.active, queued: this.waiting.length }; }
47
+ /** 先校验长度再占用执行槽;排队满抛 RATE_LIMITED,校验不匹配返回 false,派生异常继续上抛。
48
+ * finally 把槽直接移交给下一等待者,保证失败请求也会释放容量。
49
+ */
50
+ async verify(pw, stored) {
51
+ if (pw.length < 1 || pw.length > 200)
52
+ throw cageError('INVALID_INPUT', [{ path: 'password', message: '密码长度必须在 1 到 200 个字符之间' }]);
53
+ if (this.active < this.concurrency)
54
+ this.active++;
55
+ else {
56
+ if (this.waiting.length >= this.queueLimit)
57
+ throw cageError('RATE_LIMITED');
58
+ await new Promise((resolve) => this.waiting.push(resolve));
59
+ }
60
+ try {
61
+ return await comparePassword(pw, stored);
62
+ }
63
+ finally {
64
+ const next = this.waiting.shift();
65
+ if (next)
66
+ next();
67
+ else
68
+ this.active--;
69
+ }
70
+ }
71
+ }
72
+ const defaultVerifier = new PasswordVerifier();
73
+ /** 使用模块共享的默认有界校验器;HTTP 实例可另建 PasswordVerifier 使用自己的预算配置。 */
74
+ export function verifyPassword(pw, stored) {
75
+ return defaultVerifier.verify(pw, stored);
76
+ }
77
+ /** 把登录字段的格式错误统一成路径化 INVALID_INPUT;在任何密码派生前限制输入规模。 */
78
+ function loginInput(raw) {
79
+ const body = (raw ?? {});
80
+ const issues = [];
81
+ if (typeof body.username !== 'string' || body.username.length < 1 || body.username.length > 64)
82
+ issues.push({ path: 'username', message: '用户名长度必须在 1 到 64 个字符之间' });
83
+ if (typeof body.password !== 'string' || body.password.length < 1 || body.password.length > 200)
84
+ issues.push({ path: 'password', message: '密码长度必须在 1 到 200 个字符之间' });
85
+ if (issues.length)
86
+ throw cageError('INVALID_INPUT', issues);
87
+ return body;
88
+ }
89
+ /** 服务端会话持久化。调用方传入原始 sid 做验证/销毁,二次认证更新则使用已验证的数据库摘要 ID。 */
20
90
  export class Sessions {
21
91
  k;
22
92
  hours;
93
+ /** hours 为会话寿命小时数;构造函数不创建会话,也不延长已有会话。 */
23
94
  constructor(k, hours) {
24
95
  this.k = k;
25
96
  this.hours = hours;
26
97
  }
98
+ /** 为已认证用户生成 32 字节随机会话并保存摘要/到期时间;原始 sid 仅返回给 Cookie 写入方。 */
27
99
  async create(userId, tenantId, req) {
28
100
  const sid = randomBytes(32).toString('base64url');
29
101
  await this.k.app
@@ -35,17 +107,21 @@ export class Sessions {
35
107
  .execute();
36
108
  return sid;
37
109
  }
110
+ /** 只读取仍未过期的会话,未命中返回 null;用户是否禁用和当前授权由后续上下文解析复核。 */
38
111
  async verify(sid) {
39
112
  const row = await this.k.app.selectFrom('sys.session').selectAll().where('id', '=', hashSid(sid)).where('expires_at', '>', new Date()).executeTakeFirst();
40
113
  return row ? { id: row.id, userId: row.user_id, tenantId: row.tenant_id, stepUpAt: row.step_up_at ? new Date(row.step_up_at).getTime() : null } : null;
41
114
  }
115
+ /** 幂等删除该原始 Cookie 对应的会话;不存在也视为退出完成。 */
42
116
  async destroy(sid) {
43
117
  await this.k.app.deleteFrom('sys.session').where('id', '=', hashSid(sid)).execute();
44
118
  }
119
+ /** 密码复核成功后记录数据库当前时间,后续命令的 stepUp 约束据此判断认证是否足够新。 */
45
120
  async stepUp(sessionId) {
46
121
  await this.k.app.updateTable('sys.session').set({ step_up_at: sql `now()` }).where('id', '=', sessionId).execute();
47
122
  }
48
123
  }
124
+ /** 交付 HttpOnly 会话 Cookie;maxAge 单位秒,与服务端会话小时数对应。CSRF 仍需独立令牌校验。 */
49
125
  export function setSessionCookie(reply, sid, opts) {
50
126
  reply.setCookie(SESSION_COOKIE, sid, {
51
127
  httpOnly: true, // 前端脚本读不到,防 XSS 窃取
@@ -56,6 +132,9 @@ export function setSessionCookie(reply, sid, opts) {
56
132
  });
57
133
  }
58
134
  /** 认证钩子:每个请求最先执行,把会话变成上下文 */
135
+ /** 先建立匿名上下文,再用有效 Cookie 和当前授权补全身份;健康探针/验签回调不解析用户会话。
136
+ * 无效 Cookie 不在此直接抛错,让各路由按是否需要登录决定拒绝行为;请求元数据不能跨请求缓存。
137
+ */
59
138
  export function registerAuthHook(app, k, sessions, cache) {
60
139
  app.decorateRequest('cageCtx', null);
61
140
  app.decorateRequest('cageSession', null);
@@ -65,6 +144,8 @@ export function registerAuthHook(app, k, sessions, cache) {
65
144
  client: { ip: req.ip, userAgent: String(req.headers['user-agent'] ?? '').slice(0, 300) },
66
145
  };
67
146
  req.cageCtx = anonymousCtx(DEFAULT_TENANT, base);
147
+ if (req.url.split('?')[0] === '/healthz' || req.url.split('?')[0] === '/readyz')
148
+ return;
68
149
  if (req.url.startsWith('/hooks/'))
69
150
  return; // 第三方回调走签名验证,不走用户登录
70
151
  const sid = req.cookies?.[SESSION_COOKIE];
@@ -80,6 +161,7 @@ export function registerAuthHook(app, k, sessions, cache) {
80
161
  req.cageCtx = { ...ctx, ...base, stepUpAt: s.stepUpAt, source: 'api' };
81
162
  });
82
163
  }
164
+ /** 登录专用最小字段查询,包含密码摘要但不返回给前端;租户条件不可省略。 */
83
165
  export async function findLoginUser(k, username, tenantId = DEFAULT_TENANT) {
84
166
  return k.app
85
167
  .selectFrom('sys.user')
@@ -88,22 +170,28 @@ export async function findLoginUser(k, username, tenantId = DEFAULT_TENANT) {
88
170
  .where('tenant_id', '=', tenantId)
89
171
  .executeTakeFirst();
90
172
  }
173
+ /** 提供与 CSRF Cookie 配对的请求令牌;客户端后续写请求将该值放入框架要求的请求头。 */
91
174
  export function csrfRoute(app) {
92
175
  app.get('/auth/csrf', async (_req, reply) => ({ token: reply.generateCsrf() }));
93
176
  }
177
+ /** 安装密码登录、二次认证、退出和当前用户接口;由 server 的认证限流作用域承载。
178
+ * 登录失败审计尽力写入但始终拒绝登录;禁用密码登录不会禁用退出或已有会话的 /api/me。
179
+ * /api/me 公开当前能力与范围供 UI 展示,不能代替后端对下一次操作的重新授权。
180
+ */
94
181
  export function authRoutes(app, k, sessions, opts) {
182
+ const verifier = new PasswordVerifier(opts.passwordConcurrency, opts.passwordQueueLimit);
95
183
  if (opts.password !== false) {
96
184
  app.post('/auth/login', async (req, reply) => {
97
- const body = (req.body ?? {});
98
- if (typeof body.username !== 'string' || typeof body.password !== 'string')
99
- throw cageError('INVALID_INPUT');
185
+ const body = loginInput(req.body);
100
186
  const u = await findLoginUser(k, body.username);
101
- const ok = verifyPassword(body.password, u?.password_hash) && u?.status === 'active';
187
+ const ok = await verifier.verify(body.password, u?.password_hash) && u?.status === 'active';
102
188
  if (!u || !ok) {
103
189
  await k.writeAudit(k.app, {
104
190
  tenantId: DEFAULT_TENANT, actor: u?.id ?? `anonymous:${body.username.slice(0, 64)}`, actorKind: 'anonymous', source: 'api',
105
191
  kind: 'denied', command: 'auth.login', result: 'UNAUTHENTICATED', requestId: String(req.id), client: { ip: req.ip },
106
- }).catch(() => { });
192
+ }).catch((error) => {
193
+ k.log.error({ err: error, code: 'AUTH_AUDIT_FAILED', requestId: req.id }, '登录拒绝审计写入失败');
194
+ });
107
195
  throw cageError('UNAUTHENTICATED');
108
196
  }
109
197
  const sid = await sessions.create(u.id, u.tenant_id, req);
@@ -114,8 +202,10 @@ export function authRoutes(app, k, sessions, opts) {
114
202
  if (!req.cageSession)
115
203
  throw cageError('UNAUTHENTICATED');
116
204
  const pw = req.body?.password;
205
+ if (typeof pw !== 'string' || pw.length < 1 || pw.length > 200)
206
+ throw cageError('INVALID_INPUT', [{ path: 'password', message: '密码长度必须在 1 到 200 个字符之间' }]);
117
207
  const row = await k.app.selectFrom('sys.user').select('password_hash').where('id', '=', req.cageSession.userId).executeTakeFirst();
118
- if (typeof pw !== 'string' || !verifyPassword(pw, row?.password_hash))
208
+ if (!await verifier.verify(pw, row?.password_hash))
119
209
  throw cageError('UNAUTHENTICATED');
120
210
  await sessions.stepUp(req.cageSession.id);
121
211
  return { ok: true };
package/dist/auth.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,4BAA4B;AAC5B;;;GAGG;AACH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC/E,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,cAAc,EAAyB,MAAM,qBAAqB,CAAC;AAErG,OAAO,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAc7B,yCAAyC;AACzC,MAAM,CAAC,MAAM,cAAc,GAAG,UAAU,CAAC;AAkBzC,6CAA6C;AAC7C,MAAM,OAAO,GAAG,CAAC,GAAW,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAEhF,6DAA6D;AAC7D,MAAM,cAAc,GAAG,CAAC,EAAU,EAAE,IAAY,EAAmB,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;IACpG,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;AAC7E,CAAC,CAAC,CAAC;AAEH,wDAAwD;AACxD,KAAK,UAAU,eAAe,CAAC,EAAU,EAAE,MAAiC;IAC1E,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,cAAc,CAAC,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAC,0BAA0B;QACxE,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC5C,IAAI,GAAG,KAAK,QAAQ,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACrD,MAAM,GAAG,GAAG,MAAM,cAAc,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAC3C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACtC,OAAO,IAAI,CAAC,MAAM,KAAK,GAAG,CAAC,MAAM,IAAI,eAAe,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AAClE,CAAC;AAED,sCAAsC;AACtC,MAAM,OAAO,gBAAgB;IAKE;IAAkC;IAJvD,MAAM,GAAG,CAAC,CAAC;IACF,OAAO,GAAmB,EAAE,CAAC;IAE9C,qEAAqE;IACrE,YAA6B,cAAc,CAAC,EAAmB,aAAa,EAAE;QAAjD,gBAAW,GAAX,WAAW,CAAI;QAAmB,eAAU,GAAV,UAAU,CAAK;QAC5E,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,WAAW,CAAC,IAAI,WAAW,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;YACjH,MAAM,IAAI,KAAK,CAAC,sEAAsE,CAAC,CAAC;QAC1F,CAAC;IACH,CAAC;IAED,mCAAmC;IACnC,IAAI,KAAK,KAAyC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAEhH;;OAEG;IACH,KAAK,CAAC,MAAM,CAAC,EAAU,EAAE,MAAiC;QACxD,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,GAAG;YAAE,MAAM,SAAS,CAAC,eAAe,EAAE,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,uBAAuB,EAAE,CAAC,CAAC,CAAC;QACjI,IAAI,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,WAAW;YAAE,IAAI,CAAC,MAAM,EAAE,CAAC;aAC7C,CAAC;YACJ,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC,UAAU;gBAAE,MAAM,SAAS,CAAC,cAAc,CAAC,CAAC;YAC5E,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;QACnE,CAAC;QACD,IAAI,CAAC;YAAC,OAAO,MAAM,eAAe,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QAAC,CAAC;gBACzC,CAAC;YACP,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YAClC,IAAI,IAAI;gBAAE,IAAI,EAAE,CAAC;;gBACZ,IAAI,CAAC,MAAM,EAAE,CAAC;QACrB,CAAC;IACH,CAAC;CACF;AAED,MAAM,eAAe,GAAG,IAAI,gBAAgB,EAAE,CAAC;AAC/C,4DAA4D;AAC5D,MAAM,UAAU,cAAc,CAAC,EAAU,EAAE,MAAiC;IAC1E,OAAO,eAAe,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;AAC5C,CAAC;AAED,qDAAqD;AACrD,SAAS,UAAU,CAAC,GAAY;IAC9B,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,EAAE,CAA4B,CAAC;IACpD,MAAM,MAAM,GAAG,EAAE,CAAC;IAClB,IAAI,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,EAAE;QAAE,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,uBAAuB,EAAE,CAAC,CAAC;IACpK,IAAI,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,GAAG;QAAE,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,uBAAuB,EAAE,CAAC,CAAC;IACrK,IAAI,MAAM,CAAC,MAAM;QAAE,MAAM,SAAS,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC;IAC5D,OAAO,IAA8C,CAAC;AACxD,CAAC;AAED,yDAAyD;AACzD,MAAM,OAAO,QAAQ;IAEU;IAA4B;IADzD,yCAAyC;IACzC,YAA6B,CAAS,EAAmB,KAAa;QAAzC,MAAC,GAAD,CAAC,CAAQ;QAAmB,UAAK,GAAL,KAAK,CAAQ;IAAG,CAAC;IAE1E,2DAA2D;IAC3D,KAAK,CAAC,MAAM,CAAC,MAAc,EAAE,QAAgB,EAAE,GAAmB;QAChE,MAAM,GAAG,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QAClD,MAAM,IAAI,CAAC,CAAC,CAAC,GAAG;aACb,UAAU,CAAC,aAAa,CAAC;aACzB,MAAM,CAAC;YACN,EAAE,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAC;YAChH,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;SAC9E,CAAC;aACD,OAAO,EAAE,CAAC;QACb,OAAO,GAAG,CAAC;IACb,CAAC;IAED,mDAAmD;IACnD,KAAK,CAAC,MAAM,CAAC,GAAW;QACtB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,EAAE,GAAG,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC,gBAAgB,EAAE,CAAC;QAC1J,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,EAAY,EAAE,MAAM,EAAE,GAAG,CAAC,OAAiB,EAAE,QAAQ,EAAE,GAAG,CAAC,SAAmB,EAAE,QAAQ,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACvL,CAAC;IAED,uCAAuC;IACvC,KAAK,CAAC,OAAO,CAAC,GAAW;QACvB,MAAM,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;IACtF,CAAC;IAED,mDAAmD;IACnD,KAAK,CAAC,MAAM,CAAC,SAAiB;QAC5B,MAAM,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,EAAE,UAAU,EAAE,GAAG,CAAA,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC;IACpH,CAAC;CACF;AAED,kEAAkE;AAClE,MAAM,UAAU,gBAAgB,CAAC,KAAmB,EAAE,GAAW,EAAE,IAAiB;IAClF,KAAK,CAAC,SAAS,CAAC,cAAc,EAAE,GAAG,EAAE;QACnC,QAAQ,EAAE,IAAI,EAAE,mBAAmB;QACnC,MAAM,EAAE,IAAI,CAAC,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY;QAClE,QAAQ,EAAE,KAAK,EAAE,UAAU;QAC3B,IAAI,EAAE,GAAG;QACT,MAAM,EAAE,CAAC,IAAI,CAAC,YAAY,IAAI,CAAC,CAAC,GAAG,IAAI;KACxC,CAAC,CAAC;AACL,CAAC;AAED,6BAA6B;AAC7B;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAoB,EAAE,CAAS,EAAE,QAAkB,EAAE,KAAe;IACnG,GAAG,CAAC,eAAe,CAAC,SAAS,EAAE,IAAW,CAAC,CAAC;IAC5C,GAAG,CAAC,eAAe,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;IACzC,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QACrC,MAAM,IAAI,GAAiB;YACzB,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;YACzB,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE;SACzF,CAAC;QACF,GAAG,CAAC,OAAO,GAAG,YAAY,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;QACjD,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,UAAU,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,SAAS;YAAE,OAAO;QACxF,IAAI,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC;YAAE,OAAO,CAAC,oBAAoB;QAC/D,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC,cAAc,CAAC,CAAC;QAC1C,IAAI,CAAC,GAAG;YAAE,OAAO;QACjB,MAAM,CAAC,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,CAAC,CAAC;YAAE,OAAO;QACf,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC;QACtD,IAAI,CAAC,GAAG;YAAE,OAAO;QACjB,GAAG,CAAC,WAAW,GAAG,CAAC,CAAC;QACpB,GAAG,CAAC,OAAO,GAAG,EAAE,GAAG,GAAG,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IACzE,CAAC,CAAC,CAAC;AACL,CAAC;AAED,yCAAyC;AACzC,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,CAAS,EAAE,QAAgB,EAAE,QAAQ,GAAG,cAAc;IACxF,OAAO,CAAC,CAAC,GAAG;SACT,UAAU,CAAC,UAAU,CAAC;SACtB,MAAM,CAAC,CAAC,IAAI,EAAE,WAAW,EAAE,eAAe,EAAE,QAAQ,CAAC,CAAC;SACtD,KAAK,CAAC,UAAU,EAAE,GAAG,EAAE,QAAQ,CAAC;SAChC,KAAK,CAAC,WAAW,EAAE,GAAG,EAAE,QAAQ,CAAC;SACjC,gBAAgB,EAAE,CAAC;AACxB,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,SAAS,CAAC,GAAoB;IAC5C,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,CAAC;AAClF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,GAAoB,EAAE,CAAS,EAAE,QAAkB,EAAE,IAAiB;IAC/F,MAAM,QAAQ,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC,mBAAmB,EAAE,IAAI,CAAC,kBAAkB,CAAC,CAAC;IAEzF,IAAI,IAAI,CAAC,QAAQ,KAAK,KAAK,EAAE,CAAC;QAC5B,GAAG,CAAC,IAAI,CAAC,aAAa,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;YAC3C,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAClC,MAAM,CAAC,GAAG,MAAM,aAAa,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;YAChD,MAAM,EAAE,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,EAAE,aAAa,CAAC,IAAI,CAAC,EAAE,MAAM,KAAK,QAAQ,CAAC;YAC5F,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;gBACd,MAAM,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,EAAE;oBACxB,QAAQ,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,IAAI,aAAa,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK;oBAC1H,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE;iBACpH,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;oBACjB,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,mBAAmB,EAAE,SAAS,EAAE,GAAG,CAAC,EAAE,EAAE,EAAE,YAAY,CAAC,CAAC;gBAC1F,CAAC,CAAC,CAAC;gBACH,MAAM,SAAS,CAAC,iBAAiB,CAAC,CAAC;YACrC,CAAC;YACD,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;YAC1D,gBAAgB,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;YACnC,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;QACtB,CAAC,CAAC,CAAC;QAEH,GAAG,CAAC,IAAI,CAAC,eAAe,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YACtC,IAAI,CAAC,GAAG,CAAC,WAAW;gBAAE,MAAM,SAAS,CAAC,iBAAiB,CAAC,CAAC;YACzD,MAAM,EAAE,GAAI,GAAG,CAAC,IAAY,EAAE,QAAQ,CAAC;YACvC,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,GAAG;gBAAE,MAAM,SAAS,CAAC,eAAe,EAAE,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,uBAAuB,EAAE,CAAC,CAAC,CAAC;YAC3J,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,gBAAgB,EAAE,CAAC;YACnI,IAAI,CAAC,MAAM,QAAQ,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,EAAE,aAAa,CAAC;gBAAE,MAAM,SAAS,CAAC,iBAAiB,CAAC,CAAC;YACvF,MAAM,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;YAC1C,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;QACtB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,GAAG,CAAC,IAAI,CAAC,cAAc,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;QAC5C,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC,cAAc,CAAC,CAAC;QAC1C,IAAI,GAAG;YAAE,MAAM,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACrC,KAAK,CAAC,WAAW,CAAC,cAAc,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QACjD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QAC/B,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC;QACxB,IAAI,CAAC,GAAG,CAAC,MAAM;YAAE,MAAM,SAAS,CAAC,iBAAiB,CAAC,CAAC;QACpD,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,gBAAgB,EAAE,CAAC;QAC3I,OAAO;YACL,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI;YACpF,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,YAAY,EAAE,CAAC,GAAG,GAAG,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;YACjD,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,QAAQ,EAAE,GAAG,CAAC,QAAQ,IAAI,IAAI;SAC/B,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
package/dist/ctx.d.ts CHANGED
@@ -1,13 +1,30 @@
1
1
  import type { Ctx, Kernel } from '@yss123/cage-kernel';
2
+ /** 跨请求复用用户授权上下文,减少正常流量下反复查询组织表的开销。
3
+ * PostgreSQL 通知用于主动失效;监听不健康时缓存失效并逐请求读库,避免撤权后继续使用旧授权。
4
+ * generation 让通知前发起、通知后完成的查询无法把旧结果重新写回缓存。
5
+ */
2
6
  export declare class CtxCache {
3
7
  private readonly kernel;
4
8
  private readonly cache;
5
9
  private listener;
6
10
  private stopped;
11
+ private connected;
12
+ private generation;
13
+ private retry;
14
+ private connecting;
15
+ /** 复用服务的内核和授权解析,不创建额外业务连接池;监听另用专用连接。 */
7
16
  constructor(kernel: Kernel);
17
+ /** 仅代表撤权通知链路可用且尚未停止;数据库读写就绪由服务探针分别检查。 */
18
+ get healthy(): boolean;
19
+ /** 按租户和用户查当前授权;不存在返回 null,禁用用户无有效能力,监听失效时不读取/填充缓存。 */
8
20
  resolve(userId: string, tenantId: string): Promise<Ctx | null>;
21
+ /** 通知载荷为用户 ID 或 *;用户 ID 会清除所有匹配租户缓存,且推进查询代次。 */
9
22
  invalidate(payload: string): void;
10
- /** 进程启动时调用一次;连接断开后自动重连 */
23
+ /** 建立单个专用 LISTEN 连接,并合并并发连接请求;失败后每五秒重试。
24
+ * 断线立即切到逐请求授权读取,重连成功才恢复缓存,不能把重试中的状态视为健康。
25
+ * 重连成功后也清空缓存,覆盖断线期间未收到通知的授权变化。
26
+ */
11
27
  listen(appUrl: string): Promise<void>;
28
+ /** 先禁止重连和缓存命中,再等待连接尝试并关闭监听;适合服务停止阶段调用。 */
12
29
  close(): Promise<void>;
13
30
  }
package/dist/ctx.js CHANGED
@@ -1,24 +1,40 @@
1
1
  import { LRUCache } from 'lru-cache';
2
2
  import pg from 'pg';
3
+ /** 跨请求复用用户授权上下文,减少正常流量下反复查询组织表的开销。
4
+ * PostgreSQL 通知用于主动失效;监听不健康时缓存失效并逐请求读库,避免撤权后继续使用旧授权。
5
+ * generation 让通知前发起、通知后完成的查询无法把旧结果重新写回缓存。
6
+ */
3
7
  export class CtxCache {
4
8
  kernel;
5
9
  cache = new LRUCache({ max: 5000, ttl: 5 * 60_000 });
6
10
  listener = null;
7
11
  stopped = false;
12
+ connected = false;
13
+ generation = 0;
14
+ retry = null;
15
+ connecting = null;
16
+ /** 复用服务的内核和授权解析,不创建额外业务连接池;监听另用专用连接。 */
8
17
  constructor(kernel) {
9
18
  this.kernel = kernel;
10
19
  }
20
+ /** 仅代表撤权通知链路可用且尚未停止;数据库读写就绪由服务探针分别检查。 */
21
+ get healthy() { return this.connected && !this.stopped; }
22
+ /** 按租户和用户查当前授权;不存在返回 null,禁用用户无有效能力,监听失效时不读取/填充缓存。 */
11
23
  async resolve(userId, tenantId) {
12
24
  const key = `${tenantId}:${userId}`;
13
- const hit = this.cache.get(key);
25
+ const generation = this.generation;
26
+ const hit = this.healthy ? this.cache.get(key) : undefined;
14
27
  if (hit)
15
28
  return hit;
16
29
  const ctx = await this.kernel.contextFor(userId, tenantId);
17
- if (ctx)
30
+ // 解析期间收到撤权通知或发生断线,不能把旧的查询结果重新放回缓存。
31
+ if (ctx && this.healthy && generation === this.generation)
18
32
  this.cache.set(key, ctx);
19
33
  return ctx;
20
34
  }
35
+ /** 通知载荷为用户 ID 或 *;用户 ID 会清除所有匹配租户缓存,且推进查询代次。 */
21
36
  invalidate(payload) {
37
+ this.generation++;
22
38
  if (payload === '*')
23
39
  this.cache.clear();
24
40
  else
@@ -26,32 +42,67 @@ export class CtxCache {
26
42
  if (k.endsWith(`:${payload}`))
27
43
  this.cache.delete(k);
28
44
  }
29
- /** 进程启动时调用一次;连接断开后自动重连 */
45
+ /** 建立单个专用 LISTEN 连接,并合并并发连接请求;失败后每五秒重试。
46
+ * 断线立即切到逐请求授权读取,重连成功才恢复缓存,不能把重试中的状态视为健康。
47
+ * 重连成功后也清空缓存,覆盖断线期间未收到通知的授权变化。
48
+ */
30
49
  async listen(appUrl) {
50
+ if (this.stopped || this.healthy)
51
+ return;
52
+ if (this.connecting)
53
+ return this.connecting;
31
54
  const connect = async () => {
32
- if (this.stopped)
33
- return;
55
+ const client = new pg.Client({ connectionString: appUrl, application_name: 'cage-grants-listen', connectionTimeoutMillis: 5000 });
56
+ let failed = false;
57
+ const disconnected = () => {
58
+ if (failed)
59
+ return;
60
+ failed = true;
61
+ if (this.listener === client)
62
+ this.listener = null;
63
+ this.connected = false;
64
+ this.invalidate('*');
65
+ void client.end().catch(() => { });
66
+ if (!this.stopped && !this.retry) {
67
+ this.retry = setTimeout(() => {
68
+ this.retry = null;
69
+ void this.listen(appUrl);
70
+ }, 5000);
71
+ this.retry.unref();
72
+ }
73
+ };
74
+ client.on('error', disconnected);
75
+ client.on('end', disconnected);
76
+ client.on('notification', (notification) => this.invalidate(notification.payload ?? '*'));
34
77
  try {
35
- const c = new pg.Client({ connectionString: appUrl, application_name: 'cage-grants-listen' });
36
- await c.connect();
37
- await c.query('LISTEN cage_grants');
38
- c.on('notification', (n) => this.invalidate(n.payload ?? '*'));
39
- c.on('error', () => {
40
- this.cache.clear();
41
- setTimeout(() => void connect(), 5000);
42
- });
43
- this.listener = c;
78
+ await client.connect();
79
+ await client.query('LISTEN cage_grants');
80
+ if (this.stopped || failed) {
81
+ await client.end();
82
+ return;
83
+ }
84
+ this.invalidate('*');
85
+ this.listener = client;
86
+ this.connected = true;
44
87
  }
45
- catch (e) {
46
- this.kernel.log.warn({ err: String(e) }, '授权变更监听连接失败,5 秒后重试');
47
- setTimeout(() => void connect(), 5000);
88
+ catch (error) {
89
+ this.kernel.log.warn({ err: error }, '授权变更监听连接失败,实时复核授权并在 5 秒后重试');
90
+ disconnected();
48
91
  }
49
92
  };
50
- await connect();
93
+ this.connecting = connect().finally(() => { this.connecting = null; });
94
+ return this.connecting;
51
95
  }
96
+ /** 先禁止重连和缓存命中,再等待连接尝试并关闭监听;适合服务停止阶段调用。 */
52
97
  async close() {
53
98
  this.stopped = true;
54
- await this.listener?.end().catch(() => { });
99
+ this.connected = false;
100
+ this.invalidate('*');
101
+ if (this.retry)
102
+ clearTimeout(this.retry);
103
+ await this.connecting;
104
+ await this.listener?.end();
105
+ this.listener = null;
55
106
  }
56
107
  }
57
108
  //# sourceMappingURL=ctx.js.map
package/dist/ctx.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ctx.js","sourceRoot":"","sources":["../src/ctx.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ctx.js","sourceRoot":"","sources":["../src/ctx.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import { type FileService } from '@yss123/cage-files';
2
+ import type { FastifyInstance } from 'fastify';
3
+ import type { RouteInfo } from './routes.js';
4
+ /** 上传会话与私有下载的协议清单;具体授权依赖会话和目标对象,无法用单个固定 capability 表达。 */
5
+ export declare const fileRoutes: RouteInfo[];
6
+ /**
7
+ * 将 HTTP 字节流交给 FileService,统一复用当前请求的登录上下文和全局 CSRF 保护。
8
+ * 分片要求 Content-Length 与 SHA-256,长度上限使用服务配置;准确字节数和摘要由存储层校验。
9
+ * 下载仅支持一个闭区间 bytes 范围,非法范围返回 416;有效范围返回 206 和准确 Content-Length。
10
+ * 元数据读取及实际下载分别复核访问条件,FileService 在交付前记录审计;响应禁止共享缓存并强制附件下载。
11
+ */
12
+ export declare function registerFiles(app: FastifyInstance, files: FileService): Promise<void>;