@saasicat/cli 0.2.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 +202 -0
- package/README.md +141 -0
- package/bin/saas-platform.js +185 -0
- package/dist/index.cjs +2284 -0
- package/dist/index.d.cts +548 -0
- package/dist/index.d.ts +548 -0
- package/dist/index.js +2212 -0
- package/package.json +64 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2212 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// src/tokens.ts
|
|
5
|
+
var CLI_CONTEXT_CONFIG_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-cli/Config");
|
|
6
|
+
var USER_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-cli/UserPort");
|
|
7
|
+
var USER_MANAGEMENT_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-cli/UserManagementPort");
|
|
8
|
+
var AUDIT_QUERY_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-cli/AuditQueryPort");
|
|
9
|
+
var DOCTOR_CHECKS_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-cli/DoctorChecks");
|
|
10
|
+
var MANIFEST_ACCESS_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-cli/ManifestAccessPort");
|
|
11
|
+
var MANIFEST_CHECKS_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-cli/ManifestChecks");
|
|
12
|
+
|
|
13
|
+
// src/cli-context.service.ts
|
|
14
|
+
import * as os from "os";
|
|
15
|
+
import * as readline from "readline";
|
|
16
|
+
import { Inject, Injectable } from "@nestjs/common";
|
|
17
|
+
import { AdminAuditService, MfaService } from "@saasicat/nest";
|
|
18
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
19
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
20
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
21
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
22
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
23
|
+
}
|
|
24
|
+
__name(_ts_decorate, "_ts_decorate");
|
|
25
|
+
function _ts_metadata(k, v) {
|
|
26
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
27
|
+
}
|
|
28
|
+
__name(_ts_metadata, "_ts_metadata");
|
|
29
|
+
function _ts_param(paramIndex, decorator) {
|
|
30
|
+
return function(target, key) {
|
|
31
|
+
decorator(target, key, paramIndex);
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
__name(_ts_param, "_ts_param");
|
|
35
|
+
var CliContextService = class {
|
|
36
|
+
static {
|
|
37
|
+
__name(this, "CliContextService");
|
|
38
|
+
}
|
|
39
|
+
config;
|
|
40
|
+
users;
|
|
41
|
+
mfa;
|
|
42
|
+
audit;
|
|
43
|
+
constructor(config, users, mfa, audit) {
|
|
44
|
+
this.config = config;
|
|
45
|
+
this.users = users;
|
|
46
|
+
this.mfa = mfa;
|
|
47
|
+
this.audit = audit;
|
|
48
|
+
}
|
|
49
|
+
// ---------------------------------------------------------------------
|
|
50
|
+
// §1 Identity
|
|
51
|
+
// ---------------------------------------------------------------------
|
|
52
|
+
resolveIdentity(asFlag) {
|
|
53
|
+
const fromEnv = process.env[this.config.adminEmailEnvVar] ?? "";
|
|
54
|
+
const email = (asFlag ?? fromEnv).trim().toLowerCase();
|
|
55
|
+
if (!email) {
|
|
56
|
+
throw new CliError("NO_IDENTITY", `Keine Admin-Identit\xE4t gesetzt. Bitte $${this.config.adminEmailEnvVar} setzen oder --as <email> \xFCbergeben.`, 2);
|
|
57
|
+
}
|
|
58
|
+
const host = os.hostname();
|
|
59
|
+
return {
|
|
60
|
+
email,
|
|
61
|
+
host,
|
|
62
|
+
actor: `cli:${email}:${host}`
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Loads + validates the user for a CLI identity:
|
|
67
|
+
* - user exists + active
|
|
68
|
+
* - user has platform role SUPER_ADMIN
|
|
69
|
+
*
|
|
70
|
+
* Throws `CliError(NOT_SUPER_ADMIN, exit=2)` otherwise.
|
|
71
|
+
*/
|
|
72
|
+
async ensureSuperAdmin(identity) {
|
|
73
|
+
const user = await this.users.findByEmail(identity.email);
|
|
74
|
+
if (!user || user.deletedAt || !user.isActive) {
|
|
75
|
+
throw new CliError("USER_NOT_FOUND", `SUPER_ADMIN-User ${identity.email} nicht gefunden oder inaktiv.`, 2);
|
|
76
|
+
}
|
|
77
|
+
if (user.platformRole !== "SUPER_ADMIN") {
|
|
78
|
+
throw new CliError("NOT_SUPER_ADMIN", `User ${identity.email} hat Rolle ${user.platformRole} \u2014 nur SUPER_ADMIN darf das CLI nutzen.`, 2);
|
|
79
|
+
}
|
|
80
|
+
return user;
|
|
81
|
+
}
|
|
82
|
+
// ---------------------------------------------------------------------
|
|
83
|
+
// §2 MFA — mandatory TOTP
|
|
84
|
+
// ---------------------------------------------------------------------
|
|
85
|
+
/**
|
|
86
|
+
* Prompts the user for a TOTP code and verifies it against the stored
|
|
87
|
+
* secret (via the platform `MfaService` → `MfaPort`).
|
|
88
|
+
*
|
|
89
|
+
* Bypass: `process.env[mfaSkipEnvVar] === '1'` AND `!isProductionEnvironment()`.
|
|
90
|
+
*/
|
|
91
|
+
async requireMfa(userId) {
|
|
92
|
+
if (process.env[this.config.mfaSkipEnvVar] === "1" && !this.config.isProductionEnvironment()) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const enabled = await this.mfa.isEnabled(userId);
|
|
96
|
+
if (!enabled) {
|
|
97
|
+
throw new CliError("MFA_NOT_SET_UP", "MFA ist nicht konfiguriert. Bitte zuerst 'admin mfa-setup' ausf\xFChren.", 3);
|
|
98
|
+
}
|
|
99
|
+
const code = await this.prompt("TOTP-Code: ");
|
|
100
|
+
const ok2 = await this.mfa.verify({
|
|
101
|
+
userId,
|
|
102
|
+
code
|
|
103
|
+
});
|
|
104
|
+
if (!ok2) {
|
|
105
|
+
throw new CliError("MFA_FAILED", "TOTP-Code ung\xFCltig.", 3);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// ---------------------------------------------------------------------
|
|
109
|
+
// §3 Production confirmation
|
|
110
|
+
// ---------------------------------------------------------------------
|
|
111
|
+
async ensureProductionConfirmation(opts = {}) {
|
|
112
|
+
if (!this.config.isProductionEnvironment()) return;
|
|
113
|
+
if (opts.yes) return;
|
|
114
|
+
const answer = await this.prompt("Tippe production zur Best\xE4tigung: ");
|
|
115
|
+
if (answer.trim().toLowerCase() !== "production") {
|
|
116
|
+
throw new CliError("PRODUCTION_CONFIRM_ABORTED", "Production-Confirmation abgebrochen.", 1);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// ---------------------------------------------------------------------
|
|
120
|
+
// §5 Audit
|
|
121
|
+
// ---------------------------------------------------------------------
|
|
122
|
+
/**
|
|
123
|
+
* Writes an audit-log entry with the CLI actor tag. Wrapper over
|
|
124
|
+
* `AdminAuditService.log` with automatic `fromCli` construction.
|
|
125
|
+
*/
|
|
126
|
+
async log(input) {
|
|
127
|
+
const actor = this.audit.fromCli({
|
|
128
|
+
id: input.userId,
|
|
129
|
+
email: input.identity.email
|
|
130
|
+
});
|
|
131
|
+
await this.audit.log({
|
|
132
|
+
actor,
|
|
133
|
+
entity: input.entity,
|
|
134
|
+
entityId: input.entityId,
|
|
135
|
+
action: input.action,
|
|
136
|
+
changes: input.changes
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
// ---------------------------------------------------------------------
|
|
140
|
+
// Output helpers (§4)
|
|
141
|
+
// ---------------------------------------------------------------------
|
|
142
|
+
table(rows) {
|
|
143
|
+
if (rows.length === 0) {
|
|
144
|
+
console.log("\u2014 keine Eintr\xE4ge \u2014");
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
console.table(rows);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Reads a line from stdin. Override hook for tests: set
|
|
151
|
+
* `process.env.SAAS_PLATFORM_CLI_PROMPT_REPLY` to a reply, then
|
|
152
|
+
* the method succeeds without interaction.
|
|
153
|
+
*/
|
|
154
|
+
async prompt(question) {
|
|
155
|
+
const testReply = process.env.SAAS_PLATFORM_CLI_PROMPT_REPLY;
|
|
156
|
+
if (testReply !== void 0) return testReply;
|
|
157
|
+
const rl = readline.createInterface({
|
|
158
|
+
input: process.stdin,
|
|
159
|
+
output: process.stdout
|
|
160
|
+
});
|
|
161
|
+
return new Promise((resolve) => {
|
|
162
|
+
rl.question(question, (answer) => {
|
|
163
|
+
rl.close();
|
|
164
|
+
resolve(answer);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
CliContextService = _ts_decorate([
|
|
170
|
+
Injectable(),
|
|
171
|
+
_ts_param(0, Inject(CLI_CONTEXT_CONFIG_TOKEN)),
|
|
172
|
+
_ts_param(1, Inject(USER_PORT_TOKEN)),
|
|
173
|
+
_ts_metadata("design:type", Function),
|
|
174
|
+
_ts_metadata("design:paramtypes", [
|
|
175
|
+
typeof CliContextConfig === "undefined" ? Object : CliContextConfig,
|
|
176
|
+
typeof UserPort === "undefined" ? Object : UserPort,
|
|
177
|
+
typeof MfaService === "undefined" ? Object : MfaService,
|
|
178
|
+
typeof AdminAuditService === "undefined" ? Object : AdminAuditService
|
|
179
|
+
])
|
|
180
|
+
], CliContextService);
|
|
181
|
+
var CliError = class extends Error {
|
|
182
|
+
static {
|
|
183
|
+
__name(this, "CliError");
|
|
184
|
+
}
|
|
185
|
+
code;
|
|
186
|
+
exitCode;
|
|
187
|
+
constructor(code, message, exitCode) {
|
|
188
|
+
super(message), this.code = code, this.exitCode = exitCode;
|
|
189
|
+
this.name = "CliError";
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// src/mfa-setup-flow.ts
|
|
194
|
+
import { Injectable as Injectable2 } from "@nestjs/common";
|
|
195
|
+
import { MfaService as MfaService2 } from "@saasicat/nest";
|
|
196
|
+
function _ts_decorate2(decorators, target, key, desc) {
|
|
197
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
198
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
199
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
200
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
201
|
+
}
|
|
202
|
+
__name(_ts_decorate2, "_ts_decorate");
|
|
203
|
+
function _ts_metadata2(k, v) {
|
|
204
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
205
|
+
}
|
|
206
|
+
__name(_ts_metadata2, "_ts_metadata");
|
|
207
|
+
var MfaSetupFlow = class {
|
|
208
|
+
static {
|
|
209
|
+
__name(this, "MfaSetupFlow");
|
|
210
|
+
}
|
|
211
|
+
ctx;
|
|
212
|
+
mfa;
|
|
213
|
+
constructor(ctx, mfa) {
|
|
214
|
+
this.ctx = ctx;
|
|
215
|
+
this.mfa = mfa;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Runs the full setup flow. Throws `CliError` subclasses on
|
|
219
|
+
* auth/identity/confirmation errors.
|
|
220
|
+
*/
|
|
221
|
+
async run(options) {
|
|
222
|
+
const identity = this.ctx.resolveIdentity(options.asFlag);
|
|
223
|
+
const user = await this.ctx.ensureSuperAdmin(identity);
|
|
224
|
+
const alreadyEnabled = await this.mfa.isEnabled(user.id);
|
|
225
|
+
if (alreadyEnabled && !options.force) {
|
|
226
|
+
const answer = await this.ctx.prompt("MFA ist bereits konfiguriert. Tippe `yes`, um das Secret zu \xFCberschreiben: ");
|
|
227
|
+
if (answer.trim().toLowerCase() !== "yes") {
|
|
228
|
+
throw new CliError("MFA_SETUP_ABORTED", "Re-Setup nicht best\xE4tigt \u2014 bestehendes Secret bleibt aktiv.", 1);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const setup = await this.mfa.setup(user.id, user.email, options.issuer);
|
|
232
|
+
await this.ctx.log({
|
|
233
|
+
identity,
|
|
234
|
+
userId: user.id,
|
|
235
|
+
entity: "User",
|
|
236
|
+
entityId: user.id,
|
|
237
|
+
action: alreadyEnabled ? "MFA_SETUP_RESET" : "MFA_SETUP_COMPLETED",
|
|
238
|
+
changes: {
|
|
239
|
+
issuer: options.issuer
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
return {
|
|
243
|
+
secret: setup.secret,
|
|
244
|
+
otpauthUri: setup.otpauthUri,
|
|
245
|
+
userId: user.id,
|
|
246
|
+
userEmail: user.email
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Helper: returns a human-readable output block with secret + otpauthUri
|
|
251
|
+
* for `console.log` in the consumer command. Consumers may prepend their
|
|
252
|
+
* own QR-code renderer (e.g. `qrcode-terminal`).
|
|
253
|
+
*/
|
|
254
|
+
formatSetupResult(result) {
|
|
255
|
+
return [
|
|
256
|
+
`MFA-Setup f\xFCr ${result.userEmail} abgeschlossen.`,
|
|
257
|
+
"",
|
|
258
|
+
`Secret (Base32): ${result.secret}`,
|
|
259
|
+
`otpauth-URI: ${result.otpauthUri}`,
|
|
260
|
+
"",
|
|
261
|
+
"Bitte den otpauth-URI in den Authenticator (Google Authenticator,",
|
|
262
|
+
"1Password, \u2026) importieren oder als QR-Code in einem QR-Generator",
|
|
263
|
+
"rendern. Danach mit dem ersten TOTP-Code testen, dass der Login",
|
|
264
|
+
"funktioniert \u2014 sonst kommst du nicht mehr ans CLI."
|
|
265
|
+
].join("\n");
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
MfaSetupFlow = _ts_decorate2([
|
|
269
|
+
Injectable2(),
|
|
270
|
+
_ts_metadata2("design:type", Function),
|
|
271
|
+
_ts_metadata2("design:paramtypes", [
|
|
272
|
+
typeof CliContextService === "undefined" ? Object : CliContextService,
|
|
273
|
+
typeof MfaService2 === "undefined" ? Object : MfaService2
|
|
274
|
+
])
|
|
275
|
+
], MfaSetupFlow);
|
|
276
|
+
|
|
277
|
+
// src/whoami-flow.ts
|
|
278
|
+
import { Inject as Inject2, Injectable as Injectable3 } from "@nestjs/common";
|
|
279
|
+
import { MfaService as MfaService3 } from "@saasicat/nest";
|
|
280
|
+
function _ts_decorate3(decorators, target, key, desc) {
|
|
281
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
282
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
283
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
284
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
285
|
+
}
|
|
286
|
+
__name(_ts_decorate3, "_ts_decorate");
|
|
287
|
+
function _ts_metadata3(k, v) {
|
|
288
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
289
|
+
}
|
|
290
|
+
__name(_ts_metadata3, "_ts_metadata");
|
|
291
|
+
function _ts_param2(paramIndex, decorator) {
|
|
292
|
+
return function(target, key) {
|
|
293
|
+
decorator(target, key, paramIndex);
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
__name(_ts_param2, "_ts_param");
|
|
297
|
+
var WhoAmIFlow = class {
|
|
298
|
+
static {
|
|
299
|
+
__name(this, "WhoAmIFlow");
|
|
300
|
+
}
|
|
301
|
+
config;
|
|
302
|
+
ctx;
|
|
303
|
+
mfa;
|
|
304
|
+
constructor(config, ctx, mfa) {
|
|
305
|
+
this.config = config;
|
|
306
|
+
this.ctx = ctx;
|
|
307
|
+
this.mfa = mfa;
|
|
308
|
+
}
|
|
309
|
+
async run(asFlag) {
|
|
310
|
+
const identity = this.ctx.resolveIdentity(asFlag);
|
|
311
|
+
const isProduction = this.config.isProductionEnvironment();
|
|
312
|
+
const mfaSkipActive = !isProduction && process.env[this.config.mfaSkipEnvVar] === "1";
|
|
313
|
+
let userId = null;
|
|
314
|
+
let isSuperAdmin = false;
|
|
315
|
+
let mfaEnabled = false;
|
|
316
|
+
try {
|
|
317
|
+
const user = await this.ctx.ensureSuperAdmin(identity);
|
|
318
|
+
userId = user.id;
|
|
319
|
+
isSuperAdmin = true;
|
|
320
|
+
mfaEnabled = await this.mfa.isEnabled(user.id);
|
|
321
|
+
} catch {
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
email: identity.email,
|
|
325
|
+
host: identity.host,
|
|
326
|
+
actor: identity.actor,
|
|
327
|
+
userId,
|
|
328
|
+
isSuperAdmin,
|
|
329
|
+
mfaEnabled,
|
|
330
|
+
isProduction,
|
|
331
|
+
mfaSkipActive
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
formatResult(r) {
|
|
335
|
+
const lines = [
|
|
336
|
+
`Identit\xE4t: ${r.email}`,
|
|
337
|
+
`Host: ${r.host}`,
|
|
338
|
+
`Actor-Tag: ${r.actor}`,
|
|
339
|
+
`User-ID: ${r.userId ?? "\u2014 (User nicht gefunden)"}`,
|
|
340
|
+
`Plattform-Rolle: ${r.isSuperAdmin ? "SUPER_ADMIN \u2713" : "\u2014 (kein SUPER_ADMIN!)"}`,
|
|
341
|
+
`MFA konfiguriert: ${r.mfaEnabled ? "\u2713" : "\u2717 \u2014 bitte `admin mfa-setup` ausf\xFChren"}`,
|
|
342
|
+
`Environment: ${r.isProduction ? "PRODUCTION" : "non-production"}`
|
|
343
|
+
];
|
|
344
|
+
if (r.mfaSkipActive) {
|
|
345
|
+
lines.push("\u26A0 MFA-Bypass aktiv (SKIP-Env-Var gesetzt, non-prod)");
|
|
346
|
+
}
|
|
347
|
+
return lines.join("\n");
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
WhoAmIFlow = _ts_decorate3([
|
|
351
|
+
Injectable3(),
|
|
352
|
+
_ts_param2(0, Inject2(CLI_CONTEXT_CONFIG_TOKEN)),
|
|
353
|
+
_ts_metadata3("design:type", Function),
|
|
354
|
+
_ts_metadata3("design:paramtypes", [
|
|
355
|
+
typeof CliContextConfig === "undefined" ? Object : CliContextConfig,
|
|
356
|
+
typeof CliContextService === "undefined" ? Object : CliContextService,
|
|
357
|
+
typeof MfaService3 === "undefined" ? Object : MfaService3
|
|
358
|
+
])
|
|
359
|
+
], WhoAmIFlow);
|
|
360
|
+
|
|
361
|
+
// src/audit-tail-flow.ts
|
|
362
|
+
import { Inject as Inject3, Injectable as Injectable4 } from "@nestjs/common";
|
|
363
|
+
function _ts_decorate4(decorators, target, key, desc) {
|
|
364
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
365
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
366
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
367
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
368
|
+
}
|
|
369
|
+
__name(_ts_decorate4, "_ts_decorate");
|
|
370
|
+
function _ts_metadata4(k, v) {
|
|
371
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
372
|
+
}
|
|
373
|
+
__name(_ts_metadata4, "_ts_metadata");
|
|
374
|
+
function _ts_param3(paramIndex, decorator) {
|
|
375
|
+
return function(target, key) {
|
|
376
|
+
decorator(target, key, paramIndex);
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
__name(_ts_param3, "_ts_param");
|
|
380
|
+
var AuditTailFlow = class {
|
|
381
|
+
static {
|
|
382
|
+
__name(this, "AuditTailFlow");
|
|
383
|
+
}
|
|
384
|
+
auditQuery;
|
|
385
|
+
constructor(auditQuery) {
|
|
386
|
+
this.auditQuery = auditQuery;
|
|
387
|
+
}
|
|
388
|
+
async run(options = {}) {
|
|
389
|
+
const filter = {};
|
|
390
|
+
if (options.actor) filter.actorTag = options.actor;
|
|
391
|
+
if (options.action) filter.action = options.action;
|
|
392
|
+
if (options.entity) filter.entity = options.entity;
|
|
393
|
+
if (options.since) filter.from = options.since;
|
|
394
|
+
if (options.limit) filter.pageSize = options.limit;
|
|
395
|
+
return this.auditQuery.list(filter);
|
|
396
|
+
}
|
|
397
|
+
/** Format the result as an ASCII table for `console.table`. */
|
|
398
|
+
formatRows(entries) {
|
|
399
|
+
return entries.map((e) => ({
|
|
400
|
+
createdAt: e.createdAt,
|
|
401
|
+
actor: e.actorTag ?? "\u2014",
|
|
402
|
+
entity: e.entity,
|
|
403
|
+
entityId: this.truncate(e.entityId, 12),
|
|
404
|
+
action: e.action
|
|
405
|
+
}));
|
|
406
|
+
}
|
|
407
|
+
truncate(s, maxLen) {
|
|
408
|
+
return s.length > maxLen ? s.slice(0, maxLen - 1) + "\u2026" : s;
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
AuditTailFlow = _ts_decorate4([
|
|
412
|
+
Injectable4(),
|
|
413
|
+
_ts_param3(0, Inject3(AUDIT_QUERY_PORT_TOKEN)),
|
|
414
|
+
_ts_metadata4("design:type", Function),
|
|
415
|
+
_ts_metadata4("design:paramtypes", [
|
|
416
|
+
typeof AuditQueryPort === "undefined" ? Object : AuditQueryPort
|
|
417
|
+
])
|
|
418
|
+
], AuditTailFlow);
|
|
419
|
+
|
|
420
|
+
// src/doctor-flow.ts
|
|
421
|
+
import { Inject as Inject4, Injectable as Injectable5 } from "@nestjs/common";
|
|
422
|
+
function _ts_decorate5(decorators, target, key, desc) {
|
|
423
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
424
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
425
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
426
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
427
|
+
}
|
|
428
|
+
__name(_ts_decorate5, "_ts_decorate");
|
|
429
|
+
function _ts_metadata5(k, v) {
|
|
430
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
431
|
+
}
|
|
432
|
+
__name(_ts_metadata5, "_ts_metadata");
|
|
433
|
+
function _ts_param4(paramIndex, decorator) {
|
|
434
|
+
return function(target, key) {
|
|
435
|
+
decorator(target, key, paramIndex);
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
__name(_ts_param4, "_ts_param");
|
|
439
|
+
var DoctorFlow = class {
|
|
440
|
+
static {
|
|
441
|
+
__name(this, "DoctorFlow");
|
|
442
|
+
}
|
|
443
|
+
checks;
|
|
444
|
+
constructor(checks) {
|
|
445
|
+
this.checks = checks;
|
|
446
|
+
}
|
|
447
|
+
async run() {
|
|
448
|
+
const results = [];
|
|
449
|
+
let overall = "ok";
|
|
450
|
+
for (const check of this.checks) {
|
|
451
|
+
try {
|
|
452
|
+
const r = await check.run();
|
|
453
|
+
results.push({
|
|
454
|
+
id: check.id,
|
|
455
|
+
label: check.label,
|
|
456
|
+
...r
|
|
457
|
+
});
|
|
458
|
+
overall = aggregateSeverity(overall, r.severity);
|
|
459
|
+
} catch (err2) {
|
|
460
|
+
const message = err2 instanceof Error ? err2.message : String(err2);
|
|
461
|
+
results.push({
|
|
462
|
+
id: check.id,
|
|
463
|
+
label: check.label,
|
|
464
|
+
severity: "error",
|
|
465
|
+
message: `Check threw an exception: ${message}`
|
|
466
|
+
});
|
|
467
|
+
overall = "error";
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return {
|
|
471
|
+
overall,
|
|
472
|
+
checks: results
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
/** Returns the appropriate CLI exit code: 0 for `ok`/`warning`, 4 for `error`. */
|
|
476
|
+
exitCodeFor(report) {
|
|
477
|
+
return report.overall === "error" ? 4 : 0;
|
|
478
|
+
}
|
|
479
|
+
formatReport(report) {
|
|
480
|
+
const lines = [
|
|
481
|
+
`Doctor-Check (Gesamtstatus: ${report.overall.toUpperCase()})`,
|
|
482
|
+
""
|
|
483
|
+
];
|
|
484
|
+
for (const c of report.checks) {
|
|
485
|
+
const icon = c.severity === "ok" ? "\u2713" : c.severity === "warning" ? "\u26A0" : "\u2717";
|
|
486
|
+
lines.push(` ${icon} ${c.label}: ${c.message}`);
|
|
487
|
+
}
|
|
488
|
+
return lines.join("\n");
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
DoctorFlow = _ts_decorate5([
|
|
492
|
+
Injectable5(),
|
|
493
|
+
_ts_param4(0, Inject4(DOCTOR_CHECKS_TOKEN)),
|
|
494
|
+
_ts_metadata5("design:type", Function),
|
|
495
|
+
_ts_metadata5("design:paramtypes", [
|
|
496
|
+
Array
|
|
497
|
+
])
|
|
498
|
+
], DoctorFlow);
|
|
499
|
+
function aggregateSeverity(a, b) {
|
|
500
|
+
if (a === "error" || b === "error") return "error";
|
|
501
|
+
if (a === "warning" || b === "warning") return "warning";
|
|
502
|
+
return "ok";
|
|
503
|
+
}
|
|
504
|
+
__name(aggregateSeverity, "aggregateSeverity");
|
|
505
|
+
|
|
506
|
+
// src/manifest-checks.ts
|
|
507
|
+
var COMPONENT_KEY_PATTERN = /^[a-z][a-z0-9]*([.-][a-z0-9]+)+$/;
|
|
508
|
+
var ACTION_KEY_PATTERN = /^[A-Z][A-Z0-9_]+$/;
|
|
509
|
+
var TENANT_ACTION_KEY_PATTERN = /^[a-z][a-zA-Z0-9_]*(\.[a-z][a-zA-Z0-9_]*)+$/;
|
|
510
|
+
var CAPABILITY_PATTERN = /^[a-z][a-zA-Z0-9_]*(\.[a-z][a-zA-Z0-9_]*)+$/;
|
|
511
|
+
var ROUTE_PREFIX = "/admin";
|
|
512
|
+
function ok(message) {
|
|
513
|
+
return {
|
|
514
|
+
severity: "ok",
|
|
515
|
+
message
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
__name(ok, "ok");
|
|
519
|
+
function err(message, paths) {
|
|
520
|
+
return {
|
|
521
|
+
severity: "error",
|
|
522
|
+
message,
|
|
523
|
+
paths
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
__name(err, "err");
|
|
527
|
+
function allProjectPages(m) {
|
|
528
|
+
return m.navigation?.projectPages ?? [];
|
|
529
|
+
}
|
|
530
|
+
__name(allProjectPages, "allProjectPages");
|
|
531
|
+
function allKpiCards(m) {
|
|
532
|
+
return m.dashboard?.kpiCards ?? [];
|
|
533
|
+
}
|
|
534
|
+
__name(allKpiCards, "allKpiCards");
|
|
535
|
+
function allTenantActions(m) {
|
|
536
|
+
return m.tenants?.actions ?? [];
|
|
537
|
+
}
|
|
538
|
+
__name(allTenantActions, "allTenantActions");
|
|
539
|
+
function allTenantColumns(m) {
|
|
540
|
+
return m.tenants?.columns ?? [];
|
|
541
|
+
}
|
|
542
|
+
__name(allTenantColumns, "allTenantColumns");
|
|
543
|
+
function allAuditActions(m) {
|
|
544
|
+
return m.audit?.actions ?? [];
|
|
545
|
+
}
|
|
546
|
+
__name(allAuditActions, "allAuditActions");
|
|
547
|
+
function allCapabilities(m) {
|
|
548
|
+
return Object.keys(m.capabilities ?? {});
|
|
549
|
+
}
|
|
550
|
+
__name(allCapabilities, "allCapabilities");
|
|
551
|
+
var DEFAULT_MANIFEST_CHECKS = [
|
|
552
|
+
{
|
|
553
|
+
id: "manifest.schema-version",
|
|
554
|
+
label: "schemaVersion ist 1",
|
|
555
|
+
run: /* @__PURE__ */ __name((m) => m.schemaVersion === 1 ? ok("schemaVersion=1") : err(`Unerwartete schemaVersion ${m.schemaVersion}`), "run")
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
id: "manifest.hash-format",
|
|
559
|
+
label: "manifestHash folgt sha256-<base64url>-Pattern",
|
|
560
|
+
run: /* @__PURE__ */ __name((m) => {
|
|
561
|
+
const hash = m.build?.manifestHash;
|
|
562
|
+
if (!hash) return err("manifestHash fehlt");
|
|
563
|
+
return /^sha256-[A-Za-z0-9_-]+$/.test(hash) ? ok(hash) : err(`manifestHash hat falsches Format: ${hash}`);
|
|
564
|
+
}, "run")
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
id: "manifest.project-page-component-keys",
|
|
568
|
+
label: "ProjectPage.componentKey-Format (lowercase-hyphenated ODER namespace.dot)",
|
|
569
|
+
run: /* @__PURE__ */ __name((m) => {
|
|
570
|
+
const bad = [];
|
|
571
|
+
for (const p of allProjectPages(m)) {
|
|
572
|
+
if (!COMPONENT_KEY_PATTERN.test(p.componentKey)) bad.push(p.componentKey);
|
|
573
|
+
}
|
|
574
|
+
return bad.length === 0 ? ok(`${allProjectPages(m).length} ProjectPage(s) ok`) : err(`${bad.length} componentKey(s) verletzen das Pattern`, bad);
|
|
575
|
+
}, "run")
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
id: "manifest.tenant-action-keys",
|
|
579
|
+
label: "TenantAction.actionKey-Format (domain.action \u2014 SPEC \xA74.2.1)",
|
|
580
|
+
run: /* @__PURE__ */ __name((m) => {
|
|
581
|
+
const bad = [];
|
|
582
|
+
for (const a of allTenantActions(m)) {
|
|
583
|
+
if (!TENANT_ACTION_KEY_PATTERN.test(a.actionKey)) bad.push(a.actionKey);
|
|
584
|
+
}
|
|
585
|
+
return bad.length === 0 ? ok("alle actionKeys folgen domain.action") : err(`${bad.length} actionKey(s) verletzen das Pattern`, bad);
|
|
586
|
+
}, "run")
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
id: "manifest.audit-action-keys",
|
|
590
|
+
label: "AuditAction.key-Format (SCREAMING_SNAKE_CASE)",
|
|
591
|
+
run: /* @__PURE__ */ __name((m) => {
|
|
592
|
+
const bad = [];
|
|
593
|
+
for (const a of allAuditActions(m)) {
|
|
594
|
+
if (!ACTION_KEY_PATTERN.test(a.key)) bad.push(a.key);
|
|
595
|
+
}
|
|
596
|
+
return bad.length === 0 ? ok("alle AuditAction.keys SCREAMING_SNAKE_CASE") : err(`${bad.length} AuditAction.key(s) verletzen das Pattern`, bad);
|
|
597
|
+
}, "run")
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
id: "manifest.capabilities-pattern",
|
|
601
|
+
label: "Capability-Pattern <domain>.<action> (SPEC \xA74.2.1)",
|
|
602
|
+
run: /* @__PURE__ */ __name((m) => {
|
|
603
|
+
const bad = allCapabilities(m).filter((c) => !CAPABILITY_PATTERN.test(c));
|
|
604
|
+
return bad.length === 0 ? ok(`${allCapabilities(m).length} Capabilities ok`) : err(`${bad.length} Capability/-ies verletzen das Pattern`, bad);
|
|
605
|
+
}, "run")
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
id: "manifest.required-capabilities-known",
|
|
609
|
+
label: "requiredCapability-Referenzen existieren in capabilities-Map",
|
|
610
|
+
run: /* @__PURE__ */ __name((m) => {
|
|
611
|
+
const known = new Set(allCapabilities(m));
|
|
612
|
+
const bad = [];
|
|
613
|
+
const visit = /* @__PURE__ */ __name((cap, ctx) => {
|
|
614
|
+
if (cap && !known.has(cap)) bad.push(`${ctx ?? "?"}: ${cap}`);
|
|
615
|
+
}, "visit");
|
|
616
|
+
for (const p of allProjectPages(m)) visit(p.requiredCapability, p.id);
|
|
617
|
+
for (const k of allKpiCards(m)) visit(k.requiredCapability, k.id);
|
|
618
|
+
for (const a of allTenantActions(m)) visit(a.requiredCapability, a.id);
|
|
619
|
+
for (const c of allTenantColumns(m)) visit(c.requiredCapability, c.key);
|
|
620
|
+
return bad.length === 0 ? ok("alle requiredCapability-Refs aufgel\xF6st") : err(`${bad.length} unbekannte Capability-Ref(s)`, bad);
|
|
621
|
+
}, "run")
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
id: "manifest.route-prefix",
|
|
625
|
+
label: "ProjectPage.route beginnt mit /admin",
|
|
626
|
+
run: /* @__PURE__ */ __name((m) => {
|
|
627
|
+
const bad = [];
|
|
628
|
+
for (const p of allProjectPages(m)) {
|
|
629
|
+
if (!p.route.startsWith(ROUTE_PREFIX)) bad.push(p.route);
|
|
630
|
+
}
|
|
631
|
+
return bad.length === 0 ? ok("alle ProjectPage-Routes unter /admin") : err(`${bad.length} Route(s) ohne /admin-Prefix`, bad);
|
|
632
|
+
}, "run")
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
id: "manifest.kpi-slot-priority",
|
|
636
|
+
label: "KpiCard.slotPriority (falls gesetzt) ist endliche Zahl",
|
|
637
|
+
run: /* @__PURE__ */ __name((m) => {
|
|
638
|
+
const bad = [];
|
|
639
|
+
for (const k of allKpiCards(m)) {
|
|
640
|
+
if (k.slotPriority !== void 0 && !Number.isFinite(k.slotPriority)) {
|
|
641
|
+
bad.push(k.id);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return bad.length === 0 ? ok("alle slotPriority-Werte endlich") : err(`${bad.length} KpiCard(s) mit ung\xFCltiger slotPriority`, bad);
|
|
645
|
+
}, "run")
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
id: "manifest.unique-project-page-ids",
|
|
649
|
+
label: "ProjectPage.id ist unique",
|
|
650
|
+
run: /* @__PURE__ */ __name((m) => {
|
|
651
|
+
const seen = /* @__PURE__ */ new Map();
|
|
652
|
+
for (const p of allProjectPages(m)) {
|
|
653
|
+
seen.set(p.id, (seen.get(p.id) ?? 0) + 1);
|
|
654
|
+
}
|
|
655
|
+
const dups = [
|
|
656
|
+
...seen.entries()
|
|
657
|
+
].filter(([, n]) => n > 1).map(([k]) => k);
|
|
658
|
+
return dups.length === 0 ? ok("alle ProjectPage.id eindeutig") : err(`${dups.length} doppelte ProjectPage.id`, dups);
|
|
659
|
+
}, "run")
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
id: "manifest.unique-action-keys",
|
|
663
|
+
label: "actionKeys sind je Namespace (TenantAction / AuditAction) eindeutig",
|
|
664
|
+
run: /* @__PURE__ */ __name((m) => {
|
|
665
|
+
const dups = [];
|
|
666
|
+
const tenantSeen = /* @__PURE__ */ new Map();
|
|
667
|
+
for (const a of allTenantActions(m)) {
|
|
668
|
+
tenantSeen.set(a.actionKey, (tenantSeen.get(a.actionKey) ?? 0) + 1);
|
|
669
|
+
}
|
|
670
|
+
for (const [k, n] of tenantSeen) {
|
|
671
|
+
if (n > 1) dups.push(`TenantAction:${k}`);
|
|
672
|
+
}
|
|
673
|
+
const auditSeen = /* @__PURE__ */ new Map();
|
|
674
|
+
for (const a of allAuditActions(m)) {
|
|
675
|
+
auditSeen.set(a.key, (auditSeen.get(a.key) ?? 0) + 1);
|
|
676
|
+
}
|
|
677
|
+
for (const [k, n] of auditSeen) {
|
|
678
|
+
if (n > 1) dups.push(`AuditAction:${k}`);
|
|
679
|
+
}
|
|
680
|
+
return dups.length === 0 ? ok("alle actionKeys je Namespace eindeutig") : err(`${dups.length} doppelte actionKey(s)`, dups);
|
|
681
|
+
}, "run")
|
|
682
|
+
},
|
|
683
|
+
{
|
|
684
|
+
id: "manifest.tenant-columns-batchable",
|
|
685
|
+
label: "TenantColumns haben endpoint-Pfad f\xFCr Batch-Fetch",
|
|
686
|
+
run: /* @__PURE__ */ __name((m) => {
|
|
687
|
+
const bad = [];
|
|
688
|
+
for (const c of allTenantColumns(m)) {
|
|
689
|
+
if (!c.endpoint || c.endpoint.trim().length === 0) bad.push(c.key);
|
|
690
|
+
else if (c.endpoint.includes("{slug}") || c.endpoint.includes("{tenantId}")) {
|
|
691
|
+
bad.push(`${c.key} (per-Tenant statt batch)`);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return bad.length === 0 ? ok("alle TenantColumns batch-f\xE4hig") : err(`${bad.length} TenantColumn(s) verletzen Batch-Pflicht`, bad);
|
|
695
|
+
}, "run")
|
|
696
|
+
}
|
|
697
|
+
];
|
|
698
|
+
|
|
699
|
+
// src/manifest-cli-flow.ts
|
|
700
|
+
import { Inject as Inject5, Injectable as Injectable6 } from "@nestjs/common";
|
|
701
|
+
function _ts_decorate6(decorators, target, key, desc) {
|
|
702
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
703
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
704
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
705
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
706
|
+
}
|
|
707
|
+
__name(_ts_decorate6, "_ts_decorate");
|
|
708
|
+
function _ts_metadata6(k, v) {
|
|
709
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
710
|
+
}
|
|
711
|
+
__name(_ts_metadata6, "_ts_metadata");
|
|
712
|
+
function _ts_param5(paramIndex, decorator) {
|
|
713
|
+
return function(target, key) {
|
|
714
|
+
decorator(target, key, paramIndex);
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
__name(_ts_param5, "_ts_param");
|
|
718
|
+
var ManifestCliFlow = class {
|
|
719
|
+
static {
|
|
720
|
+
__name(this, "ManifestCliFlow");
|
|
721
|
+
}
|
|
722
|
+
access;
|
|
723
|
+
checks;
|
|
724
|
+
constructor(access, checks) {
|
|
725
|
+
this.access = access;
|
|
726
|
+
this.checks = checks;
|
|
727
|
+
}
|
|
728
|
+
/** `<app> manifest dump` — JSON output of the current manifest. */
|
|
729
|
+
dump() {
|
|
730
|
+
return this.access.getManifest();
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* `<app> manifest hash` — canonical manifestHash value. Returns
|
|
734
|
+
* the value from `build.manifestHash`. Consumers can pin this in CI
|
|
735
|
+
* (`expected-hash.txt`) and check for drift.
|
|
736
|
+
*/
|
|
737
|
+
hash() {
|
|
738
|
+
const m = this.access.getManifest();
|
|
739
|
+
const h = m.build?.manifestHash;
|
|
740
|
+
if (!h) throw new Error("manifestHash fehlt im Manifest \u2014 Boot-Zeit-Bug?");
|
|
741
|
+
return h;
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* `<app> manifest validate` — returns `true` when all structures
|
|
745
|
+
* are present. Deeper schema validation runs separately via Ajv with
|
|
746
|
+
* `@saasicat/spec/schemas/admin-manifest.schema.json`;
|
|
747
|
+
* this helper provides the quick diagnostic without the Ajv cost.
|
|
748
|
+
*/
|
|
749
|
+
validate() {
|
|
750
|
+
const m = this.access.getManifest();
|
|
751
|
+
if (m.schemaVersion !== 1) {
|
|
752
|
+
return {
|
|
753
|
+
ok: false,
|
|
754
|
+
reason: `Unerwartete schemaVersion ${m.schemaVersion}`
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
if (!m.project?.key) {
|
|
758
|
+
return {
|
|
759
|
+
ok: false,
|
|
760
|
+
reason: "Kein `project.key` im Manifest"
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
if (!m.build?.manifestHash) {
|
|
764
|
+
return {
|
|
765
|
+
ok: false,
|
|
766
|
+
reason: "manifestHash fehlt"
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
return {
|
|
770
|
+
ok: true
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* `<app> manifest diff <expected.json>` — flat diff over the two
|
|
775
|
+
* manifest hashes plus list differences for top-level fields. Returns
|
|
776
|
+
* `null` when the manifests are identical.
|
|
777
|
+
*/
|
|
778
|
+
diff(expected) {
|
|
779
|
+
const current = this.access.getManifest();
|
|
780
|
+
if (current.build?.manifestHash === expected.build?.manifestHash) return null;
|
|
781
|
+
const currentKeys = collectComponentKeys(current);
|
|
782
|
+
const expectedKeys = collectComponentKeys(expected);
|
|
783
|
+
return {
|
|
784
|
+
currentHash: current.build?.manifestHash ?? null,
|
|
785
|
+
expectedHash: expected.build?.manifestHash ?? null,
|
|
786
|
+
componentKeysAdded: [
|
|
787
|
+
...currentKeys
|
|
788
|
+
].filter((k) => !expectedKeys.has(k)).sort(),
|
|
789
|
+
componentKeysRemoved: [
|
|
790
|
+
...expectedKeys
|
|
791
|
+
].filter((k) => !currentKeys.has(k)).sort()
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* `<app> manifest check` — runs all registered manifest checks.
|
|
796
|
+
* Aggregates severity (ok/warning/error) into `overall`. The consumer
|
|
797
|
+
* maps `error` to exit code 7 (drift) per `cli-conventions.md` §6.
|
|
798
|
+
*/
|
|
799
|
+
async runChecks() {
|
|
800
|
+
const manifest = this.access.getManifest();
|
|
801
|
+
const checks = [];
|
|
802
|
+
let overall = "ok";
|
|
803
|
+
for (const check of this.checks) {
|
|
804
|
+
try {
|
|
805
|
+
const r = check.run(manifest);
|
|
806
|
+
checks.push({
|
|
807
|
+
id: check.id,
|
|
808
|
+
label: check.label,
|
|
809
|
+
...r
|
|
810
|
+
});
|
|
811
|
+
overall = aggregate(overall, r.severity);
|
|
812
|
+
} catch (err2) {
|
|
813
|
+
const message = err2 instanceof Error ? err2.message : String(err2);
|
|
814
|
+
checks.push({
|
|
815
|
+
id: check.id,
|
|
816
|
+
label: check.label,
|
|
817
|
+
severity: "error",
|
|
818
|
+
message: `Check warf eine Exception: ${message}`
|
|
819
|
+
});
|
|
820
|
+
overall = "error";
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
return {
|
|
824
|
+
overall,
|
|
825
|
+
checks
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
/** Returns the appropriate CLI exit code: 0 for `ok`/`warning`, 7 for `error` (drift). */
|
|
829
|
+
exitCodeFor(report) {
|
|
830
|
+
return report.overall === "error" ? 7 : 0;
|
|
831
|
+
}
|
|
832
|
+
formatReport(report) {
|
|
833
|
+
const lines = [
|
|
834
|
+
`Manifest-Check (Gesamtstatus: ${report.overall.toUpperCase()})`,
|
|
835
|
+
""
|
|
836
|
+
];
|
|
837
|
+
for (const c of report.checks) {
|
|
838
|
+
const icon = c.severity === "ok" ? "\u2713" : c.severity === "warning" ? "\u26A0" : "\u2717";
|
|
839
|
+
lines.push(` ${icon} ${c.label}: ${c.message}`);
|
|
840
|
+
if (c.paths && c.paths.length > 0) {
|
|
841
|
+
for (const p of c.paths) lines.push(` \xB7 ${p}`);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
return lines.join("\n");
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
ManifestCliFlow = _ts_decorate6([
|
|
848
|
+
Injectable6(),
|
|
849
|
+
_ts_param5(0, Inject5(MANIFEST_ACCESS_PORT_TOKEN)),
|
|
850
|
+
_ts_param5(1, Inject5(MANIFEST_CHECKS_TOKEN)),
|
|
851
|
+
_ts_metadata6("design:type", Function),
|
|
852
|
+
_ts_metadata6("design:paramtypes", [
|
|
853
|
+
typeof ManifestAccessPort === "undefined" ? Object : ManifestAccessPort,
|
|
854
|
+
Array
|
|
855
|
+
])
|
|
856
|
+
], ManifestCliFlow);
|
|
857
|
+
function aggregate(a, b) {
|
|
858
|
+
if (a === "error" || b === "error") return "error";
|
|
859
|
+
if (a === "warning" || b === "warning") return "warning";
|
|
860
|
+
return "ok";
|
|
861
|
+
}
|
|
862
|
+
__name(aggregate, "aggregate");
|
|
863
|
+
function collectComponentKeys(m) {
|
|
864
|
+
const keys = /* @__PURE__ */ new Set();
|
|
865
|
+
for (const p of m.navigation?.projectPages ?? []) keys.add(p.componentKey);
|
|
866
|
+
return keys;
|
|
867
|
+
}
|
|
868
|
+
__name(collectComponentKeys, "collectComponentKeys");
|
|
869
|
+
|
|
870
|
+
// src/default-doctor-checks.ts
|
|
871
|
+
import { Inject as Inject6, Injectable as Injectable7 } from "@nestjs/common";
|
|
872
|
+
import { AdminManifestService, DISCOVERY_SNAPSHOT_TOKEN, PLAN_CATALOG_TOKEN } from "@saasicat/nest";
|
|
873
|
+
function _ts_decorate7(decorators, target, key, desc) {
|
|
874
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
875
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
876
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
877
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
878
|
+
}
|
|
879
|
+
__name(_ts_decorate7, "_ts_decorate");
|
|
880
|
+
function _ts_metadata7(k, v) {
|
|
881
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
882
|
+
}
|
|
883
|
+
__name(_ts_metadata7, "_ts_metadata");
|
|
884
|
+
function _ts_param6(paramIndex, decorator) {
|
|
885
|
+
return function(target, key) {
|
|
886
|
+
decorator(target, key, paramIndex);
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
__name(_ts_param6, "_ts_param");
|
|
890
|
+
var PlanCatalogDoctorCheck = class {
|
|
891
|
+
static {
|
|
892
|
+
__name(this, "PlanCatalogDoctorCheck");
|
|
893
|
+
}
|
|
894
|
+
catalog;
|
|
895
|
+
id = "platform.plan-catalog";
|
|
896
|
+
label = "Plan-Catalog im DI";
|
|
897
|
+
constructor(catalog) {
|
|
898
|
+
this.catalog = catalog;
|
|
899
|
+
}
|
|
900
|
+
async run() {
|
|
901
|
+
const plans = this.catalog?.plans ?? [];
|
|
902
|
+
if (plans.length === 0) {
|
|
903
|
+
return {
|
|
904
|
+
severity: "error",
|
|
905
|
+
message: "PlanCatalog enth\xE4lt keine Pl\xE4ne \u2014 Onboarding-Pricing-Page wird leer."
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
return {
|
|
909
|
+
severity: "ok",
|
|
910
|
+
message: `${plans.length} Plan(s), ${this.catalog.features?.length ?? 0} Feature(s) geladen.`,
|
|
911
|
+
details: {
|
|
912
|
+
projectKey: this.catalog.projectKey,
|
|
913
|
+
planIds: plans.map((p) => p.id)
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
};
|
|
918
|
+
PlanCatalogDoctorCheck = _ts_decorate7([
|
|
919
|
+
Injectable7(),
|
|
920
|
+
_ts_param6(0, Inject6(PLAN_CATALOG_TOKEN)),
|
|
921
|
+
_ts_metadata7("design:type", Function),
|
|
922
|
+
_ts_metadata7("design:paramtypes", [
|
|
923
|
+
typeof PlanCatalog === "undefined" ? Object : PlanCatalog
|
|
924
|
+
])
|
|
925
|
+
], PlanCatalogDoctorCheck);
|
|
926
|
+
var DiscoverySnapshotDoctorCheck = class {
|
|
927
|
+
static {
|
|
928
|
+
__name(this, "DiscoverySnapshotDoctorCheck");
|
|
929
|
+
}
|
|
930
|
+
snapshot;
|
|
931
|
+
id = "platform.discovery-snapshot";
|
|
932
|
+
label = "Discovery-Snapshot beim Boot";
|
|
933
|
+
constructor(snapshot) {
|
|
934
|
+
this.snapshot = snapshot;
|
|
935
|
+
}
|
|
936
|
+
async run() {
|
|
937
|
+
const caps = this.snapshot?.capabilities ?? [];
|
|
938
|
+
if (caps.length === 0) {
|
|
939
|
+
return {
|
|
940
|
+
severity: "warning",
|
|
941
|
+
message: "Keine Capabilities entdeckt \u2014 Decorator-tragende Module evtl. nicht in AppModule.imports[]."
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
return {
|
|
945
|
+
severity: "ok",
|
|
946
|
+
message: `${caps.length} Capabilities, ${this.snapshot.features?.length ?? 0} Features, ${this.snapshot.quotas?.length ?? 0} Quotas.`
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
DiscoverySnapshotDoctorCheck = _ts_decorate7([
|
|
951
|
+
Injectable7(),
|
|
952
|
+
_ts_param6(0, Inject6(DISCOVERY_SNAPSHOT_TOKEN)),
|
|
953
|
+
_ts_metadata7("design:type", Function),
|
|
954
|
+
_ts_metadata7("design:paramtypes", [
|
|
955
|
+
typeof DiscoverySnapshot === "undefined" ? Object : DiscoverySnapshot
|
|
956
|
+
])
|
|
957
|
+
], DiscoverySnapshotDoctorCheck);
|
|
958
|
+
var UserPortDoctorCheck = class {
|
|
959
|
+
static {
|
|
960
|
+
__name(this, "UserPortDoctorCheck");
|
|
961
|
+
}
|
|
962
|
+
users;
|
|
963
|
+
id = "platform.user-port";
|
|
964
|
+
label = "UserPort.findByEmail erreichbar";
|
|
965
|
+
constructor(users) {
|
|
966
|
+
this.users = users;
|
|
967
|
+
}
|
|
968
|
+
async run() {
|
|
969
|
+
try {
|
|
970
|
+
await this.users.findByEmail("__doctor-check__@invalid.local");
|
|
971
|
+
return {
|
|
972
|
+
severity: "ok",
|
|
973
|
+
message: "UserPort antwortet."
|
|
974
|
+
};
|
|
975
|
+
} catch (err2) {
|
|
976
|
+
return {
|
|
977
|
+
severity: "error",
|
|
978
|
+
message: `UserPort wirft: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
};
|
|
983
|
+
UserPortDoctorCheck = _ts_decorate7([
|
|
984
|
+
Injectable7(),
|
|
985
|
+
_ts_param6(0, Inject6(USER_PORT_TOKEN)),
|
|
986
|
+
_ts_metadata7("design:type", Function),
|
|
987
|
+
_ts_metadata7("design:paramtypes", [
|
|
988
|
+
typeof UserPort === "undefined" ? Object : UserPort
|
|
989
|
+
])
|
|
990
|
+
], UserPortDoctorCheck);
|
|
991
|
+
var AdminManifestDoctorCheck = class {
|
|
992
|
+
static {
|
|
993
|
+
__name(this, "AdminManifestDoctorCheck");
|
|
994
|
+
}
|
|
995
|
+
manifest;
|
|
996
|
+
id = "platform.admin-manifest";
|
|
997
|
+
label = "AdminManifestService liefert Manifest";
|
|
998
|
+
constructor(manifest) {
|
|
999
|
+
this.manifest = manifest;
|
|
1000
|
+
}
|
|
1001
|
+
async run() {
|
|
1002
|
+
try {
|
|
1003
|
+
const m = this.manifest.getManifest();
|
|
1004
|
+
const pageCount = Object.keys(m.navigation?.standardPages ?? {}).length;
|
|
1005
|
+
return {
|
|
1006
|
+
severity: "ok",
|
|
1007
|
+
message: `Manifest mit ${pageCount} Standard-Pages, Hash ${m.build?.manifestHash?.slice(0, 12) ?? "???"}\u2026`
|
|
1008
|
+
};
|
|
1009
|
+
} catch (err2) {
|
|
1010
|
+
return {
|
|
1011
|
+
severity: "error",
|
|
1012
|
+
message: `Manifest-Build wirft: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
};
|
|
1017
|
+
AdminManifestDoctorCheck = _ts_decorate7([
|
|
1018
|
+
Injectable7(),
|
|
1019
|
+
_ts_metadata7("design:type", Function),
|
|
1020
|
+
_ts_metadata7("design:paramtypes", [
|
|
1021
|
+
typeof AdminManifestService === "undefined" ? Object : AdminManifestService
|
|
1022
|
+
])
|
|
1023
|
+
], AdminManifestDoctorCheck);
|
|
1024
|
+
var PLATFORM_DOCTOR_CHECK_PROVIDERS = [
|
|
1025
|
+
PlanCatalogDoctorCheck,
|
|
1026
|
+
DiscoverySnapshotDoctorCheck,
|
|
1027
|
+
UserPortDoctorCheck,
|
|
1028
|
+
AdminManifestDoctorCheck
|
|
1029
|
+
];
|
|
1030
|
+
|
|
1031
|
+
// src/schema-apply.ts
|
|
1032
|
+
function stripLineComment(line) {
|
|
1033
|
+
const commentStart = line.indexOf("//");
|
|
1034
|
+
return commentStart === -1 ? line : line.slice(0, commentStart);
|
|
1035
|
+
}
|
|
1036
|
+
__name(stripLineComment, "stripLineComment");
|
|
1037
|
+
function extractModelNames(schema) {
|
|
1038
|
+
const names = [];
|
|
1039
|
+
const lines = schema.split("\n");
|
|
1040
|
+
for (const line of lines) {
|
|
1041
|
+
const stripped = stripLineComment(line).trim();
|
|
1042
|
+
const match = stripped.match(/^model\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{/);
|
|
1043
|
+
if (match) names.push(match[1]);
|
|
1044
|
+
}
|
|
1045
|
+
return names;
|
|
1046
|
+
}
|
|
1047
|
+
__name(extractModelNames, "extractModelNames");
|
|
1048
|
+
function extractModelBlocks(fragment) {
|
|
1049
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
1050
|
+
const lines = fragment.split("\n");
|
|
1051
|
+
let current = null;
|
|
1052
|
+
for (const rawLine of lines) {
|
|
1053
|
+
const stripped = stripLineComment(rawLine);
|
|
1054
|
+
if (!current) {
|
|
1055
|
+
const match = rawLine.match(/^\s*model\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{/);
|
|
1056
|
+
if (match) {
|
|
1057
|
+
const openCount = (stripped.match(/\{/g) ?? []).length;
|
|
1058
|
+
const closeCount = (stripped.match(/\}/g) ?? []).length;
|
|
1059
|
+
current = {
|
|
1060
|
+
name: match[1],
|
|
1061
|
+
lines: [
|
|
1062
|
+
rawLine
|
|
1063
|
+
],
|
|
1064
|
+
depth: openCount - closeCount
|
|
1065
|
+
};
|
|
1066
|
+
if (current.depth === 0) {
|
|
1067
|
+
blocks.set(current.name, current.lines.join("\n"));
|
|
1068
|
+
current = null;
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
continue;
|
|
1072
|
+
}
|
|
1073
|
+
current.lines.push(rawLine);
|
|
1074
|
+
current.depth += (stripped.match(/\{/g) ?? []).length;
|
|
1075
|
+
current.depth -= (stripped.match(/\}/g) ?? []).length;
|
|
1076
|
+
if (current.depth <= 0) {
|
|
1077
|
+
blocks.set(current.name, current.lines.join("\n"));
|
|
1078
|
+
current = null;
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
return blocks;
|
|
1082
|
+
}
|
|
1083
|
+
__name(extractModelBlocks, "extractModelBlocks");
|
|
1084
|
+
function applyFragmentBlocks(schema, fragmentBlocks, options = {}) {
|
|
1085
|
+
const existing = new Set(extractModelNames(schema));
|
|
1086
|
+
const added = [];
|
|
1087
|
+
const skipped = [];
|
|
1088
|
+
const additions = [];
|
|
1089
|
+
for (const [name, block] of fragmentBlocks) {
|
|
1090
|
+
if (existing.has(name)) {
|
|
1091
|
+
skipped.push(name);
|
|
1092
|
+
} else {
|
|
1093
|
+
added.push(name);
|
|
1094
|
+
additions.push(block);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
if (additions.length === 0) {
|
|
1098
|
+
return {
|
|
1099
|
+
added,
|
|
1100
|
+
skipped,
|
|
1101
|
+
schema
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
const header = options.fragmentLabel ? `
|
|
1105
|
+
|
|
1106
|
+
// ============================================================
|
|
1107
|
+
// Eingef\xFCgt durch \`saas-platform schema apply\` aus ${options.fragmentLabel}
|
|
1108
|
+
// ============================================================
|
|
1109
|
+
` : `
|
|
1110
|
+
|
|
1111
|
+
// Eingef\xFCgt durch \`saas-platform schema apply\`
|
|
1112
|
+
`;
|
|
1113
|
+
const trimmedSchema = schema.endsWith("\n") ? schema : schema + "\n";
|
|
1114
|
+
return {
|
|
1115
|
+
added,
|
|
1116
|
+
skipped,
|
|
1117
|
+
schema: trimmedSchema + header + additions.join("\n\n") + "\n"
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
__name(applyFragmentBlocks, "applyFragmentBlocks");
|
|
1121
|
+
|
|
1122
|
+
// src/module.ts
|
|
1123
|
+
import { Module } from "@nestjs/common";
|
|
1124
|
+
import { asProvider } from "@saasicat/nest";
|
|
1125
|
+
function _ts_decorate8(decorators, target, key, desc) {
|
|
1126
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1127
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1128
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1129
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1130
|
+
}
|
|
1131
|
+
__name(_ts_decorate8, "_ts_decorate");
|
|
1132
|
+
var CliContextModule = class _CliContextModule {
|
|
1133
|
+
static {
|
|
1134
|
+
__name(this, "CliContextModule");
|
|
1135
|
+
}
|
|
1136
|
+
static forRoot(options) {
|
|
1137
|
+
const providers = [
|
|
1138
|
+
{
|
|
1139
|
+
provide: CLI_CONTEXT_CONFIG_TOKEN,
|
|
1140
|
+
useValue: options.config
|
|
1141
|
+
},
|
|
1142
|
+
asProvider(USER_PORT_TOKEN, options.userPort),
|
|
1143
|
+
CliContextService,
|
|
1144
|
+
MfaSetupFlow,
|
|
1145
|
+
WhoAmIFlow
|
|
1146
|
+
];
|
|
1147
|
+
const exports_ = [
|
|
1148
|
+
CLI_CONTEXT_CONFIG_TOKEN,
|
|
1149
|
+
CliContextService,
|
|
1150
|
+
MfaSetupFlow,
|
|
1151
|
+
WhoAmIFlow
|
|
1152
|
+
];
|
|
1153
|
+
if (options.auditQueryPort) {
|
|
1154
|
+
providers.push(asProvider(AUDIT_QUERY_PORT_TOKEN, options.auditQueryPort));
|
|
1155
|
+
providers.push(AuditTailFlow);
|
|
1156
|
+
exports_.push(AuditTailFlow);
|
|
1157
|
+
}
|
|
1158
|
+
if (options.defaultDoctorChecks) {
|
|
1159
|
+
providers.push(...PLATFORM_DOCTOR_CHECK_PROVIDERS);
|
|
1160
|
+
providers.push({
|
|
1161
|
+
provide: DOCTOR_CHECKS_TOKEN,
|
|
1162
|
+
useFactory: /* @__PURE__ */ __name((...platformChecks) => {
|
|
1163
|
+
const extra = Array.isArray(options.doctorChecks) ? options.doctorChecks : [];
|
|
1164
|
+
return [
|
|
1165
|
+
...platformChecks,
|
|
1166
|
+
...extra
|
|
1167
|
+
];
|
|
1168
|
+
}, "useFactory"),
|
|
1169
|
+
inject: PLATFORM_DOCTOR_CHECK_PROVIDERS
|
|
1170
|
+
});
|
|
1171
|
+
} else {
|
|
1172
|
+
providers.push(asProvider(DOCTOR_CHECKS_TOKEN, options.doctorChecks ?? []));
|
|
1173
|
+
}
|
|
1174
|
+
providers.push(DoctorFlow);
|
|
1175
|
+
exports_.push(DoctorFlow);
|
|
1176
|
+
if (options.manifestAccessPort) {
|
|
1177
|
+
providers.push(asProvider(MANIFEST_ACCESS_PORT_TOKEN, options.manifestAccessPort), asProvider(MANIFEST_CHECKS_TOKEN, options.manifestChecks ?? DEFAULT_MANIFEST_CHECKS), ManifestCliFlow);
|
|
1178
|
+
exports_.push(ManifestCliFlow);
|
|
1179
|
+
}
|
|
1180
|
+
if (options.userManagementPort) {
|
|
1181
|
+
providers.push(asProvider(USER_MANAGEMENT_PORT_TOKEN, options.userManagementPort));
|
|
1182
|
+
exports_.push(USER_MANAGEMENT_PORT_TOKEN);
|
|
1183
|
+
}
|
|
1184
|
+
return {
|
|
1185
|
+
module: _CliContextModule,
|
|
1186
|
+
global: options.global ?? false,
|
|
1187
|
+
providers,
|
|
1188
|
+
exports: exports_
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
};
|
|
1192
|
+
CliContextModule = _ts_decorate8([
|
|
1193
|
+
Module({})
|
|
1194
|
+
], CliContextModule);
|
|
1195
|
+
|
|
1196
|
+
// src/manifest.command.ts
|
|
1197
|
+
import { Injectable as Injectable8 } from "@nestjs/common";
|
|
1198
|
+
import { Command, CommandRunner, Option, SubCommand } from "nest-commander";
|
|
1199
|
+
function _ts_decorate9(decorators, target, key, desc) {
|
|
1200
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1201
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1202
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1203
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1204
|
+
}
|
|
1205
|
+
__name(_ts_decorate9, "_ts_decorate");
|
|
1206
|
+
function _ts_metadata8(k, v) {
|
|
1207
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1208
|
+
}
|
|
1209
|
+
__name(_ts_metadata8, "_ts_metadata");
|
|
1210
|
+
var ManifestDumpCommand = class extends CommandRunner {
|
|
1211
|
+
static {
|
|
1212
|
+
__name(this, "ManifestDumpCommand");
|
|
1213
|
+
}
|
|
1214
|
+
ctx;
|
|
1215
|
+
flow;
|
|
1216
|
+
constructor(ctx, flow) {
|
|
1217
|
+
super(), this.ctx = ctx, this.flow = flow;
|
|
1218
|
+
}
|
|
1219
|
+
async run(_args, flags) {
|
|
1220
|
+
const identity = this.ctx.resolveIdentity(flags.as);
|
|
1221
|
+
await this.ctx.ensureSuperAdmin(identity);
|
|
1222
|
+
process.stdout.write(JSON.stringify(this.flow.dump(), null, 2) + "\n");
|
|
1223
|
+
}
|
|
1224
|
+
parseAs(v) {
|
|
1225
|
+
return v;
|
|
1226
|
+
}
|
|
1227
|
+
};
|
|
1228
|
+
_ts_decorate9([
|
|
1229
|
+
Option({
|
|
1230
|
+
flags: "--as <email>"
|
|
1231
|
+
}),
|
|
1232
|
+
_ts_metadata8("design:type", Function),
|
|
1233
|
+
_ts_metadata8("design:paramtypes", [
|
|
1234
|
+
String
|
|
1235
|
+
]),
|
|
1236
|
+
_ts_metadata8("design:returntype", String)
|
|
1237
|
+
], ManifestDumpCommand.prototype, "parseAs", null);
|
|
1238
|
+
ManifestDumpCommand = _ts_decorate9([
|
|
1239
|
+
Injectable8(),
|
|
1240
|
+
SubCommand({
|
|
1241
|
+
name: "dump",
|
|
1242
|
+
description: "Manifest als JSON ausgeben"
|
|
1243
|
+
}),
|
|
1244
|
+
_ts_metadata8("design:type", Function),
|
|
1245
|
+
_ts_metadata8("design:paramtypes", [
|
|
1246
|
+
typeof CliContextService === "undefined" ? Object : CliContextService,
|
|
1247
|
+
typeof ManifestCliFlow === "undefined" ? Object : ManifestCliFlow
|
|
1248
|
+
])
|
|
1249
|
+
], ManifestDumpCommand);
|
|
1250
|
+
var ManifestHashCommand = class extends CommandRunner {
|
|
1251
|
+
static {
|
|
1252
|
+
__name(this, "ManifestHashCommand");
|
|
1253
|
+
}
|
|
1254
|
+
ctx;
|
|
1255
|
+
flow;
|
|
1256
|
+
constructor(ctx, flow) {
|
|
1257
|
+
super(), this.ctx = ctx, this.flow = flow;
|
|
1258
|
+
}
|
|
1259
|
+
async run(_args, flags) {
|
|
1260
|
+
const identity = this.ctx.resolveIdentity(flags.as);
|
|
1261
|
+
await this.ctx.ensureSuperAdmin(identity);
|
|
1262
|
+
process.stdout.write(this.flow.hash() + "\n");
|
|
1263
|
+
}
|
|
1264
|
+
parseAs(v) {
|
|
1265
|
+
return v;
|
|
1266
|
+
}
|
|
1267
|
+
};
|
|
1268
|
+
_ts_decorate9([
|
|
1269
|
+
Option({
|
|
1270
|
+
flags: "--as <email>"
|
|
1271
|
+
}),
|
|
1272
|
+
_ts_metadata8("design:type", Function),
|
|
1273
|
+
_ts_metadata8("design:paramtypes", [
|
|
1274
|
+
String
|
|
1275
|
+
]),
|
|
1276
|
+
_ts_metadata8("design:returntype", String)
|
|
1277
|
+
], ManifestHashCommand.prototype, "parseAs", null);
|
|
1278
|
+
ManifestHashCommand = _ts_decorate9([
|
|
1279
|
+
Injectable8(),
|
|
1280
|
+
SubCommand({
|
|
1281
|
+
name: "hash",
|
|
1282
|
+
description: "manifestHash ausgeben (CI-Pinning)"
|
|
1283
|
+
}),
|
|
1284
|
+
_ts_metadata8("design:type", Function),
|
|
1285
|
+
_ts_metadata8("design:paramtypes", [
|
|
1286
|
+
typeof CliContextService === "undefined" ? Object : CliContextService,
|
|
1287
|
+
typeof ManifestCliFlow === "undefined" ? Object : ManifestCliFlow
|
|
1288
|
+
])
|
|
1289
|
+
], ManifestHashCommand);
|
|
1290
|
+
var ManifestValidateCommand = class extends CommandRunner {
|
|
1291
|
+
static {
|
|
1292
|
+
__name(this, "ManifestValidateCommand");
|
|
1293
|
+
}
|
|
1294
|
+
ctx;
|
|
1295
|
+
flow;
|
|
1296
|
+
constructor(ctx, flow) {
|
|
1297
|
+
super(), this.ctx = ctx, this.flow = flow;
|
|
1298
|
+
}
|
|
1299
|
+
async run(_args, flags) {
|
|
1300
|
+
const identity = this.ctx.resolveIdentity(flags.as);
|
|
1301
|
+
await this.ctx.ensureSuperAdmin(identity);
|
|
1302
|
+
const result = this.flow.validate();
|
|
1303
|
+
if (result.ok) {
|
|
1304
|
+
process.stdout.write("Manifest validiert \u2713\n");
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
process.stderr.write(`Manifest invalid: ${result.reason}
|
|
1308
|
+
`);
|
|
1309
|
+
process.exit(1);
|
|
1310
|
+
}
|
|
1311
|
+
parseAs(v) {
|
|
1312
|
+
return v;
|
|
1313
|
+
}
|
|
1314
|
+
};
|
|
1315
|
+
_ts_decorate9([
|
|
1316
|
+
Option({
|
|
1317
|
+
flags: "--as <email>"
|
|
1318
|
+
}),
|
|
1319
|
+
_ts_metadata8("design:type", Function),
|
|
1320
|
+
_ts_metadata8("design:paramtypes", [
|
|
1321
|
+
String
|
|
1322
|
+
]),
|
|
1323
|
+
_ts_metadata8("design:returntype", String)
|
|
1324
|
+
], ManifestValidateCommand.prototype, "parseAs", null);
|
|
1325
|
+
ManifestValidateCommand = _ts_decorate9([
|
|
1326
|
+
Injectable8(),
|
|
1327
|
+
SubCommand({
|
|
1328
|
+
name: "validate",
|
|
1329
|
+
description: "Schnell-Sanity (schemaVersion + project.key + manifestHash)"
|
|
1330
|
+
}),
|
|
1331
|
+
_ts_metadata8("design:type", Function),
|
|
1332
|
+
_ts_metadata8("design:paramtypes", [
|
|
1333
|
+
typeof CliContextService === "undefined" ? Object : CliContextService,
|
|
1334
|
+
typeof ManifestCliFlow === "undefined" ? Object : ManifestCliFlow
|
|
1335
|
+
])
|
|
1336
|
+
], ManifestValidateCommand);
|
|
1337
|
+
var ManifestCheckCommand = class extends CommandRunner {
|
|
1338
|
+
static {
|
|
1339
|
+
__name(this, "ManifestCheckCommand");
|
|
1340
|
+
}
|
|
1341
|
+
ctx;
|
|
1342
|
+
flow;
|
|
1343
|
+
constructor(ctx, flow) {
|
|
1344
|
+
super(), this.ctx = ctx, this.flow = flow;
|
|
1345
|
+
}
|
|
1346
|
+
async run(_args, flags) {
|
|
1347
|
+
const identity = this.ctx.resolveIdentity(flags.as);
|
|
1348
|
+
await this.ctx.ensureSuperAdmin(identity);
|
|
1349
|
+
const report = await this.flow.runChecks();
|
|
1350
|
+
process.stdout.write(this.flow.formatReport(report) + "\n");
|
|
1351
|
+
const code = this.flow.exitCodeFor(report);
|
|
1352
|
+
if (code !== 0) process.exit(code);
|
|
1353
|
+
}
|
|
1354
|
+
parseAs(v) {
|
|
1355
|
+
return v;
|
|
1356
|
+
}
|
|
1357
|
+
};
|
|
1358
|
+
_ts_decorate9([
|
|
1359
|
+
Option({
|
|
1360
|
+
flags: "--as <email>"
|
|
1361
|
+
}),
|
|
1362
|
+
_ts_metadata8("design:type", Function),
|
|
1363
|
+
_ts_metadata8("design:paramtypes", [
|
|
1364
|
+
String
|
|
1365
|
+
]),
|
|
1366
|
+
_ts_metadata8("design:returntype", String)
|
|
1367
|
+
], ManifestCheckCommand.prototype, "parseAs", null);
|
|
1368
|
+
ManifestCheckCommand = _ts_decorate9([
|
|
1369
|
+
Injectable8(),
|
|
1370
|
+
SubCommand({
|
|
1371
|
+
name: "check",
|
|
1372
|
+
description: "Alle Manifest-Checks (Exit-Code 7 bei error/Drift)"
|
|
1373
|
+
}),
|
|
1374
|
+
_ts_metadata8("design:type", Function),
|
|
1375
|
+
_ts_metadata8("design:paramtypes", [
|
|
1376
|
+
typeof CliContextService === "undefined" ? Object : CliContextService,
|
|
1377
|
+
typeof ManifestCliFlow === "undefined" ? Object : ManifestCliFlow
|
|
1378
|
+
])
|
|
1379
|
+
], ManifestCheckCommand);
|
|
1380
|
+
var ManifestCommands = class extends CommandRunner {
|
|
1381
|
+
static {
|
|
1382
|
+
__name(this, "ManifestCommands");
|
|
1383
|
+
}
|
|
1384
|
+
async run() {
|
|
1385
|
+
process.stderr.write("Bitte Sub-Command angeben: dump, hash, validate, check.\n");
|
|
1386
|
+
process.exit(2);
|
|
1387
|
+
}
|
|
1388
|
+
};
|
|
1389
|
+
ManifestCommands = _ts_decorate9([
|
|
1390
|
+
Injectable8(),
|
|
1391
|
+
Command({
|
|
1392
|
+
name: "manifest",
|
|
1393
|
+
description: "Manifest-Operations (dump, hash, validate, check)",
|
|
1394
|
+
subCommands: [
|
|
1395
|
+
ManifestDumpCommand,
|
|
1396
|
+
ManifestHashCommand,
|
|
1397
|
+
ManifestValidateCommand,
|
|
1398
|
+
ManifestCheckCommand
|
|
1399
|
+
]
|
|
1400
|
+
})
|
|
1401
|
+
], ManifestCommands);
|
|
1402
|
+
|
|
1403
|
+
// src/admin.command.ts
|
|
1404
|
+
import { Inject as Inject7, Injectable as Injectable9 } from "@nestjs/common";
|
|
1405
|
+
import { Command as Command2, CommandRunner as CommandRunner2, Option as Option2, SubCommand as SubCommand2 } from "nest-commander";
|
|
1406
|
+
import qrcodeTerminal from "qrcode-terminal";
|
|
1407
|
+
function _ts_decorate10(decorators, target, key, desc) {
|
|
1408
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1409
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1410
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1411
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1412
|
+
}
|
|
1413
|
+
__name(_ts_decorate10, "_ts_decorate");
|
|
1414
|
+
function _ts_metadata9(k, v) {
|
|
1415
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1416
|
+
}
|
|
1417
|
+
__name(_ts_metadata9, "_ts_metadata");
|
|
1418
|
+
function _ts_param7(paramIndex, decorator) {
|
|
1419
|
+
return function(target, key) {
|
|
1420
|
+
decorator(target, key, paramIndex);
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
__name(_ts_param7, "_ts_param");
|
|
1424
|
+
var AdminWhoamiCommand = class extends CommandRunner2 {
|
|
1425
|
+
static {
|
|
1426
|
+
__name(this, "AdminWhoamiCommand");
|
|
1427
|
+
}
|
|
1428
|
+
flow;
|
|
1429
|
+
constructor(flow) {
|
|
1430
|
+
super(), this.flow = flow;
|
|
1431
|
+
}
|
|
1432
|
+
async run(_args, flags) {
|
|
1433
|
+
const result = await this.flow.run(flags.as);
|
|
1434
|
+
process.stdout.write(this.flow.formatResult(result) + "\n");
|
|
1435
|
+
}
|
|
1436
|
+
parseAs(v) {
|
|
1437
|
+
return v;
|
|
1438
|
+
}
|
|
1439
|
+
};
|
|
1440
|
+
_ts_decorate10([
|
|
1441
|
+
Option2({
|
|
1442
|
+
flags: "--as <email>"
|
|
1443
|
+
}),
|
|
1444
|
+
_ts_metadata9("design:type", Function),
|
|
1445
|
+
_ts_metadata9("design:paramtypes", [
|
|
1446
|
+
String
|
|
1447
|
+
]),
|
|
1448
|
+
_ts_metadata9("design:returntype", String)
|
|
1449
|
+
], AdminWhoamiCommand.prototype, "parseAs", null);
|
|
1450
|
+
AdminWhoamiCommand = _ts_decorate10([
|
|
1451
|
+
Injectable9(),
|
|
1452
|
+
SubCommand2({
|
|
1453
|
+
name: "whoami",
|
|
1454
|
+
description: "Aktive CLI-Identit\xE4t + MFA-/Production-Status"
|
|
1455
|
+
}),
|
|
1456
|
+
_ts_metadata9("design:type", Function),
|
|
1457
|
+
_ts_metadata9("design:paramtypes", [
|
|
1458
|
+
typeof WhoAmIFlow === "undefined" ? Object : WhoAmIFlow
|
|
1459
|
+
])
|
|
1460
|
+
], AdminWhoamiCommand);
|
|
1461
|
+
var AdminMfaSetupCommand = class extends CommandRunner2 {
|
|
1462
|
+
static {
|
|
1463
|
+
__name(this, "AdminMfaSetupCommand");
|
|
1464
|
+
}
|
|
1465
|
+
config;
|
|
1466
|
+
flow;
|
|
1467
|
+
constructor(config, flow) {
|
|
1468
|
+
super(), this.config = config, this.flow = flow;
|
|
1469
|
+
}
|
|
1470
|
+
async run(_args, flags) {
|
|
1471
|
+
const result = await this.flow.run({
|
|
1472
|
+
asFlag: flags.as,
|
|
1473
|
+
issuer: this.config.mfaIssuer ?? "SuperAdmin",
|
|
1474
|
+
force: flags.force
|
|
1475
|
+
});
|
|
1476
|
+
await new Promise((resolve) => {
|
|
1477
|
+
qrcodeTerminal.generate(result.otpauthUri, {
|
|
1478
|
+
small: true
|
|
1479
|
+
}, (qr) => {
|
|
1480
|
+
process.stdout.write("\n" + qr + "\n");
|
|
1481
|
+
resolve();
|
|
1482
|
+
});
|
|
1483
|
+
});
|
|
1484
|
+
process.stdout.write(this.flow.formatSetupResult(result) + "\n");
|
|
1485
|
+
}
|
|
1486
|
+
parseAs(v) {
|
|
1487
|
+
return v;
|
|
1488
|
+
}
|
|
1489
|
+
parseForce() {
|
|
1490
|
+
return true;
|
|
1491
|
+
}
|
|
1492
|
+
};
|
|
1493
|
+
_ts_decorate10([
|
|
1494
|
+
Option2({
|
|
1495
|
+
flags: "--as <email>"
|
|
1496
|
+
}),
|
|
1497
|
+
_ts_metadata9("design:type", Function),
|
|
1498
|
+
_ts_metadata9("design:paramtypes", [
|
|
1499
|
+
String
|
|
1500
|
+
]),
|
|
1501
|
+
_ts_metadata9("design:returntype", String)
|
|
1502
|
+
], AdminMfaSetupCommand.prototype, "parseAs", null);
|
|
1503
|
+
_ts_decorate10([
|
|
1504
|
+
Option2({
|
|
1505
|
+
flags: "--force",
|
|
1506
|
+
description: "bestehendes Secret ohne R\xFCckfrage \xFCberschreiben"
|
|
1507
|
+
}),
|
|
1508
|
+
_ts_metadata9("design:type", Function),
|
|
1509
|
+
_ts_metadata9("design:paramtypes", []),
|
|
1510
|
+
_ts_metadata9("design:returntype", Boolean)
|
|
1511
|
+
], AdminMfaSetupCommand.prototype, "parseForce", null);
|
|
1512
|
+
AdminMfaSetupCommand = _ts_decorate10([
|
|
1513
|
+
Injectable9(),
|
|
1514
|
+
SubCommand2({
|
|
1515
|
+
name: "mfa-setup",
|
|
1516
|
+
description: "TOTP-MFA f\xFCr den eigenen SuperAdmin einrichten"
|
|
1517
|
+
}),
|
|
1518
|
+
_ts_param7(0, Inject7(CLI_CONTEXT_CONFIG_TOKEN)),
|
|
1519
|
+
_ts_metadata9("design:type", Function),
|
|
1520
|
+
_ts_metadata9("design:paramtypes", [
|
|
1521
|
+
typeof CliContextConfig === "undefined" ? Object : CliContextConfig,
|
|
1522
|
+
typeof MfaSetupFlow === "undefined" ? Object : MfaSetupFlow
|
|
1523
|
+
])
|
|
1524
|
+
], AdminMfaSetupCommand);
|
|
1525
|
+
var AdminCommands = class extends CommandRunner2 {
|
|
1526
|
+
static {
|
|
1527
|
+
__name(this, "AdminCommands");
|
|
1528
|
+
}
|
|
1529
|
+
async run() {
|
|
1530
|
+
process.stderr.write("Bitte Sub-Command angeben: whoami, mfa-setup.\n");
|
|
1531
|
+
process.exit(2);
|
|
1532
|
+
}
|
|
1533
|
+
};
|
|
1534
|
+
AdminCommands = _ts_decorate10([
|
|
1535
|
+
Injectable9(),
|
|
1536
|
+
Command2({
|
|
1537
|
+
name: "admin",
|
|
1538
|
+
description: "SuperAdmin-Operations (whoami, mfa-setup)",
|
|
1539
|
+
subCommands: [
|
|
1540
|
+
AdminWhoamiCommand,
|
|
1541
|
+
AdminMfaSetupCommand
|
|
1542
|
+
]
|
|
1543
|
+
})
|
|
1544
|
+
], AdminCommands);
|
|
1545
|
+
|
|
1546
|
+
// src/audit.command.ts
|
|
1547
|
+
import { Injectable as Injectable10 } from "@nestjs/common";
|
|
1548
|
+
import { Command as Command3, CommandRunner as CommandRunner3, Option as Option3, SubCommand as SubCommand3 } from "nest-commander";
|
|
1549
|
+
function _ts_decorate11(decorators, target, key, desc) {
|
|
1550
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1551
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1552
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1553
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1554
|
+
}
|
|
1555
|
+
__name(_ts_decorate11, "_ts_decorate");
|
|
1556
|
+
function _ts_metadata10(k, v) {
|
|
1557
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1558
|
+
}
|
|
1559
|
+
__name(_ts_metadata10, "_ts_metadata");
|
|
1560
|
+
var AuditTailCommand = class extends CommandRunner3 {
|
|
1561
|
+
static {
|
|
1562
|
+
__name(this, "AuditTailCommand");
|
|
1563
|
+
}
|
|
1564
|
+
ctx;
|
|
1565
|
+
flow;
|
|
1566
|
+
constructor(ctx, flow) {
|
|
1567
|
+
super(), this.ctx = ctx, this.flow = flow;
|
|
1568
|
+
}
|
|
1569
|
+
async run(_args, flags) {
|
|
1570
|
+
const identity = this.ctx.resolveIdentity(flags.as);
|
|
1571
|
+
await this.ctx.ensureSuperAdmin(identity);
|
|
1572
|
+
const entries = await this.flow.run({
|
|
1573
|
+
actor: flags.actor,
|
|
1574
|
+
action: flags.action,
|
|
1575
|
+
entity: flags.entity,
|
|
1576
|
+
since: flags.since,
|
|
1577
|
+
limit: flags.limit
|
|
1578
|
+
});
|
|
1579
|
+
this.ctx.table(this.flow.formatRows(entries));
|
|
1580
|
+
}
|
|
1581
|
+
parseAs(v) {
|
|
1582
|
+
return v;
|
|
1583
|
+
}
|
|
1584
|
+
parseActor(v) {
|
|
1585
|
+
return v;
|
|
1586
|
+
}
|
|
1587
|
+
parseAction(v) {
|
|
1588
|
+
return v;
|
|
1589
|
+
}
|
|
1590
|
+
parseEntity(v) {
|
|
1591
|
+
return v;
|
|
1592
|
+
}
|
|
1593
|
+
parseSince(v) {
|
|
1594
|
+
return v;
|
|
1595
|
+
}
|
|
1596
|
+
parseLimit(v) {
|
|
1597
|
+
return Number.parseInt(v, 10);
|
|
1598
|
+
}
|
|
1599
|
+
};
|
|
1600
|
+
_ts_decorate11([
|
|
1601
|
+
Option3({
|
|
1602
|
+
flags: "--as <email>"
|
|
1603
|
+
}),
|
|
1604
|
+
_ts_metadata10("design:type", Function),
|
|
1605
|
+
_ts_metadata10("design:paramtypes", [
|
|
1606
|
+
String
|
|
1607
|
+
]),
|
|
1608
|
+
_ts_metadata10("design:returntype", String)
|
|
1609
|
+
], AuditTailCommand.prototype, "parseAs", null);
|
|
1610
|
+
_ts_decorate11([
|
|
1611
|
+
Option3({
|
|
1612
|
+
flags: "--actor <email>"
|
|
1613
|
+
}),
|
|
1614
|
+
_ts_metadata10("design:type", Function),
|
|
1615
|
+
_ts_metadata10("design:paramtypes", [
|
|
1616
|
+
String
|
|
1617
|
+
]),
|
|
1618
|
+
_ts_metadata10("design:returntype", String)
|
|
1619
|
+
], AuditTailCommand.prototype, "parseActor", null);
|
|
1620
|
+
_ts_decorate11([
|
|
1621
|
+
Option3({
|
|
1622
|
+
flags: "--action <name>"
|
|
1623
|
+
}),
|
|
1624
|
+
_ts_metadata10("design:type", Function),
|
|
1625
|
+
_ts_metadata10("design:paramtypes", [
|
|
1626
|
+
String
|
|
1627
|
+
]),
|
|
1628
|
+
_ts_metadata10("design:returntype", String)
|
|
1629
|
+
], AuditTailCommand.prototype, "parseAction", null);
|
|
1630
|
+
_ts_decorate11([
|
|
1631
|
+
Option3({
|
|
1632
|
+
flags: "--entity <name>"
|
|
1633
|
+
}),
|
|
1634
|
+
_ts_metadata10("design:type", Function),
|
|
1635
|
+
_ts_metadata10("design:paramtypes", [
|
|
1636
|
+
String
|
|
1637
|
+
]),
|
|
1638
|
+
_ts_metadata10("design:returntype", String)
|
|
1639
|
+
], AuditTailCommand.prototype, "parseEntity", null);
|
|
1640
|
+
_ts_decorate11([
|
|
1641
|
+
Option3({
|
|
1642
|
+
flags: "--since <iso-date>"
|
|
1643
|
+
}),
|
|
1644
|
+
_ts_metadata10("design:type", Function),
|
|
1645
|
+
_ts_metadata10("design:paramtypes", [
|
|
1646
|
+
String
|
|
1647
|
+
]),
|
|
1648
|
+
_ts_metadata10("design:returntype", String)
|
|
1649
|
+
], AuditTailCommand.prototype, "parseSince", null);
|
|
1650
|
+
_ts_decorate11([
|
|
1651
|
+
Option3({
|
|
1652
|
+
flags: "--limit <n>"
|
|
1653
|
+
}),
|
|
1654
|
+
_ts_metadata10("design:type", Function),
|
|
1655
|
+
_ts_metadata10("design:paramtypes", [
|
|
1656
|
+
String
|
|
1657
|
+
]),
|
|
1658
|
+
_ts_metadata10("design:returntype", Number)
|
|
1659
|
+
], AuditTailCommand.prototype, "parseLimit", null);
|
|
1660
|
+
AuditTailCommand = _ts_decorate11([
|
|
1661
|
+
Injectable10(),
|
|
1662
|
+
SubCommand3({
|
|
1663
|
+
name: "tail",
|
|
1664
|
+
description: "Letzte Audit-Log-Eintr\xE4ge (--actor/--action/--entity/--since/--limit)"
|
|
1665
|
+
}),
|
|
1666
|
+
_ts_metadata10("design:type", Function),
|
|
1667
|
+
_ts_metadata10("design:paramtypes", [
|
|
1668
|
+
typeof CliContextService === "undefined" ? Object : CliContextService,
|
|
1669
|
+
typeof AuditTailFlow === "undefined" ? Object : AuditTailFlow
|
|
1670
|
+
])
|
|
1671
|
+
], AuditTailCommand);
|
|
1672
|
+
var AuditCommands = class extends CommandRunner3 {
|
|
1673
|
+
static {
|
|
1674
|
+
__name(this, "AuditCommands");
|
|
1675
|
+
}
|
|
1676
|
+
async run() {
|
|
1677
|
+
process.stderr.write("Bitte Sub-Command angeben: tail.\n");
|
|
1678
|
+
process.exit(2);
|
|
1679
|
+
}
|
|
1680
|
+
};
|
|
1681
|
+
AuditCommands = _ts_decorate11([
|
|
1682
|
+
Injectable10(),
|
|
1683
|
+
Command3({
|
|
1684
|
+
name: "audit",
|
|
1685
|
+
description: "Audit-Log-Operations (tail)",
|
|
1686
|
+
subCommands: [
|
|
1687
|
+
AuditTailCommand
|
|
1688
|
+
]
|
|
1689
|
+
})
|
|
1690
|
+
], AuditCommands);
|
|
1691
|
+
|
|
1692
|
+
// src/doctor.command.ts
|
|
1693
|
+
import { Injectable as Injectable11 } from "@nestjs/common";
|
|
1694
|
+
import { Command as Command4, CommandRunner as CommandRunner4, Option as Option4 } from "nest-commander";
|
|
1695
|
+
function _ts_decorate12(decorators, target, key, desc) {
|
|
1696
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1697
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1698
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1699
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1700
|
+
}
|
|
1701
|
+
__name(_ts_decorate12, "_ts_decorate");
|
|
1702
|
+
function _ts_metadata11(k, v) {
|
|
1703
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1704
|
+
}
|
|
1705
|
+
__name(_ts_metadata11, "_ts_metadata");
|
|
1706
|
+
var DoctorCommands = class extends CommandRunner4 {
|
|
1707
|
+
static {
|
|
1708
|
+
__name(this, "DoctorCommands");
|
|
1709
|
+
}
|
|
1710
|
+
ctx;
|
|
1711
|
+
flow;
|
|
1712
|
+
constructor(ctx, flow) {
|
|
1713
|
+
super(), this.ctx = ctx, this.flow = flow;
|
|
1714
|
+
}
|
|
1715
|
+
async run(_args, flags) {
|
|
1716
|
+
const identity = this.ctx.resolveIdentity(flags.as);
|
|
1717
|
+
await this.ctx.ensureSuperAdmin(identity);
|
|
1718
|
+
const report = await this.flow.run();
|
|
1719
|
+
process.stdout.write(this.flow.formatReport(report) + "\n");
|
|
1720
|
+
const code = this.flow.exitCodeFor(report);
|
|
1721
|
+
if (code !== 0) process.exit(code);
|
|
1722
|
+
}
|
|
1723
|
+
parseAs(v) {
|
|
1724
|
+
return v;
|
|
1725
|
+
}
|
|
1726
|
+
};
|
|
1727
|
+
_ts_decorate12([
|
|
1728
|
+
Option4({
|
|
1729
|
+
flags: "--as <email>"
|
|
1730
|
+
}),
|
|
1731
|
+
_ts_metadata11("design:type", Function),
|
|
1732
|
+
_ts_metadata11("design:paramtypes", [
|
|
1733
|
+
String
|
|
1734
|
+
]),
|
|
1735
|
+
_ts_metadata11("design:returntype", String)
|
|
1736
|
+
], DoctorCommands.prototype, "parseAs", null);
|
|
1737
|
+
DoctorCommands = _ts_decorate12([
|
|
1738
|
+
Injectable11(),
|
|
1739
|
+
Command4({
|
|
1740
|
+
name: "doctor",
|
|
1741
|
+
description: "Health-/Drift-Checks (Exit-Code 4 bei error)"
|
|
1742
|
+
}),
|
|
1743
|
+
_ts_metadata11("design:type", Function),
|
|
1744
|
+
_ts_metadata11("design:paramtypes", [
|
|
1745
|
+
typeof CliContextService === "undefined" ? Object : CliContextService,
|
|
1746
|
+
typeof DoctorFlow === "undefined" ? Object : DoctorFlow
|
|
1747
|
+
])
|
|
1748
|
+
], DoctorCommands);
|
|
1749
|
+
|
|
1750
|
+
// src/discovery.command.ts
|
|
1751
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
1752
|
+
import { dirname, resolve as resolvePath } from "path";
|
|
1753
|
+
import { Inject as Inject8, Injectable as Injectable12, Optional } from "@nestjs/common";
|
|
1754
|
+
import { Command as Command5, CommandRunner as CommandRunner5, Option as Option5, SubCommand as SubCommand4 } from "nest-commander";
|
|
1755
|
+
import { DISCOVERY_SNAPSHOT_PATH_TOKEN, DiscoveryScanner } from "@saasicat/nest";
|
|
1756
|
+
function _ts_decorate13(decorators, target, key, desc) {
|
|
1757
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1758
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1759
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1760
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1761
|
+
}
|
|
1762
|
+
__name(_ts_decorate13, "_ts_decorate");
|
|
1763
|
+
function _ts_metadata12(k, v) {
|
|
1764
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1765
|
+
}
|
|
1766
|
+
__name(_ts_metadata12, "_ts_metadata");
|
|
1767
|
+
function _ts_param8(paramIndex, decorator) {
|
|
1768
|
+
return function(target, key) {
|
|
1769
|
+
decorator(target, key, paramIndex);
|
|
1770
|
+
};
|
|
1771
|
+
}
|
|
1772
|
+
__name(_ts_param8, "_ts_param");
|
|
1773
|
+
var DiscoveryScanCommand = class extends CommandRunner5 {
|
|
1774
|
+
static {
|
|
1775
|
+
__name(this, "DiscoveryScanCommand");
|
|
1776
|
+
}
|
|
1777
|
+
scanner;
|
|
1778
|
+
configuredPath;
|
|
1779
|
+
constructor(scanner = null, configuredPath = null) {
|
|
1780
|
+
super(), this.scanner = scanner, this.configuredPath = configuredPath;
|
|
1781
|
+
}
|
|
1782
|
+
async run(_args, flags) {
|
|
1783
|
+
if (!this.scanner) {
|
|
1784
|
+
this.fail("DiscoveryScanner nicht registriert \u2014 DiscoveryModule.forRoot() im CLI-Modul importieren.", flags);
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
try {
|
|
1788
|
+
const snapshot = this.scanner.rebuildSnapshot();
|
|
1789
|
+
if (flags.out) {
|
|
1790
|
+
const outPath = resolvePath(flags.out);
|
|
1791
|
+
mkdirSync(dirname(outPath), {
|
|
1792
|
+
recursive: true
|
|
1793
|
+
});
|
|
1794
|
+
writeFileSync(outPath, JSON.stringify(snapshot, null, 2) + "\n", "utf-8");
|
|
1795
|
+
}
|
|
1796
|
+
const target = flags.out ?? this.configuredPath;
|
|
1797
|
+
process.stdout.write(`Discovery-Scan (${snapshot.app.key} v${snapshot.app.version}): ${snapshot.capabilities.length} Capabilities \xB7 ${snapshot.features.length} Features \xB7 ${snapshot.quotas.length} Quotas \xB7 hash ${snapshot.hash.slice(0, 19)}\u2026
|
|
1798
|
+
`);
|
|
1799
|
+
if (target) {
|
|
1800
|
+
process.stdout.write(`Snapshot persistiert: ${resolvePath(target)}
|
|
1801
|
+
`);
|
|
1802
|
+
} else {
|
|
1803
|
+
process.stderr.write("WARNUNG: Snapshot wurde nicht persistiert \u2014 weder snapshotPath (DiscoveryModule.forRoot) konfiguriert noch --out angegeben. Das Seed-Gate findet so keinen Snapshot.\n");
|
|
1804
|
+
}
|
|
1805
|
+
} catch (err2) {
|
|
1806
|
+
this.fail(err2 instanceof Error ? err2.message : String(err2), flags);
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
/** Exit 4 (analogous to Seed-Gate/Preflight) — only a warning with `--non-fatal`. */
|
|
1810
|
+
fail(message, flags) {
|
|
1811
|
+
if (flags.nonFatal) {
|
|
1812
|
+
process.stderr.write(`[discovery scan] WARN (non-fatal): ${message}
|
|
1813
|
+
`);
|
|
1814
|
+
return;
|
|
1815
|
+
}
|
|
1816
|
+
process.stderr.write(`[discovery scan] FEHLER: ${message}
|
|
1817
|
+
`);
|
|
1818
|
+
process.exit(4);
|
|
1819
|
+
}
|
|
1820
|
+
parseOut(v) {
|
|
1821
|
+
return v;
|
|
1822
|
+
}
|
|
1823
|
+
parseNonFatal() {
|
|
1824
|
+
return true;
|
|
1825
|
+
}
|
|
1826
|
+
};
|
|
1827
|
+
_ts_decorate13([
|
|
1828
|
+
Option5({
|
|
1829
|
+
flags: "--out <path>",
|
|
1830
|
+
description: "Snapshot zus\xE4tzlich an diesen Pfad schreiben"
|
|
1831
|
+
}),
|
|
1832
|
+
_ts_metadata12("design:type", Function),
|
|
1833
|
+
_ts_metadata12("design:paramtypes", [
|
|
1834
|
+
String
|
|
1835
|
+
]),
|
|
1836
|
+
_ts_metadata12("design:returntype", String)
|
|
1837
|
+
], DiscoveryScanCommand.prototype, "parseOut", null);
|
|
1838
|
+
_ts_decorate13([
|
|
1839
|
+
Option5({
|
|
1840
|
+
flags: "--non-fatal",
|
|
1841
|
+
description: "Scan-Fehler nur als Warnung melden (Exit 0) \u2014 gestufter Rollout"
|
|
1842
|
+
}),
|
|
1843
|
+
_ts_metadata12("design:type", Function),
|
|
1844
|
+
_ts_metadata12("design:paramtypes", []),
|
|
1845
|
+
_ts_metadata12("design:returntype", Boolean)
|
|
1846
|
+
], DiscoveryScanCommand.prototype, "parseNonFatal", null);
|
|
1847
|
+
DiscoveryScanCommand = _ts_decorate13([
|
|
1848
|
+
Injectable12(),
|
|
1849
|
+
SubCommand4({
|
|
1850
|
+
name: "scan",
|
|
1851
|
+
description: "Discovery-Snapshot headless erzeugen + persistieren (Seed-Gate, #23)"
|
|
1852
|
+
}),
|
|
1853
|
+
_ts_param8(0, Optional()),
|
|
1854
|
+
_ts_param8(0, Inject8(DiscoveryScanner)),
|
|
1855
|
+
_ts_param8(1, Optional()),
|
|
1856
|
+
_ts_param8(1, Inject8(DISCOVERY_SNAPSHOT_PATH_TOKEN)),
|
|
1857
|
+
_ts_metadata12("design:type", Function),
|
|
1858
|
+
_ts_metadata12("design:paramtypes", [
|
|
1859
|
+
Object,
|
|
1860
|
+
Object
|
|
1861
|
+
])
|
|
1862
|
+
], DiscoveryScanCommand);
|
|
1863
|
+
var DiscoveryCommands = class extends CommandRunner5 {
|
|
1864
|
+
static {
|
|
1865
|
+
__name(this, "DiscoveryCommands");
|
|
1866
|
+
}
|
|
1867
|
+
async run() {
|
|
1868
|
+
process.stderr.write("Bitte Sub-Command angeben: scan.\n");
|
|
1869
|
+
process.exit(2);
|
|
1870
|
+
}
|
|
1871
|
+
};
|
|
1872
|
+
DiscoveryCommands = _ts_decorate13([
|
|
1873
|
+
Injectable12(),
|
|
1874
|
+
Command5({
|
|
1875
|
+
name: "discovery",
|
|
1876
|
+
description: "Discovery-Operations (scan)",
|
|
1877
|
+
subCommands: [
|
|
1878
|
+
DiscoveryScanCommand
|
|
1879
|
+
]
|
|
1880
|
+
})
|
|
1881
|
+
], DiscoveryCommands);
|
|
1882
|
+
|
|
1883
|
+
// src/user.command.ts
|
|
1884
|
+
import { Inject as Inject9, Injectable as Injectable13 } from "@nestjs/common";
|
|
1885
|
+
import { randomBytes } from "crypto";
|
|
1886
|
+
import { Command as Command6, CommandRunner as CommandRunner6, Option as Option6 } from "nest-commander";
|
|
1887
|
+
function _ts_decorate14(decorators, target, key, desc) {
|
|
1888
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1889
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1890
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1891
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1892
|
+
}
|
|
1893
|
+
__name(_ts_decorate14, "_ts_decorate");
|
|
1894
|
+
function _ts_metadata13(k, v) {
|
|
1895
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1896
|
+
}
|
|
1897
|
+
__name(_ts_metadata13, "_ts_metadata");
|
|
1898
|
+
function _ts_param9(paramIndex, decorator) {
|
|
1899
|
+
return function(target, key) {
|
|
1900
|
+
decorator(target, key, paramIndex);
|
|
1901
|
+
};
|
|
1902
|
+
}
|
|
1903
|
+
__name(_ts_param9, "_ts_param");
|
|
1904
|
+
var AUDIT_ENTITY = "User";
|
|
1905
|
+
function generatePassword() {
|
|
1906
|
+
return randomBytes(12).toString("base64url");
|
|
1907
|
+
}
|
|
1908
|
+
__name(generatePassword, "generatePassword");
|
|
1909
|
+
var UserCommands = class extends CommandRunner6 {
|
|
1910
|
+
static {
|
|
1911
|
+
__name(this, "UserCommands");
|
|
1912
|
+
}
|
|
1913
|
+
ctx;
|
|
1914
|
+
users;
|
|
1915
|
+
constructor(ctx, users) {
|
|
1916
|
+
super(), this.ctx = ctx, this.users = users;
|
|
1917
|
+
}
|
|
1918
|
+
async run(args, flags) {
|
|
1919
|
+
const sub = args[0];
|
|
1920
|
+
const identity = this.ctx.resolveIdentity(flags.as);
|
|
1921
|
+
const me = await this.ctx.ensureSuperAdmin(identity);
|
|
1922
|
+
switch (sub) {
|
|
1923
|
+
case "create-super-admin":
|
|
1924
|
+
return this.createSuperAdmin(args[1], flags, identity, me.id, me.email);
|
|
1925
|
+
case "reassign-admin":
|
|
1926
|
+
return this.reassignAdmin(args[1], flags, identity, me.id);
|
|
1927
|
+
case "list":
|
|
1928
|
+
return this.list(args[1]);
|
|
1929
|
+
case "reset-password":
|
|
1930
|
+
return this.resetPassword(args[1], flags, identity, me.id);
|
|
1931
|
+
case "deactivate":
|
|
1932
|
+
return this.deactivate(args[1], flags, identity, me.id);
|
|
1933
|
+
default:
|
|
1934
|
+
throw new CliError("UNKNOWN_SUBCOMMAND", `Unbekannter Subbefehl: user ${sub ?? "(leer)"}. Verf\xFCgbar: create-super-admin <email>, reassign-admin <slug>, list <slug>, reset-password <email>, deactivate <email>.`, 1);
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
async createSuperAdmin(email, flags, identity, meId, meEmail) {
|
|
1938
|
+
if (!email) {
|
|
1939
|
+
throw new CliError("MISSING_ARG", "user create-super-admin <email> erwartet eine E-Mail.", 1);
|
|
1940
|
+
}
|
|
1941
|
+
await this.ctx.requireMfa(meId);
|
|
1942
|
+
await this.ctx.ensureProductionConfirmation({
|
|
1943
|
+
yes: flags.yes
|
|
1944
|
+
});
|
|
1945
|
+
const generated = !flags.password;
|
|
1946
|
+
const password = flags.password ?? generatePassword();
|
|
1947
|
+
const created = await this.users.createSuperAdmin({
|
|
1948
|
+
email: email.toLowerCase(),
|
|
1949
|
+
password,
|
|
1950
|
+
firstName: flags.first,
|
|
1951
|
+
lastName: flags.last
|
|
1952
|
+
});
|
|
1953
|
+
await this.ctx.log({
|
|
1954
|
+
identity,
|
|
1955
|
+
userId: meId,
|
|
1956
|
+
entity: AUDIT_ENTITY,
|
|
1957
|
+
entityId: created.id,
|
|
1958
|
+
action: "SUPER_ADMIN_CREATE",
|
|
1959
|
+
changes: {
|
|
1960
|
+
email: created.email,
|
|
1961
|
+
createdBy: meEmail
|
|
1962
|
+
}
|
|
1963
|
+
});
|
|
1964
|
+
console.log(`\u2714 SUPER_ADMIN ${created.email} angelegt (durch ${meEmail}).`);
|
|
1965
|
+
console.log(` User-ID: ${created.id}`);
|
|
1966
|
+
if (generated) {
|
|
1967
|
+
console.log(` Passwort: ${password}`);
|
|
1968
|
+
console.log(" \u2192 Sicher \xFCbermitteln. Beim ersten Login \xE4ndern.");
|
|
1969
|
+
}
|
|
1970
|
+
console.log(` N\xE4chster Schritt: admin mfa-setup f\xFCr ${created.email}.`);
|
|
1971
|
+
}
|
|
1972
|
+
async reassignAdmin(slug, flags, identity, meId) {
|
|
1973
|
+
if (!slug) {
|
|
1974
|
+
throw new CliError("MISSING_ARG", "user reassign-admin <tenant-slug> erwartet einen Slug.", 1);
|
|
1975
|
+
}
|
|
1976
|
+
if (!flags.to) throw new CliError("MISSING_FLAG", "--to=<email> ist Pflicht.", 1);
|
|
1977
|
+
if (!flags.reason) throw new CliError("MISSING_FLAG", '--reason="\u2026" ist Pflicht.', 1);
|
|
1978
|
+
await this.ctx.requireMfa(meId);
|
|
1979
|
+
const result = await this.users.reassignTenantAdmin(slug, flags.to.toLowerCase());
|
|
1980
|
+
await this.ctx.log({
|
|
1981
|
+
identity,
|
|
1982
|
+
userId: meId,
|
|
1983
|
+
entity: AUDIT_ENTITY,
|
|
1984
|
+
entityId: result.user.id,
|
|
1985
|
+
action: result.created ? "USER_REASSIGN_ADMIN" : "USER_ROLE_CHANGE",
|
|
1986
|
+
changes: {
|
|
1987
|
+
tenant: slug,
|
|
1988
|
+
to: "TENANT_ADMIN",
|
|
1989
|
+
from: result.previousRole,
|
|
1990
|
+
reason: flags.reason,
|
|
1991
|
+
emergency: true
|
|
1992
|
+
}
|
|
1993
|
+
});
|
|
1994
|
+
if (result.created) {
|
|
1995
|
+
console.log(`\u2714 Notfall-Admin ${result.user.email} f\xFCr ${slug} angelegt.`);
|
|
1996
|
+
if (result.oneTimePassword) {
|
|
1997
|
+
console.log(` Initial-Passwort: ${result.oneTimePassword}`);
|
|
1998
|
+
console.log(" \u2192 Sicher \xFCbermitteln. Beim ersten Login \xE4ndern.");
|
|
1999
|
+
}
|
|
2000
|
+
} else {
|
|
2001
|
+
console.log(`\u2714 ${result.user.email} ist jetzt TENANT_ADMIN von ${slug}.`);
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
async list(slug) {
|
|
2005
|
+
if (!slug) {
|
|
2006
|
+
throw new CliError("MISSING_ARG", "user list <tenant-slug> erwartet einen Slug.", 1);
|
|
2007
|
+
}
|
|
2008
|
+
const rows = await this.users.listTenantUsers(slug);
|
|
2009
|
+
this.ctx.table(rows.map((u) => ({
|
|
2010
|
+
email: u.email,
|
|
2011
|
+
role: u.role,
|
|
2012
|
+
status: u.status,
|
|
2013
|
+
lastLogin: u.lastLoginAt?.slice(0, 10) ?? "\u2014"
|
|
2014
|
+
})));
|
|
2015
|
+
}
|
|
2016
|
+
async resetPassword(email, flags, identity, meId) {
|
|
2017
|
+
if (!email) {
|
|
2018
|
+
throw new CliError("MISSING_ARG", "user reset-password <email> erwartet eine E-Mail.", 1);
|
|
2019
|
+
}
|
|
2020
|
+
if (!flags.reason) throw new CliError("MISSING_FLAG", '--reason="\u2026" ist Pflicht.', 1);
|
|
2021
|
+
const result = await this.users.triggerPasswordReset(email.toLowerCase());
|
|
2022
|
+
await this.ctx.log({
|
|
2023
|
+
identity,
|
|
2024
|
+
userId: meId,
|
|
2025
|
+
entity: AUDIT_ENTITY,
|
|
2026
|
+
entityId: result.user.id,
|
|
2027
|
+
action: "USER_PASSWORD_RESET_TRIGGERED",
|
|
2028
|
+
changes: {
|
|
2029
|
+
reason: flags.reason
|
|
2030
|
+
}
|
|
2031
|
+
});
|
|
2032
|
+
if (result.oneTimePassword) {
|
|
2033
|
+
console.log(`\u2714 Einmal-Passwort f\xFCr ${result.user.email} gesetzt.`);
|
|
2034
|
+
console.log(` Passwort: ${result.oneTimePassword}`);
|
|
2035
|
+
console.log(" \u2192 Sicher \xFCbermitteln. Beim ersten Login \xE4ndern.");
|
|
2036
|
+
} else {
|
|
2037
|
+
console.log(`\u2714 Passwort-Reset f\xFCr ${result.user.email} ausgel\xF6st.`);
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
async deactivate(email, flags, identity, meId) {
|
|
2041
|
+
if (!email) {
|
|
2042
|
+
throw new CliError("MISSING_ARG", "user deactivate <email> erwartet eine E-Mail.", 1);
|
|
2043
|
+
}
|
|
2044
|
+
if (!flags.reason) throw new CliError("MISSING_FLAG", '--reason="\u2026" ist Pflicht.', 1);
|
|
2045
|
+
await this.ctx.requireMfa(meId);
|
|
2046
|
+
await this.ctx.ensureProductionConfirmation({
|
|
2047
|
+
yes: flags.yes
|
|
2048
|
+
});
|
|
2049
|
+
const user = await this.users.deactivate(email.toLowerCase(), flags.reason);
|
|
2050
|
+
await this.ctx.log({
|
|
2051
|
+
identity,
|
|
2052
|
+
userId: meId,
|
|
2053
|
+
entity: AUDIT_ENTITY,
|
|
2054
|
+
entityId: user.id,
|
|
2055
|
+
action: "USER_DEACTIVATED",
|
|
2056
|
+
changes: {
|
|
2057
|
+
reason: flags.reason,
|
|
2058
|
+
emergency: true
|
|
2059
|
+
}
|
|
2060
|
+
});
|
|
2061
|
+
console.log(`\u2714 ${user.email} deaktiviert.`);
|
|
2062
|
+
}
|
|
2063
|
+
parseAs(val) {
|
|
2064
|
+
return val;
|
|
2065
|
+
}
|
|
2066
|
+
parseTo(val) {
|
|
2067
|
+
return val;
|
|
2068
|
+
}
|
|
2069
|
+
parseReason(val) {
|
|
2070
|
+
return val;
|
|
2071
|
+
}
|
|
2072
|
+
parseYes() {
|
|
2073
|
+
return true;
|
|
2074
|
+
}
|
|
2075
|
+
parseFirst(val) {
|
|
2076
|
+
return val;
|
|
2077
|
+
}
|
|
2078
|
+
parseLast(val) {
|
|
2079
|
+
return val;
|
|
2080
|
+
}
|
|
2081
|
+
parsePassword(val) {
|
|
2082
|
+
return val;
|
|
2083
|
+
}
|
|
2084
|
+
};
|
|
2085
|
+
_ts_decorate14([
|
|
2086
|
+
Option6({
|
|
2087
|
+
flags: "--as <email>",
|
|
2088
|
+
description: "CLI-Identit\xE4t (sonst <APP>_ADMIN_EMAIL)"
|
|
2089
|
+
}),
|
|
2090
|
+
_ts_metadata13("design:type", Function),
|
|
2091
|
+
_ts_metadata13("design:paramtypes", [
|
|
2092
|
+
String
|
|
2093
|
+
]),
|
|
2094
|
+
_ts_metadata13("design:returntype", String)
|
|
2095
|
+
], UserCommands.prototype, "parseAs", null);
|
|
2096
|
+
_ts_decorate14([
|
|
2097
|
+
Option6({
|
|
2098
|
+
flags: "--to <email>",
|
|
2099
|
+
description: "Ziel-User (reassign-admin)"
|
|
2100
|
+
}),
|
|
2101
|
+
_ts_metadata13("design:type", Function),
|
|
2102
|
+
_ts_metadata13("design:paramtypes", [
|
|
2103
|
+
String
|
|
2104
|
+
]),
|
|
2105
|
+
_ts_metadata13("design:returntype", String)
|
|
2106
|
+
], UserCommands.prototype, "parseTo", null);
|
|
2107
|
+
_ts_decorate14([
|
|
2108
|
+
Option6({
|
|
2109
|
+
flags: "--reason <text>",
|
|
2110
|
+
description: "Begr\xFCndung (Audit)"
|
|
2111
|
+
}),
|
|
2112
|
+
_ts_metadata13("design:type", Function),
|
|
2113
|
+
_ts_metadata13("design:paramtypes", [
|
|
2114
|
+
String
|
|
2115
|
+
]),
|
|
2116
|
+
_ts_metadata13("design:returntype", String)
|
|
2117
|
+
], UserCommands.prototype, "parseReason", null);
|
|
2118
|
+
_ts_decorate14([
|
|
2119
|
+
Option6({
|
|
2120
|
+
flags: "-y, --yes",
|
|
2121
|
+
description: "Production-Confirmation \xFCberspringen"
|
|
2122
|
+
}),
|
|
2123
|
+
_ts_metadata13("design:type", Function),
|
|
2124
|
+
_ts_metadata13("design:paramtypes", []),
|
|
2125
|
+
_ts_metadata13("design:returntype", Boolean)
|
|
2126
|
+
], UserCommands.prototype, "parseYes", null);
|
|
2127
|
+
_ts_decorate14([
|
|
2128
|
+
Option6({
|
|
2129
|
+
flags: "--first <name>",
|
|
2130
|
+
description: "Vorname (create-super-admin)"
|
|
2131
|
+
}),
|
|
2132
|
+
_ts_metadata13("design:type", Function),
|
|
2133
|
+
_ts_metadata13("design:paramtypes", [
|
|
2134
|
+
String
|
|
2135
|
+
]),
|
|
2136
|
+
_ts_metadata13("design:returntype", String)
|
|
2137
|
+
], UserCommands.prototype, "parseFirst", null);
|
|
2138
|
+
_ts_decorate14([
|
|
2139
|
+
Option6({
|
|
2140
|
+
flags: "--last <name>",
|
|
2141
|
+
description: "Nachname (create-super-admin)"
|
|
2142
|
+
}),
|
|
2143
|
+
_ts_metadata13("design:type", Function),
|
|
2144
|
+
_ts_metadata13("design:paramtypes", [
|
|
2145
|
+
String
|
|
2146
|
+
]),
|
|
2147
|
+
_ts_metadata13("design:returntype", String)
|
|
2148
|
+
], UserCommands.prototype, "parseLast", null);
|
|
2149
|
+
_ts_decorate14([
|
|
2150
|
+
Option6({
|
|
2151
|
+
flags: "--password <pwd>",
|
|
2152
|
+
description: "Passwort (create-super-admin; ohne Angabe generiert)"
|
|
2153
|
+
}),
|
|
2154
|
+
_ts_metadata13("design:type", Function),
|
|
2155
|
+
_ts_metadata13("design:paramtypes", [
|
|
2156
|
+
String
|
|
2157
|
+
]),
|
|
2158
|
+
_ts_metadata13("design:returntype", String)
|
|
2159
|
+
], UserCommands.prototype, "parsePassword", null);
|
|
2160
|
+
UserCommands = _ts_decorate14([
|
|
2161
|
+
Injectable13(),
|
|
2162
|
+
Command6({
|
|
2163
|
+
name: "user",
|
|
2164
|
+
description: "User-Operationen (create-super-admin, reassign-admin, list, reset-password, deactivate)"
|
|
2165
|
+
}),
|
|
2166
|
+
_ts_param9(1, Inject9(USER_MANAGEMENT_PORT_TOKEN)),
|
|
2167
|
+
_ts_metadata13("design:type", Function),
|
|
2168
|
+
_ts_metadata13("design:paramtypes", [
|
|
2169
|
+
typeof CliContextService === "undefined" ? Object : CliContextService,
|
|
2170
|
+
typeof UserManagementPort === "undefined" ? Object : UserManagementPort
|
|
2171
|
+
])
|
|
2172
|
+
], UserCommands);
|
|
2173
|
+
export {
|
|
2174
|
+
AUDIT_QUERY_PORT_TOKEN,
|
|
2175
|
+
AdminCommands,
|
|
2176
|
+
AdminManifestDoctorCheck,
|
|
2177
|
+
AdminMfaSetupCommand,
|
|
2178
|
+
AdminWhoamiCommand,
|
|
2179
|
+
AuditCommands,
|
|
2180
|
+
AuditTailCommand,
|
|
2181
|
+
AuditTailFlow,
|
|
2182
|
+
CLI_CONTEXT_CONFIG_TOKEN,
|
|
2183
|
+
CliContextModule,
|
|
2184
|
+
CliContextService,
|
|
2185
|
+
CliError,
|
|
2186
|
+
DEFAULT_MANIFEST_CHECKS,
|
|
2187
|
+
DOCTOR_CHECKS_TOKEN,
|
|
2188
|
+
DiscoveryCommands,
|
|
2189
|
+
DiscoveryScanCommand,
|
|
2190
|
+
DiscoverySnapshotDoctorCheck,
|
|
2191
|
+
DoctorCommands,
|
|
2192
|
+
DoctorFlow,
|
|
2193
|
+
MANIFEST_ACCESS_PORT_TOKEN,
|
|
2194
|
+
MANIFEST_CHECKS_TOKEN,
|
|
2195
|
+
ManifestCheckCommand,
|
|
2196
|
+
ManifestCliFlow,
|
|
2197
|
+
ManifestCommands,
|
|
2198
|
+
ManifestDumpCommand,
|
|
2199
|
+
ManifestHashCommand,
|
|
2200
|
+
ManifestValidateCommand,
|
|
2201
|
+
MfaSetupFlow,
|
|
2202
|
+
PLATFORM_DOCTOR_CHECK_PROVIDERS,
|
|
2203
|
+
PlanCatalogDoctorCheck,
|
|
2204
|
+
USER_MANAGEMENT_PORT_TOKEN,
|
|
2205
|
+
USER_PORT_TOKEN,
|
|
2206
|
+
UserCommands,
|
|
2207
|
+
UserPortDoctorCheck,
|
|
2208
|
+
WhoAmIFlow,
|
|
2209
|
+
applyFragmentBlocks,
|
|
2210
|
+
extractModelBlocks,
|
|
2211
|
+
extractModelNames
|
|
2212
|
+
};
|