feat(auth-adapter,bff-kit,client-shell): 마지막 추출 배치
@yakcloud/auth-adapter: core 인터페이스(프레임워크 무관, ds-sdk type-only) + dev-bypass + next-auth 격리 계층(peer). exports 서브패스(./core·./dev-bypass). @yakcloud/bff-kit: respond(web 표준)·serialize(Prisma-free)·ratelimit·session(AuthAdapter DI). auth-adapter=devDependency(type-only), 죽은 next peer 제거. @yakcloud/client-shell: ServiceClientShell 셸 크롬(헤더/탭/pane/가드)+패널 슬롯+BackupPanel 스텁. (FileBrowser 는 Phase 1) FIX-FIRST 반영: client-shell tsconfig jsx:react-jsx + @types/react(-dom), bff-kit/auth-adapter += @types/node. 7개 패키지 전부 tsc --noEmit 통과. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
75
packages/bff-kit/src/serialize.ts
Normal file
75
packages/bff-kit/src/serialize.ts
Normal file
@ -0,0 +1,75 @@
|
||||
// @yakcloud/bff-kit — serialize
|
||||
// DTO 마스킹·필터. Prisma-free: 전체 모델 대신 필요한 필드만 가진 구조적 입력 타입을 받는다.
|
||||
// connSecretRef(내부 Secret 이름) 및 시크릿 env 값은 절대 출력에 노출하지 않는다(마스킹 계약).
|
||||
// yakconsole api/serialize.ts 하강.
|
||||
|
||||
import type { EnvVarDTO } from "@yakcloud/ds-sdk";
|
||||
|
||||
// ── 구조적 입력 타입(Repo 결과에서 핸들러가 구성; Prisma 모델도 구조적으로 호환) ──
|
||||
|
||||
// 날짜는 Repo 가 Date 로 주든 ISO string 으로 주든 그대로 통과시킨다(직렬화는 Response.json 소관).
|
||||
type DateLike = Date | string;
|
||||
|
||||
export interface DeploymentInput {
|
||||
env: unknown; // Deployment.env(Json) — 배열이 아니면 []
|
||||
[k: string]: unknown; // 나머지 필드는 그대로 스프레드(구조적)
|
||||
}
|
||||
|
||||
export interface ServiceInput {
|
||||
id: string;
|
||||
ownerId: string;
|
||||
clusterId: string;
|
||||
name: string;
|
||||
type: string;
|
||||
mode: string;
|
||||
status: string;
|
||||
statusMessage: string | null;
|
||||
size: string;
|
||||
storageGb: number;
|
||||
persist: boolean;
|
||||
connSecretRef?: string | null; // ⛔ 출력에서 제외(마스킹)
|
||||
connInfo: unknown; // 비밀 제외 접속 메타 JSON
|
||||
createdAt: DateLike;
|
||||
updatedAt: DateLike;
|
||||
}
|
||||
|
||||
export interface PublicServiceExtra {
|
||||
clusterName?: string;
|
||||
bindings?: { app: string; alias: string }[];
|
||||
}
|
||||
|
||||
// Deployment.env(Json) → EnvVar[]. 배열이 아니면 빈 배열.
|
||||
export function envOf(dep: DeploymentInput): EnvVarDTO[] {
|
||||
const raw = dep.env;
|
||||
if (!Array.isArray(raw)) return [];
|
||||
return raw as EnvVarDTO[];
|
||||
}
|
||||
|
||||
// API 응답용: 시크릿 환경변수의 값을 마스킹(빈 문자열)해 클라이언트로 노출하지 않는다.
|
||||
export function publicDeployment<D extends DeploymentInput>(dep: D): Omit<D, "env"> & { env: EnvVarDTO[] } {
|
||||
const env = envOf(dep).map((e) => (e.secret ? { key: e.key, value: "", secret: true } : e));
|
||||
return { ...dep, env };
|
||||
}
|
||||
|
||||
// 외부 서비스 응답 DTO — connSecretRef 미노출, connInfo(host/port/db/bucket)만.
|
||||
// 크리덴셜은 애초에 포털에 저장되지 않으므로 여기서 마스킹할 비밀이 없다(설계 C).
|
||||
export function publicService(svc: ServiceInput, extra?: PublicServiceExtra) {
|
||||
return {
|
||||
id: svc.id,
|
||||
ownerId: svc.ownerId,
|
||||
clusterId: svc.clusterId,
|
||||
name: svc.name,
|
||||
type: svc.type,
|
||||
mode: svc.mode,
|
||||
status: svc.status,
|
||||
statusMessage: svc.statusMessage,
|
||||
size: svc.size,
|
||||
storageGb: svc.storageGb,
|
||||
persist: svc.persist,
|
||||
connInfo: (svc.connInfo as Record<string, unknown> | null) ?? null,
|
||||
createdAt: svc.createdAt,
|
||||
updatedAt: svc.updatedAt,
|
||||
...(extra?.clusterName !== undefined ? { clusterName: extra.clusterName } : {}),
|
||||
...(extra?.bindings !== undefined ? { bindings: extra.bindings } : {}),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user