@isaacthoman/pulpo 0.51.2 → 0.52.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/README.md +6 -0
- package/dist/index.js +121 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,6 +6,9 @@ Operator-first command-line client for a Pulpo instance.
|
|
|
6
6
|
npm install --global @isaacthoman/pulpo
|
|
7
7
|
pulpo context add production --url https://pulpo.example.com
|
|
8
8
|
pulpo auth login --email admin@example.com
|
|
9
|
+
pulpo auth 2fa status
|
|
10
|
+
pulpo auth 2fa setup
|
|
11
|
+
pulpo auth 2fa confirm
|
|
9
12
|
pulpo settings export --output pulpo-settings.json
|
|
10
13
|
```
|
|
11
14
|
|
|
@@ -32,6 +35,9 @@ lives in the platform config directory. Session tokens use Keychain on macOS or
|
|
|
32
35
|
Secret Service on Linux when available, with a mode-`0600` file and warning as
|
|
33
36
|
the fallback. Passwords are read from a hidden prompt, stdin, or
|
|
34
37
|
`PULPO_PASSWORD`; no secret-bearing password/token option is accepted.
|
|
38
|
+
Authenticator and recovery codes use the hidden prompt or `PULPO_2FA_CODE`.
|
|
39
|
+
Enrollment secrets and recovery codes are printed only when created; store them
|
|
40
|
+
securely before continuing.
|
|
35
41
|
|
|
36
42
|
Settings exports use `apiVersion: pulpo.dev/management/v1` and carry an opaque
|
|
37
43
|
revision. `settings diff` exits with status 2 when changes exist. A stale
|
package/dist/index.js
CHANGED
|
@@ -16304,7 +16304,8 @@ var userSchema = external_exports.object({
|
|
|
16304
16304
|
});
|
|
16305
16305
|
var loginInputSchema = external_exports.object({
|
|
16306
16306
|
email: external_exports.email(),
|
|
16307
|
-
password: external_exports.string().min(1).max(1024)
|
|
16307
|
+
password: external_exports.string().min(1).max(1024),
|
|
16308
|
+
twoFactorCode: external_exports.string().trim().min(6).max(32).optional()
|
|
16308
16309
|
});
|
|
16309
16310
|
var signupInputSchema = external_exports.object({
|
|
16310
16311
|
name: external_exports.string().trim().min(1).max(120),
|
|
@@ -16344,9 +16345,34 @@ var mobileConfigSchema = external_exports.object({
|
|
|
16344
16345
|
chatDuplication: external_exports.literal(true),
|
|
16345
16346
|
publicSharing: external_exports.literal(true),
|
|
16346
16347
|
attachments: external_exports.literal(true),
|
|
16347
|
-
folders: external_exports.literal(true)
|
|
16348
|
+
folders: external_exports.literal(true),
|
|
16349
|
+
twoFactorAuth: external_exports.boolean().optional().default(false)
|
|
16348
16350
|
})
|
|
16349
16351
|
});
|
|
16352
|
+
var twoFactorStatusSchema = external_exports.object({
|
|
16353
|
+
enabled: external_exports.boolean(),
|
|
16354
|
+
recoveryCodesRemaining: external_exports.number().int().nonnegative()
|
|
16355
|
+
});
|
|
16356
|
+
var beginTwoFactorEnrollmentInputSchema = external_exports.object({
|
|
16357
|
+
currentPassword: external_exports.string().min(1).max(1024),
|
|
16358
|
+
verificationCode: external_exports.string().trim().min(6).max(32).optional()
|
|
16359
|
+
});
|
|
16360
|
+
var twoFactorEnrollmentSchema = external_exports.object({
|
|
16361
|
+
manualKey: external_exports.string().min(16),
|
|
16362
|
+
otpauthUri: external_exports.string().startsWith("otpauth://totp/"),
|
|
16363
|
+
qrCodeDataUrl: external_exports.string().startsWith("data:image/"),
|
|
16364
|
+
expiresAt: isoDateSchema
|
|
16365
|
+
});
|
|
16366
|
+
var confirmTwoFactorEnrollmentInputSchema = external_exports.object({
|
|
16367
|
+
code: external_exports.string().trim().regex(/^\d{6}$/, "Enter a six-digit authenticator code")
|
|
16368
|
+
});
|
|
16369
|
+
var verifyTwoFactorChangeInputSchema = external_exports.object({
|
|
16370
|
+
currentPassword: external_exports.string().min(1).max(1024),
|
|
16371
|
+
verificationCode: external_exports.string().trim().min(6).max(32)
|
|
16372
|
+
});
|
|
16373
|
+
var twoFactorRecoveryCodesSchema = external_exports.object({
|
|
16374
|
+
recoveryCodes: external_exports.array(external_exports.string()).length(10)
|
|
16375
|
+
});
|
|
16350
16376
|
var updateProfileInputSchema = external_exports.object({
|
|
16351
16377
|
name: external_exports.string().trim().min(1).max(120)
|
|
16352
16378
|
});
|
|
@@ -17033,8 +17059,8 @@ var PulpoManagementClient = class {
|
|
|
17033
17059
|
info() {
|
|
17034
17060
|
return this.request("/api/management/v1/info");
|
|
17035
17061
|
}
|
|
17036
|
-
login(email3, password, deviceLabel = "Pulpo CLI") {
|
|
17037
|
-
return this.request("/api/management/v1/auth/login", { method: "POST", body: { email: email3, password, deviceLabel } });
|
|
17062
|
+
login(email3, password, deviceLabel = "Pulpo CLI", twoFactorCode) {
|
|
17063
|
+
return this.request("/api/management/v1/auth/login", { method: "POST", body: { email: email3, password, deviceLabel, twoFactorCode } });
|
|
17038
17064
|
}
|
|
17039
17065
|
logout() {
|
|
17040
17066
|
return this.request("/api/management/v1/auth/logout", { method: "POST" });
|
|
@@ -17042,6 +17068,21 @@ var PulpoManagementClient = class {
|
|
|
17042
17068
|
me() {
|
|
17043
17069
|
return this.request("/api/management/v1/auth/me");
|
|
17044
17070
|
}
|
|
17071
|
+
twoFactorStatus() {
|
|
17072
|
+
return this.request("/api/me/two-factor");
|
|
17073
|
+
}
|
|
17074
|
+
beginTwoFactorEnrollment(input) {
|
|
17075
|
+
return this.request("/api/me/two-factor/enrollment", { method: "POST", body: input });
|
|
17076
|
+
}
|
|
17077
|
+
confirmTwoFactorEnrollment(code) {
|
|
17078
|
+
return this.request("/api/me/two-factor/enrollment/confirm", { method: "POST", body: { code } });
|
|
17079
|
+
}
|
|
17080
|
+
regenerateTwoFactorRecoveryCodes(input) {
|
|
17081
|
+
return this.request("/api/me/two-factor/recovery-codes", { method: "POST", body: input });
|
|
17082
|
+
}
|
|
17083
|
+
disableTwoFactor(input) {
|
|
17084
|
+
return this.request("/api/me/two-factor", { method: "DELETE", body: input });
|
|
17085
|
+
}
|
|
17045
17086
|
tokens() {
|
|
17046
17087
|
return this.request("/api/management/v1/tokens");
|
|
17047
17088
|
}
|
|
@@ -17344,7 +17385,7 @@ async function confirmExact(io, expected, yes, json2) {
|
|
|
17344
17385
|
}
|
|
17345
17386
|
|
|
17346
17387
|
// src/index.ts
|
|
17347
|
-
var CLI_VERSION = true ? "0.
|
|
17388
|
+
var CLI_VERSION = true ? "0.52.0" : "0.1.0";
|
|
17348
17389
|
var CLI_BUNDLED = true;
|
|
17349
17390
|
var commandIo = /* @__PURE__ */ new WeakMap();
|
|
17350
17391
|
var commandClientFactory = /* @__PURE__ */ new WeakMap();
|
|
@@ -17464,7 +17505,31 @@ async function clientFor(command, authenticated = true) {
|
|
|
17464
17505
|
if (capability && !info.capabilities.includes(capability)) {
|
|
17465
17506
|
throw new Error(`The selected Pulpo instance does not advertise the ${capability} capability`);
|
|
17466
17507
|
}
|
|
17467
|
-
return { client, contextName: connection.contextName, context: connection.context };
|
|
17508
|
+
return { client, contextName: connection.contextName, context: connection.context, info };
|
|
17509
|
+
}
|
|
17510
|
+
async function twoFactorSecret(io, json2, prompt = "Authenticator or recovery code: ") {
|
|
17511
|
+
const value = process.env.PULPO_2FA_CODE;
|
|
17512
|
+
if (value !== void 0) return value;
|
|
17513
|
+
if (!io.stdin.isTTY) throw new Error("Noninteractive two-factor verification requires PULPO_2FA_CODE");
|
|
17514
|
+
if (json2) throw new Error("Machine-mode two-factor verification requires PULPO_2FA_CODE");
|
|
17515
|
+
return readSecret(io, prompt);
|
|
17516
|
+
}
|
|
17517
|
+
async function currentPassword(io, json2) {
|
|
17518
|
+
const value = process.env.PULPO_PASSWORD;
|
|
17519
|
+
if (value !== void 0) return value;
|
|
17520
|
+
if (!io.stdin.isTTY) throw new Error("Noninteractive account security changes require PULPO_PASSWORD");
|
|
17521
|
+
if (json2) throw new Error("Machine-mode account security changes require PULPO_PASSWORD");
|
|
17522
|
+
return readSecret(io, "Current password: ");
|
|
17523
|
+
}
|
|
17524
|
+
function requireTwoFactorCapability(info) {
|
|
17525
|
+
if (!info.capabilities.includes("twoFactor")) throw new Error("The selected Pulpo instance does not support two-factor authentication");
|
|
17526
|
+
}
|
|
17527
|
+
function emitRecoveryCodes(io, command, recoveryCodes) {
|
|
17528
|
+
io.stderr.write("Save these recovery codes now. They will not be shown again.\n");
|
|
17529
|
+
if (globalOptions(command).json) emit(io, command, { recoveryCodes });
|
|
17530
|
+
else {
|
|
17531
|
+
writeOutput(io, recoveryCodes.map((recoveryCode) => ({ recoveryCode })), false);
|
|
17532
|
+
}
|
|
17468
17533
|
}
|
|
17469
17534
|
function emit(io, command, value) {
|
|
17470
17535
|
writeOutput(io, value, Boolean(globalOptions(command).json));
|
|
@@ -17563,7 +17628,14 @@ function createProgram(io = processIo, dependencies = {}) {
|
|
|
17563
17628
|
throw new Error("Machine-mode login requires the password on stdin or in PULPO_PASSWORD");
|
|
17564
17629
|
}
|
|
17565
17630
|
const password = process.env.PULPO_PASSWORD ?? await readSecret(io, "Password: ");
|
|
17566
|
-
|
|
17631
|
+
let result;
|
|
17632
|
+
try {
|
|
17633
|
+
result = await client.login(email3, password);
|
|
17634
|
+
} catch (error51) {
|
|
17635
|
+
if (!(error51 instanceof ManagementApiError) || error51.code !== "two_factor_required") throw error51;
|
|
17636
|
+
const code = await twoFactorSecret(io, Boolean(globalOptions(command).json));
|
|
17637
|
+
result = await client.login(email3, password, "Pulpo CLI", code);
|
|
17638
|
+
}
|
|
17567
17639
|
await setCredential(contextName, result.session.token, (message) => io.stderr.write(`${message}
|
|
17568
17640
|
`));
|
|
17569
17641
|
const config2 = await loadConfig();
|
|
@@ -17581,6 +17653,48 @@ function createProgram(io = processIo, dependencies = {}) {
|
|
|
17581
17653
|
const { client } = await clientFor(command);
|
|
17582
17654
|
emit(io, command, await client.me());
|
|
17583
17655
|
});
|
|
17656
|
+
const twoFactor = auth.command("2fa").description("Manage authenticator-app two-factor authentication");
|
|
17657
|
+
twoFactor.command("status").action(async (_options, command) => {
|
|
17658
|
+
const { client, info } = await clientFor(command);
|
|
17659
|
+
requireTwoFactorCapability(info);
|
|
17660
|
+
emit(io, command, await client.twoFactorStatus());
|
|
17661
|
+
});
|
|
17662
|
+
twoFactor.command("setup").description("Start or replace authenticator enrollment").action(async (_options, command) => {
|
|
17663
|
+
const { client, info } = await clientFor(command);
|
|
17664
|
+
requireTwoFactorCapability(info);
|
|
17665
|
+
const status = await client.twoFactorStatus();
|
|
17666
|
+
const password = await currentPassword(io, Boolean(globalOptions(command).json));
|
|
17667
|
+
const verificationCode = status.enabled ? await twoFactorSecret(io, Boolean(globalOptions(command).json), "Current authenticator or recovery code: ") : void 0;
|
|
17668
|
+
const enrollment = await client.beginTwoFactorEnrollment({ currentPassword: password, verificationCode });
|
|
17669
|
+
io.stderr.write("Save this enrollment secret now. It will not be shown again.\n");
|
|
17670
|
+
if (globalOptions(command).json) emit(io, command, enrollment);
|
|
17671
|
+
else {
|
|
17672
|
+
io.stderr.write("Add this account to your authenticator, then run `pulpo auth 2fa confirm`.\n");
|
|
17673
|
+
writeOutput(io, [{ manualKey: enrollment.manualKey, otpauthUri: enrollment.otpauthUri, expiresAt: enrollment.expiresAt }], false);
|
|
17674
|
+
}
|
|
17675
|
+
});
|
|
17676
|
+
twoFactor.command("confirm").description("Confirm a pending enrollment").action(async (_options, command) => {
|
|
17677
|
+
const { client, info } = await clientFor(command);
|
|
17678
|
+
requireTwoFactorCapability(info);
|
|
17679
|
+
const code = await twoFactorSecret(io, Boolean(globalOptions(command).json), "New six-digit authenticator code: ");
|
|
17680
|
+
emitRecoveryCodes(io, command, (await client.confirmTwoFactorEnrollment(code)).recoveryCodes);
|
|
17681
|
+
});
|
|
17682
|
+
twoFactor.command("regenerate-recovery-codes").description("Replace all recovery codes").action(async (_options, command) => {
|
|
17683
|
+
const { client, info } = await clientFor(command);
|
|
17684
|
+
requireTwoFactorCapability(info);
|
|
17685
|
+
const password = await currentPassword(io, Boolean(globalOptions(command).json));
|
|
17686
|
+
const verificationCode = await twoFactorSecret(io, Boolean(globalOptions(command).json));
|
|
17687
|
+
emitRecoveryCodes(io, command, (await client.regenerateTwoFactorRecoveryCodes({ currentPassword: password, verificationCode })).recoveryCodes);
|
|
17688
|
+
});
|
|
17689
|
+
twoFactor.command("disable").description("Disable two-factor authentication").action(async (_options, command) => {
|
|
17690
|
+
await confirmExact(io, "DISABLE", Boolean(globalOptions(command).yes), Boolean(globalOptions(command).json));
|
|
17691
|
+
const { client, info } = await clientFor(command);
|
|
17692
|
+
requireTwoFactorCapability(info);
|
|
17693
|
+
const password = await currentPassword(io, Boolean(globalOptions(command).json));
|
|
17694
|
+
const verificationCode = await twoFactorSecret(io, Boolean(globalOptions(command).json));
|
|
17695
|
+
await client.disableTwoFactor({ currentPassword: password, verificationCode });
|
|
17696
|
+
emit(io, command, { enabled: false });
|
|
17697
|
+
});
|
|
17584
17698
|
const token = program.command("token").description("Manage scoped automation tokens");
|
|
17585
17699
|
token.command("list").action(async (_options, command) => {
|
|
17586
17700
|
const { client } = await clientFor(command);
|