@kjrjay/varlock-betterpass 0.1.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 +21 -0
- package/README.md +85 -0
- package/dist/plugin.cjs +335 -0
- package/dist/plugin.cjs.map +1 -0
- package/dist/plugin.d.cts +1 -0
- package/package.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DMNO Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# BetterPass Varlock plugin
|
|
2
|
+
|
|
3
|
+
This Varlock plugin reads selected BetterPass credential fields on macOS after you approve each request in the BetterPass app. It uses the signed helper bundled with BetterPass. You do not need to install or run a separate BetterPass CLI.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
Install BetterPass 1.1.0 or later. In BetterPass Settings, turn on **Enable Varlock integration**. This setting starts off. Keep BetterPass running and sign in to iCloud. In a credential's detail view, use the copy icon beside its ID. The ID is an exact, case-sensitive reference; the plugin does not search by title.
|
|
8
|
+
|
|
9
|
+
### JavaScript projects
|
|
10
|
+
|
|
11
|
+
Install the plugin from npm:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm install @kjrjay/varlock-betterpass
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Then add it to `.env.schema`:
|
|
18
|
+
|
|
19
|
+
```env-spec title=".env.schema"
|
|
20
|
+
# @plugin(@kjrjay/varlock-betterpass)
|
|
21
|
+
# ---
|
|
22
|
+
DATABASE_PASSWORD=betterPass("opaque-entry-id")
|
|
23
|
+
DATABASE_USER=betterPass("opaque-entry-id", field=username)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Standalone Varlock CLI
|
|
27
|
+
|
|
28
|
+
Install the exact plugin version into Varlock's plugin cache:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
varlock install-plugin @kjrjay/varlock-betterpass@0.1.0
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Use the matching version in `.env.schema`:
|
|
35
|
+
|
|
36
|
+
```env-spec title=".env.schema"
|
|
37
|
+
# @plugin(@kjrjay/varlock-betterpass@0.1.0)
|
|
38
|
+
# ---
|
|
39
|
+
DATABASE_PASSWORD=betterPass("opaque-entry-id")
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The npm package includes the built helper client; consumers do not need Bun or a build step. The BetterPass helper itself is bundled with the app, not this package.
|
|
43
|
+
|
|
44
|
+
### Custom app locations
|
|
45
|
+
|
|
46
|
+
`@initBetterPass()` is optional when BetterPass is installed at `/Applications/BetterPass.app`. The plugin calls the bundled helper at `/Applications/BetterPass.app/Contents/Helpers/betterpass-varlock`. For a Debug build or another app location, set its absolute path:
|
|
47
|
+
|
|
48
|
+
```env-spec title=".env.schema"
|
|
49
|
+
# @initBetterPass(helperPath="/path/to/BetterPass.app/Contents/Helpers/betterpass-varlock")
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Resolver
|
|
53
|
+
|
|
54
|
+
`betterPass(credentialId, field=password)` accepts the exact credential ID and one of these fields: `password`, `username`, `website`, or `notes`. The one-argument form reads `password`. An absent optional field is an error. Varlock identifies the resolver for the absent field and rejects the whole batch without returning values. An empty password can be returned by BetterPass, but Varlock may also report it as an empty required value if the schema requires a value.
|
|
55
|
+
|
|
56
|
+
Every value is marked sensitive by default, including metadata fields. Varlock groups reads made in the same resolution turn into batches of up to 32 fields. Each batch needs one approval. Larger groups may require several approvals. The plugin does not keep a cache across requests or retry a failed approval automatically.
|
|
57
|
+
|
|
58
|
+
When Varlock requests a batch, it prints a code such as `B0D8-E637` to stderr. Check that the same code appears in the BetterPass approval window, then review the listed fields. BetterPass may first ask you to unlock. The approval expires after 90 seconds. Declining or closing the window releases no values.
|
|
59
|
+
|
|
60
|
+
Approval releases the selected values in plaintext to the requesting local process. If you run an application through Varlock, that application receives the values through its environment and can copy or transmit them. BetterPass cannot verify that the process calling its bundled helper is Varlock, so review every request before approving it. This integration is for interactive local macOS use, not CI or unattended jobs.
|
|
61
|
+
|
|
62
|
+
## Publishing
|
|
63
|
+
|
|
64
|
+
From a checkout with Bun installed:
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
bun install --frozen-lockfile
|
|
68
|
+
npm login --registry=https://registry.npmjs.org/
|
|
69
|
+
npm publish
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The `prepublishOnly` script type-checks, builds, and runs the tests before uploading. Only `dist`, the README, license, and package metadata are included. Increment the package version in `package.json` before subsequent releases; it is independent of the BetterPass app version.
|
|
73
|
+
|
|
74
|
+
## Common errors
|
|
75
|
+
|
|
76
|
+
| Error | What to do |
|
|
77
|
+
| --- | --- |
|
|
78
|
+
| Integration unavailable | Install or open BetterPass, enable Varlock integration, and check `helperPath` if you set it. |
|
|
79
|
+
| Denied or unlock cancelled | Run the Varlock command again and approve or unlock in BetterPass. |
|
|
80
|
+
| Expired | Run the command again and respond within 90 seconds. |
|
|
81
|
+
| Credential not found | Copy the exact ID again from BetterPass. |
|
|
82
|
+
| Field unavailable | Choose a field present on that credential. |
|
|
83
|
+
| Cloud unavailable | Check your iCloud connection and BetterPass account state. |
|
|
84
|
+
| Session changed | Unlock BetterPass and retry. |
|
|
85
|
+
| Busy | Wait for the current BetterPass request to finish, then retry. |
|
package/dist/plugin.cjs
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
let varlock_plugin_lib = require("varlock/plugin-lib");
|
|
2
|
+
let node_child_process = require("node:child_process");
|
|
3
|
+
let node_crypto = require("node:crypto");
|
|
4
|
+
let node_os = require("node:os");
|
|
5
|
+
//#region src/bridge-client.ts
|
|
6
|
+
const MAX_REQUEST_BYTES = 65536;
|
|
7
|
+
const MAX_OUTPUT_BYTES = 4194304;
|
|
8
|
+
const MAX_DIAGNOSTIC_BYTES = 8192;
|
|
9
|
+
const DEFAULT_TIMEOUT_MS = 1e5;
|
|
10
|
+
const ERROR_MESSAGES = {
|
|
11
|
+
invalid_request: "BetterPass rejected the request. Check the plugin and app versions.",
|
|
12
|
+
unauthorized: "BetterPass rejected the helper connection. Restart the integration.",
|
|
13
|
+
denied: "BetterPass request was denied.",
|
|
14
|
+
unlock_cancelled: "BetterPass unlock was cancelled.",
|
|
15
|
+
expired: "BetterPass approval timed out.",
|
|
16
|
+
credential_not_found: "BetterPass credential was not found. Check its ID.",
|
|
17
|
+
field_unavailable: "The requested BetterPass field is absent.",
|
|
18
|
+
session_changed: "BetterPass locked or changed during the request. Try again.",
|
|
19
|
+
busy: "BetterPass is handling another request. Try again.",
|
|
20
|
+
cloud_unavailable: "BetterPass could not verify iCloud availability.",
|
|
21
|
+
integration_unavailable: "Open BetterPass and enable Varlock integration.",
|
|
22
|
+
internal_error: "BetterPass returned an invalid response or encountered an internal error."
|
|
23
|
+
};
|
|
24
|
+
var BetterPassClientError = class extends Error {
|
|
25
|
+
code;
|
|
26
|
+
failedRef;
|
|
27
|
+
constructor(code, message = ERROR_MESSAGES[code], failedRef) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.code = code;
|
|
30
|
+
this.failedRef = failedRef;
|
|
31
|
+
this.name = "BetterPassClientError";
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
function fail(code) {
|
|
35
|
+
return new BetterPassClientError(code);
|
|
36
|
+
}
|
|
37
|
+
function isObject(value) {
|
|
38
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
39
|
+
}
|
|
40
|
+
function hasOnlyKeys(value, keys) {
|
|
41
|
+
return Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
|
|
42
|
+
}
|
|
43
|
+
function validateItems(items) {
|
|
44
|
+
if (items.length < 1 || items.length > 32) throw fail("invalid_request");
|
|
45
|
+
const refs = /* @__PURE__ */ new Set();
|
|
46
|
+
for (const item of items) {
|
|
47
|
+
if (typeof item.ref !== "string" || !item.ref || Buffer.byteLength(item.ref, "utf8") > 128 || refs.has(item.ref) || typeof item.credential_id !== "string" || !item.credential_id || Buffer.byteLength(item.credential_id, "utf8") > 256 || ![
|
|
48
|
+
"password",
|
|
49
|
+
"username",
|
|
50
|
+
"website",
|
|
51
|
+
"notes"
|
|
52
|
+
].includes(item.field)) throw fail("invalid_request");
|
|
53
|
+
refs.add(item.ref);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function parseResponse(bytes, exitCode, requestID, items) {
|
|
57
|
+
let parsed;
|
|
58
|
+
try {
|
|
59
|
+
parsed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
60
|
+
} catch {
|
|
61
|
+
throw fail("internal_error");
|
|
62
|
+
}
|
|
63
|
+
if (!isObject(parsed) || parsed.version !== 1 || typeof parsed.request_id !== "string") throw fail("internal_error");
|
|
64
|
+
if (parsed.request_id !== "" && parsed.request_id !== requestID) throw fail("internal_error");
|
|
65
|
+
if (exitCode === 0) {
|
|
66
|
+
if (parsed.request_id !== requestID || !hasOnlyKeys(parsed, [
|
|
67
|
+
"version",
|
|
68
|
+
"request_id",
|
|
69
|
+
"values"
|
|
70
|
+
]) || !Array.isArray(parsed.values) || parsed.values.length !== items.length) throw fail("internal_error");
|
|
71
|
+
const values = /* @__PURE__ */ new Map();
|
|
72
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
73
|
+
const entry = parsed.values[index];
|
|
74
|
+
if (!isObject(entry) || !hasOnlyKeys(entry, ["ref", "value"]) || entry.ref !== items[index].ref || typeof entry.value !== "string") throw fail("internal_error");
|
|
75
|
+
values.set(entry.ref, entry.value);
|
|
76
|
+
}
|
|
77
|
+
return values;
|
|
78
|
+
}
|
|
79
|
+
if (!hasOnlyKeys(parsed, [
|
|
80
|
+
"version",
|
|
81
|
+
"request_id",
|
|
82
|
+
"error"
|
|
83
|
+
]) || !isObject(parsed.error) || !hasOnlyKeys(parsed.error, ["code", "message"]) && !hasOnlyKeys(parsed.error, [
|
|
84
|
+
"code",
|
|
85
|
+
"message",
|
|
86
|
+
"failed_ref"
|
|
87
|
+
]) || typeof parsed.error.code !== "string" || typeof parsed.error.message !== "string" || !Object.hasOwn(ERROR_MESSAGES, parsed.error.code)) throw fail("internal_error");
|
|
88
|
+
const code = parsed.error.code;
|
|
89
|
+
if (Object.hasOwn(parsed.error, "failed_ref")) {
|
|
90
|
+
const failedRef = parsed.error.failed_ref;
|
|
91
|
+
if (code !== "field_unavailable" && code !== "credential_not_found" || typeof failedRef !== "string" || !items.some((item) => item.ref === failedRef) || parsed.request_id !== requestID) throw fail("internal_error");
|
|
92
|
+
throw new BetterPassClientError(code, void 0, failedRef);
|
|
93
|
+
}
|
|
94
|
+
throw fail(code);
|
|
95
|
+
}
|
|
96
|
+
async function resolveWithBridge(helperPath, items, options = {}) {
|
|
97
|
+
validateItems(items);
|
|
98
|
+
const requestID = (0, node_crypto.randomUUID)();
|
|
99
|
+
const request = Buffer.from(`${JSON.stringify({
|
|
100
|
+
version: 1,
|
|
101
|
+
request_id: requestID,
|
|
102
|
+
items
|
|
103
|
+
})}\n`, "utf8");
|
|
104
|
+
if (request.byteLength > MAX_REQUEST_BYTES) throw fail("invalid_request");
|
|
105
|
+
const requestCode = requestID.replaceAll("-", "").slice(0, 8).toUpperCase();
|
|
106
|
+
options.onRequestCode?.(`${requestCode.slice(0, 4)}-${requestCode.slice(4)}`);
|
|
107
|
+
if (options.signal?.aborted) throw fail("expired");
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
const child = (0, node_child_process.spawn)(helperPath, ["resolve"], {
|
|
110
|
+
shell: false,
|
|
111
|
+
stdio: [
|
|
112
|
+
"pipe",
|
|
113
|
+
"pipe",
|
|
114
|
+
"pipe"
|
|
115
|
+
],
|
|
116
|
+
env: {
|
|
117
|
+
HOME: (0, node_os.homedir)(),
|
|
118
|
+
TMPDIR: (0, node_os.tmpdir)(),
|
|
119
|
+
PATH: "/usr/bin:/bin",
|
|
120
|
+
LANG: "en_US.UTF-8"
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
const stdout = [];
|
|
124
|
+
let stdoutBytes = 0;
|
|
125
|
+
let stderrBytes = 0;
|
|
126
|
+
let settled = false;
|
|
127
|
+
const lifecycle = {};
|
|
128
|
+
const settle = (error, values) => {
|
|
129
|
+
if (settled) return;
|
|
130
|
+
settled = true;
|
|
131
|
+
if (lifecycle.timer) clearTimeout(lifecycle.timer);
|
|
132
|
+
lifecycle.removeAbortListener?.();
|
|
133
|
+
if (error) reject(error);
|
|
134
|
+
else resolve(values);
|
|
135
|
+
};
|
|
136
|
+
const stop = (error) => {
|
|
137
|
+
child.kill("SIGKILL");
|
|
138
|
+
child.stdin.destroy();
|
|
139
|
+
child.stdout.destroy();
|
|
140
|
+
child.stderr.destroy();
|
|
141
|
+
settle(error);
|
|
142
|
+
};
|
|
143
|
+
const abort = () => stop(fail("expired"));
|
|
144
|
+
lifecycle.timer = setTimeout(() => stop(fail("expired")), options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
145
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
146
|
+
lifecycle.removeAbortListener = () => options.signal?.removeEventListener("abort", abort);
|
|
147
|
+
child.on("error", () => settle(fail("integration_unavailable")));
|
|
148
|
+
child.stdout.on("data", (chunk) => {
|
|
149
|
+
stdoutBytes += chunk.byteLength;
|
|
150
|
+
if (stdoutBytes > MAX_OUTPUT_BYTES) stop(fail("internal_error"));
|
|
151
|
+
else stdout.push(chunk);
|
|
152
|
+
});
|
|
153
|
+
child.stderr.on("data", (chunk) => {
|
|
154
|
+
stderrBytes += chunk.byteLength;
|
|
155
|
+
if (stderrBytes > MAX_DIAGNOSTIC_BYTES) stop(fail("internal_error"));
|
|
156
|
+
});
|
|
157
|
+
child.stdin.on("error", () => child.stdin.destroy());
|
|
158
|
+
child.on("close", (exitCode) => {
|
|
159
|
+
if (settled) return;
|
|
160
|
+
try {
|
|
161
|
+
settle(void 0, parseResponse(Buffer.concat(stdout), exitCode, requestID, items));
|
|
162
|
+
} catch (error) {
|
|
163
|
+
settle(error instanceof BetterPassClientError ? error : fail("internal_error"));
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
child.stdin.end(request);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
//#endregion
|
|
170
|
+
//#region src/batcher.ts
|
|
171
|
+
var BetterPassBatcher = class {
|
|
172
|
+
send;
|
|
173
|
+
collecting = /* @__PURE__ */ new Map();
|
|
174
|
+
queue = [];
|
|
175
|
+
scheduled = false;
|
|
176
|
+
draining = false;
|
|
177
|
+
constructor(send) {
|
|
178
|
+
this.send = send;
|
|
179
|
+
}
|
|
180
|
+
resolve(credentialID, field) {
|
|
181
|
+
const key = JSON.stringify([credentialID, field]);
|
|
182
|
+
let entry = this.collecting.get(key);
|
|
183
|
+
if (!entry) {
|
|
184
|
+
entry = {
|
|
185
|
+
credentialID,
|
|
186
|
+
field,
|
|
187
|
+
resolve: [],
|
|
188
|
+
reject: []
|
|
189
|
+
};
|
|
190
|
+
this.collecting.set(key, entry);
|
|
191
|
+
}
|
|
192
|
+
if (!this.scheduled) {
|
|
193
|
+
this.scheduled = true;
|
|
194
|
+
setImmediate(() => this.flush());
|
|
195
|
+
}
|
|
196
|
+
return new Promise((resolve, reject) => {
|
|
197
|
+
entry.resolve.push(resolve);
|
|
198
|
+
entry.reject.push(reject);
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
flush() {
|
|
202
|
+
this.scheduled = false;
|
|
203
|
+
const entries = Array.from(this.collecting.values());
|
|
204
|
+
this.collecting.clear();
|
|
205
|
+
for (let index = 0; index < entries.length; index += 32) this.queue.push(entries.slice(index, index + 32));
|
|
206
|
+
if (!this.draining) this.drain();
|
|
207
|
+
}
|
|
208
|
+
async drain() {
|
|
209
|
+
this.draining = true;
|
|
210
|
+
while (this.queue.length > 0) {
|
|
211
|
+
const batch = this.queue.shift();
|
|
212
|
+
const items = batch.map((entry, index) => ({
|
|
213
|
+
ref: `r${index}`,
|
|
214
|
+
credential_id: entry.credentialID,
|
|
215
|
+
field: entry.field
|
|
216
|
+
}));
|
|
217
|
+
try {
|
|
218
|
+
const values = await this.send(items);
|
|
219
|
+
const resolvedValues = items.map((item) => {
|
|
220
|
+
const value = values.get(item.ref);
|
|
221
|
+
if (value === void 0) throw new Error("BetterPass response omitted a value");
|
|
222
|
+
return value;
|
|
223
|
+
});
|
|
224
|
+
for (let index = 0; index < batch.length; index += 1) batch[index].resolve.forEach((resolve) => resolve(resolvedValues[index]));
|
|
225
|
+
} catch (error) {
|
|
226
|
+
const failedIndex = error instanceof BetterPassClientError ? items.findIndex((item) => item.ref === error.failedRef) : -1;
|
|
227
|
+
for (let index = 0; index < batch.length; index += 1) {
|
|
228
|
+
let rejection = error;
|
|
229
|
+
if (failedIndex >= 0 && error instanceof BetterPassClientError) {
|
|
230
|
+
const field = batch[failedIndex].field;
|
|
231
|
+
const reason = error.code === "field_unavailable" ? `${field} field is absent` : `credential for ${field} field was not found`;
|
|
232
|
+
const message = index === failedIndex ? `The requested BetterPass ${reason}.` : `No BetterPass values were returned because another requested ${reason}.`;
|
|
233
|
+
rejection = new BetterPassClientError(error.code, message, error.failedRef);
|
|
234
|
+
}
|
|
235
|
+
batch[index].reject.forEach((reject) => reject(rejection));
|
|
236
|
+
}
|
|
237
|
+
for (const queued of this.queue) for (const entry of queued) entry.reject.forEach((reject) => reject(error));
|
|
238
|
+
this.queue.length = 0;
|
|
239
|
+
for (const entry of this.collecting.values()) entry.reject.forEach((reject) => reject(error));
|
|
240
|
+
this.collecting.clear();
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
this.draining = false;
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
//#endregion
|
|
248
|
+
//#region src/platform.ts
|
|
249
|
+
function assertMacOS(platform) {
|
|
250
|
+
if (platform !== "darwin") throw new varlock_plugin_lib.ResolutionError("BetterPass requires macOS");
|
|
251
|
+
}
|
|
252
|
+
//#endregion
|
|
253
|
+
//#region src/plugin.ts
|
|
254
|
+
const { SchemaError, ResolutionError } = varlock_plugin_lib.plugin.ERRORS;
|
|
255
|
+
const DEFAULT_HELPER_PATH = "/Applications/BetterPass.app/Contents/Helpers/betterpass-varlock";
|
|
256
|
+
const FIELDS = [
|
|
257
|
+
"password",
|
|
258
|
+
"username",
|
|
259
|
+
"website",
|
|
260
|
+
"notes"
|
|
261
|
+
];
|
|
262
|
+
const graphStates = /* @__PURE__ */ new WeakMap();
|
|
263
|
+
function getState(graph) {
|
|
264
|
+
let state = graphStates.get(graph);
|
|
265
|
+
if (!state) {
|
|
266
|
+
state = {
|
|
267
|
+
helperPath: DEFAULT_HELPER_PATH,
|
|
268
|
+
initialized: false,
|
|
269
|
+
batcher: new BetterPassBatcher((items) => resolveWithBridge(state.helperPath, items, { onRequestCode: (code) => process.stderr.write(`BetterPass request ${code}\n`) }))
|
|
270
|
+
};
|
|
271
|
+
graphStates.set(graph, state);
|
|
272
|
+
}
|
|
273
|
+
return state;
|
|
274
|
+
}
|
|
275
|
+
varlock_plugin_lib.plugin.name = "betterpass";
|
|
276
|
+
varlock_plugin_lib.plugin.registerRootDecorator({
|
|
277
|
+
name: "initBetterPass",
|
|
278
|
+
description: "Configure the BetterPass helper path for betterPass() resolvers",
|
|
279
|
+
isFunction: true,
|
|
280
|
+
process(argsVal) {
|
|
281
|
+
const graph = argsVal.envGraph;
|
|
282
|
+
const state = getState(graph);
|
|
283
|
+
if (state.initialized) throw new SchemaError("BetterPass is already initialized");
|
|
284
|
+
if ((argsVal.arrArgs?.length ?? 0) > 0) throw new SchemaError("Expected only named BetterPass options");
|
|
285
|
+
const options = argsVal.objArgs ?? {};
|
|
286
|
+
if (Object.keys(options).some((key) => key !== "helperPath")) throw new SchemaError("Unknown BetterPass option");
|
|
287
|
+
const helperPathArg = options.helperPath;
|
|
288
|
+
const helperPathValue = helperPathArg?.staticValue;
|
|
289
|
+
if (helperPathArg && (!helperPathArg.isStatic || typeof helperPathValue !== "string" || !helperPathValue)) throw new SchemaError("Expected a static helper path");
|
|
290
|
+
state.initialized = true;
|
|
291
|
+
return {
|
|
292
|
+
graph,
|
|
293
|
+
helperPath: typeof helperPathValue === "string" ? helperPathValue : DEFAULT_HELPER_PATH
|
|
294
|
+
};
|
|
295
|
+
},
|
|
296
|
+
execute({ graph, helperPath }) {
|
|
297
|
+
getState(graph).helperPath = helperPath;
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
varlock_plugin_lib.plugin.registerResolverFunction({
|
|
301
|
+
name: "betterPass",
|
|
302
|
+
label: "Fetch an approved field from BetterPass",
|
|
303
|
+
impliesSensitive: true,
|
|
304
|
+
argsSchema: {
|
|
305
|
+
type: "mixed",
|
|
306
|
+
arrayExactLength: 1
|
|
307
|
+
},
|
|
308
|
+
process() {
|
|
309
|
+
const idResolver = this.arrArgs?.[0];
|
|
310
|
+
if (!idResolver) throw new SchemaError("Expected a BetterPass credential ID");
|
|
311
|
+
if (Object.keys(this.objArgs ?? {}).some((key) => key !== "field")) throw new SchemaError("Unknown BetterPass resolver option");
|
|
312
|
+
const fieldResolver = this.objArgs?.field;
|
|
313
|
+
if (fieldResolver && (!fieldResolver.isStatic || typeof fieldResolver.staticValue !== "string")) throw new SchemaError("Expected a static field name");
|
|
314
|
+
const field = fieldResolver?.staticValue ?? "password";
|
|
315
|
+
if (!FIELDS.includes(field)) throw new SchemaError("Expected password, username, website, or notes");
|
|
316
|
+
return {
|
|
317
|
+
idResolver,
|
|
318
|
+
field
|
|
319
|
+
};
|
|
320
|
+
},
|
|
321
|
+
async resolve({ idResolver, field }) {
|
|
322
|
+
assertMacOS(process.platform);
|
|
323
|
+
const id = await idResolver.resolve();
|
|
324
|
+
if (typeof id !== "string" || !id || Buffer.byteLength(id, "utf8") > 256) throw new ResolutionError("Expected a credential ID of at most 256 UTF-8 bytes");
|
|
325
|
+
try {
|
|
326
|
+
return await getState(this.envGraph).batcher.resolve(id, field);
|
|
327
|
+
} catch (error) {
|
|
328
|
+
if (error instanceof BetterPassClientError) throw new ResolutionError(error.message, { code: error.code });
|
|
329
|
+
throw new ResolutionError("BetterPass helper failed. Try again.");
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
//#endregion
|
|
334
|
+
|
|
335
|
+
//# sourceMappingURL=plugin.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.cjs","names":["randomUUID","spawn","homedir","tmpdir","ResolutionError","plugin"],"sources":["../src/bridge-client.ts","../src/batcher.ts","../src/platform.ts","../src/plugin.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\nimport { randomUUID } from 'node:crypto';\nimport { homedir, tmpdir } from 'node:os';\n\nexport type BetterPassField = 'password' | 'username' | 'website' | 'notes';\nexport type BetterPassItem = { ref: string; credential_id: string; field: BetterPassField };\n\nconst MAX_REQUEST_BYTES = 64 * 1024;\nconst MAX_OUTPUT_BYTES = 4 * 1024 * 1024;\nconst MAX_DIAGNOSTIC_BYTES = 8 * 1024;\nconst DEFAULT_TIMEOUT_MS = 100_000;\n\nconst ERROR_MESSAGES = {\n invalid_request: 'BetterPass rejected the request. Check the plugin and app versions.',\n unauthorized: 'BetterPass rejected the helper connection. Restart the integration.',\n denied: 'BetterPass request was denied.',\n unlock_cancelled: 'BetterPass unlock was cancelled.',\n expired: 'BetterPass approval timed out.',\n credential_not_found: 'BetterPass credential was not found. Check its ID.',\n field_unavailable: 'The requested BetterPass field is absent.',\n session_changed: 'BetterPass locked or changed during the request. Try again.',\n busy: 'BetterPass is handling another request. Try again.',\n cloud_unavailable: 'BetterPass could not verify iCloud availability.',\n integration_unavailable: 'Open BetterPass and enable Varlock integration.',\n internal_error: 'BetterPass returned an invalid response or encountered an internal error.',\n} as const;\n\ntype ErrorCode = keyof typeof ERROR_MESSAGES;\n\nexport class BetterPassClientError extends Error {\n constructor(readonly code: ErrorCode, message: string = ERROR_MESSAGES[code], readonly failedRef?: string) {\n super(message);\n this.name = 'BetterPassClientError';\n }\n}\n\nfunction fail(code: ErrorCode): BetterPassClientError {\n return new BetterPassClientError(code);\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction hasOnlyKeys(value: Record<string, unknown>, keys: Array<string>): boolean {\n return Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));\n}\n\nfunction validateItems(items: Array<BetterPassItem>): void {\n if (items.length < 1 || items.length > 32) throw fail('invalid_request');\n const refs = new Set<string>();\n for (const item of items) {\n if (typeof item.ref !== 'string' || !item.ref || Buffer.byteLength(item.ref, 'utf8') > 128\n || refs.has(item.ref) || typeof item.credential_id !== 'string' || !item.credential_id\n || Buffer.byteLength(item.credential_id, 'utf8') > 256\n || !['password', 'username', 'website', 'notes'].includes(item.field)) {\n throw fail('invalid_request');\n }\n refs.add(item.ref);\n }\n}\n\nfunction parseResponse(\n bytes: Buffer,\n exitCode: number | null,\n requestID: string,\n items: Array<BetterPassItem>,\n): Map<string, string> {\n let parsed: unknown;\n try {\n parsed = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));\n } catch {\n throw fail('internal_error');\n }\n if (!isObject(parsed) || parsed.version !== 1 || typeof parsed.request_id !== 'string') {\n throw fail('internal_error');\n }\n if (parsed.request_id !== '' && parsed.request_id !== requestID) throw fail('internal_error');\n\n if (exitCode === 0) {\n if (parsed.request_id !== requestID || !hasOnlyKeys(parsed, ['version', 'request_id', 'values'])\n || !Array.isArray(parsed.values) || parsed.values.length !== items.length) {\n throw fail('internal_error');\n }\n const values = new Map<string, string>();\n for (let index = 0; index < items.length; index += 1) {\n const entry: unknown = parsed.values[index];\n if (!isObject(entry) || !hasOnlyKeys(entry, ['ref', 'value'])\n || entry.ref !== items[index].ref || typeof entry.value !== 'string') {\n throw fail('internal_error');\n }\n values.set(entry.ref, entry.value);\n }\n return values;\n }\n\n if (!hasOnlyKeys(parsed, ['version', 'request_id', 'error']) || !isObject(parsed.error)\n || (!hasOnlyKeys(parsed.error, ['code', 'message'])\n && !hasOnlyKeys(parsed.error, ['code', 'message', 'failed_ref']))\n || typeof parsed.error.code !== 'string'\n || typeof parsed.error.message !== 'string'\n || !Object.hasOwn(ERROR_MESSAGES, parsed.error.code)) {\n throw fail('internal_error');\n }\n const code = parsed.error.code as ErrorCode;\n if (Object.hasOwn(parsed.error, 'failed_ref')) {\n const failedRef = parsed.error.failed_ref;\n if ((code !== 'field_unavailable' && code !== 'credential_not_found')\n || typeof failedRef !== 'string' || !items.some((item) => item.ref === failedRef)\n || parsed.request_id !== requestID) throw fail('internal_error');\n throw new BetterPassClientError(code, undefined, failedRef);\n }\n throw fail(code);\n}\n\nexport async function resolveWithBridge(\n helperPath: string,\n items: Array<BetterPassItem>,\n options: { timeoutMs?: number; signal?: AbortSignal; onRequestCode?: (code: string) => void } = {},\n): Promise<Map<string, string>> {\n validateItems(items);\n const requestID = randomUUID();\n const request = Buffer.from(`${JSON.stringify({ version: 1, request_id: requestID, items })}\\n`, 'utf8');\n if (request.byteLength > MAX_REQUEST_BYTES) throw fail('invalid_request');\n const requestCode = requestID.replaceAll('-', '').slice(0, 8).toUpperCase();\n options.onRequestCode?.(`${requestCode.slice(0, 4)}-${requestCode.slice(4)}`);\n if (options.signal?.aborted) throw fail('expired');\n\n return new Promise((resolve, reject) => {\n const child = spawn(helperPath, ['resolve'], {\n shell: false,\n stdio: ['pipe', 'pipe', 'pipe'],\n env: {\n HOME: homedir(), TMPDIR: tmpdir(), PATH: '/usr/bin:/bin', LANG: 'en_US.UTF-8',\n },\n });\n const stdout: Array<Buffer> = [];\n let stdoutBytes = 0;\n let stderrBytes = 0;\n let settled = false;\n const lifecycle: {\n timer?: ReturnType<typeof setTimeout>;\n removeAbortListener?: () => void;\n } = {};\n const settle = (error?: BetterPassClientError, values?: Map<string, string>) => {\n if (settled) return;\n settled = true;\n if (lifecycle.timer) clearTimeout(lifecycle.timer);\n lifecycle.removeAbortListener?.();\n if (error) reject(error);\n else resolve(values!);\n };\n const stop = (error: BetterPassClientError) => {\n child.kill('SIGKILL');\n child.stdin.destroy();\n child.stdout.destroy();\n child.stderr.destroy();\n settle(error);\n };\n const abort = () => stop(fail('expired'));\n lifecycle.timer = setTimeout(() => stop(fail('expired')), options.timeoutMs ?? DEFAULT_TIMEOUT_MS);\n options.signal?.addEventListener('abort', abort, { once: true });\n lifecycle.removeAbortListener = () => options.signal?.removeEventListener('abort', abort);\n child.on('error', () => settle(fail('integration_unavailable')));\n child.stdout.on('data', (chunk: Buffer) => {\n stdoutBytes += chunk.byteLength;\n if (stdoutBytes > MAX_OUTPUT_BYTES) stop(fail('internal_error'));\n else stdout.push(chunk);\n });\n child.stderr.on('data', (chunk: Buffer) => {\n stderrBytes += chunk.byteLength;\n if (stderrBytes > MAX_DIAGNOSTIC_BYTES) stop(fail('internal_error'));\n });\n child.stdin.on('error', () => child.stdin.destroy());\n child.on('close', (exitCode) => {\n if (settled) return;\n try {\n settle(undefined, parseResponse(Buffer.concat(stdout), exitCode, requestID, items));\n } catch (error) {\n settle(error instanceof BetterPassClientError ? error : fail('internal_error'));\n }\n });\n child.stdin.end(request);\n });\n}\n","import { BetterPassClientError, type BetterPassField, type BetterPassItem } from './bridge-client';\n\ntype PendingEntry = {\n credentialID: string;\n field: BetterPassField;\n resolve: Array<(value: string) => void>;\n reject: Array<(error: unknown) => void>;\n};\n\nexport class BetterPassBatcher {\n private readonly collecting = new Map<string, PendingEntry>();\n private readonly queue: Array<Array<PendingEntry>> = [];\n private scheduled = false;\n private draining = false;\n\n constructor(private readonly send: (items: Array<BetterPassItem>) => Promise<Map<string, string>>) {}\n\n resolve(credentialID: string, field: BetterPassField): Promise<string> {\n const key = JSON.stringify([credentialID, field]);\n let entry = this.collecting.get(key);\n if (!entry) {\n entry = {\n credentialID, field, resolve: [], reject: [],\n };\n this.collecting.set(key, entry);\n }\n if (!this.scheduled) {\n this.scheduled = true;\n setImmediate(() => this.flush());\n }\n return new Promise<string>((resolve, reject) => {\n entry.resolve.push(resolve);\n entry.reject.push(reject);\n });\n }\n\n private flush(): void {\n this.scheduled = false;\n const entries = Array.from(this.collecting.values());\n this.collecting.clear();\n for (let index = 0; index < entries.length; index += 32) {\n this.queue.push(entries.slice(index, index + 32));\n }\n if (!this.draining) this.drain();\n }\n\n private async drain(): Promise<void> {\n this.draining = true;\n while (this.queue.length > 0) {\n const batch = this.queue.shift()!;\n const items = batch.map((entry, index) => ({\n ref: `r${index}`,\n credential_id: entry.credentialID,\n field: entry.field,\n }));\n try {\n const values = await this.send(items);\n const resolvedValues = items.map((item) => {\n const value = values.get(item.ref);\n if (value === undefined) throw new Error('BetterPass response omitted a value');\n return value;\n });\n for (let index = 0; index < batch.length; index += 1) {\n batch[index].resolve.forEach((resolve) => resolve(resolvedValues[index]));\n }\n } catch (error) {\n const failedIndex = error instanceof BetterPassClientError\n ? items.findIndex((item) => item.ref === error.failedRef) : -1;\n for (let index = 0; index < batch.length; index += 1) {\n let rejection = error;\n if (failedIndex >= 0 && error instanceof BetterPassClientError) {\n const field = batch[failedIndex].field;\n const reason = error.code === 'field_unavailable'\n ? `${field} field is absent` : `credential for ${field} field was not found`;\n const message = index === failedIndex\n ? `The requested BetterPass ${reason}.`\n : `No BetterPass values were returned because another requested ${reason}.`;\n rejection = new BetterPassClientError(error.code, message, error.failedRef);\n }\n batch[index].reject.forEach((reject) => reject(rejection));\n }\n for (const queued of this.queue) {\n for (const entry of queued) entry.reject.forEach((reject) => reject(error));\n }\n this.queue.length = 0;\n for (const entry of this.collecting.values()) entry.reject.forEach((reject) => reject(error));\n this.collecting.clear();\n break;\n }\n }\n this.draining = false;\n }\n}\n","import { ResolutionError } from 'varlock/plugin-lib';\n\nexport function assertMacOS(platform: string): void {\n if (platform !== 'darwin') throw new ResolutionError('BetterPass requires macOS');\n}\n","import { plugin } from 'varlock/plugin-lib';\nimport { BetterPassBatcher } from './batcher';\nimport {\n BetterPassClientError, resolveWithBridge, type BetterPassField,\n} from './bridge-client';\nimport { assertMacOS } from './platform';\n\nconst { SchemaError, ResolutionError } = plugin.ERRORS;\nconst DEFAULT_HELPER_PATH = '/Applications/BetterPass.app/Contents/Helpers/betterpass-varlock';\nconst FIELDS: ReadonlyArray<BetterPassField> = ['password', 'username', 'website', 'notes'];\n\ntype GraphState = { helperPath: string; initialized: boolean; batcher: BetterPassBatcher };\nconst graphStates = new WeakMap<object, GraphState>();\n\nfunction getState(graph: object): GraphState {\n let state = graphStates.get(graph);\n if (!state) {\n state = {\n helperPath: DEFAULT_HELPER_PATH,\n initialized: false,\n batcher: new BetterPassBatcher((items) => resolveWithBridge(state!.helperPath, items, {\n onRequestCode: (code) => process.stderr.write(`BetterPass request ${code}\\n`),\n })),\n };\n graphStates.set(graph, state);\n }\n return state;\n}\n\nplugin.name = 'betterpass';\n\nplugin.registerRootDecorator({\n name: 'initBetterPass',\n description: 'Configure the BetterPass helper path for betterPass() resolvers',\n isFunction: true,\n process(argsVal) {\n const graph = argsVal.envGraph!;\n const state = getState(graph);\n if (state.initialized) throw new SchemaError('BetterPass is already initialized');\n if ((argsVal.arrArgs?.length ?? 0) > 0) throw new SchemaError('Expected only named BetterPass options');\n const options = argsVal.objArgs ?? {};\n if (Object.keys(options).some((key) => key !== 'helperPath')) {\n throw new SchemaError('Unknown BetterPass option');\n }\n const helperPathArg = options.helperPath;\n const helperPathValue = helperPathArg?.staticValue;\n if (helperPathArg && (!helperPathArg.isStatic || typeof helperPathValue !== 'string'\n || !helperPathValue)) {\n throw new SchemaError('Expected a static helper path');\n }\n state.initialized = true;\n return { graph, helperPath: typeof helperPathValue === 'string' ? helperPathValue : DEFAULT_HELPER_PATH };\n },\n execute({ graph, helperPath }) {\n getState(graph).helperPath = helperPath;\n },\n});\n\nplugin.registerResolverFunction({\n name: 'betterPass',\n label: 'Fetch an approved field from BetterPass',\n impliesSensitive: true,\n argsSchema: { type: 'mixed', arrayExactLength: 1 },\n process() {\n const idResolver = this.arrArgs?.[0];\n if (!idResolver) throw new SchemaError('Expected a BetterPass credential ID');\n if (Object.keys(this.objArgs ?? {}).some((key) => key !== 'field')) {\n throw new SchemaError('Unknown BetterPass resolver option');\n }\n const fieldResolver = this.objArgs?.field;\n if (fieldResolver && (!fieldResolver.isStatic || typeof fieldResolver.staticValue !== 'string')) {\n throw new SchemaError('Expected a static field name');\n }\n const field = fieldResolver?.staticValue ?? 'password';\n if (!FIELDS.includes(field as BetterPassField)) {\n throw new SchemaError('Expected password, username, website, or notes');\n }\n return { idResolver, field: field as BetterPassField };\n },\n async resolve({ idResolver, field }) {\n assertMacOS(process.platform);\n const id = await idResolver.resolve();\n if (typeof id !== 'string' || !id || Buffer.byteLength(id, 'utf8') > 256) {\n throw new ResolutionError('Expected a credential ID of at most 256 UTF-8 bytes');\n }\n try {\n return await getState(this.envGraph!).batcher.resolve(id, field);\n } catch (error) {\n if (error instanceof BetterPassClientError) {\n throw new ResolutionError(error.message, { code: error.code });\n }\n throw new ResolutionError('BetterPass helper failed. Try again.');\n }\n },\n});\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kjrjay/varlock-betterpass",
|
|
3
|
+
"description": "Varlock plugin for approved local credential reads from BetterPass",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/kjrjay/betterpass-varlock-plugin.git"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/kjrjay/betterpass-varlock-plugin#readme",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/kjrjay/betterpass-varlock-plugin/issues"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public",
|
|
15
|
+
"registry": "https://registry.npmjs.org/"
|
|
16
|
+
},
|
|
17
|
+
"type": "module",
|
|
18
|
+
"exports": {
|
|
19
|
+
"./plugin": "./dist/plugin.cjs"
|
|
20
|
+
},
|
|
21
|
+
"files": ["dist", "README.md", "LICENSE"],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"dev": "tsdown --watch",
|
|
24
|
+
"build": "tsdown",
|
|
25
|
+
"test": "vitest",
|
|
26
|
+
"test:ci": "bun run build && vitest --run",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"prepublishOnly": "bun run typecheck && bun run test:ci"
|
|
29
|
+
},
|
|
30
|
+
"keywords": ["varlock", "plugin", "betterpass", "secrets", "env"],
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=22"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"varlock": "^1.20.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^25.3.2",
|
|
40
|
+
"tsdown": "^0.22.14",
|
|
41
|
+
"typescript": "^5.9.3",
|
|
42
|
+
"varlock": "^1.21.0",
|
|
43
|
+
"vitest": "^4.0.18"
|
|
44
|
+
}
|
|
45
|
+
}
|