@positronic/cli 0.0.59 → 0.0.60

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.
@@ -157,13 +157,24 @@ function _ts_generator(thisArg, body) {
157
157
  };
158
158
  }
159
159
  }
160
- import { SignJWT, importPKCS8 } from 'jose';
160
+ import { SignJWT, importPKCS8, base64url } from 'jose';
161
161
  import { existsSync } from 'fs';
162
- import { loadPrivateKey, getPrivateKeyFingerprint, resolvePrivateKeyPath } from './ssh-key-utils.js';
162
+ import { createPrivateKey } from 'crypto';
163
+ import { loadPrivateKey, getPrivateKeyFingerprint, getPublicKeyFingerprint, resolvePrivateKeyPath } from './ssh-key-utils.js';
163
164
  import { ProjectConfigManager } from '../commands/project-config-manager.js';
165
+ import { AgentSigner } from './ssh-agent-signer.js';
166
+ /**
167
+ * Check if an error indicates an encrypted key
168
+ */ function isEncryptedKeyError(error) {
169
+ if (_instanceof(error, Error) && error.name === 'KeyEncryptedError') {
170
+ return true;
171
+ }
172
+ return false;
173
+ }
164
174
  /**
165
175
  * JWT Auth Provider for authenticating API requests
166
176
  * Uses SSH private keys to sign short-lived JWTs
177
+ * Falls back to ssh-agent for encrypted keys
167
178
  */ export var JwtAuthProvider = /*#__PURE__*/ function() {
168
179
  "use strict";
169
180
  function JwtAuthProvider() {
@@ -172,6 +183,11 @@ import { ProjectConfigManager } from '../commands/project-config-manager.js';
172
183
  _define_property(this, "fingerprint", null);
173
184
  _define_property(this, "initialized", false);
174
185
  _define_property(this, "initError", null);
186
+ // Agent fallback support
187
+ _define_property(this, "encryptedKeyPath", null);
188
+ _define_property(this, "agentSigner", null);
189
+ _define_property(this, "agentKey", null);
190
+ _define_property(this, "useAgent", false);
175
191
  this.initialize();
176
192
  }
177
193
  _create_class(JwtAuthProvider, [
@@ -191,16 +207,42 @@ import { ProjectConfigManager } from '../commands/project-config-manager.js';
191
207
  this.fingerprint = getPrivateKeyFingerprint(this.privateKey);
192
208
  this.initialized = true;
193
209
  } catch (error) {
194
- this.initError = _instanceof(error, Error) ? error : new Error('Failed to initialize JWT auth provider');
210
+ if (isEncryptedKeyError(error)) {
211
+ // Store the path for agent fallback - we'll try the agent in createToken()
212
+ var configManager1 = new ProjectConfigManager();
213
+ var configuredPath1 = configManager1.getPrivateKeyPath();
214
+ this.encryptedKeyPath = resolvePrivateKeyPath(configuredPath1);
215
+ this.initError = _instanceof(error, Error) ? error : new Error('Key is encrypted');
216
+ } else {
217
+ this.initError = _instanceof(error, Error) ? error : new Error('Failed to initialize JWT auth provider');
218
+ }
195
219
  }
196
220
  }
197
221
  },
198
222
  {
199
223
  /**
200
224
  * Check if the provider is ready to create JWTs
225
+ * Returns true if we have a direct key OR if we have an encrypted key
226
+ * that might work with agent fallback
201
227
  */ key: "isReady",
202
228
  value: function isReady() {
203
- return this.initialized && this.privateKey !== null;
229
+ // Direct key is loaded and ready
230
+ if (this.initialized && this.privateKey !== null) {
231
+ return true;
232
+ }
233
+ // Encrypted key might work with agent fallback
234
+ if (this.encryptedKeyPath !== null) {
235
+ return true;
236
+ }
237
+ return false;
238
+ }
239
+ },
240
+ {
241
+ /**
242
+ * Check if we have an encrypted key that requires agent fallback
243
+ */ key: "hasEncryptedKey",
244
+ value: function hasEncryptedKey() {
245
+ return this.encryptedKeyPath !== null;
204
246
  }
205
247
  },
206
248
  {
@@ -227,12 +269,18 @@ import { ProjectConfigManager } from '../commands/project-config-manager.js';
227
269
  if (!this.privateKey) {
228
270
  throw new Error('Private key not loaded');
229
271
  }
230
- var keyType = this.privateKey.type;
272
+ return this.getAlgorithmForKeyType(this.privateKey.type, this.privateKey.curve);
273
+ }
274
+ },
275
+ {
276
+ key: "getAlgorithmForKeyType",
277
+ value: /**
278
+ * Map SSH key type string to JWT algorithm
279
+ */ function getAlgorithmForKeyType(keyType, curve) {
231
280
  if (keyType === 'rsa') {
232
281
  return 'RS256';
233
282
  } else if (keyType === 'ecdsa') {
234
283
  // ECDSA curve determines algorithm
235
- var curve = this.privateKey.curve;
236
284
  if (curve === 'nistp256') {
237
285
  return 'ES256';
238
286
  } else if (curve === 'nistp384') {
@@ -248,22 +296,102 @@ import { ProjectConfigManager } from '../commands/project-config-manager.js';
248
296
  throw new Error("Unsupported key type: ".concat(keyType));
249
297
  }
250
298
  },
299
+ {
300
+ key: "getPkcs8Pem",
301
+ value: /**
302
+ * Convert the SSH private key to PKCS8 PEM format
303
+ * Ed25519 keys need special handling because sshpk's PKCS8 output
304
+ * is not compatible with Node.js/OpenSSL
305
+ */ function getPkcs8Pem() {
306
+ if (!this.privateKey) {
307
+ throw new Error('Private key not loaded');
308
+ }
309
+ if (this.privateKey.type === 'ed25519') {
310
+ // For Ed25519, sshpk's PKCS8 output includes the public key in a format
311
+ // that Node.js/OpenSSL doesn't understand. Instead, we construct a JWK
312
+ // from the raw key parts and let Node's crypto handle the conversion.
313
+ // sshpk stores Ed25519 key data in 'k' (seed) and 'A' (public) parts
314
+ var parts = this.privateKey.part;
315
+ var seed = parts.k.data;
316
+ var publicKey = parts.A.data;
317
+ // Construct JWK and let Node's crypto convert to PKCS8
318
+ var jwk = {
319
+ kty: 'OKP',
320
+ crv: 'Ed25519',
321
+ d: seed.toString('base64url'),
322
+ x: publicKey.toString('base64url')
323
+ };
324
+ var keyObj = createPrivateKey({
325
+ key: jwk,
326
+ format: 'jwk'
327
+ });
328
+ return keyObj.export({
329
+ type: 'pkcs8',
330
+ format: 'pem'
331
+ });
332
+ }
333
+ // For RSA and ECDSA, sshpk's PKCS8 output works fine
334
+ return this.privateKey.toString('pkcs8');
335
+ }
336
+ },
251
337
  {
252
338
  key: "createToken",
253
339
  value: /**
254
340
  * Create a short-lived JWT for authentication
255
341
  */ function createToken() {
342
+ return _async_to_generator(function() {
343
+ return _ts_generator(this, function(_state) {
344
+ switch(_state.label){
345
+ case 0:
346
+ // If we have a direct private key, use the standard jose signing path
347
+ if (this.privateKey && this.fingerprint) {
348
+ return [
349
+ 2,
350
+ this.createTokenDirect()
351
+ ];
352
+ }
353
+ if (!this.encryptedKeyPath) return [
354
+ 3,
355
+ 2
356
+ ];
357
+ return [
358
+ 4,
359
+ this.tryAgentFallback()
360
+ ];
361
+ case 1:
362
+ _state.sent();
363
+ _state.label = 2;
364
+ case 2:
365
+ // If agent fallback succeeded, use agent signing
366
+ if (this.useAgent && this.agentSigner && this.agentKey && this.fingerprint) {
367
+ return [
368
+ 2,
369
+ this.createTokenWithAgent()
370
+ ];
371
+ }
372
+ // No authentication method available
373
+ throw this.initError || new Error('JWT auth provider not initialized');
374
+ }
375
+ });
376
+ }).call(this);
377
+ }
378
+ },
379
+ {
380
+ key: "createTokenDirect",
381
+ value: /**
382
+ * Create JWT using direct private key (jose library)
383
+ */ function createTokenDirect() {
256
384
  return _async_to_generator(function() {
257
385
  var algorithm, pkcs8Pem, joseKey, jwt;
258
386
  return _ts_generator(this, function(_state) {
259
387
  switch(_state.label){
260
388
  case 0:
261
389
  if (!this.privateKey || !this.fingerprint) {
262
- throw new Error('JWT auth provider not initialized');
390
+ throw new Error('Private key not loaded');
263
391
  }
264
392
  algorithm = this.getAlgorithm();
265
393
  // Convert SSH private key to PKCS8 PEM format
266
- pkcs8Pem = this.privateKey.toString('pkcs8');
394
+ pkcs8Pem = this.getPkcs8Pem();
267
395
  return [
268
396
  4,
269
397
  importPKCS8(pkcs8Pem, algorithm)
@@ -286,6 +414,106 @@ import { ProjectConfigManager } from '../commands/project-config-manager.js';
286
414
  });
287
415
  }).call(this);
288
416
  }
417
+ },
418
+ {
419
+ key: "tryAgentFallback",
420
+ value: /**
421
+ * Try to use ssh-agent for signing when private key is encrypted
422
+ */ function tryAgentFallback() {
423
+ return _async_to_generator(function() {
424
+ var pubKeyPath, fingerprint, agent, agentKey;
425
+ return _ts_generator(this, function(_state) {
426
+ switch(_state.label){
427
+ case 0:
428
+ if (!this.encryptedKeyPath) {
429
+ return [
430
+ 2
431
+ ];
432
+ }
433
+ // Get fingerprint from public key file
434
+ pubKeyPath = this.encryptedKeyPath + '.pub';
435
+ if (!existsSync(pubKeyPath)) {
436
+ throw new Error("Key is encrypted and public key file not found at ".concat(pubKeyPath, ".\n") + "Cannot determine key fingerprint for ssh-agent lookup.");
437
+ }
438
+ fingerprint = getPublicKeyFingerprint(pubKeyPath);
439
+ agent = new AgentSigner();
440
+ if (!agent.isAvailable()) {
441
+ throw new Error("Key is encrypted and ssh-agent is not running.\n" + "Start ssh-agent or use an unencrypted key.");
442
+ }
443
+ return [
444
+ 4,
445
+ agent.hasKey(fingerprint)
446
+ ];
447
+ case 1:
448
+ agentKey = _state.sent();
449
+ if (!agentKey) {
450
+ throw new Error("Key is encrypted and not loaded in ssh-agent.\n" + "Run: ssh-add ".concat(this.encryptedKeyPath));
451
+ }
452
+ this.agentSigner = agent;
453
+ this.agentKey = agentKey;
454
+ this.fingerprint = fingerprint;
455
+ this.useAgent = true;
456
+ this.initError = null;
457
+ this.initialized = true;
458
+ return [
459
+ 2
460
+ ];
461
+ }
462
+ });
463
+ }).call(this);
464
+ }
465
+ },
466
+ {
467
+ key: "createTokenWithAgent",
468
+ value: /**
469
+ * Create JWT using ssh-agent for signing
470
+ * Manually constructs the JWT since jose expects to do signing itself
471
+ */ function createTokenWithAgent() {
472
+ return _async_to_generator(function() {
473
+ var keyType, curve, algorithm, header, encodedHeader, now, payload, encodedPayload, signingInput, signature, signatureBytes, encodedSignature;
474
+ return _ts_generator(this, function(_state) {
475
+ switch(_state.label){
476
+ case 0:
477
+ if (!this.agentSigner || !this.agentKey || !this.fingerprint) {
478
+ throw new Error('Agent signing not configured');
479
+ }
480
+ // Get algorithm from agent key type
481
+ keyType = this.agentKey.type;
482
+ curve = this.agentKey.curve;
483
+ algorithm = this.getAlgorithmForKeyType(keyType, curve);
484
+ // Build JWT header
485
+ header = {
486
+ alg: algorithm
487
+ };
488
+ encodedHeader = base64url.encode(JSON.stringify(header));
489
+ // Build JWT payload
490
+ now = Math.floor(Date.now() / 1000);
491
+ payload = {
492
+ sub: this.fingerprint,
493
+ iat: now,
494
+ exp: now + 30
495
+ };
496
+ encodedPayload = base64url.encode(JSON.stringify(payload));
497
+ // Create signing input
498
+ signingInput = "".concat(encodedHeader, ".").concat(encodedPayload);
499
+ return [
500
+ 4,
501
+ this.agentSigner.sign(this.agentKey, Buffer.from(signingInput))
502
+ ];
503
+ case 1:
504
+ signature = _state.sent();
505
+ // Convert sshpk.Signature to raw bytes for JWT
506
+ // sshpk's toBuffer() gives us the raw signature bytes
507
+ signatureBytes = signature.toBuffer('raw');
508
+ encodedSignature = base64url.encode(signatureBytes);
509
+ return [
510
+ 2,
511
+ "".concat(signingInput, ".").concat(encodedSignature)
512
+ ];
513
+ }
514
+ });
515
+ }).call(this);
516
+ }
289
517
  }
290
518
  ]);
291
519
  return JwtAuthProvider;
@@ -313,33 +541,31 @@ var providerInstance = null;
313
541
  }
314
542
  /**
315
543
  * Get the Authorization header if auth is available
316
- * Returns { Authorization: 'Bearer <token>' } or empty object
544
+ * Throws if there's an auth configuration error
545
+ * Returns empty object with warning if no key is configured
317
546
  */ export function getAuthHeader() {
318
547
  return _async_to_generator(function() {
319
- var provider, token, error;
548
+ var provider, error, token;
320
549
  return _ts_generator(this, function(_state) {
321
550
  switch(_state.label){
322
551
  case 0:
323
552
  provider = getJwtAuthProvider();
324
553
  if (!provider.isReady()) {
554
+ error = provider.getError();
555
+ if (error) {
556
+ throw error;
557
+ }
558
+ console.warn('Warning: No SSH key configured for authentication. Run "px auth login" to configure.');
325
559
  return [
326
560
  2,
327
561
  {}
328
562
  ];
329
563
  }
330
- _state.label = 1;
331
- case 1:
332
- _state.trys.push([
333
- 1,
334
- 3,
335
- ,
336
- 4
337
- ]);
338
564
  return [
339
565
  4,
340
566
  provider.createToken()
341
567
  ];
342
- case 2:
568
+ case 1:
343
569
  token = _state.sent();
344
570
  return [
345
571
  2,
@@ -347,17 +573,6 @@ var providerInstance = null;
347
573
  Authorization: "Bearer ".concat(token)
348
574
  }
349
575
  ];
350
- case 3:
351
- error = _state.sent();
352
- console.error('Warning: Failed to create auth token:', error);
353
- return [
354
- 2,
355
- {}
356
- ];
357
- case 4:
358
- return [
359
- 2
360
- ];
361
576
  }
362
577
  });
363
578
  })();
@@ -0,0 +1,296 @@
1
+ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
2
+ try {
3
+ var info = gen[key](arg);
4
+ var value = info.value;
5
+ } catch (error) {
6
+ reject(error);
7
+ return;
8
+ }
9
+ if (info.done) {
10
+ resolve(value);
11
+ } else {
12
+ Promise.resolve(value).then(_next, _throw);
13
+ }
14
+ }
15
+ function _async_to_generator(fn) {
16
+ return function() {
17
+ var self = this, args = arguments;
18
+ return new Promise(function(resolve, reject) {
19
+ var gen = fn.apply(self, args);
20
+ function _next(value) {
21
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
22
+ }
23
+ function _throw(err) {
24
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
25
+ }
26
+ _next(undefined);
27
+ });
28
+ };
29
+ }
30
+ function _class_call_check(instance, Constructor) {
31
+ if (!(instance instanceof Constructor)) {
32
+ throw new TypeError("Cannot call a class as a function");
33
+ }
34
+ }
35
+ function _defineProperties(target, props) {
36
+ for(var i = 0; i < props.length; i++){
37
+ var descriptor = props[i];
38
+ descriptor.enumerable = descriptor.enumerable || false;
39
+ descriptor.configurable = true;
40
+ if ("value" in descriptor) descriptor.writable = true;
41
+ Object.defineProperty(target, descriptor.key, descriptor);
42
+ }
43
+ }
44
+ function _create_class(Constructor, protoProps, staticProps) {
45
+ if (protoProps) _defineProperties(Constructor.prototype, protoProps);
46
+ if (staticProps) _defineProperties(Constructor, staticProps);
47
+ return Constructor;
48
+ }
49
+ function _define_property(obj, key, value) {
50
+ if (key in obj) {
51
+ Object.defineProperty(obj, key, {
52
+ value: value,
53
+ enumerable: true,
54
+ configurable: true,
55
+ writable: true
56
+ });
57
+ } else {
58
+ obj[key] = value;
59
+ }
60
+ return obj;
61
+ }
62
+ function _ts_generator(thisArg, body) {
63
+ var f, y, t, _ = {
64
+ label: 0,
65
+ sent: function() {
66
+ if (t[0] & 1) throw t[1];
67
+ return t[1];
68
+ },
69
+ trys: [],
70
+ ops: []
71
+ }, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
72
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() {
73
+ return this;
74
+ }), g;
75
+ function verb(n) {
76
+ return function(v) {
77
+ return step([
78
+ n,
79
+ v
80
+ ]);
81
+ };
82
+ }
83
+ function step(op) {
84
+ if (f) throw new TypeError("Generator is already executing.");
85
+ while(g && (g = 0, op[0] && (_ = 0)), _)try {
86
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
87
+ if (y = 0, t) op = [
88
+ op[0] & 2,
89
+ t.value
90
+ ];
91
+ switch(op[0]){
92
+ case 0:
93
+ case 1:
94
+ t = op;
95
+ break;
96
+ case 4:
97
+ _.label++;
98
+ return {
99
+ value: op[1],
100
+ done: false
101
+ };
102
+ case 5:
103
+ _.label++;
104
+ y = op[1];
105
+ op = [
106
+ 0
107
+ ];
108
+ continue;
109
+ case 7:
110
+ op = _.ops.pop();
111
+ _.trys.pop();
112
+ continue;
113
+ default:
114
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
115
+ _ = 0;
116
+ continue;
117
+ }
118
+ if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
119
+ _.label = op[1];
120
+ break;
121
+ }
122
+ if (op[0] === 6 && _.label < t[1]) {
123
+ _.label = t[1];
124
+ t = op;
125
+ break;
126
+ }
127
+ if (t && _.label < t[2]) {
128
+ _.label = t[2];
129
+ _.ops.push(op);
130
+ break;
131
+ }
132
+ if (t[2]) _.ops.pop();
133
+ _.trys.pop();
134
+ continue;
135
+ }
136
+ op = body.call(thisArg, _);
137
+ } catch (e) {
138
+ op = [
139
+ 6,
140
+ e
141
+ ];
142
+ y = 0;
143
+ } finally{
144
+ f = t = 0;
145
+ }
146
+ if (op[0] & 5) throw op[1];
147
+ return {
148
+ value: op[0] ? op[1] : void 0,
149
+ done: true
150
+ };
151
+ }
152
+ }
153
+ import { Client as AgentClient } from 'sshpk-agent';
154
+ /**
155
+ * Wrapper for ssh-agent operations using sshpk-agent
156
+ */ export var AgentSigner = /*#__PURE__*/ function() {
157
+ "use strict";
158
+ function AgentSigner() {
159
+ _class_call_check(this, AgentSigner);
160
+ _define_property(this, "client", null);
161
+ _define_property(this, "keys", null);
162
+ }
163
+ _create_class(AgentSigner, [
164
+ {
165
+ /**
166
+ * Check if ssh-agent is available (SSH_AUTH_SOCK environment variable exists)
167
+ */ key: "isAvailable",
168
+ value: function isAvailable() {
169
+ return !!process.env.SSH_AUTH_SOCK;
170
+ }
171
+ },
172
+ {
173
+ key: "getClient",
174
+ value: /**
175
+ * Get the agent client, creating it lazily
176
+ */ function getClient() {
177
+ if (!this.client) {
178
+ this.client = new AgentClient();
179
+ }
180
+ return this.client;
181
+ }
182
+ },
183
+ {
184
+ key: "getKeys",
185
+ value: /**
186
+ * List all keys available in the ssh-agent
187
+ */ function getKeys() {
188
+ return _async_to_generator(function() {
189
+ var _this, client;
190
+ return _ts_generator(this, function(_state) {
191
+ _this = this;
192
+ if (this.keys) {
193
+ return [
194
+ 2,
195
+ this.keys
196
+ ];
197
+ }
198
+ client = this.getClient();
199
+ return [
200
+ 2,
201
+ new Promise(function(resolve, reject) {
202
+ client.listKeys(function(err, keys) {
203
+ if (err) {
204
+ reject(err);
205
+ return;
206
+ }
207
+ _this.keys = keys;
208
+ resolve(keys);
209
+ });
210
+ })
211
+ ];
212
+ });
213
+ }).call(this);
214
+ }
215
+ },
216
+ {
217
+ key: "hasKey",
218
+ value: /**
219
+ * Check if the agent has a key with the given fingerprint
220
+ * Returns the key if found, null otherwise
221
+ */ function hasKey(fingerprint) {
222
+ return _async_to_generator(function() {
223
+ var keys, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, key, keyFingerprint;
224
+ return _ts_generator(this, function(_state) {
225
+ switch(_state.label){
226
+ case 0:
227
+ return [
228
+ 4,
229
+ this.getKeys()
230
+ ];
231
+ case 1:
232
+ keys = _state.sent();
233
+ _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
234
+ try {
235
+ for(_iterator = keys[Symbol.iterator](); !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
236
+ key = _step.value;
237
+ keyFingerprint = key.fingerprint('sha256').toString();
238
+ if (keyFingerprint === fingerprint) {
239
+ return [
240
+ 2,
241
+ key
242
+ ];
243
+ }
244
+ }
245
+ } catch (err) {
246
+ _didIteratorError = true;
247
+ _iteratorError = err;
248
+ } finally{
249
+ try {
250
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
251
+ _iterator.return();
252
+ }
253
+ } finally{
254
+ if (_didIteratorError) {
255
+ throw _iteratorError;
256
+ }
257
+ }
258
+ }
259
+ return [
260
+ 2,
261
+ null
262
+ ];
263
+ }
264
+ });
265
+ }).call(this);
266
+ }
267
+ },
268
+ {
269
+ key: "sign",
270
+ value: /**
271
+ * Sign data with a key from the agent
272
+ * Returns the sshpk.Signature object
273
+ */ function sign(key, data) {
274
+ return _async_to_generator(function() {
275
+ var client;
276
+ return _ts_generator(this, function(_state) {
277
+ client = this.getClient();
278
+ return [
279
+ 2,
280
+ new Promise(function(resolve, reject) {
281
+ client.sign(key, data, function(err, signature) {
282
+ if (err) {
283
+ reject(err);
284
+ return;
285
+ }
286
+ resolve(signature);
287
+ });
288
+ })
289
+ ];
290
+ });
291
+ }).call(this);
292
+ }
293
+ }
294
+ ]);
295
+ return AgentSigner;
296
+ }();
@@ -171,6 +171,15 @@ import { createPublicKey } from 'crypto';
171
171
  var publicKey = privateKey.toPublic();
172
172
  return publicKey.fingerprint('sha256').toString();
173
173
  }
174
+ /**
175
+ * Get the fingerprint from a public key file (.pub file)
176
+ * This is useful when the private key is encrypted but we need the fingerprint
177
+ * to look up the key in ssh-agent
178
+ */ export function getPublicKeyFingerprint(pubKeyPath) {
179
+ var content = readFileSync(pubKeyPath, 'utf-8').trim();
180
+ var sshKey = sshpk.parseKey(content, 'auto');
181
+ return sshKey.fingerprint('sha256').toString();
182
+ }
174
183
  /**
175
184
  * Resolve the private key path from environment, config, or default
176
185
  * @param configuredPath - Optional configured path from ProjectConfigManager
File without changes
@@ -1,18 +1,29 @@
1
1
  /**
2
2
  * JWT Auth Provider for authenticating API requests
3
3
  * Uses SSH private keys to sign short-lived JWTs
4
+ * Falls back to ssh-agent for encrypted keys
4
5
  */
5
6
  export declare class JwtAuthProvider {
6
7
  private privateKey;
7
8
  private fingerprint;
8
9
  private initialized;
9
10
  private initError;
11
+ private encryptedKeyPath;
12
+ private agentSigner;
13
+ private agentKey;
14
+ private useAgent;
10
15
  constructor();
11
16
  private initialize;
12
17
  /**
13
18
  * Check if the provider is ready to create JWTs
19
+ * Returns true if we have a direct key OR if we have an encrypted key
20
+ * that might work with agent fallback
14
21
  */
15
22
  isReady(): boolean;
23
+ /**
24
+ * Check if we have an encrypted key that requires agent fallback
25
+ */
26
+ hasEncryptedKey(): boolean;
16
27
  /**
17
28
  * Get the error that occurred during initialization, if any
18
29
  */
@@ -25,10 +36,33 @@ export declare class JwtAuthProvider {
25
36
  * Map SSH key type to JWT algorithm
26
37
  */
27
38
  private getAlgorithm;
39
+ /**
40
+ * Map SSH key type string to JWT algorithm
41
+ */
42
+ private getAlgorithmForKeyType;
43
+ /**
44
+ * Convert the SSH private key to PKCS8 PEM format
45
+ * Ed25519 keys need special handling because sshpk's PKCS8 output
46
+ * is not compatible with Node.js/OpenSSL
47
+ */
48
+ private getPkcs8Pem;
28
49
  /**
29
50
  * Create a short-lived JWT for authentication
30
51
  */
31
52
  createToken(): Promise<string>;
53
+ /**
54
+ * Create JWT using direct private key (jose library)
55
+ */
56
+ private createTokenDirect;
57
+ /**
58
+ * Try to use ssh-agent for signing when private key is encrypted
59
+ */
60
+ private tryAgentFallback;
61
+ /**
62
+ * Create JWT using ssh-agent for signing
63
+ * Manually constructs the JWT since jose expects to do signing itself
64
+ */
65
+ private createTokenWithAgent;
32
66
  }
33
67
  /**
34
68
  * Get the singleton JWT auth provider instance
@@ -45,7 +79,8 @@ export declare function resetJwtAuthProvider(): void;
45
79
  export declare function isAuthAvailable(): boolean;
46
80
  /**
47
81
  * Get the Authorization header if auth is available
48
- * Returns { Authorization: 'Bearer <token>' } or empty object
82
+ * Throws if there's an auth configuration error
83
+ * Returns empty object with warning if no key is configured
49
84
  */
50
85
  export declare function getAuthHeader(): Promise<Record<string, string>>;
51
86
  //# sourceMappingURL=jwt-auth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"jwt-auth.d.ts","sourceRoot":"","sources":["../../../src/lib/jwt-auth.ts"],"names":[],"mappings":"AAUA;;;GAGG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,UAAU,CAAiC;IACnD,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,SAAS,CAAsB;;IAMvC,OAAO,CAAC,UAAU;IA0BlB;;OAEG;IACH,OAAO,IAAI,OAAO;IAIlB;;OAEG;IACH,QAAQ,IAAI,KAAK,GAAG,IAAI;IAIxB;;OAEG;IACH,cAAc,IAAI,MAAM,GAAG,IAAI;IAI/B;;OAEG;IACH,OAAO,CAAC,YAAY;IA4BpB;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC;CAuBrC;AAKD;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,eAAe,CAKpD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAED;;;GAGG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAarE"}
1
+ {"version":3,"file":"jwt-auth.d.ts","sourceRoot":"","sources":["../../../src/lib/jwt-auth.ts"],"names":[],"mappings":"AAuBA;;;;GAIG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,UAAU,CAAiC;IACnD,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,SAAS,CAAsB;IAGvC,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,QAAQ,CAA0B;IAC1C,OAAO,CAAC,QAAQ,CAAS;;IAMzB,OAAO,CAAC,UAAU;IAqClB;;;;OAIG;IACH,OAAO,IAAI,OAAO;IAYlB;;OAEG;IACH,eAAe,IAAI,OAAO;IAI1B;;OAEG;IACH,QAAQ,IAAI,KAAK,GAAG,IAAI;IAIxB;;OAEG;IACH,cAAc,IAAI,MAAM,GAAG,IAAI;IAI/B;;OAEG;IACH,OAAO,CAAC,YAAY;IAQpB;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAqB9B;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAiCnB;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC;IAoBpC;;OAEG;YACW,iBAAiB;IAwB/B;;OAEG;YACW,gBAAgB;IAwC9B;;;OAGG;YACW,oBAAoB;CAuCnC;AAKD;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,eAAe,CAKpD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAED;;;;GAIG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAgBrE"}
@@ -0,0 +1,31 @@
1
+ import sshpk from 'sshpk';
2
+ /**
3
+ * Wrapper for ssh-agent operations using sshpk-agent
4
+ */
5
+ export declare class AgentSigner {
6
+ private client;
7
+ private keys;
8
+ /**
9
+ * Check if ssh-agent is available (SSH_AUTH_SOCK environment variable exists)
10
+ */
11
+ isAvailable(): boolean;
12
+ /**
13
+ * Get the agent client, creating it lazily
14
+ */
15
+ private getClient;
16
+ /**
17
+ * List all keys available in the ssh-agent
18
+ */
19
+ getKeys(): Promise<sshpk.Key[]>;
20
+ /**
21
+ * Check if the agent has a key with the given fingerprint
22
+ * Returns the key if found, null otherwise
23
+ */
24
+ hasKey(fingerprint: string): Promise<sshpk.Key | null>;
25
+ /**
26
+ * Sign data with a key from the agent
27
+ * Returns the sshpk.Signature object
28
+ */
29
+ sign(key: sshpk.Key, data: Buffer): Promise<sshpk.Signature>;
30
+ }
31
+ //# sourceMappingURL=ssh-agent-signer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssh-agent-signer.d.ts","sourceRoot":"","sources":["../../../src/lib/ssh-agent-signer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,IAAI,CAA4B;IAExC;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,OAAO,CAAC,SAAS;IAOjB;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;IAmBrC;;;OAGG;IACG,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC;IAa5D;;;OAGG;IACG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC;CAanE"}
@@ -29,6 +29,12 @@ export declare function loadPrivateKey(pathOrEnv?: string): sshpk.PrivateKey;
29
29
  * Get the fingerprint of a private key (from its public component)
30
30
  */
31
31
  export declare function getPrivateKeyFingerprint(privateKey: sshpk.PrivateKey): string;
32
+ /**
33
+ * Get the fingerprint from a public key file (.pub file)
34
+ * This is useful when the private key is encrypted but we need the fingerprint
35
+ * to look up the key in ssh-agent
36
+ */
37
+ export declare function getPublicKeyFingerprint(pubKeyPath: string): string;
32
38
  /**
33
39
  * Resolve the private key path from environment, config, or default
34
40
  * @param configuredPath - Optional configured path from ProjectConfigManager
@@ -1 +1 @@
1
- {"version":3,"file":"ssh-key-utils.d.ts","sourceRoot":"","sources":["../../../src/lib/ssh-key-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,OAAO,EAAmB,UAAU,EAAE,MAAM,QAAQ,CAAC;AAErD,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,UAAU,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI,aAAa,EAAE,CAkDjD;AA0CD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,CAiBpE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC,UAAU,CAyBnE;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,GAAG,MAAM,CAG7E;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAoB5E;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAKlD"}
1
+ {"version":3,"file":"ssh-key-utils.d.ts","sourceRoot":"","sources":["../../../src/lib/ssh-key-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,OAAO,EAAmB,UAAU,EAAE,MAAM,QAAQ,CAAC;AAErD,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,UAAU,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI,aAAa,EAAE,CAkDjD;AA0CD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,CAiBpE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC,UAAU,CAyBnE;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,GAAG,MAAM,CAG7E;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAIlE;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAoB5E;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAKlD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@positronic/cli",
3
- "version": "0.0.59",
3
+ "version": "0.0.60",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -23,9 +23,9 @@
23
23
  "clean": "rm -rf tsconfig.tsbuildinfo dist node_modules"
24
24
  },
25
25
  "dependencies": {
26
- "@positronic/core": "^0.0.59",
27
- "@positronic/spec": "^0.0.59",
28
- "@positronic/template-new-project": "^0.0.59",
26
+ "@positronic/core": "^0.0.60",
27
+ "@positronic/spec": "^0.0.60",
28
+ "@positronic/template-new-project": "^0.0.60",
29
29
  "caz": "^2.0.0",
30
30
  "chokidar": "^3.6.0",
31
31
  "dotenv": "^16.4.7",
@@ -38,6 +38,7 @@
38
38
  "react": "^18.3.1",
39
39
  "react-robot": "^1.2.1",
40
40
  "sshpk": "^1.18.0",
41
+ "sshpk-agent": "^1.8.1",
41
42
  "yargs": "^17.7.2"
42
43
  },
43
44
  "devDependencies": {