@opengeni/vaults-protonpass 0.0.0-stage → 3.3.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 +190 -0
- package/README.md +136 -2
- package/dist/contract.d.ts +6 -0
- package/dist/errors.d.ts +57 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1836 -0
- package/dist/index.js.map +1 -0
- package/dist/mapping.d.ts +41 -0
- package/dist/naming.d.ts +8 -0
- package/dist/operations.d.ts +7 -0
- package/dist/pool.d.ts +49 -0
- package/dist/provider.d.ts +38 -0
- package/dist/runner.d.ts +31 -0
- package/dist/schema.d.ts +545 -0
- package/dist/session.d.ts +113 -0
- package/package.json +42 -4
- package/src/contract.ts +29 -0
- package/src/errors.ts +287 -0
- package/src/index.ts +29 -0
- package/src/mapping.ts +247 -0
- package/src/naming.ts +44 -0
- package/src/operations.ts +65 -0
- package/src/pool.ts +199 -0
- package/src/provider.ts +690 -0
- package/src/runner.ts +144 -0
- package/src/schema.ts +164 -0
- package/src/session.ts +578 -0
package/src/runner.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
/** One bounded subprocess run. No shell, no inherited environment. */
|
|
4
|
+
export interface ProcessRequest {
|
|
5
|
+
file: string;
|
|
6
|
+
args: readonly string[];
|
|
7
|
+
env: Readonly<Record<string, string>>;
|
|
8
|
+
cwd: string;
|
|
9
|
+
stdin?: string;
|
|
10
|
+
timeoutMs: number;
|
|
11
|
+
maxStdoutBytes: number;
|
|
12
|
+
maxStderrBytes: number;
|
|
13
|
+
signal?: AbortSignal;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ProcessResult {
|
|
17
|
+
exitCode: number | null;
|
|
18
|
+
signal: NodeJS.Signals | null;
|
|
19
|
+
stdout: string;
|
|
20
|
+
stderr: string;
|
|
21
|
+
timedOut: boolean;
|
|
22
|
+
aborted: boolean;
|
|
23
|
+
/** stdout exceeded its bound; the process was killed and stdout is incomplete. */
|
|
24
|
+
stdoutOverflow: boolean;
|
|
25
|
+
/** The binary could not be started (missing, not executable). */
|
|
26
|
+
spawnError: string | null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type ProcessRunner = (request: ProcessRequest) => Promise<ProcessResult>;
|
|
30
|
+
|
|
31
|
+
const KILL_GRACE_MS = 1_000;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Runs `file` with an argv array and an explicit environment. stdout is capped
|
|
35
|
+
* (the process is killed past the cap), stderr is truncated, and the process
|
|
36
|
+
* group is terminated on timeout or abort, so a hung CLI cannot outlive the call.
|
|
37
|
+
*/
|
|
38
|
+
export const runProcess: ProcessRunner = (request) =>
|
|
39
|
+
new Promise<ProcessResult>((resolve) => {
|
|
40
|
+
const stdout: Buffer[] = [];
|
|
41
|
+
const stderr: Buffer[] = [];
|
|
42
|
+
let stdoutBytes = 0;
|
|
43
|
+
let stderrBytes = 0;
|
|
44
|
+
let stdoutOverflow = false;
|
|
45
|
+
let timedOut = false;
|
|
46
|
+
let aborted = false;
|
|
47
|
+
let settled = false;
|
|
48
|
+
let killTimer: ReturnType<typeof setTimeout> | undefined;
|
|
49
|
+
|
|
50
|
+
if (request.signal?.aborted) {
|
|
51
|
+
resolve({
|
|
52
|
+
exitCode: null,
|
|
53
|
+
signal: null,
|
|
54
|
+
stdout: "",
|
|
55
|
+
stderr: "",
|
|
56
|
+
timedOut: false,
|
|
57
|
+
aborted: true,
|
|
58
|
+
stdoutOverflow: false,
|
|
59
|
+
spawnError: null,
|
|
60
|
+
});
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const child = spawn(request.file, [...request.args], {
|
|
65
|
+
cwd: request.cwd,
|
|
66
|
+
env: { ...request.env },
|
|
67
|
+
shell: false,
|
|
68
|
+
detached: process.platform !== "win32",
|
|
69
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
70
|
+
windowsHide: true,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const terminate = () => {
|
|
74
|
+
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
75
|
+
const kill = (sig: NodeJS.Signals) => {
|
|
76
|
+
try {
|
|
77
|
+
if (child.pid !== undefined && process.platform !== "win32")
|
|
78
|
+
process.kill(-child.pid, sig);
|
|
79
|
+
else child.kill(sig);
|
|
80
|
+
} catch {
|
|
81
|
+
// Already gone.
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
kill("SIGTERM");
|
|
85
|
+
killTimer ??= setTimeout(() => kill("SIGKILL"), KILL_GRACE_MS);
|
|
86
|
+
killTimer.unref?.();
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const timer = setTimeout(() => {
|
|
90
|
+
timedOut = true;
|
|
91
|
+
terminate();
|
|
92
|
+
}, request.timeoutMs);
|
|
93
|
+
timer.unref?.();
|
|
94
|
+
const onAbort = () => {
|
|
95
|
+
aborted = true;
|
|
96
|
+
terminate();
|
|
97
|
+
};
|
|
98
|
+
request.signal?.addEventListener("abort", onAbort, { once: true });
|
|
99
|
+
|
|
100
|
+
const finish = (
|
|
101
|
+
result: Omit<ProcessResult, "stdout" | "stderr" | "timedOut" | "aborted" | "stdoutOverflow">,
|
|
102
|
+
) => {
|
|
103
|
+
if (settled) return;
|
|
104
|
+
settled = true;
|
|
105
|
+
clearTimeout(timer);
|
|
106
|
+
if (killTimer) clearTimeout(killTimer);
|
|
107
|
+
request.signal?.removeEventListener("abort", onAbort);
|
|
108
|
+
resolve({
|
|
109
|
+
...result,
|
|
110
|
+
stdout: Buffer.concat(stdout).toString("utf8"),
|
|
111
|
+
stderr: Buffer.concat(stderr).toString("utf8"),
|
|
112
|
+
timedOut,
|
|
113
|
+
aborted,
|
|
114
|
+
stdoutOverflow,
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
child.stdout.on("data", (chunk: Buffer) => {
|
|
119
|
+
if (stdoutOverflow) return;
|
|
120
|
+
stdoutBytes += chunk.length;
|
|
121
|
+
if (stdoutBytes > request.maxStdoutBytes) {
|
|
122
|
+
stdoutOverflow = true;
|
|
123
|
+
terminate();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
stdout.push(chunk);
|
|
127
|
+
});
|
|
128
|
+
child.stderr.on("data", (chunk: Buffer) => {
|
|
129
|
+
if (stderrBytes >= request.maxStderrBytes) return;
|
|
130
|
+
const room = request.maxStderrBytes - stderrBytes;
|
|
131
|
+
const kept = chunk.length > room ? chunk.subarray(0, room) : chunk;
|
|
132
|
+
stderrBytes += kept.length;
|
|
133
|
+
stderr.push(kept);
|
|
134
|
+
});
|
|
135
|
+
child.on("error", (error: NodeJS.ErrnoException) => {
|
|
136
|
+
finish({ exitCode: null, signal: null, spawnError: error.code ?? "spawn_failed" });
|
|
137
|
+
});
|
|
138
|
+
child.on("close", (exitCode, signal) => {
|
|
139
|
+
finish({ exitCode, signal, spawnError: null });
|
|
140
|
+
});
|
|
141
|
+
// A CLI that exits before reading stdin raises EPIPE; the exit status still decides.
|
|
142
|
+
child.stdin.on("error", () => {});
|
|
143
|
+
child.stdin.end(request.stdin ?? "");
|
|
144
|
+
});
|
package/src/schema.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* JSON shapes printed by pass-cli 2.4.2 with `--output json` (serde of the
|
|
5
|
+
* pass crate and proton-pass-common 2.1.1 types). Unknown keys are dropped;
|
|
6
|
+
* only what the adapter maps is validated, so additive upstream fields do not
|
|
7
|
+
* break parsing while a changed shape fails closed.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const Id = z.string().min(1).max(512);
|
|
11
|
+
/** jiff civil date-time; pass-cli runs with TZ=UTC so it is UTC wall time. */
|
|
12
|
+
const CivilDateTime = z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d{1,9})?)?$/);
|
|
13
|
+
|
|
14
|
+
const ShareRole = z.union([
|
|
15
|
+
z.enum(["Owner", "Manager", "Editor", "Viewer"]),
|
|
16
|
+
z.object({
|
|
17
|
+
Custom: z.object({ name: z.string().optional(), permission: z.number().int().min(0) }),
|
|
18
|
+
}),
|
|
19
|
+
]);
|
|
20
|
+
export type ShareRole = z.infer<typeof ShareRole>;
|
|
21
|
+
|
|
22
|
+
/** `share list --only-vaults=true --output json` */
|
|
23
|
+
export const ShareListOutput = z.object({
|
|
24
|
+
shares: z.array(
|
|
25
|
+
z.object({
|
|
26
|
+
id: Id,
|
|
27
|
+
name: z.string(),
|
|
28
|
+
share_type: z.string(),
|
|
29
|
+
share_role: ShareRole,
|
|
30
|
+
}),
|
|
31
|
+
),
|
|
32
|
+
});
|
|
33
|
+
export type ShareListOutput = z.infer<typeof ShareListOutput>;
|
|
34
|
+
|
|
35
|
+
/** `vault list --output json` (fallback when share roles are unavailable). */
|
|
36
|
+
export const VaultListOutput = z.object({
|
|
37
|
+
vaults: z.array(z.object({ name: z.string(), vault_id: Id, share_id: Id })),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export const ExtraFieldContent = z.union([
|
|
41
|
+
z.object({ Text: z.string() }),
|
|
42
|
+
z.object({ Hidden: z.string() }),
|
|
43
|
+
z.object({ Totp: z.string() }),
|
|
44
|
+
z.object({ Timestamp: z.number().int() }),
|
|
45
|
+
]);
|
|
46
|
+
export const ExtraField = z.object({ name: z.string(), content: ExtraFieldContent });
|
|
47
|
+
export type ExtraField = z.infer<typeof ExtraField>;
|
|
48
|
+
|
|
49
|
+
const Section = z.object({ section_name: z.string(), section_fields: z.array(ExtraField) });
|
|
50
|
+
export type Section = z.infer<typeof Section>;
|
|
51
|
+
|
|
52
|
+
const OptionalString = z
|
|
53
|
+
.string()
|
|
54
|
+
.nullish()
|
|
55
|
+
.transform((value) => value ?? "");
|
|
56
|
+
|
|
57
|
+
const LoginContent = z.object({
|
|
58
|
+
email: OptionalString,
|
|
59
|
+
username: OptionalString,
|
|
60
|
+
password: OptionalString,
|
|
61
|
+
urls: z.array(z.string()).default([]),
|
|
62
|
+
totp_uri: OptionalString,
|
|
63
|
+
});
|
|
64
|
+
const SshKeyContent = z.object({
|
|
65
|
+
private_key: OptionalString,
|
|
66
|
+
public_key: OptionalString,
|
|
67
|
+
sections: z.array(Section).default([]),
|
|
68
|
+
});
|
|
69
|
+
const WifiContent = z.object({
|
|
70
|
+
ssid: OptionalString,
|
|
71
|
+
password: OptionalString,
|
|
72
|
+
sections: z.array(Section).default([]),
|
|
73
|
+
});
|
|
74
|
+
const CreditCardContent = z.object({
|
|
75
|
+
cardholder_name: OptionalString,
|
|
76
|
+
number: OptionalString,
|
|
77
|
+
verification_number: OptionalString,
|
|
78
|
+
expiration_date: OptionalString,
|
|
79
|
+
pin: OptionalString,
|
|
80
|
+
});
|
|
81
|
+
/** Identity carries many scalar strings plus extra field groups; kept loose. */
|
|
82
|
+
const IdentityContent = z.record(z.string(), z.unknown());
|
|
83
|
+
const CustomContent = z.object({ sections: z.array(Section).default([]) });
|
|
84
|
+
|
|
85
|
+
export const ItemContentVariant = z.union([
|
|
86
|
+
z.object({ Note: z.null() }).strict(),
|
|
87
|
+
z.object({ Alias: z.null() }).strict(),
|
|
88
|
+
z.object({ Login: LoginContent }),
|
|
89
|
+
z.object({ SshKey: SshKeyContent }),
|
|
90
|
+
z.object({ Wifi: WifiContent }),
|
|
91
|
+
z.object({ CreditCard: CreditCardContent }),
|
|
92
|
+
z.object({ Identity: IdentityContent }),
|
|
93
|
+
z.object({ Custom: CustomContent }),
|
|
94
|
+
z.literal("Note"),
|
|
95
|
+
z.literal("Alias"),
|
|
96
|
+
]);
|
|
97
|
+
export type ItemContentVariant = z.infer<typeof ItemContentVariant>;
|
|
98
|
+
|
|
99
|
+
export const Item = z.object({
|
|
100
|
+
id: Id,
|
|
101
|
+
share_id: Id,
|
|
102
|
+
vault_id: z.string().optional(),
|
|
103
|
+
content: z.object({
|
|
104
|
+
title: z.string(),
|
|
105
|
+
note: OptionalString,
|
|
106
|
+
content: ItemContentVariant,
|
|
107
|
+
extra_fields: z.array(ExtraField).default([]),
|
|
108
|
+
}),
|
|
109
|
+
state: z.string(),
|
|
110
|
+
flags: z.array(z.string()).default([]),
|
|
111
|
+
create_time: CivilDateTime,
|
|
112
|
+
modify_time: CivilDateTime,
|
|
113
|
+
});
|
|
114
|
+
export type Item = z.infer<typeof Item>;
|
|
115
|
+
|
|
116
|
+
export const Attachment = z.object({
|
|
117
|
+
id: Id,
|
|
118
|
+
size: z.number().int().min(0),
|
|
119
|
+
content: z.object({ name: z.string(), mime_type: z.string().optional() }),
|
|
120
|
+
});
|
|
121
|
+
export type Attachment = z.infer<typeof Attachment>;
|
|
122
|
+
|
|
123
|
+
/** `item view --output json`: `ItemDetails`. */
|
|
124
|
+
export const ItemDetails = z.object({
|
|
125
|
+
item: Item,
|
|
126
|
+
attachments: z.array(Attachment).default([]),
|
|
127
|
+
});
|
|
128
|
+
export type ItemDetails = z.infer<typeof ItemDetails>;
|
|
129
|
+
|
|
130
|
+
/** `item list --show-secrets --output json` (non-agent sessions only). */
|
|
131
|
+
export const FullItemList = z.object({ items: z.array(Item) });
|
|
132
|
+
|
|
133
|
+
/** `item list --output json`: secret-free summaries (the only form agent sessions get). */
|
|
134
|
+
export const ItemSummary = z.object({
|
|
135
|
+
id: Id,
|
|
136
|
+
share_id: Id,
|
|
137
|
+
state: z.string(),
|
|
138
|
+
flags: z.array(z.string()).default([]),
|
|
139
|
+
create_time: CivilDateTime,
|
|
140
|
+
modify_time: CivilDateTime,
|
|
141
|
+
title: z.string(),
|
|
142
|
+
item_type: z.string(),
|
|
143
|
+
});
|
|
144
|
+
export type ItemSummary = z.infer<typeof ItemSummary>;
|
|
145
|
+
export const ItemSummaryList = z.object({ items: z.array(ItemSummary) });
|
|
146
|
+
|
|
147
|
+
/** `info --output json` for a personal-access-token or agent session. */
|
|
148
|
+
export const InfoOutput = z.object({
|
|
149
|
+
personal_access_token_name: z.string().optional(),
|
|
150
|
+
username: z.string().optional(),
|
|
151
|
+
session_has_lock: z.boolean().optional(),
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
export const ITEM_HAS_FILES = "ItemHasFiles";
|
|
155
|
+
|
|
156
|
+
/** Proton share permission flags (proton-pass-common ShareRole::Custom). */
|
|
157
|
+
export const SHARE_PERMISSION = {
|
|
158
|
+
admin: 1,
|
|
159
|
+
read: 2,
|
|
160
|
+
create: 4,
|
|
161
|
+
update: 8,
|
|
162
|
+
trash: 16,
|
|
163
|
+
delete: 32,
|
|
164
|
+
} as const;
|