Archived
org domain:claims 归一、角色→端与锁死不变量、名册差异、测试账号策略(ADR-012,纯函数)
为统一鉴权中心(Keycloak)落地铺地基:五个零 IO 的纯函数文件,后续 org 用例 重写(Task 8)直接复用。本任务只加不删 —— 旧的岗位授权 / 口令会话 / 停用检查 原样保留,现役 org-service 仍在用它们。 - claims.ts:token claims → Identity,滤掉三种 Keycloak 家具角色 - ends.ts:角色 → 端的并集,并守住「改完之后至少一人能进运营中心」这条锁死不变量 - roster-diff.ts:与 Keycloak 名册的插/改/停/复活差异(远端说了算) - test-account.ts:测试账号判定与独立工作组构造(ADR-011 语义原样搬) - in-flight.ts:停用前置检查要看的在途工作(与旧 disable-check.ts 并存一轮) contract.ts / port.ts / index.ts / seed-data.ts 均为文件末尾追加,契约变更已 经 pnpm api:check --update 刷新快照。
This commit is contained in:
@@ -74,3 +74,27 @@ export type OrgService = {
|
||||
staffOfPosition(positionCode: string): Promise<StaffWithPosition[]>;
|
||||
ensureSeeded(): Promise<void>;
|
||||
};
|
||||
/** 从 token 或 Admin API 归一出来的一个人 —— `sub` 即 Staff.id */
|
||||
export type Identity = {
|
||||
readonly sub: string;
|
||||
/** preferred_username */
|
||||
readonly account: string;
|
||||
/** name claim,缺则回退 account */
|
||||
readonly name: string;
|
||||
readonly email?: string;
|
||||
/** realm 角色,噪声角色已滤、已排序 */
|
||||
readonly roles: readonly string[];
|
||||
};
|
||||
/** 验签没过 —— message 是手册 4.4 的人话,中间件原样回 401 */
|
||||
export declare class TokenRejectedError extends Error {
|
||||
constructor(message: string);
|
||||
}
|
||||
/** 名册同步标了停用 —— 即使 token 还有效也拒(403);Keycloak 恢复后再同步一次 */
|
||||
export declare class AccountDisabledError extends Error {
|
||||
readonly account: string;
|
||||
constructor(account: string);
|
||||
}
|
||||
/** 角色→端改完之后没有任何在册员工能进运营中心 —— 那张表就再也没人改得了 */
|
||||
export declare class AccessLockoutError extends Error {
|
||||
constructor();
|
||||
}
|
||||
|
||||
@@ -11,3 +11,10 @@ export { DEFAULT_LOGIN_ACCOUNT, POSITION_SEED, STAFF_SEED } from './internal/dom
|
||||
export { createGrantRepository, createPositionRepository, createSessionRepository, createStaffRepository, createWorkGroupRepository, objectKeyOf, } from './internal/adapter/org-repository.ts';
|
||||
export { hashPassword, newSessionToken, verifyPassword } from './internal/adapter/password.ts';
|
||||
export { grantTable, positionTable, sessionTable, staffTable, workGroupTable } from './internal/adapter/org-schema.ts';
|
||||
export type { Identity } from './contract.ts';
|
||||
export { AccessLockoutError, AccountDisabledError, TokenRejectedError } from './contract.ts';
|
||||
export type { IdentityPatch, RosterSource, RosterUser, TokenVerifier, VerifiedClaims } from './port.ts';
|
||||
export { identityOf, isNoiseRole, normalizeRoles } from './internal/domain/claims.ts';
|
||||
export { assertNotLockedOut, endsOf, normalizeEnds } from './internal/domain/ends.ts';
|
||||
export { buildInFlightWork } from './internal/domain/in-flight.ts';
|
||||
export { DEFAULT_ROLE_ACCESS, NONE_POSITION_CODE, TEST_POSITION_CODE } from './internal/domain/seed-data.ts';
|
||||
|
||||
+30
@@ -3,6 +3,7 @@
|
||||
* adapter 实现之,usecase 依赖之,app 装配之。
|
||||
*/
|
||||
import type { Grant, Position, PositionId, Staff, StaffId, WorkGroup } from '@contracts';
|
||||
import type { Identity } from './contract.ts';
|
||||
export type Clock = {
|
||||
now(): Date;
|
||||
};
|
||||
@@ -79,3 +80,32 @@ export type WorkGroupRepository = {
|
||||
export type IdGenerator = {
|
||||
next(): string;
|
||||
};
|
||||
/** 验签通过后的 claims —— 只认手册 §17 列的那几个 */
|
||||
export type VerifiedClaims = {
|
||||
readonly sub: string;
|
||||
readonly preferred_username: string;
|
||||
readonly name?: string;
|
||||
readonly email?: string;
|
||||
readonly realm_access?: {
|
||||
readonly roles?: readonly string[];
|
||||
};
|
||||
};
|
||||
/** 验签 —— 实现在 adapter(jose);失败抛 TokenRejectedError;测试注入本地密钥对 */
|
||||
export type TokenVerifier = {
|
||||
verify(token: string): Promise<VerifiedClaims>;
|
||||
};
|
||||
/** 名册来源(Keycloak Admin API)—— app 未配 provisioning 服务账号时不注入 */
|
||||
export type RosterUser = Identity & {
|
||||
readonly enabled: boolean;
|
||||
};
|
||||
export type RosterSource = {
|
||||
listUsers(): Promise<readonly RosterUser[]>;
|
||||
};
|
||||
/** 名册即时更新 / 同步要改的那几列 */
|
||||
export type IdentityPatch = {
|
||||
readonly account: string;
|
||||
readonly name: string;
|
||||
readonly email?: string;
|
||||
readonly roles: readonly string[];
|
||||
readonly lastSeenAt?: Date;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user