borgmcp 0.9.57 → 0.9.58

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.
@@ -0,0 +1,167 @@
1
+ /**
2
+ * gh#557 — Google OAuth 2.0 Device Authorization Grant (RFC 8628).
3
+ *
4
+ * The no-browser counterpart to the loopback flow in auth.ts. Instead of
5
+ * opening a browser and listening on localhost, the device flow:
6
+ * 1. asks Google for a device_code + a short human-typable user_code,
7
+ * 2. prints a verification URL + the user_code for the human to open on
8
+ * ANY device (their phone, a laptop with a browser), and
9
+ * 3. polls Google's token endpoint until the human authorizes (or the
10
+ * code expires / is denied).
11
+ *
12
+ * This module is decoupled from the live Google client: `fetch`, `sleep`,
13
+ * and `now` are injected, and the client_id / client_secret / endpoints
14
+ * come from the caller. The live device flow needs a Google OAuth client
15
+ * of type "TVs & Limited Input devices" (a separate GOOGLE_DEVICE_CLIENT_ID
16
+ * — Desktop/loopback clients reject /device/code with invalid_client); the
17
+ * wiring layer supplies those credentials. Everything here is unit-tested
18
+ * against a mocked Google.
19
+ */
20
+ const GOOGLE_DEVICE_CODE_URL = 'https://oauth2.googleapis.com/device/code';
21
+ const GOOGLE_TOKEN_URL = 'https://oauth2.googleapis.com/token';
22
+ const DEVICE_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:device_code';
23
+ const DEFAULT_INTERVAL_SECONDS = 5;
24
+ const SLOW_DOWN_INCREMENT_SECONDS = 5;
25
+ /**
26
+ * Failure of the device-grant flow. `code` is Google's OAuth error code
27
+ * where one exists (`access_denied`, `expired_token`, `invalid_client`,
28
+ * `slow_down`, `authorization_pending`) or a synthetic code for
29
+ * transport/shape failures (`device_code_request_failed`,
30
+ * `device_token_request_failed`, `malformed_token_response`).
31
+ *
32
+ * Token material is never placed in the message — only Google's error
33
+ * code + description, mirroring RefreshTokenInvalidError's discipline.
34
+ */
35
+ export class DeviceAuthError extends Error {
36
+ code;
37
+ constructor(code, message) {
38
+ super(message ?? code);
39
+ this.code = code;
40
+ this.name = 'DeviceAuthError';
41
+ }
42
+ }
43
+ /**
44
+ * Step 1 — request a device_code + user_code from Google.
45
+ */
46
+ export async function requestDeviceCode(config, deps) {
47
+ const url = config.deviceCodeUrl ?? GOOGLE_DEVICE_CODE_URL;
48
+ let response;
49
+ try {
50
+ response = await deps.fetch(url, {
51
+ method: 'POST',
52
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
53
+ body: new URLSearchParams({
54
+ client_id: config.clientId,
55
+ scope: config.scopes.join(' '),
56
+ }),
57
+ });
58
+ }
59
+ catch (err) {
60
+ throw new DeviceAuthError('device_code_request_failed', `Could not reach Google device endpoint: ${err?.message ?? 'unknown'}`);
61
+ }
62
+ if (!response.ok) {
63
+ const code = await readErrorCode(response);
64
+ throw new DeviceAuthError(code ?? 'device_code_request_failed', `Device-code request failed (HTTP ${response.status}${code ? `, ${code}` : ''})`);
65
+ }
66
+ let data;
67
+ try {
68
+ data = (await response.json());
69
+ }
70
+ catch {
71
+ throw new DeviceAuthError('malformed_token_response', 'Device-code response was not JSON');
72
+ }
73
+ if (!data.device_code || !data.user_code || !data.verification_url) {
74
+ throw new DeviceAuthError('malformed_token_response', 'Device-code response missing device_code/user_code/verification_url');
75
+ }
76
+ return {
77
+ device_code: data.device_code,
78
+ user_code: data.user_code,
79
+ verification_url: data.verification_url,
80
+ expires_in: typeof data.expires_in === 'number' ? data.expires_in : 1800,
81
+ interval: typeof data.interval === 'number' ? data.interval : DEFAULT_INTERVAL_SECONDS,
82
+ };
83
+ }
84
+ /**
85
+ * Step 2 — poll Google's token endpoint until the user authorizes the
86
+ * device_code, honoring the RFC 8628 poll semantics.
87
+ *
88
+ * Sleeps `interval` BEFORE each poll (never hammers immediately). A local
89
+ * deadline derived from `expires_in` bounds the loop so a code the user
90
+ * abandons can't poll forever even if Google never returns expired_token.
91
+ */
92
+ export async function pollForDeviceToken(deviceCode, config, deps) {
93
+ const tokenUrl = config.tokenUrl ?? GOOGLE_TOKEN_URL;
94
+ const now = deps.now ?? Date.now;
95
+ const deadline = now() + deviceCode.expires_in * 1000;
96
+ let intervalSeconds = deviceCode.interval > 0 ? deviceCode.interval : DEFAULT_INTERVAL_SECONDS;
97
+ // eslint-disable-next-line no-constant-condition
98
+ while (true) {
99
+ if (now() >= deadline) {
100
+ throw new DeviceAuthError('expired_token', 'Device code expired before the authorization was completed');
101
+ }
102
+ await deps.sleep(intervalSeconds * 1000);
103
+ let response;
104
+ try {
105
+ response = await deps.fetch(tokenUrl, {
106
+ method: 'POST',
107
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
108
+ body: new URLSearchParams({
109
+ client_id: config.clientId,
110
+ ...(config.clientSecret ? { client_secret: config.clientSecret } : {}),
111
+ device_code: deviceCode.device_code,
112
+ grant_type: DEVICE_GRANT_TYPE,
113
+ }),
114
+ });
115
+ }
116
+ catch (err) {
117
+ throw new DeviceAuthError('device_token_request_failed', `Could not reach Google token endpoint: ${err?.message ?? 'unknown'}`);
118
+ }
119
+ if (response.ok) {
120
+ let data;
121
+ try {
122
+ data = (await response.json());
123
+ }
124
+ catch {
125
+ throw new DeviceAuthError('malformed_token_response', 'Token response was not JSON');
126
+ }
127
+ if (!data.id_token || typeof data.expires_in !== 'number') {
128
+ throw new DeviceAuthError('malformed_token_response', 'Token response missing id_token or expires_in');
129
+ }
130
+ return {
131
+ id_token: data.id_token,
132
+ refresh_token: data.refresh_token,
133
+ expires_in: data.expires_in,
134
+ };
135
+ }
136
+ const code = await readErrorCode(response);
137
+ switch (code) {
138
+ case 'authorization_pending':
139
+ // The user hasn't finished yet — keep polling at the same interval.
140
+ continue;
141
+ case 'slow_down':
142
+ // Google asks us to back off; RFC 8628 §3.5 → bump the interval.
143
+ intervalSeconds += SLOW_DOWN_INCREMENT_SECONDS;
144
+ continue;
145
+ case 'access_denied':
146
+ throw new DeviceAuthError('access_denied', 'Authorization was denied by the user');
147
+ case 'expired_token':
148
+ throw new DeviceAuthError('expired_token', 'Device code expired before authorization');
149
+ default:
150
+ throw new DeviceAuthError(code ?? 'device_token_request_failed', `Device token poll failed (HTTP ${response.status}${code ? `, ${code}` : ''})`);
151
+ }
152
+ }
153
+ }
154
+ /**
155
+ * Extract Google's OAuth `error` code from a non-2xx response body without
156
+ * throwing. Returns null when the body isn't JSON or has no error field.
157
+ */
158
+ async function readErrorCode(response) {
159
+ try {
160
+ const body = (await response.json());
161
+ return typeof body?.error === 'string' ? body.error : null;
162
+ }
163
+ catch {
164
+ return null;
165
+ }
166
+ }
167
+ //# sourceMappingURL=device-auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-auth.js","sourceRoot":"","sources":["../src/device-auth.ts"],"names":[],"mappings":"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"}
package/dist/setup.js CHANGED
@@ -23,6 +23,11 @@ import { handleVersionFlag } from './version.js';
23
23
  async function main() {
24
24
  handleVersionFlag();
25
25
  console.log(chalk.blue.bold('\n◼ Borg MCP Setup Wizard ◼'));
26
+ // gh#557: `--no-browser` (alias `--device`) forces the device-code OAuth
27
+ // flow for SSH / headless / container terminals. Scanned from argv so it
28
+ // works both as `borg setup --no-browser` and `borg-setup --no-browser`.
29
+ // (SSH/headless are auto-detected too; the flag is the explicit override.)
30
+ const noBrowser = process.argv.includes('--no-browser') || process.argv.includes('--device');
26
31
  // Step 0: Check which agent CLIs exist
27
32
  let claudeCliPath = null;
28
33
  let codexCliPath = null;
@@ -86,10 +91,14 @@ async function main() {
86
91
  const authed = await isAuthenticated();
87
92
  if (!authed) {
88
93
  try {
89
- await authenticateWithGoogle();
94
+ await authenticateWithGoogle(noBrowser ? { noBrowser: true } : undefined);
90
95
  }
91
96
  catch (error) {
92
97
  console.error(chalk.red(`\n◼ Authentication failed: ${error.message}\n`));
98
+ // gh#557 NOTE-2: device-flow errors (access_denied / expired_token) and
99
+ // any other auth failure exit here — give the remote user a recovery
100
+ // path instead of a bare exit.
101
+ console.error(chalk.yellow('Re-run `borg setup` to try again.\n'));
93
102
  process.exit(1);
94
103
  }
95
104
  }
package/dist/setup.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * gh#557 — AES-256-GCM crypto for the keychain-less token store.
3
+ *
4
+ * ⚠ OBFUSCATION-GRADE, BY DESIGN. The encryption key is DERIVED from stable
5
+ * machine+user identifiers (hostname, username, platform) — there is no
6
+ * passphrase. This means:
7
+ *
8
+ * - It DEFENDS against casual/accidental exposure: a dotfile backup, an
9
+ * `scp -r ~`, a synced home directory, a shoulder-surfed `cat`. The
10
+ * on-disk bytes are ciphertext, not a readable token.
11
+ * - It does NOT defend against a same-uid process or root on the SAME
12
+ * machine: anything that can read ~/.borg/credentials can also read the
13
+ * same hostname/username/platform and re-derive the key. That is an
14
+ * accepted limitation (SR-endorsed, gh#557 ESCALATION 2) and matches
15
+ * gcloud's own at-rest posture for its credential files.
16
+ *
17
+ * The OS keychain (config.ts default) remains the real at-rest encryption
18
+ * path; this fallback only engages when no keychain is available (headless
19
+ * Linux without Secret Service, etc.).
20
+ *
21
+ * Machine identifiers are injected (MachineKeyInputs) rather than read here,
22
+ * both for testability and so the production caller can choose OS primitives
23
+ * that work in headless/container environments (os.hostname()/os.userInfo()
24
+ * never spawn a subprocess, unlike a hardware machine-id probe).
25
+ */
26
+ export interface MachineKeyInputs {
27
+ hostname: string;
28
+ username: string;
29
+ platform: string;
30
+ }
31
+ /**
32
+ * Derive a stable 32-byte AES-256 key from machine+user identifiers.
33
+ * Deterministic for a given machine+user (so a token written today decrypts
34
+ * tomorrow) and distinct across machines/users.
35
+ */
36
+ export declare function deriveMachineKey(inputs: MachineKeyInputs): Buffer;
37
+ /**
38
+ * Encrypt a plaintext string under the given key. Returns a versioned,
39
+ * dot-delimited envelope: `v1.<base64(iv)>.<base64(tag)>.<base64(ct)>`.
40
+ * A fresh random IV per call means the same plaintext encrypts differently
41
+ * every time (no deterministic-ciphertext leak).
42
+ */
43
+ export declare function encryptString(plaintext: string, key: Buffer): string;
44
+ /**
45
+ * Decrypt an envelope produced by encryptString. Throws on a malformed
46
+ * envelope, a wrong key, or a tampered ciphertext (the GCM auth tag fails
47
+ * verification) — fail-closed is correct for credential material.
48
+ */
49
+ export declare function decryptString(envelope: string, key: Buffer): string;
50
+ //# sourceMappingURL=token-crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-crypto.d.ts","sourceRoot":"","sources":["../src/token-crypto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAeH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAGjE;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAcpE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAkBnE"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * gh#557 — AES-256-GCM crypto for the keychain-less token store.
3
+ *
4
+ * ⚠ OBFUSCATION-GRADE, BY DESIGN. The encryption key is DERIVED from stable
5
+ * machine+user identifiers (hostname, username, platform) — there is no
6
+ * passphrase. This means:
7
+ *
8
+ * - It DEFENDS against casual/accidental exposure: a dotfile backup, an
9
+ * `scp -r ~`, a synced home directory, a shoulder-surfed `cat`. The
10
+ * on-disk bytes are ciphertext, not a readable token.
11
+ * - It does NOT defend against a same-uid process or root on the SAME
12
+ * machine: anything that can read ~/.borg/credentials can also read the
13
+ * same hostname/username/platform and re-derive the key. That is an
14
+ * accepted limitation (SR-endorsed, gh#557 ESCALATION 2) and matches
15
+ * gcloud's own at-rest posture for its credential files.
16
+ *
17
+ * The OS keychain (config.ts default) remains the real at-rest encryption
18
+ * path; this fallback only engages when no keychain is available (headless
19
+ * Linux without Secret Service, etc.).
20
+ *
21
+ * Machine identifiers are injected (MachineKeyInputs) rather than read here,
22
+ * both for testability and so the production caller can choose OS primitives
23
+ * that work in headless/container environments (os.hostname()/os.userInfo()
24
+ * never spawn a subprocess, unlike a hardware machine-id probe).
25
+ */
26
+ import crypto from 'crypto';
27
+ /**
28
+ * Static application salt — domain-separates this key derivation from any
29
+ * other use of the same machine identifiers. NOT a secret (it ships in the
30
+ * published client); its only job is to make the derived key specific to
31
+ * borg-mcp token storage.
32
+ */
33
+ const KEY_SALT = 'borg-mcp/token-store/v1';
34
+ const ENVELOPE_VERSION = 'v1';
35
+ const IV_BYTES = 12; // 96-bit nonce, the GCM standard
36
+ const KEY_BYTES = 32; // AES-256
37
+ /**
38
+ * Derive a stable 32-byte AES-256 key from machine+user identifiers.
39
+ * Deterministic for a given machine+user (so a token written today decrypts
40
+ * tomorrow) and distinct across machines/users.
41
+ */
42
+ export function deriveMachineKey(inputs) {
43
+ const material = [inputs.hostname, inputs.username, inputs.platform, KEY_SALT].join('\0');
44
+ return crypto.createHash('sha256').update(material).digest(); // 32 bytes
45
+ }
46
+ /**
47
+ * Encrypt a plaintext string under the given key. Returns a versioned,
48
+ * dot-delimited envelope: `v1.<base64(iv)>.<base64(tag)>.<base64(ct)>`.
49
+ * A fresh random IV per call means the same plaintext encrypts differently
50
+ * every time (no deterministic-ciphertext leak).
51
+ */
52
+ export function encryptString(plaintext, key) {
53
+ if (key.length !== KEY_BYTES) {
54
+ throw new Error(`token-crypto: key must be ${KEY_BYTES} bytes`);
55
+ }
56
+ const iv = crypto.randomBytes(IV_BYTES);
57
+ const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
58
+ const ciphertext = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
59
+ const tag = cipher.getAuthTag();
60
+ return [
61
+ ENVELOPE_VERSION,
62
+ iv.toString('base64'),
63
+ tag.toString('base64'),
64
+ ciphertext.toString('base64'),
65
+ ].join('.');
66
+ }
67
+ /**
68
+ * Decrypt an envelope produced by encryptString. Throws on a malformed
69
+ * envelope, a wrong key, or a tampered ciphertext (the GCM auth tag fails
70
+ * verification) — fail-closed is correct for credential material.
71
+ */
72
+ export function decryptString(envelope, key) {
73
+ if (key.length !== KEY_BYTES) {
74
+ throw new Error(`token-crypto: key must be ${KEY_BYTES} bytes`);
75
+ }
76
+ const parts = envelope.split('.');
77
+ if (parts.length !== 4 || parts[0] !== ENVELOPE_VERSION) {
78
+ throw new Error('token-crypto: malformed or unsupported envelope');
79
+ }
80
+ const iv = Buffer.from(parts[1], 'base64');
81
+ const tag = Buffer.from(parts[2], 'base64');
82
+ const ciphertext = Buffer.from(parts[3], 'base64');
83
+ if (iv.length !== IV_BYTES) {
84
+ throw new Error('token-crypto: malformed IV');
85
+ }
86
+ const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
87
+ decipher.setAuthTag(tag);
88
+ const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
89
+ return plaintext.toString('utf8');
90
+ }
91
+ //# sourceMappingURL=token-crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-crypto.js","sourceRoot":"","sources":["../src/token-crypto.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * gh#557 — token storage backends + selection.
3
+ *
4
+ * config.ts exposes the public token API (storeIdToken/getIdToken/...). This
5
+ * module supplies the interchangeable storage engines it sits on top of:
6
+ *
7
+ * - KeychainBackend — OS keychain via @napi-rs/keyring (the default;
8
+ * real platform at-rest encryption).
9
+ * - EncryptedFileBackend — ~/.borg/credentials, all accounts in one
10
+ * AES-256-GCM blob, file 0600 / dir 0700. Engages
11
+ * only when no keychain is available. Obfuscation-
12
+ * grade (see token-crypto.ts).
13
+ * - caller-managed — BORG_TOKEN / BORG_TOKEN_FILE: an externally
14
+ * supplied id_token, used read-only with no store
15
+ * (the caller owns its lifecycle/freshness).
16
+ *
17
+ * Every engine takes its side-effecting dependencies (keyring entry factory,
18
+ * fs, machine key) by injection so the logic is unit-tested without a real
19
+ * keychain or disk.
20
+ */
21
+ export type TokenBackendName = 'keychain' | 'encrypted-file';
22
+ /**
23
+ * Account-agnostic key/value store over a backing engine. `account` is one
24
+ * of config.ts's three slots (id-token, refresh-token, expiry).
25
+ */
26
+ export interface TokenBackend {
27
+ readonly name: TokenBackendName;
28
+ get(account: string): Promise<string | null>;
29
+ set(account: string, value: string): Promise<void>;
30
+ delete(account: string): Promise<void>;
31
+ }
32
+ /**
33
+ * The slice of @napi-rs/keyring's AsyncEntry this backend depends on. The
34
+ * return types mirror AsyncEntry exactly (deletePassword resolves to an
35
+ * implementation-defined value we ignore) so the real class is assignable.
36
+ */
37
+ export interface KeyringEntry {
38
+ setPassword(value: string): Promise<void>;
39
+ getPassword(): Promise<string | null | undefined>;
40
+ deletePassword(): Promise<unknown>;
41
+ }
42
+ export type KeyringEntryFactory = (account: string) => KeyringEntry;
43
+ /**
44
+ * Build the OS-keychain backend. Preserves config.ts's prior semantics:
45
+ * a missing entry reads as null, and delete is silent on a NoEntry error
46
+ * (idempotent clear) while other errors propagate (fail-loud).
47
+ */
48
+ export declare function makeKeychainBackend(entryFactory?: KeyringEntryFactory): TokenBackend;
49
+ /** The minimal fs surface the file backend needs (injected for tests). */
50
+ export interface FileStoreFs {
51
+ readFile(filePath: string): Promise<string>;
52
+ writeFile(filePath: string, data: string, mode: number): Promise<void>;
53
+ mkdir(dir: string, mode: number): Promise<void>;
54
+ }
55
+ export interface EncryptedFileBackendDeps {
56
+ filePath: string;
57
+ key: Buffer;
58
+ fs: FileStoreFs;
59
+ }
60
+ /**
61
+ * Build the encrypted-file backend. All accounts live in one JSON object
62
+ * encrypted as a single AES-256-GCM envelope at `filePath`.
63
+ *
64
+ * A missing file reads as an empty map. A file that won't decrypt (wrong
65
+ * machine key after a hostname change, truncation, tampering) is ALSO
66
+ * treated as empty: the only consequence is the user re-runs `borg setup`,
67
+ * which is the right fail-safe for credential material — a hard crash on a
68
+ * corrupt dotfile would be worse UX than transparent re-auth.
69
+ */
70
+ export declare function makeEncryptedFileBackend(deps: EncryptedFileBackendDeps): TokenBackend;
71
+ /** User-facing BORG_TOKEN_STORE values (friendlier than the backend names). */
72
+ export type ForcedStore = 'keychain' | 'file';
73
+ export interface SelectTokenBackendDeps {
74
+ keyringAvailable: () => Promise<boolean>;
75
+ makeKeychain: () => TokenBackend;
76
+ makeFile: () => TokenBackend;
77
+ /** BORG_TOKEN_STORE override: skip probing and force a backend. */
78
+ forced?: ForcedStore;
79
+ }
80
+ /**
81
+ * Select the persistent backend: a forced choice (BORG_TOKEN_STORE=keychain|file)
82
+ * wins and skips the probe; otherwise probe the keychain and fall back to the
83
+ * encrypted file when it's unavailable.
84
+ */
85
+ export declare function selectTokenBackend(deps: SelectTokenBackendDeps): Promise<TokenBackend>;
86
+ export interface CallerManagedDeps {
87
+ env: NodeJS.ProcessEnv;
88
+ readFile: (filePath: string) => Promise<string>;
89
+ }
90
+ /**
91
+ * Resolve an externally-supplied id_token (no storage). BORG_TOKEN takes
92
+ * precedence; otherwise BORG_TOKEN_FILE is read from disk. Returns null when
93
+ * neither is configured. The value is trimmed (env vars and files commonly
94
+ * carry trailing newlines). The caller owns this token's freshness, so it
95
+ * bypasses the keychain AND the expiry check in config.ts.
96
+ */
97
+ export declare function readCallerManagedIdToken(deps: CallerManagedDeps): Promise<string | null>;
98
+ //# sourceMappingURL=token-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-store.d.ts","sourceRoot":"","sources":["../src/token-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAQH,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,gBAAgB,CAAC;AAE7D;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC;AAID;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,WAAW,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IAClD,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,YAAY,CAAC;AAKpE;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,YAAY,GAAE,mBAAyC,GACtD,YAAY,CAmBd;AAID,0EAA0E;AAC1E,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjD;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,WAAW,CAAC;CACjB;AAID;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,wBAAwB,GAAG,YAAY,CA2CrF;AAID,+EAA+E;AAC/E,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,MAAM,CAAC;AAE9C,MAAM,WAAW,sBAAsB;IACrC,gBAAgB,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACzC,YAAY,EAAE,MAAM,YAAY,CAAC;IACjC,QAAQ,EAAE,MAAM,YAAY,CAAC;IAC7B,mEAAmE;IACnE,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,sBAAsB,GAC3B,OAAO,CAAC,YAAY,CAAC,CAIvB;AAID,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACjD;AAED;;;;;;GAMG;AACH,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAWxB"}
@@ -0,0 +1,136 @@
1
+ /**
2
+ * gh#557 — token storage backends + selection.
3
+ *
4
+ * config.ts exposes the public token API (storeIdToken/getIdToken/...). This
5
+ * module supplies the interchangeable storage engines it sits on top of:
6
+ *
7
+ * - KeychainBackend — OS keychain via @napi-rs/keyring (the default;
8
+ * real platform at-rest encryption).
9
+ * - EncryptedFileBackend — ~/.borg/credentials, all accounts in one
10
+ * AES-256-GCM blob, file 0600 / dir 0700. Engages
11
+ * only when no keychain is available. Obfuscation-
12
+ * grade (see token-crypto.ts).
13
+ * - caller-managed — BORG_TOKEN / BORG_TOKEN_FILE: an externally
14
+ * supplied id_token, used read-only with no store
15
+ * (the caller owns its lifecycle/freshness).
16
+ *
17
+ * Every engine takes its side-effecting dependencies (keyring entry factory,
18
+ * fs, machine key) by injection so the logic is unit-tested without a real
19
+ * keychain or disk.
20
+ */
21
+ import path from 'path';
22
+ import { AsyncEntry } from '@napi-rs/keyring';
23
+ import { decryptString, encryptString } from './token-crypto.js';
24
+ const SERVICE_NAME = 'borg-mcp';
25
+ const defaultEntryFactory = (account) => new AsyncEntry(SERVICE_NAME, account);
26
+ /**
27
+ * Build the OS-keychain backend. Preserves config.ts's prior semantics:
28
+ * a missing entry reads as null, and delete is silent on a NoEntry error
29
+ * (idempotent clear) while other errors propagate (fail-loud).
30
+ */
31
+ export function makeKeychainBackend(entryFactory = defaultEntryFactory) {
32
+ return {
33
+ name: 'keychain',
34
+ async get(account) {
35
+ return (await entryFactory(account).getPassword()) ?? null;
36
+ },
37
+ async set(account, value) {
38
+ await entryFactory(account).setPassword(value);
39
+ },
40
+ async delete(account) {
41
+ try {
42
+ await entryFactory(account).deletePassword();
43
+ }
44
+ catch (err) {
45
+ const msg = String(err?.message ?? '');
46
+ if (/no entry|not found|no matching/i.test(msg))
47
+ return;
48
+ throw err;
49
+ }
50
+ },
51
+ };
52
+ }
53
+ /**
54
+ * Build the encrypted-file backend. All accounts live in one JSON object
55
+ * encrypted as a single AES-256-GCM envelope at `filePath`.
56
+ *
57
+ * A missing file reads as an empty map. A file that won't decrypt (wrong
58
+ * machine key after a hostname change, truncation, tampering) is ALSO
59
+ * treated as empty: the only consequence is the user re-runs `borg setup`,
60
+ * which is the right fail-safe for credential material — a hard crash on a
61
+ * corrupt dotfile would be worse UX than transparent re-auth.
62
+ */
63
+ export function makeEncryptedFileBackend(deps) {
64
+ const { filePath, key, fs } = deps;
65
+ async function readMap() {
66
+ let raw;
67
+ try {
68
+ raw = await fs.readFile(filePath);
69
+ }
70
+ catch {
71
+ return {}; // missing file → no stored tokens yet
72
+ }
73
+ try {
74
+ const json = decryptString(raw.trim(), key);
75
+ const parsed = JSON.parse(json);
76
+ return parsed && typeof parsed === 'object' ? parsed : {};
77
+ }
78
+ catch {
79
+ return {}; // undecryptable / corrupt → fail safe to re-auth
80
+ }
81
+ }
82
+ async function writeMap(map) {
83
+ await fs.mkdir(path.dirname(filePath), 0o700);
84
+ await fs.writeFile(filePath, encryptString(JSON.stringify(map), key), 0o600);
85
+ }
86
+ return {
87
+ name: 'encrypted-file',
88
+ async get(account) {
89
+ const map = await readMap();
90
+ return Object.prototype.hasOwnProperty.call(map, account) ? map[account] : null;
91
+ },
92
+ async set(account, value) {
93
+ const map = await readMap();
94
+ map[account] = value;
95
+ await writeMap(map);
96
+ },
97
+ async delete(account) {
98
+ const map = await readMap();
99
+ if (Object.prototype.hasOwnProperty.call(map, account)) {
100
+ delete map[account];
101
+ await writeMap(map);
102
+ }
103
+ },
104
+ };
105
+ }
106
+ /**
107
+ * Select the persistent backend: a forced choice (BORG_TOKEN_STORE=keychain|file)
108
+ * wins and skips the probe; otherwise probe the keychain and fall back to the
109
+ * encrypted file when it's unavailable.
110
+ */
111
+ export async function selectTokenBackend(deps) {
112
+ if (deps.forced === 'keychain')
113
+ return deps.makeKeychain();
114
+ if (deps.forced === 'file')
115
+ return deps.makeFile();
116
+ return (await deps.keyringAvailable()) ? deps.makeKeychain() : deps.makeFile();
117
+ }
118
+ /**
119
+ * Resolve an externally-supplied id_token (no storage). BORG_TOKEN takes
120
+ * precedence; otherwise BORG_TOKEN_FILE is read from disk. Returns null when
121
+ * neither is configured. The value is trimmed (env vars and files commonly
122
+ * carry trailing newlines). The caller owns this token's freshness, so it
123
+ * bypasses the keychain AND the expiry check in config.ts.
124
+ */
125
+ export async function readCallerManagedIdToken(deps) {
126
+ const inline = deps.env.BORG_TOKEN?.trim();
127
+ if (inline)
128
+ return inline;
129
+ const file = deps.env.BORG_TOKEN_FILE?.trim();
130
+ if (file) {
131
+ const contents = await deps.readFile(file);
132
+ return contents.trim();
133
+ }
134
+ return null;
135
+ }
136
+ //# sourceMappingURL=token-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-store.js","sourceRoot":"","sources":["../src/token-store.ts"],"names":[],"mappings":"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"}