estatehelm 1.0.0 → 1.0.1

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/dist/index.js CHANGED
@@ -27,6 +27,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
27
  var import_commander = require("commander");
28
28
 
29
29
  // src/login.ts
30
+ var http = __toESM(require("http"));
30
31
  var readline = __toESM(require("readline"));
31
32
  var import_open = __toESM(require("open"));
32
33
 
@@ -1312,16 +1313,7 @@ var KEYTAR_ACCOUNTS = {
1312
1313
  DEVICE_CREDENTIALS: "device-credentials"
1313
1314
  };
1314
1315
  var API_BASE_URL = process.env.ESTATEHELM_API_URL || "https://api.estatehelm.com";
1315
- var OAUTH_CONFIG = {
1316
- // OAuth authorization URL (opens in browser)
1317
- authUrl: `${API_BASE_URL}/.ory/self-service/login/browser`,
1318
- // OAuth token exchange endpoint
1319
- tokenUrl: `${API_BASE_URL}/api/v2/auth/device-token`,
1320
- // Device code flow endpoint
1321
- deviceCodeUrl: `${API_BASE_URL}/api/v2/auth/device-code`,
1322
- // Client ID for CLI app
1323
- clientId: "estatehelm-cli"
1324
- };
1316
+ var APP_URL = process.env.ESTATEHELM_APP_URL || "https://app.estatehelm.com";
1325
1317
  var DEFAULT_CONFIG = {
1326
1318
  defaultMode: "full"
1327
1319
  };
@@ -1447,6 +1439,76 @@ function prompt(question) {
1447
1439
  });
1448
1440
  });
1449
1441
  }
1442
+ async function findAvailablePort(startPort = 19847) {
1443
+ return new Promise((resolve, reject) => {
1444
+ const server = http.createServer();
1445
+ server.listen(startPort, "127.0.0.1", () => {
1446
+ const address = server.address();
1447
+ const port = typeof address === "object" && address ? address.port : startPort;
1448
+ server.close(() => resolve(port));
1449
+ });
1450
+ server.on("error", (err) => {
1451
+ if (err.code === "EADDRINUSE") {
1452
+ resolve(findAvailablePort(startPort + 1));
1453
+ } else {
1454
+ reject(err);
1455
+ }
1456
+ });
1457
+ });
1458
+ }
1459
+ function waitForCallback(port) {
1460
+ return new Promise((resolve, reject) => {
1461
+ const server = http.createServer((req, res) => {
1462
+ const url = new URL(req.url || "/", `http://127.0.0.1:${port}`);
1463
+ const sessionToken = url.searchParams.get("session_token");
1464
+ if (sessionToken) {
1465
+ res.writeHead(200, { "Content-Type": "text/html" });
1466
+ res.end(`
1467
+ <!DOCTYPE html>
1468
+ <html>
1469
+ <head>
1470
+ <title>CLI Authentication Successful</title>
1471
+ <style>
1472
+ body { font-family: system-ui, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: linear-gradient(115deg, #fff1be 28%, #ee87cb 70%, #b060ff 100%); }
1473
+ .card { background: white; padding: 2rem; border-radius: 1rem; box-shadow: 0 10px 25px rgba(0,0,0,0.1); text-align: center; }
1474
+ h1 { color: #059669; margin: 0 0 0.5rem; }
1475
+ p { color: #6b7280; margin: 0; }
1476
+ </style>
1477
+ </head>
1478
+ <body>
1479
+ <div class="card">
1480
+ <h1>\u2713 Authentication Successful</h1>
1481
+ <p>You can close this window and return to your terminal.</p>
1482
+ </div>
1483
+ </body>
1484
+ </html>
1485
+ `);
1486
+ server.close();
1487
+ resolve(sessionToken);
1488
+ } else {
1489
+ res.writeHead(400, { "Content-Type": "text/html" });
1490
+ res.end(`
1491
+ <!DOCTYPE html>
1492
+ <html>
1493
+ <head><title>Authentication Failed</title></head>
1494
+ <body>
1495
+ <h1>Authentication Failed</h1>
1496
+ <p>No session token received. Please try again.</p>
1497
+ </body>
1498
+ </html>
1499
+ `);
1500
+ }
1501
+ });
1502
+ server.listen(port, "127.0.0.1", () => {
1503
+ console.log(`Callback server listening on http://127.0.0.1:${port}`);
1504
+ });
1505
+ server.on("error", reject);
1506
+ setTimeout(() => {
1507
+ server.close();
1508
+ reject(new Error("Authentication timed out. Please try again."));
1509
+ }, 5 * 60 * 1e3);
1510
+ });
1511
+ }
1450
1512
  function createApiClient(token) {
1451
1513
  return new ApiClient({
1452
1514
  baseUrl: API_BASE_URL,
@@ -1454,54 +1516,6 @@ function createApiClient(token) {
1454
1516
  auth: new TokenAuthAdapter(async () => token)
1455
1517
  });
1456
1518
  }
1457
- async function startDeviceCodeFlow() {
1458
- const response = await fetch(`${API_BASE_URL}/api/v2/auth/device-code`, {
1459
- method: "POST",
1460
- headers: {
1461
- "Content-Type": "application/json"
1462
- },
1463
- body: JSON.stringify({
1464
- clientId: OAUTH_CONFIG.clientId,
1465
- scope: "openid profile email offline_access"
1466
- })
1467
- });
1468
- if (!response.ok) {
1469
- const error = await response.json().catch(() => ({}));
1470
- throw new Error(error.message || `Device code request failed: ${response.status}`);
1471
- }
1472
- return response.json();
1473
- }
1474
- async function pollForToken(deviceCode, interval, expiresIn) {
1475
- const startTime = Date.now();
1476
- const expireTime = startTime + expiresIn * 1e3;
1477
- while (Date.now() < expireTime) {
1478
- await new Promise((resolve) => setTimeout(resolve, interval * 1e3));
1479
- const response = await fetch(`${API_BASE_URL}/api/v2/auth/device-token`, {
1480
- method: "POST",
1481
- headers: {
1482
- "Content-Type": "application/json"
1483
- },
1484
- body: JSON.stringify({
1485
- clientId: OAUTH_CONFIG.clientId,
1486
- deviceCode,
1487
- grantType: "urn:ietf:params:oauth:grant-type:device_code"
1488
- })
1489
- });
1490
- if (response.ok) {
1491
- return response.json();
1492
- }
1493
- const error = await response.json().catch(() => ({}));
1494
- if (error.error === "authorization_pending") {
1495
- continue;
1496
- }
1497
- if (error.error === "slow_down") {
1498
- interval += 5;
1499
- continue;
1500
- }
1501
- throw new Error(error.message || `Token request failed: ${response.status}`);
1502
- }
1503
- throw new Error("Device code expired");
1504
- }
1505
1519
  async function getServerWrapSecret(client) {
1506
1520
  const response = await client.post("/webauthn/initialize", {});
1507
1521
  return base64Decode(response.serverWrapSecret);
@@ -1567,25 +1581,23 @@ async function decryptDeviceCredentials(encryptedPayload) {
1567
1581
  async function login() {
1568
1582
  console.log("\nEstateHelm Login");
1569
1583
  console.log("================\n");
1570
- console.log("Starting authentication...");
1571
- const deviceCodeResponse = await startDeviceCodeFlow();
1584
+ console.log("Starting authentication server...");
1585
+ const port = await findAvailablePort();
1586
+ const callbackUrl = `http://127.0.0.1:${port}/callback`;
1587
+ const loginUrl = `${APP_URL}/signin?redirect=/cli-auth?callback=${encodeURIComponent(callbackUrl)}`;
1572
1588
  console.log(`
1573
- Please visit: ${deviceCodeResponse.verificationUri}`);
1574
- console.log(`Enter code: ${deviceCodeResponse.userCode}
1589
+ Opening browser for authentication...`);
1590
+ console.log(`If the browser doesn't open, visit: ${loginUrl}
1575
1591
  `);
1576
- console.log("Opening browser...");
1577
- await (0, import_open.default)(deviceCodeResponse.verificationUri);
1592
+ const callbackPromise = waitForCallback(port);
1593
+ await (0, import_open.default)(loginUrl);
1578
1594
  console.log("Waiting for authentication...");
1579
- const tokenResponse = await pollForToken(
1580
- deviceCodeResponse.deviceCode,
1581
- deviceCodeResponse.interval,
1582
- deviceCodeResponse.expiresIn
1583
- );
1595
+ const sessionToken = await callbackPromise;
1584
1596
  console.log("Authentication successful!");
1585
- console.log(`Token: ${sanitizeToken(tokenResponse.accessToken)}`);
1586
- await saveBearerToken(tokenResponse.accessToken);
1587
- await saveRefreshToken(tokenResponse.refreshToken);
1588
- const client = createApiClient(tokenResponse.accessToken);
1597
+ console.log(`Token: ${sanitizeToken(sessionToken)}`);
1598
+ await saveBearerToken(sessionToken);
1599
+ await saveRefreshToken("");
1600
+ const client = createApiClient(sessionToken);
1589
1601
  console.log("\nFetching encryption keys...");
1590
1602
  const serverWrapSecret = await getServerWrapSecret(client);
1591
1603
  const keyBundle = await getCurrentKeyBundle(client);