接入统一鉴权中心(Keycloak,ADR-012);活动模块 L1/L2 运营态模版(历史活动列表+检索+沙箱);数据域与工作台界面改版;个人中心 /me

This commit is contained in:
shanqin
2026-08-26 13:29:36 +08:00
parent f3397a60ef
commit f01286a89d
40 changed files with 2502 additions and 480 deletions
+39 -3
View File
@@ -4,12 +4,16 @@
*/
import { z } from 'zod';
import type { RouteContract } from './http.ts';
import { sessionSchema } from '../org.ts';
import { principalSchema, sessionSchema } from '../org.ts';
import { chatMessageSchema, deliverableSchema, fileDocSchema, formFieldSchema, stageSchema, viewBlockSchema, workOrderSchema } from '../card.ts';
import { attributionSideSchema, taskRuntimeStateSchema, terminalStateSchema, workflowStateSchema } from '../terminal.ts';
import { staffIdSchema, taskIdSchema } from '../id.ts';
/** 我是谁 —— 无会话即 401,前端据此落登录页(ADR-010) */
/**
* 我是谁 —— 无会话即 401,前端据此落登录页(ADR-010)
*
* @deprecated 统一鉴权中心(ADR-012)后已退役(410):改用 `meRoute`。
*/
export const sessionRoute = {
method: 'GET',
path: '/api/session',
@@ -19,6 +23,9 @@ export const sessionRoute = {
/**
* 登录 —— 单组织 cookie session(编码规范 §4 / ADR-010)。
* 成功即种 `jcai_session`(HttpOnly);失败只回一句话,不区分账号不存在还是口令错。
*
* @deprecated 统一鉴权中心(ADR-012)后已退役(410):登录由 Keycloak 完成,
* 请求带 `Authorization: Bearer <access_token>`。
*/
export const loginRoute = {
method: 'POST',
@@ -27,13 +34,42 @@ export const loginRoute = {
output: sessionSchema,
} as const satisfies RouteContract;
/** 登出 —— 服务端会话立刻作废,不等 cookie 到期 */
/**
* 登出 —— 服务端会话立刻作废,不等 cookie 到期
*
* @deprecated 统一鉴权中心(ADR-012)后已退役(410):
* 登出 = 前端丢弃 token + Keycloak end-session。
*/
export const logoutRoute = {
method: 'POST',
path: '/api/logout',
output: z.object({ ok: z.boolean() }),
} as const satisfies RouteContract;
/**
* 我是谁、能进哪几个端(ADR-012)—— access: `any`(登录即可)。
*
* `ends` 为空也 200:前端据此摆「未授权」页,不落登录页。
* 副作用:第一次见到这个 `sub` 后端会在名册建档(岗位 NONE 未分配)。
*/
export const meRoute = {
method: 'GET',
path: '/api/me',
output: principalSchema,
} as const satisfies RouteContract;
/**
* 换一枚 60 秒票据(ADR-012)—— access: `any`。
*
* 给 `EventSource` 与 `<a href download>` 这两条带不了 Authorization 头的路:
* 只在 GET 的 `?ticket=` 上认;票据里没有权限,后端按它对应的 sub 回库现算。
*/
export const authTicketRoute = {
method: 'GET',
path: '/api/auth/ticket',
output: z.object({ ticket: z.string(), expiresAt: z.string() }),
} as const satisfies RouteContract;
export const healthRoute = {
method: 'GET',
path: '/api/health',
+29
View File
@@ -101,6 +101,10 @@ export type DisableCheck = z.infer<typeof disableCheckSchema>;
/* ── 会话 ─────────────────────────────────────────────────────────────── */
/**
* @deprecated 统一鉴权中心(ADR-012)后退役:`GET /api/session` 已 410,
* 改用 `principalSchema`(`GET /api/me`)。本仓库 apps/api 仍是旧后端,契约暂留。
*/
export const sessionSchema = z.object({
staff: staffSchema,
position: positionSchema,
@@ -108,6 +112,31 @@ export const sessionSchema = z.object({
});
export type Session = z.infer<typeof sessionSchema>;
/* ── 统一鉴权中心(ADR-012,2026-08-26)───────────────────────────────── */
/** 三个端(台面):权限只到端,端内接口对能进的人全开 */
export const SURFACES = ['design', 'ops', 'workbench'] as const;
export const surfaceSchema = z.enum(SURFACES);
export type Surface = z.infer<typeof surfaceSchema>;
/**
* 「我是谁、能进哪几个端」—— `GET /api/me` 的出参。
*
* `staff.id` 就是 Keycloak 的 `sub`(UUID);`roles` 是 realm 角色快照
* (噪声角色已被后端滤掉);`ends` 是「角色 → 端」表的并集,可以为空 —
* 空也 200,前端据此摆「未授权」页而不是登录页。
*/
export const principalSchema = z.object({
staff: staffSchema.extend({
email: z.string().optional(),
roles: z.array(z.string()).default([]),
}),
position: positionSchema,
roles: z.array(z.string()),
ends: z.array(surfaceSchema),
});
export type Principal = z.infer<typeof principalSchema>;
/* ── 工作组(ADR-011) ────────────────────────────────────────────────── */
/**