@polardbx/pxbaas-client 0.1.0-beta.1 → 0.1.0-beta.10
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 +57 -0
- package/dist/drivers/apps.driver.d.ts +33 -0
- package/dist/drivers/apps.driver.d.ts.map +1 -1
- package/dist/drivers/apps.driver.js +8 -0
- package/dist/drivers/apps.driver.js.map +1 -1
- package/dist/drivers/meta.driver.d.ts +3 -0
- package/dist/drivers/meta.driver.d.ts.map +1 -1
- package/dist/drivers/meta.driver.js +22 -15
- package/dist/drivers/meta.driver.js.map +1 -1
- package/dist/drivers/share-links.driver.d.ts.map +1 -1
- package/dist/drivers/share-links.driver.js +7 -5
- package/dist/drivers/share-links.driver.js.map +1 -1
- package/dist/drivers/storage.driver.d.ts +21 -6
- package/dist/drivers/storage.driver.d.ts.map +1 -1
- package/dist/drivers/storage.driver.js +44 -33
- package/dist/drivers/storage.driver.js.map +1 -1
- package/dist/drivers/tenant-admin.driver.d.ts +130 -0
- package/dist/drivers/tenant-admin.driver.d.ts.map +1 -0
- package/dist/drivers/tenant-admin.driver.js +222 -0
- package/dist/drivers/tenant-admin.driver.js.map +1 -0
- package/dist/drivers/tenant-ddl.driver.d.ts +106 -0
- package/dist/drivers/tenant-ddl.driver.d.ts.map +1 -0
- package/dist/drivers/tenant-ddl.driver.js +131 -0
- package/dist/drivers/tenant-ddl.driver.js.map +1 -0
- package/dist/drivers/zero.driver.d.ts +51 -4
- package/dist/drivers/zero.driver.d.ts.map +1 -1
- package/dist/drivers/zero.driver.js +51 -3
- package/dist/drivers/zero.driver.js.map +1 -1
- package/dist/errors.d.ts +37 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +64 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tenant Admin Driver — 多租户实例的「平台管理员」侧客户端。
|
|
3
|
+
*
|
|
4
|
+
* 对接 Studio BFF 的平台管理端点(经 Kong `dashboard` catch-all + basic-auth 保护,
|
|
5
|
+
* 见 kong/mysql/kong.yml 的 dashboard 服务与 DASHBOARD consumer):
|
|
6
|
+
* - GET /api/platform/tenants 列租户(`{ tenants, multiTenantMode }`)
|
|
7
|
+
* - GET /api/platform/tenants/{id} 单租户(响应即租户行,顶层展开)
|
|
8
|
+
* - POST /api/platform/tenants 建/复活租户(201,响应为租户行 + `realtime_provisioned`)
|
|
9
|
+
* - PATCH /api/platform/tenants/{id} 改属性/改键(白名单字段,不含 jwt_secret)
|
|
10
|
+
* - PATCH /api/platform/tenants 补空密钥(仅 anon_key/service_role_key 为 NULL 的行)
|
|
11
|
+
* - DELETE /api/platform/tenants/{id} 删除(软删)
|
|
12
|
+
*
|
|
13
|
+
* 认证模型:HTTP Basic(DASHBOARD_USERNAME / DASHBOARD_PASSWORD)。数据面的 anon /
|
|
14
|
+
* service_role key 在这里一律不适用——全局密钥访问租户管理面会被网关挡掉。
|
|
15
|
+
*
|
|
16
|
+
* ── 脱敏纪律(务必遵守)──────────────────────────────────────────────
|
|
17
|
+
* 这些端点的响应体**同时包含 `jwt_secret`(租户签名母密钥)与两把明文密钥**。母密钥
|
|
18
|
+
* 一旦外泄,持有者可自签任意 role / tenant_id / 期限的 token,危害高于 service_role。
|
|
19
|
+
* 因此本 driver 在**解析层**就把 `jwt_secret` 从所有返回类型里剥离,密钥值只在调用方
|
|
20
|
+
* 显式要求({@link TenantAdminDriver.getKeys} / {@link TenantAdminDriver.create})时以
|
|
21
|
+
* `TenantKeyBundle` 单独返回,其余任何路径都拿不到密钥明文。脱敏不依赖调用方自觉。
|
|
22
|
+
*/
|
|
23
|
+
export interface TenantAdminDriverOptions {
|
|
24
|
+
/** Kong 网关地址(必须是网关,不能是 Studio 端口,否则绕过 basic-auth)。 */
|
|
25
|
+
endpoint: string;
|
|
26
|
+
username: string;
|
|
27
|
+
password: string;
|
|
28
|
+
timeout?: number;
|
|
29
|
+
}
|
|
30
|
+
/** 平台侧租户行中可安全外传的部分(无任何密钥材料)。 */
|
|
31
|
+
export interface RedactedTenant {
|
|
32
|
+
tenantId: string;
|
|
33
|
+
tenantCode?: string;
|
|
34
|
+
displayName?: string;
|
|
35
|
+
status: string;
|
|
36
|
+
plan?: string;
|
|
37
|
+
dbName?: string;
|
|
38
|
+
dbUser?: string;
|
|
39
|
+
ossPrefix?: string;
|
|
40
|
+
retentionDays?: number | string | null;
|
|
41
|
+
createdAt?: string;
|
|
42
|
+
updatedAt?: string;
|
|
43
|
+
deletedAt?: string | null;
|
|
44
|
+
/** `anon_key` 是否已签发(NULL 表示存量租户缺密钥,需要 repair-keys)。 */
|
|
45
|
+
hasAnonKey: boolean;
|
|
46
|
+
hasServiceRoleKey: boolean;
|
|
47
|
+
/** 是否处于软删除态:同名 create 会走「复活并重置密钥」分支,属破坏性动作。 */
|
|
48
|
+
softDeleted: boolean;
|
|
49
|
+
}
|
|
50
|
+
/** 仅由显式取键路径返回;调用方只能把它写进钥匙串或 0600 交付包,绝不打印。 */
|
|
51
|
+
export interface TenantKeyBundle {
|
|
52
|
+
anonKey: string | null;
|
|
53
|
+
serviceRoleKey: string | null;
|
|
54
|
+
}
|
|
55
|
+
export interface TenantListResult {
|
|
56
|
+
multiTenantMode: boolean;
|
|
57
|
+
tenants: RedactedTenant[];
|
|
58
|
+
}
|
|
59
|
+
export interface TenantCreateInput {
|
|
60
|
+
tenantId: string;
|
|
61
|
+
plan?: string;
|
|
62
|
+
ossPrefix?: string;
|
|
63
|
+
retentionDays?: number;
|
|
64
|
+
tenantCode?: string;
|
|
65
|
+
displayName?: string;
|
|
66
|
+
dbName?: string;
|
|
67
|
+
dbUser?: string;
|
|
68
|
+
}
|
|
69
|
+
export interface TenantCreateResult {
|
|
70
|
+
tenant: RedactedTenant;
|
|
71
|
+
keys: TenantKeyBundle;
|
|
72
|
+
/** 平台是否在创建时成功把租户注册进 Realtime(false 时该租户的 WS 握手会 403)。 */
|
|
73
|
+
realtimeProvisioned?: boolean;
|
|
74
|
+
}
|
|
75
|
+
/** 补空密钥的结果(`PATCH /api/platform/tenants`)。 */
|
|
76
|
+
export interface TenantRepairKeysResult {
|
|
77
|
+
message: string;
|
|
78
|
+
updated: number;
|
|
79
|
+
}
|
|
80
|
+
/** 平台管理面调用的统一错误(携带机器码 + HTTP 状态)。 */
|
|
81
|
+
export declare class TenantAdminError extends Error {
|
|
82
|
+
readonly code: string;
|
|
83
|
+
readonly status?: number;
|
|
84
|
+
constructor(code: string, message: string, status?: number);
|
|
85
|
+
}
|
|
86
|
+
export declare class TenantAdminDriver {
|
|
87
|
+
private readonly client;
|
|
88
|
+
constructor(options: TenantAdminDriverOptions);
|
|
89
|
+
/**
|
|
90
|
+
* 列租户。注意 Studio 侧在多租户关闭时返回 `{ tenants: [], multiTenantMode: false }`,
|
|
91
|
+
* 而元数据查询失败时同样返回 200 + 空列表(`multiTenantMode: true`)——两者需在
|
|
92
|
+
* 上层区分,见 `tenant/probe.ts`。
|
|
93
|
+
*/
|
|
94
|
+
list(statusFilter?: string): Promise<TenantListResult>;
|
|
95
|
+
/** 单租户详情(脱敏行)。404 归一化为 TENANT_NOT_FOUND。 */
|
|
96
|
+
get(tenantId: string): Promise<RedactedTenant>;
|
|
97
|
+
/**
|
|
98
|
+
* 显式取该租户的密钥。唯一会返回密钥明文的读路径:调用方必须只把它送进
|
|
99
|
+
* 钥匙串或 `0600` 交付包,不得写入 stdout / `--json` / 日志。
|
|
100
|
+
*/
|
|
101
|
+
getKeys(tenantId: string): Promise<TenantKeyBundle>;
|
|
102
|
+
/**
|
|
103
|
+
* 建租户。**撞名是破坏性的**:同名租户若处于软删除态,平台会复活它并**重新生成
|
|
104
|
+
* `jwt_secret`、覆写两把密钥**。调用方必须先 `get()` 判重并显式确认。
|
|
105
|
+
*/
|
|
106
|
+
create(input: TenantCreateInput): Promise<TenantCreateResult>;
|
|
107
|
+
/** 改租户属性(服务端白名单:display_name/status/plan/db_name/db_user/oss_prefix/anon_key/service_role_key)。 */
|
|
108
|
+
update(tenantId: string, patch: {
|
|
109
|
+
displayName?: string;
|
|
110
|
+
status?: string;
|
|
111
|
+
plan?: string;
|
|
112
|
+
}): Promise<RedactedTenant>;
|
|
113
|
+
/**
|
|
114
|
+
* 补空密钥:只对 `jwt_secret` 有值但 `anon_key`/`service_role_key` 为 NULL 的存量行,
|
|
115
|
+
* 用**原 secret** 重签两把键。幂等、非破坏性。
|
|
116
|
+
*
|
|
117
|
+
* 这不是轮换:已签发的密钥不会被作废(HS256 无 jti / 无撤销表)。
|
|
118
|
+
*/
|
|
119
|
+
repairKeys(): Promise<TenantRepairKeysResult>;
|
|
120
|
+
/** 删除租户(平台侧软删 + 资源回收,具体语义由平台 purge 流程负责)。 */
|
|
121
|
+
remove(tenantId: string): Promise<void>;
|
|
122
|
+
private request;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Studio 的错误体是 `{ error: { message } }`(apiWrapper 兜底时为 `{ error: "<msg>" }`),
|
|
126
|
+
* 401 也可能是 Kong basic-auth 直接返回的 `{ message }`。逐级兜底并保留状态码语义,
|
|
127
|
+
* 否则调用方无法区分"口令错"与"租户不存在"。
|
|
128
|
+
*/
|
|
129
|
+
export declare function normalizeTenantAdminError(error: unknown): TenantAdminError;
|
|
130
|
+
//# sourceMappingURL=tenant-admin.driver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-admin.driver.d.ts","sourceRoot":"","sources":["../../src/drivers/tenant-admin.driver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tenant Admin Driver — 多租户实例的「平台管理员」侧客户端。
|
|
3
|
+
*
|
|
4
|
+
* 对接 Studio BFF 的平台管理端点(经 Kong `dashboard` catch-all + basic-auth 保护,
|
|
5
|
+
* 见 kong/mysql/kong.yml 的 dashboard 服务与 DASHBOARD consumer):
|
|
6
|
+
* - GET /api/platform/tenants 列租户(`{ tenants, multiTenantMode }`)
|
|
7
|
+
* - GET /api/platform/tenants/{id} 单租户(响应即租户行,顶层展开)
|
|
8
|
+
* - POST /api/platform/tenants 建/复活租户(201,响应为租户行 + `realtime_provisioned`)
|
|
9
|
+
* - PATCH /api/platform/tenants/{id} 改属性/改键(白名单字段,不含 jwt_secret)
|
|
10
|
+
* - PATCH /api/platform/tenants 补空密钥(仅 anon_key/service_role_key 为 NULL 的行)
|
|
11
|
+
* - DELETE /api/platform/tenants/{id} 删除(软删)
|
|
12
|
+
*
|
|
13
|
+
* 认证模型:HTTP Basic(DASHBOARD_USERNAME / DASHBOARD_PASSWORD)。数据面的 anon /
|
|
14
|
+
* service_role key 在这里一律不适用——全局密钥访问租户管理面会被网关挡掉。
|
|
15
|
+
*
|
|
16
|
+
* ── 脱敏纪律(务必遵守)──────────────────────────────────────────────
|
|
17
|
+
* 这些端点的响应体**同时包含 `jwt_secret`(租户签名母密钥)与两把明文密钥**。母密钥
|
|
18
|
+
* 一旦外泄,持有者可自签任意 role / tenant_id / 期限的 token,危害高于 service_role。
|
|
19
|
+
* 因此本 driver 在**解析层**就把 `jwt_secret` 从所有返回类型里剥离,密钥值只在调用方
|
|
20
|
+
* 显式要求({@link TenantAdminDriver.getKeys} / {@link TenantAdminDriver.create})时以
|
|
21
|
+
* `TenantKeyBundle` 单独返回,其余任何路径都拿不到密钥明文。脱敏不依赖调用方自觉。
|
|
22
|
+
*/
|
|
23
|
+
import axios from "axios";
|
|
24
|
+
/** 平台管理面调用的统一错误(携带机器码 + HTTP 状态)。 */
|
|
25
|
+
export class TenantAdminError extends Error {
|
|
26
|
+
code;
|
|
27
|
+
status;
|
|
28
|
+
constructor(code, message, status) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = "TenantAdminError";
|
|
31
|
+
this.code = code;
|
|
32
|
+
this.status = status;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export class TenantAdminDriver {
|
|
36
|
+
client;
|
|
37
|
+
constructor(options) {
|
|
38
|
+
const endpoint = options.endpoint.replace(/\/$/, "");
|
|
39
|
+
this.client = axios.create({
|
|
40
|
+
baseURL: `${endpoint}/api/platform/tenants`,
|
|
41
|
+
timeout: options.timeout ?? 30_000,
|
|
42
|
+
auth: { username: options.username, password: options.password },
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 列租户。注意 Studio 侧在多租户关闭时返回 `{ tenants: [], multiTenantMode: false }`,
|
|
47
|
+
* 而元数据查询失败时同样返回 200 + 空列表(`multiTenantMode: true`)——两者需在
|
|
48
|
+
* 上层区分,见 `tenant/probe.ts`。
|
|
49
|
+
*/
|
|
50
|
+
async list(statusFilter) {
|
|
51
|
+
const response = await this.request(() => this.client.get("", {
|
|
52
|
+
params: statusFilter ? { status: statusFilter } : undefined,
|
|
53
|
+
}));
|
|
54
|
+
return {
|
|
55
|
+
multiTenantMode: response?.multiTenantMode !== false,
|
|
56
|
+
tenants: (response?.tenants ?? []).map(redact),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** 单租户详情(脱敏行)。404 归一化为 TENANT_NOT_FOUND。 */
|
|
60
|
+
async get(tenantId) {
|
|
61
|
+
const row = await this.request(() => this.client.get(`/${encodeURIComponent(tenantId)}`));
|
|
62
|
+
return redact(row);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 显式取该租户的密钥。唯一会返回密钥明文的读路径:调用方必须只把它送进
|
|
66
|
+
* 钥匙串或 `0600` 交付包,不得写入 stdout / `--json` / 日志。
|
|
67
|
+
*/
|
|
68
|
+
async getKeys(tenantId) {
|
|
69
|
+
const row = await this.request(() => this.client.get(`/${encodeURIComponent(tenantId)}`));
|
|
70
|
+
return keysOf(row);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 建租户。**撞名是破坏性的**:同名租户若处于软删除态,平台会复活它并**重新生成
|
|
74
|
+
* `jwt_secret`、覆写两把密钥**。调用方必须先 `get()` 判重并显式确认。
|
|
75
|
+
*/
|
|
76
|
+
async create(input) {
|
|
77
|
+
const body = { tenant_id: input.tenantId };
|
|
78
|
+
if (input.plan !== undefined)
|
|
79
|
+
body.plan = input.plan;
|
|
80
|
+
if (input.ossPrefix !== undefined)
|
|
81
|
+
body.oss_prefix = input.ossPrefix;
|
|
82
|
+
if (input.retentionDays !== undefined)
|
|
83
|
+
body.retention_days = input.retentionDays;
|
|
84
|
+
if (input.tenantCode !== undefined)
|
|
85
|
+
body.tenant_code = input.tenantCode;
|
|
86
|
+
if (input.displayName !== undefined)
|
|
87
|
+
body.display_name = input.displayName;
|
|
88
|
+
if (input.dbName !== undefined)
|
|
89
|
+
body.db_name = input.dbName;
|
|
90
|
+
if (input.dbUser !== undefined)
|
|
91
|
+
body.db_user = input.dbUser;
|
|
92
|
+
const row = await this.request(() => this.client.post("", body));
|
|
93
|
+
return {
|
|
94
|
+
tenant: redact(row),
|
|
95
|
+
keys: keysOf(row),
|
|
96
|
+
realtimeProvisioned: typeof row?.realtime_provisioned === "boolean" ? row.realtime_provisioned : undefined,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/** 改租户属性(服务端白名单:display_name/status/plan/db_name/db_user/oss_prefix/anon_key/service_role_key)。 */
|
|
100
|
+
async update(tenantId, patch) {
|
|
101
|
+
const body = {};
|
|
102
|
+
if (patch.displayName !== undefined)
|
|
103
|
+
body.display_name = patch.displayName;
|
|
104
|
+
if (patch.status !== undefined)
|
|
105
|
+
body.status = patch.status;
|
|
106
|
+
if (patch.plan !== undefined)
|
|
107
|
+
body.plan = patch.plan;
|
|
108
|
+
const row = await this.request(() => this.client.patch(`/${encodeURIComponent(tenantId)}`, body));
|
|
109
|
+
return redact(row);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 补空密钥:只对 `jwt_secret` 有值但 `anon_key`/`service_role_key` 为 NULL 的存量行,
|
|
113
|
+
* 用**原 secret** 重签两把键。幂等、非破坏性。
|
|
114
|
+
*
|
|
115
|
+
* 这不是轮换:已签发的密钥不会被作废(HS256 无 jti / 无撤销表)。
|
|
116
|
+
*/
|
|
117
|
+
async repairKeys() {
|
|
118
|
+
const body = await this.request(() => this.client.patch("", {}));
|
|
119
|
+
return {
|
|
120
|
+
message: typeof body?.message === "string" ? body.message : "backfill attempted",
|
|
121
|
+
updated: typeof body?.updated === "number" ? body.updated : 0,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/** 删除租户(平台侧软删 + 资源回收,具体语义由平台 purge 流程负责)。 */
|
|
125
|
+
async remove(tenantId) {
|
|
126
|
+
await this.request(() => this.client.delete(`/${encodeURIComponent(tenantId)}`));
|
|
127
|
+
}
|
|
128
|
+
async request(operation) {
|
|
129
|
+
try {
|
|
130
|
+
const response = await operation();
|
|
131
|
+
return (response.data ?? {});
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
throw normalizeTenantAdminError(error);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* 租户行 → 可安全外传的结构。**显式构造**而非展开,新增列默认不会外泄;
|
|
140
|
+
* `jwt_secret` 因此无论平台侧返回什么都不会离开解析层。
|
|
141
|
+
*/
|
|
142
|
+
function redact(row) {
|
|
143
|
+
const r = row ?? {};
|
|
144
|
+
const status = str(r.status) ?? "";
|
|
145
|
+
return {
|
|
146
|
+
tenantId: str(r.tenant_id) ?? "",
|
|
147
|
+
tenantCode: optStr(r.tenant_code),
|
|
148
|
+
displayName: optStr(r.display_name),
|
|
149
|
+
status,
|
|
150
|
+
plan: optStr(r.plan),
|
|
151
|
+
dbName: optStr(r.db_name),
|
|
152
|
+
dbUser: optStr(r.db_user),
|
|
153
|
+
ossPrefix: optStr(r.oss_prefix),
|
|
154
|
+
retentionDays: typeof r.retention_days === "number" || r.retention_days == null
|
|
155
|
+
? r.retention_days
|
|
156
|
+
: String(r.retention_days),
|
|
157
|
+
createdAt: optStr(r.created_at),
|
|
158
|
+
updatedAt: optStr(r.updated_at),
|
|
159
|
+
deletedAt: r.deleted_at == null ? null : String(r.deleted_at),
|
|
160
|
+
hasAnonKey: isNonEmptyString(r.anon_key),
|
|
161
|
+
hasServiceRoleKey: isNonEmptyString(r.service_role_key),
|
|
162
|
+
softDeleted: status === "deleted",
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function keysOf(row) {
|
|
166
|
+
return {
|
|
167
|
+
anonKey: isNonEmptyString(row?.anon_key) ? String(row?.anon_key) : null,
|
|
168
|
+
serviceRoleKey: isNonEmptyString(row?.service_role_key)
|
|
169
|
+
? String(row?.service_role_key)
|
|
170
|
+
: null,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Studio 的错误体是 `{ error: { message } }`(apiWrapper 兜底时为 `{ error: "<msg>" }`),
|
|
175
|
+
* 401 也可能是 Kong basic-auth 直接返回的 `{ message }`。逐级兜底并保留状态码语义,
|
|
176
|
+
* 否则调用方无法区分"口令错"与"租户不存在"。
|
|
177
|
+
*/
|
|
178
|
+
export function normalizeTenantAdminError(error) {
|
|
179
|
+
if (!axios.isAxiosError(error)) {
|
|
180
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
181
|
+
return new TenantAdminError("TENANT_ADMIN_UNKNOWN_ERROR", message);
|
|
182
|
+
}
|
|
183
|
+
const axiosError = error;
|
|
184
|
+
const status = axiosError.response?.status;
|
|
185
|
+
const body = axiosError.response?.data;
|
|
186
|
+
const nested = body?.error && typeof body.error === "object"
|
|
187
|
+
? body.error.message
|
|
188
|
+
: typeof body?.error === "string"
|
|
189
|
+
? body.error
|
|
190
|
+
: undefined;
|
|
191
|
+
const message = typeof nested === "string" && nested
|
|
192
|
+
? nested
|
|
193
|
+
: typeof body?.message === "string" && body.message
|
|
194
|
+
? body.message
|
|
195
|
+
: status
|
|
196
|
+
? `Tenant admin request failed (HTTP ${status})`
|
|
197
|
+
: axiosError.message || "Tenant admin network request failed";
|
|
198
|
+
const code = status === 401
|
|
199
|
+
? "TENANT_ADMIN_UNAUTHORIZED"
|
|
200
|
+
: status === 403
|
|
201
|
+
? "TENANT_ADMIN_FORBIDDEN"
|
|
202
|
+
: status === 404
|
|
203
|
+
? "TENANT_NOT_FOUND"
|
|
204
|
+
: status === 409
|
|
205
|
+
? "TENANT_ALREADY_EXISTS"
|
|
206
|
+
: status === 400
|
|
207
|
+
? "TENANT_ADMIN_BAD_REQUEST"
|
|
208
|
+
: status
|
|
209
|
+
? `TENANT_ADMIN_HTTP_${status}`
|
|
210
|
+
: "TENANT_ADMIN_NETWORK_ERROR";
|
|
211
|
+
return new TenantAdminError(code, message, status);
|
|
212
|
+
}
|
|
213
|
+
function str(value) {
|
|
214
|
+
return typeof value === "string" && value !== "" ? value : undefined;
|
|
215
|
+
}
|
|
216
|
+
function optStr(value) {
|
|
217
|
+
return value == null ? undefined : String(value);
|
|
218
|
+
}
|
|
219
|
+
function isNonEmptyString(value) {
|
|
220
|
+
return typeof value === "string" && value.length > 0;
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=tenant-admin.driver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-admin.driver.js","sourceRoot":"","sources":["../../src/drivers/tenant-admin.driver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tenant DDL Driver — 多租户「普通租户」自助 DDL / RLS 注册客户端。
|
|
3
|
+
*
|
|
4
|
+
* 对接 Studio BFF 的租户自助端点(Aone #87795993):
|
|
5
|
+
* - POST /api/tenant-ddl/query 在租户自有库执行 DDL/DML
|
|
6
|
+
* - POST /api/tenant-ddl/rls/tables 开/关表级 RLS
|
|
7
|
+
* - GET /api/tenant-ddl/rls/tables 列出本租户已注册 RLS 的表
|
|
8
|
+
* - DELETE /api/tenant-ddl/rls/tables 移除一张表的 RLS 注册(开关行)
|
|
9
|
+
* - POST /api/tenant-ddl/rls/policies 增/改一条 RLS 策略
|
|
10
|
+
* - GET /api/tenant-ddl/rls/policies 列出本租户策略(可按 table 过滤)
|
|
11
|
+
* - DELETE /api/tenant-ddl/rls/policies 删除一条策略
|
|
12
|
+
*
|
|
13
|
+
* 认证模型(务必遵守,见方案文档「三、认证与信任链」):
|
|
14
|
+
* 只发送 `Authorization: Bearer <per-tenant service_role key>`。**绝不**自行拼
|
|
15
|
+
* `X-Tenant-Id`——Kong 入口会剥离客户端伪造的该头、验签 per-tenant JWT 后注入
|
|
16
|
+
* 可信 X-Tenant-Id,并把下游 Authorization 替换为全局 key。租户身份完全由网关推导。
|
|
17
|
+
*
|
|
18
|
+
* 成功响应统一为 `{ data: ... }`,失败为 `{ code, message }`;本 driver 解包 data、
|
|
19
|
+
* 并把失败规范化为 TenantDdlError(携带 code/message/status)。
|
|
20
|
+
*/
|
|
21
|
+
/** RLS 命令行枚举(与服务端 resolver 白名单一致)。 */
|
|
22
|
+
export type RlsCommand = "ALL" | "SELECT" | "INSERT" | "UPDATE" | "DELETE";
|
|
23
|
+
export interface TenantDdlDriverOptions {
|
|
24
|
+
endpoint: string;
|
|
25
|
+
/** per-tenant service_role key(带 tenant_id claim)。不可用全局 key。 */
|
|
26
|
+
serviceRoleKey: string;
|
|
27
|
+
timeout?: number;
|
|
28
|
+
}
|
|
29
|
+
export interface RlsTableInput {
|
|
30
|
+
table_name: string;
|
|
31
|
+
/** 默认 true(启用 RLS)。 */
|
|
32
|
+
rls_enabled?: boolean;
|
|
33
|
+
/** 默认 false。 */
|
|
34
|
+
rls_forced?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export interface RlsPolicyInput {
|
|
37
|
+
table_name: string;
|
|
38
|
+
policy_name: string;
|
|
39
|
+
/** `*` 或 `[a-zA-Z0-9_]`;缺省服务端按 `*` 处理。 */
|
|
40
|
+
role_name?: string;
|
|
41
|
+
command?: RlsCommand | string;
|
|
42
|
+
using_expr?: string | null;
|
|
43
|
+
check_expr?: string | null;
|
|
44
|
+
permissive?: boolean;
|
|
45
|
+
}
|
|
46
|
+
/** GET /rls/tables 的一行(rls_* 以 tinyint 0/1 返回)。 */
|
|
47
|
+
export interface RlsTableRow {
|
|
48
|
+
table_schema: string;
|
|
49
|
+
table_name: string;
|
|
50
|
+
rls_enabled: number | boolean;
|
|
51
|
+
rls_forced: number | boolean;
|
|
52
|
+
}
|
|
53
|
+
/** GET /rls/policies 的一行。 */
|
|
54
|
+
export interface RlsPolicyRow {
|
|
55
|
+
table_schema: string;
|
|
56
|
+
table_name: string;
|
|
57
|
+
policy_name: string;
|
|
58
|
+
role_name: string;
|
|
59
|
+
command: string;
|
|
60
|
+
using_expr: string | null;
|
|
61
|
+
check_expr: string | null;
|
|
62
|
+
permissive: number | boolean;
|
|
63
|
+
}
|
|
64
|
+
/** 租户自助 DDL/RLS 调用的统一错误(携带机器码 + HTTP 状态)。 */
|
|
65
|
+
export declare class TenantDdlError extends Error {
|
|
66
|
+
readonly code: string;
|
|
67
|
+
readonly status?: number;
|
|
68
|
+
constructor(code: string, message: string, status?: number);
|
|
69
|
+
}
|
|
70
|
+
export declare class TenantDdlDriver {
|
|
71
|
+
private readonly client;
|
|
72
|
+
constructor(options: TenantDdlDriverOptions);
|
|
73
|
+
/**
|
|
74
|
+
* Execute a DDL/DML statement on the tenant's OWN database. The tenant MySQL
|
|
75
|
+
* account (GRANT ALL ON own_db.* only) is the hard isolation boundary; the
|
|
76
|
+
* server additionally pre-checks and rejects privilege/instance-level statements.
|
|
77
|
+
* Returns the raw pg-meta rows (`data`), or `[]` for statements with no result set.
|
|
78
|
+
*/
|
|
79
|
+
query(sql: string): Promise<unknown[]>;
|
|
80
|
+
/** Enable/force RLS on one of the tenant's tables (central registry, schema server-bound). */
|
|
81
|
+
upsertRlsTable(input: RlsTableInput): Promise<void>;
|
|
82
|
+
/** List the tenant's own RLS-registered tables. */
|
|
83
|
+
listRlsTables(): Promise<RlsTableRow[]>;
|
|
84
|
+
/**
|
|
85
|
+
* Remove a table's RLS registration (the central enable/force toggle row),
|
|
86
|
+
* symmetric to {@link deleteRlsPolicy}. The server binds table_schema to the
|
|
87
|
+
* tenant db and does NOT require the table to still exist, so this is safe to
|
|
88
|
+
* call after a DROP (idempotent cleanup).
|
|
89
|
+
*/
|
|
90
|
+
deleteRlsTable(tableName: string): Promise<void>;
|
|
91
|
+
/** Insert/update one RLS policy for the tenant (schema server-bound). */
|
|
92
|
+
upsertRlsPolicy(input: RlsPolicyInput): Promise<void>;
|
|
93
|
+
/** List the tenant's own policies, optionally filtered by table name. */
|
|
94
|
+
listRlsPolicies(tableName?: string): Promise<RlsPolicyRow[]>;
|
|
95
|
+
/** Delete one of the tenant's policies (scoped by table + policy name). */
|
|
96
|
+
deleteRlsPolicy(tableName: string, policyName: string): Promise<void>;
|
|
97
|
+
private request;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Map any failure to a TenantDdlError. Server errors carry `{ code, message }`
|
|
101
|
+
* (e.g. PERMISSION_DENIED / GLOBAL_KEY_NOT_ALLOWED / ROLE_NOT_ALLOWED /
|
|
102
|
+
* TENANT_NOT_ACTIVE / STATEMENT_NOT_ALLOWED); fall back to an HTTP-status code
|
|
103
|
+
* or a network code when the body is absent.
|
|
104
|
+
*/
|
|
105
|
+
export declare function normalizeTenantDdlError(error: unknown): TenantDdlError;
|
|
106
|
+
//# sourceMappingURL=tenant-ddl.driver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-ddl.driver.d.ts","sourceRoot":"","sources":["../../src/drivers/tenant-ddl.driver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,sCAAsC;AACtC,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE3E,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,uBAAuB;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,gBAAgB;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,mDAAmD;AACnD,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9B,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;CAC9B;AAED,6BAA6B;AAC7B,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;CAC9B;AAED,6CAA6C;AAC7C,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;gBAEb,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;CAM3D;AAQD,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;gBAE3B,OAAO,EAAE,sBAAsB;IAW3C;;;;;OAKG;IACG,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAO5C,8FAA8F;IACxF,cAAc,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAMzD,mDAAmD;IAC7C,aAAa,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAO7C;;;;;OAKG;IACG,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQtD,yEAAyE;IACnE,eAAe,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAM3D,yEAAyE;IACnE,eAAe,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IASlE,2EAA2E;IACrE,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAQ7D,OAAO;CAUtB;AAQD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAwBtE"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tenant DDL Driver — 多租户「普通租户」自助 DDL / RLS 注册客户端。
|
|
3
|
+
*
|
|
4
|
+
* 对接 Studio BFF 的租户自助端点(Aone #87795993):
|
|
5
|
+
* - POST /api/tenant-ddl/query 在租户自有库执行 DDL/DML
|
|
6
|
+
* - POST /api/tenant-ddl/rls/tables 开/关表级 RLS
|
|
7
|
+
* - GET /api/tenant-ddl/rls/tables 列出本租户已注册 RLS 的表
|
|
8
|
+
* - DELETE /api/tenant-ddl/rls/tables 移除一张表的 RLS 注册(开关行)
|
|
9
|
+
* - POST /api/tenant-ddl/rls/policies 增/改一条 RLS 策略
|
|
10
|
+
* - GET /api/tenant-ddl/rls/policies 列出本租户策略(可按 table 过滤)
|
|
11
|
+
* - DELETE /api/tenant-ddl/rls/policies 删除一条策略
|
|
12
|
+
*
|
|
13
|
+
* 认证模型(务必遵守,见方案文档「三、认证与信任链」):
|
|
14
|
+
* 只发送 `Authorization: Bearer <per-tenant service_role key>`。**绝不**自行拼
|
|
15
|
+
* `X-Tenant-Id`——Kong 入口会剥离客户端伪造的该头、验签 per-tenant JWT 后注入
|
|
16
|
+
* 可信 X-Tenant-Id,并把下游 Authorization 替换为全局 key。租户身份完全由网关推导。
|
|
17
|
+
*
|
|
18
|
+
* 成功响应统一为 `{ data: ... }`,失败为 `{ code, message }`;本 driver 解包 data、
|
|
19
|
+
* 并把失败规范化为 TenantDdlError(携带 code/message/status)。
|
|
20
|
+
*/
|
|
21
|
+
import axios from "axios";
|
|
22
|
+
/** 租户自助 DDL/RLS 调用的统一错误(携带机器码 + HTTP 状态)。 */
|
|
23
|
+
export class TenantDdlError extends Error {
|
|
24
|
+
code;
|
|
25
|
+
status;
|
|
26
|
+
constructor(code, message, status) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = "TenantDdlError";
|
|
29
|
+
this.code = code;
|
|
30
|
+
this.status = status;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export class TenantDdlDriver {
|
|
34
|
+
client;
|
|
35
|
+
constructor(options) {
|
|
36
|
+
const endpoint = options.endpoint.replace(/\/$/, "");
|
|
37
|
+
this.client = axios.create({
|
|
38
|
+
baseURL: `${endpoint}/api/tenant-ddl`,
|
|
39
|
+
timeout: options.timeout ?? 60_000,
|
|
40
|
+
// Only the per-tenant service_role bearer. Never send X-Tenant-Id here —
|
|
41
|
+
// Kong strips any client-supplied value and injects the trusted one.
|
|
42
|
+
headers: { Authorization: `Bearer ${options.serviceRoleKey}` },
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Execute a DDL/DML statement on the tenant's OWN database. The tenant MySQL
|
|
47
|
+
* account (GRANT ALL ON own_db.* only) is the hard isolation boundary; the
|
|
48
|
+
* server additionally pre-checks and rejects privilege/instance-level statements.
|
|
49
|
+
* Returns the raw pg-meta rows (`data`), or `[]` for statements with no result set.
|
|
50
|
+
*/
|
|
51
|
+
async query(sql) {
|
|
52
|
+
const data = await this.request(() => this.client.post("/query", { sql }));
|
|
53
|
+
return data ?? [];
|
|
54
|
+
}
|
|
55
|
+
/** Enable/force RLS on one of the tenant's tables (central registry, schema server-bound). */
|
|
56
|
+
async upsertRlsTable(input) {
|
|
57
|
+
await this.request(() => this.client.post("/rls/tables", compact(input)));
|
|
58
|
+
}
|
|
59
|
+
/** List the tenant's own RLS-registered tables. */
|
|
60
|
+
async listRlsTables() {
|
|
61
|
+
const data = await this.request(() => this.client.get("/rls/tables"));
|
|
62
|
+
return data ?? [];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Remove a table's RLS registration (the central enable/force toggle row),
|
|
66
|
+
* symmetric to {@link deleteRlsPolicy}. The server binds table_schema to the
|
|
67
|
+
* tenant db and does NOT require the table to still exist, so this is safe to
|
|
68
|
+
* call after a DROP (idempotent cleanup).
|
|
69
|
+
*/
|
|
70
|
+
async deleteRlsTable(tableName) {
|
|
71
|
+
await this.request(() => this.client.delete("/rls/tables", {
|
|
72
|
+
params: { table_name: tableName },
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
/** Insert/update one RLS policy for the tenant (schema server-bound). */
|
|
76
|
+
async upsertRlsPolicy(input) {
|
|
77
|
+
await this.request(() => this.client.post("/rls/policies", compact(input)));
|
|
78
|
+
}
|
|
79
|
+
/** List the tenant's own policies, optionally filtered by table name. */
|
|
80
|
+
async listRlsPolicies(tableName) {
|
|
81
|
+
const data = await this.request(() => this.client.get("/rls/policies", {
|
|
82
|
+
params: tableName ? { table_name: tableName } : undefined,
|
|
83
|
+
}));
|
|
84
|
+
return data ?? [];
|
|
85
|
+
}
|
|
86
|
+
/** Delete one of the tenant's policies (scoped by table + policy name). */
|
|
87
|
+
async deleteRlsPolicy(tableName, policyName) {
|
|
88
|
+
await this.request(() => this.client.delete("/rls/policies", {
|
|
89
|
+
params: { table_name: tableName, policy_name: policyName },
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
async request(operation) {
|
|
93
|
+
try {
|
|
94
|
+
const response = await operation();
|
|
95
|
+
return response.data?.data;
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
throw normalizeTenantDdlError(error);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function compact(value) {
|
|
103
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Map any failure to a TenantDdlError. Server errors carry `{ code, message }`
|
|
107
|
+
* (e.g. PERMISSION_DENIED / GLOBAL_KEY_NOT_ALLOWED / ROLE_NOT_ALLOWED /
|
|
108
|
+
* TENANT_NOT_ACTIVE / STATEMENT_NOT_ALLOWED); fall back to an HTTP-status code
|
|
109
|
+
* or a network code when the body is absent.
|
|
110
|
+
*/
|
|
111
|
+
export function normalizeTenantDdlError(error) {
|
|
112
|
+
if (!axios.isAxiosError(error)) {
|
|
113
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
114
|
+
return new TenantDdlError("TENANT_DDL_UNKNOWN_ERROR", message);
|
|
115
|
+
}
|
|
116
|
+
const axiosError = error;
|
|
117
|
+
const status = axiosError.response?.status;
|
|
118
|
+
const body = axiosError.response?.data;
|
|
119
|
+
const code = typeof body?.code === "string"
|
|
120
|
+
? body.code
|
|
121
|
+
: status
|
|
122
|
+
? `TENANT_DDL_HTTP_${status}`
|
|
123
|
+
: "TENANT_DDL_NETWORK_ERROR";
|
|
124
|
+
const message = typeof body?.message === "string"
|
|
125
|
+
? body.message
|
|
126
|
+
: status
|
|
127
|
+
? `Tenant DDL request failed (HTTP ${status})`
|
|
128
|
+
: axiosError.message || "Tenant DDL network request failed";
|
|
129
|
+
return new TenantDdlError(code, message, status);
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=tenant-ddl.driver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-ddl.driver.js","sourceRoot":"","sources":["../../src/drivers/tenant-ddl.driver.ts"],"names":[],"mappings":"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"}
|