@yss123/cage-http 0.0.0-stage → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 shunshunyue and Cage contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,19 @@
1
- # Temporary Holding Version
1
+ # @yss123/cage-http
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 笼子 HTTP 层:Fastify 插件、路由生成、认证、安全基线
4
+
5
+ Cage 是通过 TypeScript 声明业务、由框架统一执行权限和数据规则的中后台框架。此包是框架的一部分。
6
+
7
+ ## 使用
8
+
9
+ ```powershell
10
+ pnpm add @yss123/cage-http
11
+ ```
12
+
13
+ 首次使用建议通过脚手架创建完整项目,所需依赖由模板统一配置。需要 Node.js 24 或更新版本。
14
+
15
+ - [完整使用说明](https://github.com/shunshunyue/cage/blob/main/docs/使用说明.md)
16
+ - [框架源码与范例](https://github.com/shunshunyue/cage)
17
+ - [问题反馈](https://github.com/shunshunyue/cage/issues)
18
+
19
+ 许可证:[MIT](https://github.com/shunshunyue/cage/blob/main/LICENSE)。
package/dist/auth.d.ts ADDED
@@ -0,0 +1,58 @@
1
+ import { type Ctx, type Kernel } from '@yss123/cage-kernel';
2
+ import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
3
+ import type { CtxCache } from './ctx.js';
4
+ declare module 'fastify' {
5
+ interface FastifyRequest {
6
+ cageCtx: Ctx;
7
+ cageSession: {
8
+ id: string;
9
+ userId: string;
10
+ tenantId: string;
11
+ stepUpAt: number | null;
12
+ } | null;
13
+ rawBody?: string;
14
+ }
15
+ }
16
+ export declare const SESSION_COOKIE = "cage_sid";
17
+ export interface AuthOptions {
18
+ /** 会话时长(小时),默认 8 */
19
+ sessionHours?: number;
20
+ /** cookie 只走 HTTPS;默认生产环境开启 */
21
+ cookieSecure?: boolean;
22
+ /** 是否开放账号密码登录,默认开启 */
23
+ password?: boolean;
24
+ oidc?: {
25
+ issuer: string;
26
+ clientId: string;
27
+ clientSecret: string;
28
+ redirectUri: string;
29
+ scope?: string;
30
+ deptClaim?: string;
31
+ };
32
+ }
33
+ export declare function verifyPassword(pw: string, stored: string | null | undefined): boolean;
34
+ export declare class Sessions {
35
+ private readonly k;
36
+ private readonly hours;
37
+ constructor(k: Kernel, hours: number);
38
+ create(userId: string, tenantId: string, req: FastifyRequest): Promise<string>;
39
+ verify(sid: string): Promise<{
40
+ id: string;
41
+ userId: string;
42
+ tenantId: string;
43
+ stepUpAt: number | null;
44
+ } | null>;
45
+ destroy(sid: string): Promise<void>;
46
+ stepUp(sessionId: string): Promise<void>;
47
+ }
48
+ export declare function setSessionCookie(reply: FastifyReply, sid: string, opts: AuthOptions): void;
49
+ /** 认证钩子:每个请求最先执行,把会话变成上下文 */
50
+ export declare function registerAuthHook(app: FastifyInstance, k: Kernel, sessions: Sessions, cache: CtxCache): void;
51
+ export declare function findLoginUser(k: Kernel, username: string, tenantId?: string): Promise<{
52
+ tenant_id: any;
53
+ id: any;
54
+ password_hash: any;
55
+ status: any;
56
+ } | undefined>;
57
+ export declare function csrfRoute(app: FastifyInstance): void;
58
+ export declare function authRoutes(app: FastifyInstance, k: Kernel, sessions: Sessions, opts: AuthOptions): void;
package/dist/auth.js ADDED
@@ -0,0 +1,145 @@
1
+ // 认证:服务端会话 + 会话 cookie。优先对接公司 SSO(OIDC,见 oidc.ts),没有 SSO 时用账号密码加二次认证。
2
+ // 之后每个请求都由认证钩子把会话变成上下文 ctx。
3
+ import { createHash, randomBytes, scryptSync, timingSafeEqual } from 'node:crypto';
4
+ import { anonymousCtx, cageError, DEFAULT_TENANT } from '@yss123/cage-kernel';
5
+ import { sql } from 'kysely';
6
+ export const SESSION_COOKIE = 'cage_sid';
7
+ const hashSid = (sid) => createHash('sha256').update(sid).digest('hex');
8
+ export function verifyPassword(pw, stored) {
9
+ if (!stored) {
10
+ scryptSync(pw, 'timing-equalizer', 32); // 用户不存在时也做一次哈希,避免按耗时探测用户名
11
+ return false;
12
+ }
13
+ const [alg, salt, hash] = stored.split('$');
14
+ if (alg !== 'scrypt' || !salt || !hash)
15
+ return false;
16
+ const got = scryptSync(pw, salt, 32);
17
+ const want = Buffer.from(hash, 'hex');
18
+ return want.length === got.length && timingSafeEqual(got, want);
19
+ }
20
+ export class Sessions {
21
+ k;
22
+ hours;
23
+ constructor(k, hours) {
24
+ this.k = k;
25
+ this.hours = hours;
26
+ }
27
+ async create(userId, tenantId, req) {
28
+ const sid = randomBytes(32).toString('base64url');
29
+ await this.k.app
30
+ .insertInto('sys.session')
31
+ .values({
32
+ id: hashSid(sid), user_id: userId, tenant_id: tenantId, expires_at: new Date(Date.now() + this.hours * 3600_000),
33
+ ip: req.ip, user_agent: String(req.headers['user-agent'] ?? '').slice(0, 300),
34
+ })
35
+ .execute();
36
+ return sid;
37
+ }
38
+ async verify(sid) {
39
+ const row = await this.k.app.selectFrom('sys.session').selectAll().where('id', '=', hashSid(sid)).where('expires_at', '>', new Date()).executeTakeFirst();
40
+ 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
+ }
42
+ async destroy(sid) {
43
+ await this.k.app.deleteFrom('sys.session').where('id', '=', hashSid(sid)).execute();
44
+ }
45
+ async stepUp(sessionId) {
46
+ await this.k.app.updateTable('sys.session').set({ step_up_at: sql `now()` }).where('id', '=', sessionId).execute();
47
+ }
48
+ }
49
+ export function setSessionCookie(reply, sid, opts) {
50
+ reply.setCookie(SESSION_COOKIE, sid, {
51
+ httpOnly: true, // 前端脚本读不到,防 XSS 窃取
52
+ secure: opts.cookieSecure ?? process.env.NODE_ENV === 'production',
53
+ sameSite: 'lax', // 防跨站请求伪造
54
+ path: '/',
55
+ maxAge: (opts.sessionHours ?? 8) * 3600,
56
+ });
57
+ }
58
+ /** 认证钩子:每个请求最先执行,把会话变成上下文 */
59
+ export function registerAuthHook(app, k, sessions, cache) {
60
+ app.decorateRequest('cageCtx', null);
61
+ app.decorateRequest('cageSession', null);
62
+ app.addHook('onRequest', async (req) => {
63
+ const base = {
64
+ requestId: String(req.id),
65
+ client: { ip: req.ip, userAgent: String(req.headers['user-agent'] ?? '').slice(0, 300) },
66
+ };
67
+ req.cageCtx = anonymousCtx(DEFAULT_TENANT, base);
68
+ if (req.url.startsWith('/hooks/'))
69
+ return; // 第三方回调走签名验证,不走用户登录
70
+ const sid = req.cookies?.[SESSION_COOKIE];
71
+ if (!sid)
72
+ return;
73
+ const s = await sessions.verify(sid);
74
+ if (!s)
75
+ return;
76
+ const ctx = await cache.resolve(s.userId, s.tenantId);
77
+ if (!ctx)
78
+ return;
79
+ req.cageSession = s;
80
+ req.cageCtx = { ...ctx, ...base, stepUpAt: s.stepUpAt, source: 'api' };
81
+ });
82
+ }
83
+ export async function findLoginUser(k, username, tenantId = DEFAULT_TENANT) {
84
+ return k.app
85
+ .selectFrom('sys.user')
86
+ .select(['id', 'tenant_id', 'password_hash', 'status'])
87
+ .where('username', '=', username)
88
+ .where('tenant_id', '=', tenantId)
89
+ .executeTakeFirst();
90
+ }
91
+ export function csrfRoute(app) {
92
+ app.get('/auth/csrf', async (_req, reply) => ({ token: reply.generateCsrf() }));
93
+ }
94
+ export function authRoutes(app, k, sessions, opts) {
95
+ if (opts.password !== false) {
96
+ 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');
100
+ const u = await findLoginUser(k, body.username);
101
+ const ok = verifyPassword(body.password, u?.password_hash) && u?.status === 'active';
102
+ if (!u || !ok) {
103
+ await k.writeAudit(k.app, {
104
+ tenantId: DEFAULT_TENANT, actor: u?.id ?? `anonymous:${body.username.slice(0, 64)}`, actorKind: 'anonymous', source: 'api',
105
+ kind: 'denied', command: 'auth.login', result: 'UNAUTHENTICATED', requestId: String(req.id), client: { ip: req.ip },
106
+ }).catch(() => { });
107
+ throw cageError('UNAUTHENTICATED');
108
+ }
109
+ const sid = await sessions.create(u.id, u.tenant_id, req);
110
+ setSessionCookie(reply, sid, opts);
111
+ return { ok: true };
112
+ });
113
+ app.post('/auth/step-up', async (req) => {
114
+ if (!req.cageSession)
115
+ throw cageError('UNAUTHENTICATED');
116
+ const pw = req.body?.password;
117
+ 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))
119
+ throw cageError('UNAUTHENTICATED');
120
+ await sessions.stepUp(req.cageSession.id);
121
+ return { ok: true };
122
+ });
123
+ }
124
+ app.post('/auth/logout', async (req, reply) => {
125
+ const sid = req.cookies?.[SESSION_COOKIE];
126
+ if (sid)
127
+ await sessions.destroy(sid);
128
+ reply.clearCookie(SESSION_COOKIE, { path: '/' });
129
+ return { ok: true };
130
+ });
131
+ app.get('/api/me', async (req) => {
132
+ const ctx = req.cageCtx;
133
+ if (!ctx.userId)
134
+ throw cageError('UNAUTHENTICATED');
135
+ const u = await k.app.selectFrom('sys.user').select(['id', 'username', 'name', 'dept_id']).where('id', '=', ctx.userId).executeTakeFirst();
136
+ return {
137
+ user: u ? { id: u.id, username: u.username, name: u.name, deptId: u.dept_id } : null,
138
+ tenantId: ctx.tenantId,
139
+ capabilities: [...ctx.capabilities.keys()].sort(),
140
+ scopes: ctx.scopes,
141
+ stepUpAt: ctx.stepUpAt ?? null,
142
+ };
143
+ });
144
+ }
145
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
package/dist/ctx.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import type { Ctx, Kernel } from '@yss123/cage-kernel';
2
+ export declare class CtxCache {
3
+ private readonly kernel;
4
+ private readonly cache;
5
+ private listener;
6
+ private stopped;
7
+ constructor(kernel: Kernel);
8
+ resolve(userId: string, tenantId: string): Promise<Ctx | null>;
9
+ invalidate(payload: string): void;
10
+ /** 进程启动时调用一次;连接断开后自动重连 */
11
+ listen(appUrl: string): Promise<void>;
12
+ close(): Promise<void>;
13
+ }
package/dist/ctx.js ADDED
@@ -0,0 +1,57 @@
1
+ import { LRUCache } from 'lru-cache';
2
+ import pg from 'pg';
3
+ export class CtxCache {
4
+ kernel;
5
+ cache = new LRUCache({ max: 5000, ttl: 5 * 60_000 });
6
+ listener = null;
7
+ stopped = false;
8
+ constructor(kernel) {
9
+ this.kernel = kernel;
10
+ }
11
+ async resolve(userId, tenantId) {
12
+ const key = `${tenantId}:${userId}`;
13
+ const hit = this.cache.get(key);
14
+ if (hit)
15
+ return hit;
16
+ const ctx = await this.kernel.contextFor(userId, tenantId);
17
+ if (ctx)
18
+ this.cache.set(key, ctx);
19
+ return ctx;
20
+ }
21
+ invalidate(payload) {
22
+ if (payload === '*')
23
+ this.cache.clear();
24
+ else
25
+ for (const k of [...this.cache.keys()])
26
+ if (k.endsWith(`:${payload}`))
27
+ this.cache.delete(k);
28
+ }
29
+ /** 进程启动时调用一次;连接断开后自动重连 */
30
+ async listen(appUrl) {
31
+ const connect = async () => {
32
+ if (this.stopped)
33
+ return;
34
+ 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;
44
+ }
45
+ catch (e) {
46
+ this.kernel.log.warn({ err: String(e) }, '授权变更监听连接失败,5 秒后重试');
47
+ setTimeout(() => void connect(), 5000);
48
+ }
49
+ };
50
+ await connect();
51
+ }
52
+ async close() {
53
+ this.stopped = true;
54
+ await this.listener?.end().catch(() => { });
55
+ }
56
+ }
57
+ //# sourceMappingURL=ctx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ctx.js","sourceRoot":"","sources":["../src/ctx.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export { createServer, type ServerOptions, type CageServer } from './server.js';
2
+ export { registerRoutes, routeTable, commandUrl, type RouteInfo } from './routes.js';
3
+ export { buildOpenApi } from './openapi.js';
4
+ export { buildLogger, sensitiveFieldPaths } from './logger.js';
5
+ export { CtxCache } from './ctx.js';
6
+ export { Sessions, verifyPassword, SESSION_COOKIE, type AuthOptions } from './auth.js';
7
+ export { upsertUserFromSso } from './oidc.js';
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ // @yss123/cage-http:Fastify 插件、路由生成、认证、安全基线。
2
+ export { createServer } from './server.js';
3
+ export { registerRoutes, routeTable, commandUrl } from './routes.js';
4
+ export { buildOpenApi } from './openapi.js';
5
+ export { buildLogger, sensitiveFieldPaths } from './logger.js';
6
+ export { CtxCache } from './ctx.js';
7
+ export { Sessions, verifyPassword, SESSION_COOKIE } from './auth.js';
8
+ export { upsertUserFromSso } from './oidc.js';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,OAAO,EAAE,YAAY,EAAuC,MAAM,aAAa,CAAC;AAChF,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,UAAU,EAAkB,MAAM,aAAa,CAAC;AACrF,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,cAAc,EAAoB,MAAM,WAAW,CAAC;AACvF,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Registry } from '@yss123/cage-kernel';
2
+ export declare function sensitiveFieldPaths(reg: Registry): string[];
3
+ export declare function buildLogger(reg: Registry): object;
package/dist/logger.js ADDED
@@ -0,0 +1,27 @@
1
+ export function sensitiveFieldPaths(reg) {
2
+ const names = new Set();
3
+ for (const a of reg.aggregates) {
4
+ for (const [k, f] of Object.entries(a.fields))
5
+ if (f.sensitiveKind || f.isHidden)
6
+ names.add(k);
7
+ }
8
+ for (const e of reg.events) {
9
+ for (const [k, f] of Object.entries(e.fields))
10
+ if (f.sensitiveKind)
11
+ names.add(k);
12
+ }
13
+ return [...names].flatMap((n) => [`*.${n}`, `req.body.${n}`]);
14
+ }
15
+ export function buildLogger(reg) {
16
+ return {
17
+ level: process.env.LOG_LEVEL ?? 'info',
18
+ redact: {
19
+ paths: [
20
+ 'req.headers.cookie', 'req.headers.authorization', 'req.headers["x-csrf-token"]', 'res.headers["set-cookie"]',
21
+ 'req.body.password', '*.password', ...sensitiveFieldPaths(reg),
22
+ ],
23
+ censor: '***',
24
+ },
25
+ };
26
+ }
27
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,mBAAmB,CAAC,GAAa;IAC/C,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;QAC/B,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAgB,CAAC;YAAE,IAAI,CAAC,CAAC,aAAa,IAAI,CAAC,CAAC,QAAQ;gBAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAC3G,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;QAC3B,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAgB,CAAC;YAAE,IAAI,CAAC,CAAC,aAAa;gBAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAC7F,CAAC;IACD,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC;AAChE,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,GAAa;IACvC,OAAO;QACL,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,MAAM;QACtC,MAAM,EAAE;YACN,KAAK,EAAE;gBACL,oBAAoB,EAAE,2BAA2B,EAAE,6BAA6B,EAAE,2BAA2B;gBAC7G,mBAAmB,EAAE,YAAY,EAAE,GAAG,mBAAmB,CAAC,GAAG,CAAC;aAC/D;YACD,MAAM,EAAE,KAAK;SACd;KACF,CAAC;AACJ,CAAC"}
package/dist/oidc.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { type Kernel } from '@yss123/cage-kernel';
2
+ import type { FastifyInstance } from 'fastify';
3
+ import { type AuthOptions, type Sessions } from './auth.js';
4
+ export declare function oidcRoutes(app: FastifyInstance, k: Kernel, sessions: Sessions, opts: AuthOptions): void;
5
+ export declare function upsertUserFromSso(k: Kernel, claims: Record<string, any>, deptClaim?: string): Promise<{
6
+ id: string;
7
+ }>;
package/dist/oidc.js ADDED
@@ -0,0 +1,83 @@
1
+ // SSO 登录(OIDC 授权码 + PKCE)。首次登录创建用户(默认没有任何角色),之后更新姓名和部门。
2
+ // 创建和更新用户都是命令,以 external 身份调用,同样被鉴权、写审计。
3
+ import { createHash, randomBytes } from 'node:crypto';
4
+ import { cageError, DEFAULT_TENANT, systemCtx } from '@yss123/cage-kernel';
5
+ import { setSessionCookie } from './auth.js';
6
+ export function oidcRoutes(app, k, sessions, opts) {
7
+ const o = opts.oidc;
8
+ if (!o)
9
+ return;
10
+ let disco = null;
11
+ const discover = () => (disco ??= fetch(new URL('.well-known/openid-configuration', o.issuer.endsWith('/') ? o.issuer : `${o.issuer}/`)).then(async (r) => {
12
+ if (!r.ok)
13
+ throw new Error(`[CAGE-AUTH-001] OIDC 发现文档获取失败:${r.status}`);
14
+ return (await r.json());
15
+ }));
16
+ const secure = opts.cookieSecure ?? process.env.NODE_ENV === 'production';
17
+ app.get('/auth/sso', async (_req, reply) => {
18
+ const d = await discover();
19
+ const state = randomBytes(16).toString('base64url');
20
+ const verifier = randomBytes(32).toString('base64url');
21
+ const challenge = createHash('sha256').update(verifier).digest('base64url');
22
+ reply.setCookie('cage_oidc', JSON.stringify({ state, verifier }), { httpOnly: true, secure, sameSite: 'lax', path: '/auth', maxAge: 600, signed: true });
23
+ const u = new URL(d.authorization_endpoint);
24
+ u.searchParams.set('response_type', 'code');
25
+ u.searchParams.set('client_id', o.clientId);
26
+ u.searchParams.set('redirect_uri', o.redirectUri);
27
+ u.searchParams.set('scope', o.scope ?? 'openid profile email');
28
+ u.searchParams.set('state', state);
29
+ u.searchParams.set('code_challenge', challenge);
30
+ u.searchParams.set('code_challenge_method', 'S256');
31
+ return reply.redirect(u.toString());
32
+ });
33
+ app.get('/auth/callback', async (req, reply) => {
34
+ const q = req.query;
35
+ const raw = req.cookies['cage_oidc'];
36
+ const unsigned = raw ? req.unsignCookie(raw) : null;
37
+ const saved = unsigned?.valid && unsigned.value ? JSON.parse(unsigned.value) : null;
38
+ if (!q.code || !saved || saved.state !== q.state)
39
+ throw cageError('UNAUTHENTICATED');
40
+ reply.clearCookie('cage_oidc', { path: '/auth' });
41
+ const d = await discover();
42
+ const tok = await fetch(d.token_endpoint, {
43
+ method: 'POST',
44
+ headers: { 'content-type': 'application/x-www-form-urlencoded' },
45
+ body: new URLSearchParams({
46
+ grant_type: 'authorization_code', code: q.code, redirect_uri: o.redirectUri,
47
+ client_id: o.clientId, client_secret: o.clientSecret, code_verifier: saved.verifier,
48
+ }),
49
+ });
50
+ if (!tok.ok)
51
+ throw cageError('UNAUTHENTICATED');
52
+ const { access_token } = (await tok.json());
53
+ const info = await fetch(d.userinfo_endpoint, { headers: { authorization: `Bearer ${access_token}` } });
54
+ if (!info.ok)
55
+ throw cageError('UNAUTHENTICATED');
56
+ const claims = (await info.json());
57
+ const user = await upsertUserFromSso(k, claims, o.deptClaim);
58
+ const sid = await sessions.create(user.id, DEFAULT_TENANT, req);
59
+ setSessionCookie(reply, sid, opts);
60
+ return reply.redirect('/');
61
+ });
62
+ }
63
+ export async function upsertUserFromSso(k, claims, deptClaim = 'department') {
64
+ const tenantId = DEFAULT_TENANT;
65
+ const ctx = systemCtx({ kind: 'external', name: 'sso', tenantId, capabilities: ['User.create', 'User.update'] });
66
+ const subject = String(claims.sub);
67
+ const username = String(claims.preferred_username ?? claims.email ?? subject).slice(0, 64);
68
+ const name = String(claims.name ?? username).slice(0, 100);
69
+ const deptCode = claims[deptClaim];
70
+ const dept = (deptCode && (await k.app.selectFrom('sys.dept').select('id').where('code', '=', String(deptCode)).where('tenant_id', '=', tenantId).executeTakeFirst())) ||
71
+ (await k.app.selectFrom('sys.dept').select('id').where('code', '=', 'root').where('tenant_id', '=', tenantId).executeTakeFirstOrThrow());
72
+ const existing = await k.app.selectFrom('sys.user').select(['id', 'status']).where('tenant_id', '=', tenantId)
73
+ .where((eb) => eb.or([eb('sso_subject', '=', subject), eb('username', '=', username)])).executeTakeFirst();
74
+ if (existing) {
75
+ if (existing.status !== 'active')
76
+ throw cageError('UNAUTHENTICATED');
77
+ await k.execute('User.update', { id: existing.id, name, deptId: dept.id }, ctx);
78
+ return { id: existing.id };
79
+ }
80
+ const u = await k.execute('User.create', { username, name, deptId: dept.id, ssoSubject: subject, email: claims.email ?? null }, ctx);
81
+ return { id: u.id };
82
+ }
83
+ //# sourceMappingURL=oidc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oidc.js","sourceRoot":"","sources":["../src/oidc.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { type Kernel } from '@yss123/cage-kernel';
2
+ import type { RouteInfo } from './routes.js';
3
+ export declare function buildOpenApi(k: Kernel, routes: RouteInfo[], info?: {
4
+ title?: string;
5
+ version?: string;
6
+ }): {
7
+ openapi: string;
8
+ info: {
9
+ title: string;
10
+ version: string;
11
+ };
12
+ paths: Record<string, Record<string, unknown>>;
13
+ components: {
14
+ schemas: Record<string, unknown>;
15
+ };
16
+ };
@@ -0,0 +1,49 @@
1
+ // 启动时根据注册表输出一份 OpenAPI 文档;前端 SDK 由 cage gen 从声明里的 Zod schema 生成。
2
+ import { hiddenFields } from '@yss123/cage-kernel';
3
+ import { z } from 'zod';
4
+ const ERR = { $ref: '#/components/schemas/CageError' };
5
+ function schemaOf(s) {
6
+ try {
7
+ return z.toJSONSchema(s, { unrepresentable: 'any', io: 'input' });
8
+ }
9
+ catch {
10
+ return {};
11
+ }
12
+ }
13
+ export function buildOpenApi(k, routes, info = {}) {
14
+ const schemas = {
15
+ CageError: {
16
+ type: 'object',
17
+ properties: { code: { type: 'string' }, domainCode: { type: 'string' }, detail: {} },
18
+ required: ['code'],
19
+ },
20
+ };
21
+ for (const a of k.registry.aggregates) {
22
+ const props = { id: { type: 'string', format: 'uuid' }, status: { type: 'string', enum: [...a.states.values] }, version: { type: 'integer' }, createdAt: { type: 'string' }, updatedAt: { type: 'string' } };
23
+ const hidden = new Set(hiddenFields(a));
24
+ const js = schemaOf(a.schema);
25
+ for (const [f, p] of Object.entries(js.properties ?? {}))
26
+ if (!hidden.has(f))
27
+ props[f] = a.fields[f]?.sensitiveKind ? { type: ['string', 'null'], description: '敏感字段:按权限返回掩码值' } : p;
28
+ schemas[a.name] = { type: 'object', properties: props };
29
+ }
30
+ const paths = {};
31
+ for (const r of routes) {
32
+ const url = r.url.replace(/:([a-zA-Z]+)/g, '{$1}');
33
+ const op = {
34
+ operationId: `${r.kind}_${url.replace(/[^a-zA-Z0-9]+/g, '_')}`,
35
+ summary: r.command?.cmd.description ?? r.capability,
36
+ 'x-capability': r.capability,
37
+ responses: { 200: { description: 'OK', content: { 'application/json': { schema: r.aggregate && r.kind !== 'list' && r.kind !== 'export' ? { $ref: `#/components/schemas/${r.aggregate}` } : {} } } }, default: { description: '错误', content: { 'application/json': { schema: ERR } } } },
38
+ };
39
+ if (url.includes('{id}'))
40
+ op.parameters = [{ name: 'id', in: 'path', required: true, schema: { type: 'string', format: 'uuid' } }];
41
+ if (r.method === 'POST' && r.input)
42
+ op.requestBody = { content: { 'application/json': { schema: schemaOf(r.input) } } };
43
+ if (r.kind === 'command')
44
+ op.parameters = [...(op.parameters ?? []), { name: 'Idempotency-Key', in: 'header', required: false, schema: { type: 'string' } }];
45
+ (paths[url] ??= {})[r.method.toLowerCase()] = op;
46
+ }
47
+ return { openapi: '3.1.0', info: { title: info.title ?? 'cage api', version: info.version ?? '0.0.0' }, paths, components: { schemas } };
48
+ }
49
+ //# sourceMappingURL=openapi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi.js","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { type Kernel, type RegisteredCommand } from '@yss123/cage-kernel';
2
+ import type { FastifyInstance } from 'fastify';
3
+ import { z } from 'zod';
4
+ export interface RouteInfo {
5
+ method: 'GET' | 'POST';
6
+ url: string;
7
+ kind: 'command' | 'list' | 'get' | 'export' | 'query' | 'hook';
8
+ capability?: string;
9
+ aggregate?: string;
10
+ command?: RegisteredCommand;
11
+ input?: z.ZodType;
12
+ }
13
+ export declare function commandUrl(c: RegisteredCommand): string;
14
+ /** 注册表 → 路由清单(cage gen 也用它生成路由清单和前端 SDK) */
15
+ export declare function routeTable(k: Kernel): RouteInfo[];
16
+ export declare function registerRoutes(app: FastifyInstance, k: Kernel): RouteInfo[];
package/dist/routes.js ADDED
@@ -0,0 +1,107 @@
1
+ // 路由生成:启动时遍历注册表,为每个命令、聚合、查询、入站回调挂路由。路由本身不做任何判断,全部交给内核。
2
+ // 业务项目里禁止调用 app.get / app.post(lint 拦截),所以没有声明就没有路由。
3
+ import { cageError, kebab, systemCtx } from '@yss123/cage-kernel';
4
+ import { z } from 'zod';
5
+ export function commandUrl(c) {
6
+ const base = `/api/${kebab(c.cmd.agg.name)}`;
7
+ return c.cmd.from === 'new' ? `${base}/${kebab(c.name)}` : `${base}/:id/${kebab(c.name)}`;
8
+ }
9
+ /** 注册表 → 路由清单(cage gen 也用它生成路由清单和前端 SDK) */
10
+ export function routeTable(k) {
11
+ const out = [];
12
+ for (const c of k.registry.commands) {
13
+ if (!c.cmd.invokedBy.includes('user'))
14
+ continue; // 流程、任务、外部专用命令不对浏览器开放
15
+ out.push({ method: 'POST', url: commandUrl(c), kind: 'command', capability: c.capability, aggregate: c.cmd.agg.name, command: c, input: c.cmd.input });
16
+ }
17
+ for (const a of k.registry.aggregates) {
18
+ const base = `/api/${kebab(a.name)}`;
19
+ out.push({ method: 'GET', url: base, kind: 'list', capability: `${a.name}.read`, aggregate: a.name });
20
+ out.push({ method: 'POST', url: `${base}/export`, kind: 'export', capability: `${a.name}.export`, aggregate: a.name });
21
+ out.push({ method: 'GET', url: `${base}/:id`, kind: 'get', capability: `${a.name}.read`, aggregate: a.name });
22
+ }
23
+ for (const q of k.registry.queries) {
24
+ out.push({ method: 'GET', url: `/api/queries/${kebab(q.name)}`, kind: 'query', capability: q.reads.map((r) => `${r.name}.read`).join(','), input: q.input });
25
+ }
26
+ for (const e of k.registry.extensions) {
27
+ if (e.kind === 'INBOUND')
28
+ out.push({ method: 'POST', url: `/hooks/${e.value.connector.name}/${kebab(e.value.event)}`, kind: 'hook' });
29
+ }
30
+ return out;
31
+ }
32
+ export function registerRoutes(app, k) {
33
+ const table = routeTable(k);
34
+ for (const c of k.registry.commands) {
35
+ if (!c.cmd.invokedBy.includes('user'))
36
+ continue;
37
+ app.post(commandUrl(c), async (req) => {
38
+ const body = req.body && typeof req.body === 'object' ? req.body : {};
39
+ const params = req.params;
40
+ const input = params.id ? { ...body, id: params.id } : body;
41
+ const key = req.headers['idempotency-key'];
42
+ return k.execute(c.cmd, input, { ...req.cageCtx, idempotencyKey: typeof key === 'string' ? key.slice(0, 200) : undefined });
43
+ });
44
+ }
45
+ for (const a of k.registry.aggregates)
46
+ registerAggregate(app, k, a);
47
+ for (const q of k.registry.queries) {
48
+ app.get(`/api/queries/${kebab(q.name)}`, async (req) => k.read.run(q, coerceQuery(q.input, req.query), req.cageCtx));
49
+ }
50
+ for (const e of k.registry.extensions) {
51
+ if (e.kind !== 'INBOUND')
52
+ continue;
53
+ const inb = e.value;
54
+ app.post(`/hooks/${inb.connector.name}/${kebab(inb.event)}`, async (req) => {
55
+ // 先验签,再映射到命令;以 external 身份调用
56
+ const secret = process.env[inb.verify.secret];
57
+ if (!secret)
58
+ throw new Error(`[CAGE-SECRET-001] 缺少密钥 ${inb.verify.secret}`);
59
+ if (!inb.verify.verify(req.rawBody ?? '', req.headers, secret))
60
+ throw cageError('UNAUTHENTICATED');
61
+ const parsed = inb.payload.safeParse(req.body);
62
+ if (!parsed.success)
63
+ throw cageError('INVALID_INPUT', parsed.error.issues.map((i) => ({ path: i.path.join('.'), message: i.message })));
64
+ const ctx = systemCtx({
65
+ kind: 'external', name: inb.connector.name, tenantId: req.cageCtx.tenantId,
66
+ capabilities: [k.registry.capabilityOf(inb.command)], requestId: String(req.id),
67
+ });
68
+ const key = req.headers['idempotency-key'];
69
+ return k.execute(inb.command, inb.map(parsed.data), { ...ctx, idempotencyKey: typeof key === 'string' ? key : undefined });
70
+ });
71
+ }
72
+ return table;
73
+ }
74
+ function registerAggregate(app, k, a) {
75
+ const base = `/api/${kebab(a.name)}`;
76
+ app.get(base, async (req) => k.read.list(a, req.query, req.cageCtx));
77
+ app.get(`${base}/:id`, async (req) => k.read.get(a, req.params.id, req.cageCtx));
78
+ app.post(`${base}/export`, async (req, reply) => {
79
+ const out = await k.read.export(a, (req.body ?? {}), req.cageCtx);
80
+ reply.header('content-type', out.contentType).header('content-disposition', `attachment; filename="${out.filename}"`);
81
+ return out.body;
82
+ });
83
+ }
84
+ /** 查询字符串都是字符串:按输入 schema 把数字和布尔转换过来 */
85
+ function coerceQuery(schema, q) {
86
+ if (!(schema instanceof z.ZodObject))
87
+ return q;
88
+ const out = { ...q };
89
+ for (const [k, s] of Object.entries(schema.shape)) {
90
+ const v = q[k];
91
+ if (typeof v !== 'string')
92
+ continue;
93
+ const inner = unwrap(s);
94
+ if (inner instanceof z.ZodNumber)
95
+ out[k] = Number(v);
96
+ else if (inner instanceof z.ZodBoolean)
97
+ out[k] = v === 'true';
98
+ }
99
+ return out;
100
+ }
101
+ function unwrap(s) {
102
+ let cur = s;
103
+ while (cur && (cur instanceof z.ZodOptional || cur instanceof z.ZodNullable || cur instanceof z.ZodDefault))
104
+ cur = cur.unwrap();
105
+ return cur;
106
+ }
107
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,qDAAqD;AACrD,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAA0D,MAAM,qBAAqB,CAAC;AAE1H,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAYxB,MAAM,UAAU,UAAU,CAAC,CAAoB;IAC7C,MAAM,IAAI,GAAG,QAAQ,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;IAC7C,OAAO,CAAC,CAAC,GAAG,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,QAAQ,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;AAC5F,CAAC;AAED,4CAA4C;AAC5C,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,MAAM,GAAG,GAAgB,EAAE,CAAC;IAC5B,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;QACpC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,SAAS,CAAC,sBAAsB;QACvE,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC,CAAC,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;IACzJ,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,QAAQ,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC,IAAI,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACtG,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC,IAAI,SAAS,EAAE,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACvH,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,IAAI,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC,IAAI,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAChH,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;QACnC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,gBAAgB,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;IAC/J,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;QACtC,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS;YAAE,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IACxI,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,GAAoB,EAAE,CAAS;IAC5D,MAAM,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;IAC5B,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;QACpC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,SAAS;QAChD,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YACpC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAE,GAAG,CAAC,IAAgC,CAAC,CAAC,CAAC,EAAE,CAAC;YACnG,MAAM,MAAM,GAAG,GAAG,CAAC,MAAyB,CAAC;YAC7C,MAAM,KAAK,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;YAC5D,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;YAC3C,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,GAAG,GAAG,CAAC,OAAO,EAAE,cAAc,EAAE,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC;QAC9H,CAAC,CAAC,CAAC;IACL,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,UAAU;QAAE,iBAAiB,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACpE,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;QACnC,GAAG,CAAC,GAAG,CAAC,gBAAgB,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,KAAK,EAAE,GAAG,CAAC,KAAgC,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IAClJ,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;QACtC,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS;YAAE,SAAS;QACnC,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC;QACpB,GAAG,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,SAAS,CAAC,IAAI,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YACzE,6BAA6B;YAC7B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC9C,IAAI,CAAC,MAAM;gBAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;YAC5E,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,EAAE,GAAG,CAAC,OAAc,EAAE,MAAM,CAAC;gBAAE,MAAM,SAAS,CAAC,iBAAiB,CAAC,CAAC;YAC1G,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC/C,IAAI,CAAC,MAAM,CAAC,OAAO;gBAAE,MAAM,SAAS,CAAC,eAAe,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;YAC7I,MAAM,GAAG,GAAG,SAAS,CAAC;gBACpB,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC,QAAQ;gBAC1E,YAAY,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;aAChF,CAAC,CAAC;YACH,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;YAC3C,OAAO,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,GAAG,EAAE,cAAc,EAAE,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC;QAC7H,CAAC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,iBAAiB,CAAC,GAAoB,EAAE,CAAS,EAAE,CAAe;IACzE,MAAM,IAAI,GAAG,QAAQ,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;IACrC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,CAAC,KAAgC,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IAChG,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAG,GAAG,CAAC,MAAyB,CAAC,EAAE,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IACrG,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;QAC9C,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAA4B,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;QAC7F,KAAK,CAAC,MAAM,CAAC,cAAc,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,qBAAqB,EAAE,yBAAyB,GAAG,CAAC,QAAQ,GAAG,CAAC,CAAC;QACtH,OAAO,GAAG,CAAC,IAAI,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,uCAAuC;AACvC,SAAS,WAAW,CAAC,MAAiB,EAAE,CAA0B;IAChE,IAAI,CAAC,CAAC,MAAM,YAAY,CAAC,CAAC,SAAS,CAAC;QAAE,OAAO,CAAC,CAAC;IAC/C,MAAM,GAAG,GAA4B,EAAE,GAAG,CAAC,EAAE,CAAC;IAC9C,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,KAAkC,CAAC,EAAE,CAAC;QAC/E,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACf,IAAI,OAAO,CAAC,KAAK,QAAQ;YAAE,SAAS;QACpC,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACxB,IAAI,KAAK,YAAY,CAAC,CAAC,SAAS;YAAE,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;aAChD,IAAI,KAAK,YAAY,CAAC,CAAC,UAAU;YAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC;IAChE,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,MAAM,CAAC,CAAY;IAC1B,IAAI,GAAG,GAAQ,CAAC,CAAC;IACjB,OAAO,GAAG,IAAI,CAAC,GAAG,YAAY,CAAC,CAAC,WAAW,IAAI,GAAG,YAAY,CAAC,CAAC,WAAW,IAAI,GAAG,YAAY,CAAC,CAAC,UAAU,CAAC;QAAE,GAAG,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC;IAChI,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,38 @@
1
+ import { type DatabaseUrls, type Kernel } from '@yss123/cage-kernel';
2
+ import { type FastifyInstance } from 'fastify';
3
+ import { type AuthOptions } from './auth.js';
4
+ export interface ServerOptions {
5
+ /** domain 包的导出:import * as domain from '@app/domain' */
6
+ domain: object;
7
+ urls?: DatabaseUrls;
8
+ auth?: AuthOptions & {
9
+ oidcIssuer?: string;
10
+ };
11
+ /** 业务前端构建产物目录(单页应用),不传时不托管 */
12
+ webDir?: string;
13
+ /** /admin 系统管理,默认开启 */
14
+ admin?: boolean;
15
+ /** 首次启动时初始化组织并打印初始管理员账号,默认开启 */
16
+ bootstrap?: boolean;
17
+ port?: number;
18
+ host?: string;
19
+ /** 默认每个客户端每分钟 300 次 */
20
+ rateLimitPerMinute?: number;
21
+ /** 登录类接口每个客户端每分钟次数,默认 10 */
22
+ authRateLimitPerMinute?: number;
23
+ /** 关闭 CSRF(只用于测试) */
24
+ csrf?: boolean;
25
+ logger?: boolean;
26
+ title?: string;
27
+ }
28
+ export interface CageServer {
29
+ app: FastifyInstance;
30
+ kernel: Kernel;
31
+ ready(): Promise<FastifyInstance>;
32
+ listen(opts?: {
33
+ port?: number;
34
+ host?: string;
35
+ }): Promise<string>;
36
+ close(): Promise<void>;
37
+ }
38
+ export declare function createServer(opts: ServerOptions): CageServer;
package/dist/server.js ADDED
@@ -0,0 +1,163 @@
1
+ // createServer:安全基线默认开启(安全响应头、CSRF、限流、请求体大小、日志打码),业务项目不需要记得加。
2
+ import { existsSync } from 'node:fs';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ import { createRequire } from 'node:module';
5
+ import { fileURLToPath } from 'node:url';
6
+ import cookie from '@fastify/cookie';
7
+ import csrf from '@fastify/csrf-protection';
8
+ import helmet from '@fastify/helmet';
9
+ import rateLimit from '@fastify/rate-limit';
10
+ import fastifyStatic from '@fastify/static';
11
+ import * as adminDomain from '@yss123/cage-admin/domain';
12
+ import { adminRoutes, bootstrapOrg } from '@yss123/cage-admin';
13
+ import { approvalRoutes } from '@yss123/cage-approval';
14
+ import { createKernel, toCageError, urlsFromEnv } from '@yss123/cage-kernel';
15
+ import Fastify from 'fastify';
16
+ import { authRoutes, csrfRoute, registerAuthHook, Sessions } from './auth.js';
17
+ import { CtxCache } from './ctx.js';
18
+ import { buildLogger } from './logger.js';
19
+ import { oidcRoutes } from './oidc.js';
20
+ import { buildOpenApi } from './openapi.js';
21
+ import { registerRoutes } from './routes.js';
22
+ const here = dirname(fileURLToPath(import.meta.url));
23
+ function adminUiDir() {
24
+ try {
25
+ const req = createRequire(import.meta.url);
26
+ const pkg = req.resolve('@yss123/cage-admin/package.json');
27
+ const dir = join(dirname(pkg), 'ui');
28
+ return existsSync(dir) ? dir : null;
29
+ }
30
+ catch {
31
+ const dir = resolve(here, '../../admin/ui');
32
+ return existsSync(dir) ? dir : null;
33
+ }
34
+ }
35
+ export function createServer(opts) {
36
+ const urls = opts.urls ?? urlsFromEnv();
37
+ const app = Fastify({
38
+ bodyLimit: 1_048_576, // 请求体最大 1 MB
39
+ trustProxy: true, // 在负载均衡后面取真实客户端 IP
40
+ logger: false,
41
+ genReqId: () => crypto.randomUUID(),
42
+ });
43
+ // 组织模型永远在注册表里(同一对象重复导出会自动去重)
44
+ const kernel = createKernel({ domain: [adminDomain, opts.domain], urls, logger: undefined });
45
+ if (opts.logger !== false) {
46
+ const pino = Fastify({ logger: buildLogger(kernel.registry) }).log;
47
+ kernel.log = pino;
48
+ app.log = pino;
49
+ }
50
+ const cache = new CtxCache(kernel);
51
+ const authOpts = {
52
+ ...opts.auth,
53
+ oidc: opts.auth?.oidc ?? (opts.auth?.oidcIssuer && process.env.OIDC_CLIENT_ID
54
+ ? {
55
+ issuer: opts.auth.oidcIssuer,
56
+ clientId: process.env.OIDC_CLIENT_ID,
57
+ clientSecret: process.env.OIDC_CLIENT_SECRET ?? '',
58
+ redirectUri: process.env.OIDC_REDIRECT_URI ?? 'http://localhost:3000/auth/callback',
59
+ }
60
+ : undefined),
61
+ };
62
+ const sessions = new Sessions(kernel, authOpts.sessionHours ?? 8);
63
+ const build = async () => {
64
+ // 原始请求体:入站回调验签要用
65
+ app.addContentTypeParser('application/json', { parseAs: 'string' }, (req, body, done) => {
66
+ req.rawBody = body;
67
+ if (!body)
68
+ return done(null, {});
69
+ try {
70
+ done(null, JSON.parse(body));
71
+ }
72
+ catch {
73
+ done(Object.assign(new Error('请求体不是合法的 JSON'), { statusCode: 400 }), undefined);
74
+ }
75
+ });
76
+ await app.register(helmet, {
77
+ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'"], imgSrc: ["'self'", 'data:'] } },
78
+ });
79
+ await app.register(rateLimit, { max: opts.rateLimitPerMinute ?? 300, timeWindow: '1 minute' });
80
+ await app.register(cookie, { secret: process.env.CAGE_COOKIE_SECRET ?? 'cage-dev-cookie-secret-change-me' });
81
+ await app.register(csrf, { sessionPlugin: '@fastify/cookie', cookieOpts: { signed: true, httpOnly: true, sameSite: 'lax', path: '/' } });
82
+ registerAuthHook(app, kernel, sessions, cache);
83
+ if (opts.csrf !== false) {
84
+ app.addHook('preHandler', (req, reply, done) => {
85
+ // 基于 cookie 会话:所有写请求都要带 CSRF 令牌;第三方回调走签名验证
86
+ if (req.method === 'GET' || req.method === 'HEAD' || req.method === 'OPTIONS' || req.url.startsWith('/hooks/'))
87
+ return done();
88
+ app.csrfProtection(req, reply, done);
89
+ });
90
+ }
91
+ app.setErrorHandler((err, req, reply) => {
92
+ const e = toCageError(err);
93
+ if (e.code === 'INTERNAL')
94
+ kernel.log.error({ err, requestId: req.id, url: req.url }, '请求异常');
95
+ reply.status(e.http).send({ code: e.code, ...(e.domainCode ? { domainCode: e.domainCode } : {}), detail: e.code === 'INTERNAL' ? undefined : e.detail, requestId: req.id });
96
+ });
97
+ app.setNotFoundHandler((req, reply) => {
98
+ if (req.url.startsWith('/api/') || req.url.startsWith('/hooks/') || req.url.startsWith('/auth/')) {
99
+ return reply.status(404).send({ code: 'NOT_FOUND' });
100
+ }
101
+ if (opts.webDir && existsSync(join(opts.webDir, 'index.html')))
102
+ return reply.sendFile('index.html', opts.webDir);
103
+ return reply.status(404).send({ code: 'NOT_FOUND' });
104
+ });
105
+ // 登录相关接口单独一组,限流更严(防暴力破解)
106
+ await app.register(async (auth) => {
107
+ await auth.register(rateLimit, { max: opts.authRateLimitPerMinute ?? 10, timeWindow: '1 minute' });
108
+ authRoutes(auth, kernel, sessions, authOpts);
109
+ oidcRoutes(auth, kernel, sessions, authOpts);
110
+ });
111
+ csrfRoute(app);
112
+ app.get('/healthz', async () => ({ ok: true }));
113
+ const routes = registerRoutes(app, kernel);
114
+ app.get('/api/openapi.json', async () => buildOpenApi(kernel, routes, { title: opts.title }));
115
+ if (opts.admin !== false)
116
+ adminRoutes(app, kernel);
117
+ const engines = kernel.registry.extensions.filter((e) => e.kind === 'ROUTE').map((e) => e.value.engine(kernel));
118
+ if (engines.length)
119
+ approvalRoutes(app, { kernel, engines, ctxOf: (req) => req.cageCtx });
120
+ const ui = opts.admin !== false ? adminUiDir() : null;
121
+ if (ui) {
122
+ await app.register(fastifyStatic, { root: ui, prefix: '/admin/', decorateReply: !opts.webDir });
123
+ app.get('/admin', (_req, reply) => reply.redirect('/admin/'));
124
+ }
125
+ if (opts.webDir && existsSync(opts.webDir)) {
126
+ await app.register(fastifyStatic, { root: opts.webDir, prefix: '/', decorateReply: true, wildcard: false });
127
+ }
128
+ await cache.listen(urls.app);
129
+ if (opts.bootstrap !== false) {
130
+ const b = await bootstrapOrg(kernel).catch((e) => {
131
+ kernel.log.warn({ err: String(e) }, '组织初始化失败(是否已执行 cage migrate?)');
132
+ return { created: false };
133
+ });
134
+ if (b.created) {
135
+ // 初始管理员账号只在终端里打印一次
136
+ console.log(`\n 初始管理员 用户名 ${b.username} 密码 ${b.password}\n 请登录 /admin 后立即修改密码\n`);
137
+ }
138
+ }
139
+ await app.ready();
140
+ return app;
141
+ };
142
+ let readyP = null;
143
+ const ready = () => (readyP ??= build());
144
+ return {
145
+ app,
146
+ kernel,
147
+ ready,
148
+ async listen(l = {}) {
149
+ await ready();
150
+ const port = l.port ?? opts.port ?? Number(process.env.PORT ?? 3000);
151
+ const host = l.host ?? opts.host ?? process.env.HOST ?? '0.0.0.0';
152
+ const addr = await app.listen({ port, host });
153
+ kernel.log.info({ addr }, 'api 已启动');
154
+ return addr;
155
+ },
156
+ async close() {
157
+ await app.close();
158
+ await cache.close();
159
+ await kernel.close();
160
+ },
161
+ };
162
+ }
163
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { type WorkerOptions } from '@yss123/cage-kernel/worker';
2
+ export declare function startWorker(opts: WorkerOptions & {
3
+ domain: object;
4
+ }): Promise<import("@yss123/cage-kernel/worker").WorkerHandle>;
package/dist/worker.js ADDED
@@ -0,0 +1,7 @@
1
+ // worker 入口的便捷封装:自动并入组织模型。也可以直接用 @yss123/cage-kernel/worker 的 startWorker。
2
+ import * as adminDomain from '@yss123/cage-admin/domain';
3
+ import { startWorker as start } from '@yss123/cage-kernel/worker';
4
+ export function startWorker(opts) {
5
+ return start({ ...opts, domain: [adminDomain, opts.domain] });
6
+ }
7
+ //# sourceMappingURL=worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker.js","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,OAAO,KAAK,WAAW,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,WAAW,IAAI,KAAK,EAAsB,MAAM,4BAA4B,CAAC;AAEtF,MAAM,UAAU,WAAW,CAAC,IAAwC;IAClE,OAAO,KAAK,CAAC,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,CAAC,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AAChE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,61 @@
1
1
  {
2
2
  "name": "@yss123/cage-http",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "笼子 HTTP 层:Fastify 插件、路由生成、认证、安全基线",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "./worker": {
14
+ "types": "./dist/worker.d.ts",
15
+ "default": "./dist/worker.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "dependencies": {
22
+ "@fastify/cookie": "^11.0.2",
23
+ "@fastify/csrf-protection": "^7.1.0",
24
+ "@fastify/helmet": "^13.0.1",
25
+ "@fastify/rate-limit": "^10.3.0",
26
+ "@fastify/static": "^10.1.8",
27
+ "fastify": "^5.6.0",
28
+ "fastify-plugin": "^5.0.1",
29
+ "lru-cache": "^11.2.1",
30
+ "pg": "^8.16.3",
31
+ "zod": "^4.1.5",
32
+ "kysely": "^0.28.5",
33
+ "@yss123/cage-admin": "0.1.0",
34
+ "@yss123/cage-approval": "0.1.0",
35
+ "@yss123/cage-kernel": "0.1.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/pg": "^8.15.5"
39
+ },
40
+ "license": "MIT",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/shunshunyue/cage.git",
44
+ "directory": "packages/http"
45
+ },
46
+ "homepage": "https://github.com/shunshunyue/cage#readme",
47
+ "bugs": {
48
+ "url": "https://github.com/shunshunyue/cage/issues"
49
+ },
50
+ "publishConfig": {
51
+ "access": "public",
52
+ "registry": "https://registry.npmjs.org/"
53
+ },
54
+ "engines": {
55
+ "node": ">=24"
56
+ },
57
+ "scripts": {
58
+ "build": "tsc -p tsconfig.json",
59
+ "typecheck": "tsc -p tsconfig.json --noEmit"
60
+ }
6
61
  }