mobbdev 1.1.40 → 1.1.41

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.
@@ -1371,15 +1371,141 @@ import { withFile } from "tmp-promise";
1371
1371
  import z26 from "zod";
1372
1372
 
1373
1373
  // src/commands/handleMobbLogin.ts
1374
- import crypto from "crypto";
1375
- import os from "os";
1376
1374
  import chalk2 from "chalk";
1377
1375
  import Debug6 from "debug";
1378
- import open from "open";
1379
1376
 
1380
- // src/constants.ts
1377
+ // src/utils/dirname.ts
1378
+ import fs from "fs";
1379
+ import path from "path";
1380
+ import { fileURLToPath } from "url";
1381
+ function getModuleRootDir() {
1382
+ let manifestDir = getDirName();
1383
+ for (let i = 0; i < 10; i++) {
1384
+ const manifestPath = path.join(manifestDir, "package.json");
1385
+ if (fs.existsSync(manifestPath)) {
1386
+ return manifestDir;
1387
+ }
1388
+ manifestDir = path.join(manifestDir, "..");
1389
+ }
1390
+ throw new Error("Cannot locate package.json file");
1391
+ }
1392
+ function getDirName() {
1393
+ if (typeof __filename !== "undefined") {
1394
+ return path.dirname(__filename);
1395
+ } else {
1396
+ try {
1397
+ const getImportMetaUrl = new Function("return import.meta.url");
1398
+ const importMetaUrl = getImportMetaUrl();
1399
+ return path.dirname(fileURLToPath(importMetaUrl));
1400
+ } catch (e) {
1401
+ try {
1402
+ const err = new Error();
1403
+ const stack = err.stack || "";
1404
+ const match = stack.match(/file:\/\/[^\s)]+/);
1405
+ if (match) {
1406
+ const fileUrl = match[0];
1407
+ return path.dirname(fileURLToPath(fileUrl));
1408
+ }
1409
+ } catch {
1410
+ }
1411
+ throw new Error("Unable to determine directory name in this environment");
1412
+ }
1413
+ }
1414
+ }
1415
+
1416
+ // src/utils/keypress.ts
1417
+ import readline from "readline";
1418
+ async function keypress() {
1419
+ const rl = readline.createInterface({
1420
+ input: process.stdin,
1421
+ output: process.stdout
1422
+ });
1423
+ return new Promise((resolve) => {
1424
+ rl.question("", (answer) => {
1425
+ rl.close();
1426
+ process.stderr.moveCursor(0, -1);
1427
+ process.stderr.clearLine(1);
1428
+ resolve(answer);
1429
+ });
1430
+ });
1431
+ }
1432
+
1433
+ // src/utils/spinner.ts
1434
+ import {
1435
+ createSpinner as _createSpinner
1436
+ } from "nanospinner";
1437
+ function printToStdError(opts) {
1438
+ if (opts?.text) console.error(opts.text);
1439
+ }
1440
+ var mockSpinner = {
1441
+ success: (opts) => {
1442
+ printToStdError(opts);
1443
+ return mockSpinner;
1444
+ },
1445
+ error: (opts) => {
1446
+ printToStdError(opts);
1447
+ return mockSpinner;
1448
+ },
1449
+ warn: (opts) => {
1450
+ printToStdError(opts);
1451
+ return mockSpinner;
1452
+ },
1453
+ stop: (opts) => {
1454
+ printToStdError(opts);
1455
+ return mockSpinner;
1456
+ },
1457
+ start: (opts) => {
1458
+ printToStdError(opts);
1459
+ return mockSpinner;
1460
+ },
1461
+ update: (opts) => {
1462
+ printToStdError(opts);
1463
+ return mockSpinner;
1464
+ },
1465
+ reset: () => mockSpinner,
1466
+ clear: () => mockSpinner,
1467
+ spin: () => mockSpinner
1468
+ };
1469
+ function Spinner({ ci = false } = {}) {
1470
+ return {
1471
+ createSpinner: (text, options) => ci ? mockSpinner : _createSpinner(text, options)
1472
+ };
1473
+ }
1474
+
1475
+ // src/utils/check_node_version.ts
1381
1476
  import fs2 from "fs";
1382
1477
  import path2 from "path";
1478
+ import semver from "semver";
1479
+ function getPackageJson() {
1480
+ return JSON.parse(
1481
+ fs2.readFileSync(path2.join(getModuleRootDir(), "package.json"), "utf8")
1482
+ );
1483
+ }
1484
+ var packageJson = getPackageJson();
1485
+ if (!semver.satisfies(process.version, packageJson.engines.node)) {
1486
+ console.error(
1487
+ `
1488
+ \u26A0\uFE0F ${packageJson.name} requires node version ${packageJson.engines.node}, but running ${process.version}.`
1489
+ );
1490
+ process.exit(1);
1491
+ }
1492
+
1493
+ // src/utils/gitUtils.ts
1494
+ import simpleGit from "simple-git";
1495
+
1496
+ // src/utils/index.ts
1497
+ var sleep = (ms = 2e3) => new Promise((r) => setTimeout(r, ms));
1498
+ var CliError = class extends Error {
1499
+ };
1500
+
1501
+ // src/commands/AuthManager.ts
1502
+ import crypto from "crypto";
1503
+ import os from "os";
1504
+ import open from "open";
1505
+
1506
+ // src/constants.ts
1507
+ import fs3 from "fs";
1508
+ import path3 from "path";
1383
1509
  import chalk from "chalk";
1384
1510
  import Debug from "debug";
1385
1511
  import * as dotenv from "dotenv";
@@ -3481,56 +3607,17 @@ var ScmType = /* @__PURE__ */ ((ScmType2) => {
3481
3607
  return ScmType2;
3482
3608
  })(ScmType || {});
3483
3609
 
3484
- // src/utils/dirname.ts
3485
- import fs from "fs";
3486
- import path from "path";
3487
- import { fileURLToPath } from "url";
3488
- function getModuleRootDir() {
3489
- let manifestDir = getDirName();
3490
- for (let i = 0; i < 10; i++) {
3491
- const manifestPath = path.join(manifestDir, "package.json");
3492
- if (fs.existsSync(manifestPath)) {
3493
- return manifestDir;
3494
- }
3495
- manifestDir = path.join(manifestDir, "..");
3496
- }
3497
- throw new Error("Cannot locate package.json file");
3498
- }
3499
- function getDirName() {
3500
- if (typeof __filename !== "undefined") {
3501
- return path.dirname(__filename);
3502
- } else {
3503
- try {
3504
- const getImportMetaUrl = new Function("return import.meta.url");
3505
- const importMetaUrl = getImportMetaUrl();
3506
- return path.dirname(fileURLToPath(importMetaUrl));
3507
- } catch (e) {
3508
- try {
3509
- const err = new Error();
3510
- const stack = err.stack || "";
3511
- const match = stack.match(/file:\/\/[^\s)]+/);
3512
- if (match) {
3513
- const fileUrl = match[0];
3514
- return path.dirname(fileURLToPath(fileUrl));
3515
- }
3516
- } catch {
3517
- }
3518
- throw new Error("Unable to determine directory name in this environment");
3519
- }
3520
- }
3521
- }
3522
-
3523
3610
  // src/constants.ts
3524
3611
  var debug = Debug("mobbdev:constants");
3525
- var runtimeConfigPath = path2.join(
3612
+ var runtimeConfigPath = path3.join(
3526
3613
  getModuleRootDir(),
3527
3614
  "out",
3528
3615
  "runtime.config.json"
3529
3616
  );
3530
- if (fs2.existsSync(runtimeConfigPath)) {
3617
+ if (fs3.existsSync(runtimeConfigPath)) {
3531
3618
  try {
3532
3619
  const runtimeConfig = JSON.parse(
3533
- fs2.readFileSync(runtimeConfigPath, "utf-8")
3620
+ fs3.readFileSync(runtimeConfigPath, "utf-8")
3534
3621
  );
3535
3622
  Object.assign(process.env, runtimeConfig);
3536
3623
  debug("Loaded runtime config from %s", runtimeConfigPath);
@@ -3538,7 +3625,7 @@ if (fs2.existsSync(runtimeConfigPath)) {
3538
3625
  debug("Failed to load runtime config: %o", e);
3539
3626
  }
3540
3627
  }
3541
- dotenv.config({ path: path2.join(getModuleRootDir(), ".env") });
3628
+ dotenv.config({ path: path3.join(getModuleRootDir(), ".env") });
3542
3629
  var DEFAULT_API_URL = "https://api.mobb.ai/v1/graphql";
3543
3630
  var DEFAULT_WEB_APP_URL = "https://app.mobb.ai";
3544
3631
  var scmFriendlyText = {
@@ -3635,91 +3722,6 @@ var ScanContext = {
3635
3722
  BUGSY: "BUGSY"
3636
3723
  };
3637
3724
 
3638
- // src/utils/keypress.ts
3639
- import readline from "readline";
3640
- async function keypress() {
3641
- const rl = readline.createInterface({
3642
- input: process.stdin,
3643
- output: process.stdout
3644
- });
3645
- return new Promise((resolve) => {
3646
- rl.question("", (answer) => {
3647
- rl.close();
3648
- process.stderr.moveCursor(0, -1);
3649
- process.stderr.clearLine(1);
3650
- resolve(answer);
3651
- });
3652
- });
3653
- }
3654
-
3655
- // src/utils/spinner.ts
3656
- import {
3657
- createSpinner as _createSpinner
3658
- } from "nanospinner";
3659
- function printToStdError(opts) {
3660
- if (opts?.text) console.error(opts.text);
3661
- }
3662
- var mockSpinner = {
3663
- success: (opts) => {
3664
- printToStdError(opts);
3665
- return mockSpinner;
3666
- },
3667
- error: (opts) => {
3668
- printToStdError(opts);
3669
- return mockSpinner;
3670
- },
3671
- warn: (opts) => {
3672
- printToStdError(opts);
3673
- return mockSpinner;
3674
- },
3675
- stop: (opts) => {
3676
- printToStdError(opts);
3677
- return mockSpinner;
3678
- },
3679
- start: (opts) => {
3680
- printToStdError(opts);
3681
- return mockSpinner;
3682
- },
3683
- update: (opts) => {
3684
- printToStdError(opts);
3685
- return mockSpinner;
3686
- },
3687
- reset: () => mockSpinner,
3688
- clear: () => mockSpinner,
3689
- spin: () => mockSpinner
3690
- };
3691
- function Spinner({ ci = false } = {}) {
3692
- return {
3693
- createSpinner: (text, options) => ci ? mockSpinner : _createSpinner(text, options)
3694
- };
3695
- }
3696
-
3697
- // src/utils/check_node_version.ts
3698
- import fs3 from "fs";
3699
- import path3 from "path";
3700
- import semver from "semver";
3701
- function getPackageJson() {
3702
- return JSON.parse(
3703
- fs3.readFileSync(path3.join(getModuleRootDir(), "package.json"), "utf8")
3704
- );
3705
- }
3706
- var packageJson = getPackageJson();
3707
- if (!semver.satisfies(process.version, packageJson.engines.node)) {
3708
- console.error(
3709
- `
3710
- \u26A0\uFE0F ${packageJson.name} requires node version ${packageJson.engines.node}, but running ${process.version}.`
3711
- );
3712
- process.exit(1);
3713
- }
3714
-
3715
- // src/utils/gitUtils.ts
3716
- import simpleGit from "simple-git";
3717
-
3718
- // src/utils/index.ts
3719
- var sleep = (ms = 2e3) => new Promise((r) => setTimeout(r, ms));
3720
- var CliError = class extends Error {
3721
- };
3722
-
3723
3725
  // src/utils/subscribe/subscribe.ts
3724
3726
  import { createClient } from "graphql-ws";
3725
3727
  import WebsocketNode from "isomorphic-ws";
@@ -6493,10 +6495,174 @@ function getConfigStore() {
6493
6495
  }
6494
6496
  var configStore = getConfigStore();
6495
6497
 
6496
- // src/commands/handleMobbLogin.ts
6497
- var debug7 = Debug6("mobbdev:commands");
6498
+ // src/commands/AuthManager.ts
6498
6499
  var LOGIN_MAX_WAIT = 10 * 60 * 1e3;
6499
6500
  var LOGIN_CHECK_DELAY = 5 * 1e3;
6501
+ var AuthManager = class {
6502
+ constructor(webAppUrl, apiUrl) {
6503
+ __publicField(this, "publicKey");
6504
+ __publicField(this, "privateKey");
6505
+ __publicField(this, "loginId");
6506
+ __publicField(this, "gqlClient");
6507
+ __publicField(this, "currentBrowserUrl");
6508
+ __publicField(this, "authenticated", null);
6509
+ __publicField(this, "resolvedWebAppUrl");
6510
+ __publicField(this, "resolvedApiUrl");
6511
+ this.resolvedWebAppUrl = webAppUrl || WEB_APP_URL;
6512
+ this.resolvedApiUrl = apiUrl || API_URL;
6513
+ }
6514
+ openUrlInBrowser() {
6515
+ if (this.currentBrowserUrl) {
6516
+ open(this.currentBrowserUrl);
6517
+ return true;
6518
+ }
6519
+ return false;
6520
+ }
6521
+ async waitForAuthentication() {
6522
+ let newApiToken = null;
6523
+ for (let i = 0; i < LOGIN_MAX_WAIT / LOGIN_CHECK_DELAY; i++) {
6524
+ newApiToken = await this.getApiToken();
6525
+ if (newApiToken) {
6526
+ break;
6527
+ }
6528
+ await sleep(LOGIN_CHECK_DELAY);
6529
+ }
6530
+ if (!newApiToken) {
6531
+ return false;
6532
+ }
6533
+ this.gqlClient = new GQLClient({
6534
+ apiKey: newApiToken,
6535
+ type: "apiKey",
6536
+ apiUrl: this.resolvedApiUrl
6537
+ });
6538
+ const loginSuccess = await this.gqlClient.validateUserToken();
6539
+ if (loginSuccess) {
6540
+ configStore.set("apiToken", newApiToken);
6541
+ this.authenticated = true;
6542
+ return true;
6543
+ }
6544
+ return false;
6545
+ }
6546
+ /**
6547
+ * Checks if the user is already authenticated
6548
+ */
6549
+ async isAuthenticated() {
6550
+ if (this.authenticated === null) {
6551
+ const result = await this.checkAuthentication();
6552
+ this.authenticated = result.isAuthenticated;
6553
+ }
6554
+ return this.authenticated;
6555
+ }
6556
+ /**
6557
+ * Private function to check if the user is authenticated with the server
6558
+ */
6559
+ async checkAuthentication(apiKey) {
6560
+ try {
6561
+ if (!this.gqlClient) {
6562
+ this.gqlClient = this.getGQLClient(apiKey);
6563
+ }
6564
+ const isConnected = await this.gqlClient.verifyApiConnection();
6565
+ if (!isConnected) {
6566
+ return {
6567
+ isAuthenticated: false,
6568
+ message: "Failed to connect to Mobb server"
6569
+ };
6570
+ }
6571
+ const userVerify = await this.gqlClient.validateUserToken();
6572
+ if (!userVerify) {
6573
+ return {
6574
+ isAuthenticated: false,
6575
+ message: "User token validation failed"
6576
+ };
6577
+ }
6578
+ } catch (error) {
6579
+ return {
6580
+ isAuthenticated: false,
6581
+ message: error instanceof Error ? error.message : "Unknown authentication error"
6582
+ };
6583
+ }
6584
+ return { isAuthenticated: true, message: "Successfully authenticated" };
6585
+ }
6586
+ /**
6587
+ * Generates a login URL for manual authentication
6588
+ */
6589
+ async generateLoginUrl(loginContext) {
6590
+ try {
6591
+ if (!this.gqlClient) {
6592
+ this.gqlClient = this.getGQLClient();
6593
+ }
6594
+ const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
6595
+ modulusLength: 2048
6596
+ });
6597
+ this.publicKey = publicKey;
6598
+ this.privateKey = privateKey;
6599
+ this.loginId = await this.gqlClient.createCliLogin({
6600
+ publicKey: this.publicKey.export({ format: "pem", type: "pkcs1" }).toString()
6601
+ });
6602
+ const webLoginUrl = `${this.resolvedWebAppUrl}/cli-login`;
6603
+ const browserUrl = loginContext ? buildLoginUrl(webLoginUrl, this.loginId, os.hostname(), loginContext) : `${webLoginUrl}/${this.loginId}?hostname=${os.hostname()}`;
6604
+ this.currentBrowserUrl = browserUrl;
6605
+ return browserUrl;
6606
+ } catch (error) {
6607
+ console.error("Failed to generate login URL:", error);
6608
+ return null;
6609
+ }
6610
+ }
6611
+ /**
6612
+ * Retrieves and decrypts the API token after authentication
6613
+ */
6614
+ async getApiToken() {
6615
+ if (!this.gqlClient || !this.loginId || !this.privateKey) {
6616
+ return null;
6617
+ }
6618
+ const encryptedApiToken = await this.gqlClient.getEncryptedApiToken({
6619
+ loginId: this.loginId
6620
+ });
6621
+ if (encryptedApiToken) {
6622
+ return crypto.privateDecrypt(
6623
+ this.privateKey,
6624
+ Buffer.from(encryptedApiToken, "base64")
6625
+ ).toString("utf-8");
6626
+ }
6627
+ return null;
6628
+ }
6629
+ /**
6630
+ * Gets the current GQL client (if authenticated)
6631
+ */
6632
+ getGQLClient(inputApiKey) {
6633
+ if (this.gqlClient === void 0) {
6634
+ this.gqlClient = new GQLClient({
6635
+ apiKey: inputApiKey || configStore.get("apiToken") || "",
6636
+ type: "apiKey",
6637
+ apiUrl: this.resolvedApiUrl
6638
+ });
6639
+ }
6640
+ return this.gqlClient;
6641
+ }
6642
+ /**
6643
+ * Assigns a GQL client instance to the AuthManager, and resets auth state
6644
+ * @param gqlClient The GQL client instance to set
6645
+ */
6646
+ setGQLClient(gqlClient) {
6647
+ this.gqlClient = gqlClient;
6648
+ this.cleanup();
6649
+ }
6650
+ /**
6651
+ * Cleans up any active login session
6652
+ */
6653
+ cleanup() {
6654
+ this.publicKey = void 0;
6655
+ this.privateKey = void 0;
6656
+ this.loginId = void 0;
6657
+ this.authenticated = null;
6658
+ this.currentBrowserUrl = null;
6659
+ }
6660
+ };
6661
+
6662
+ // src/commands/handleMobbLogin.ts
6663
+ var debug7 = Debug6("mobbdev:commands");
6664
+ var LOGIN_MAX_WAIT2 = 10 * 60 * 1e3;
6665
+ var LOGIN_CHECK_DELAY2 = 5 * 1e3;
6500
6666
  var MOBB_LOGIN_REQUIRED_MSG = `\u{1F513} Login to Mobb is Required, you will be redirected to our login page, once the authorization is complete return to this prompt, ${chalk2.bgBlue(
6501
6667
  "press any key to continue"
6502
6668
  )};`;
@@ -6511,11 +6677,8 @@ async function getAuthenticatedGQLClient({
6511
6677
  apiUrl || "undefined",
6512
6678
  webAppUrl || "undefined"
6513
6679
  );
6514
- let gqlClient = new GQLClient({
6515
- apiKey: inputApiKey || configStore.get("apiToken") || "",
6516
- type: "apiKey",
6517
- apiUrl
6518
- });
6680
+ const authManager = new AuthManager(webAppUrl, apiUrl);
6681
+ let gqlClient = authManager.getGQLClient(inputApiKey);
6519
6682
  gqlClient = await handleMobbLogin({
6520
6683
  inGqlClient: gqlClient,
6521
6684
  skipPrompts: isSkipPrompts,
@@ -6532,35 +6695,28 @@ async function handleMobbLogin({
6532
6695
  webAppUrl,
6533
6696
  loginContext
6534
6697
  }) {
6535
- const resolvedWebAppUrl = webAppUrl || WEB_APP_URL;
6536
- const resolvedApiUrl = apiUrl || API_URL;
6537
6698
  debug7(
6538
6699
  "handleMobbLogin: resolved URLs - apiUrl=%s (from param: %s), webAppUrl=%s (from param: %s)",
6539
- resolvedApiUrl,
6540
6700
  apiUrl || "fallback",
6541
- resolvedWebAppUrl,
6701
+ apiUrl || "fallback",
6702
+ webAppUrl || "fallback",
6542
6703
  webAppUrl || "fallback"
6543
6704
  );
6544
6705
  const { createSpinner } = Spinner({ ci: skipPrompts });
6545
- const isConnected = await inGqlClient.verifyApiConnection();
6546
- if (!isConnected) {
6547
- createSpinner().start().error({
6548
- text: "\u{1F513} Connection to Mobb: failed to connect to the Mobb server"
6549
- });
6550
- throw new CliError(
6551
- "Connection to Mobb: failed to connect to the Mobb server"
6552
- );
6706
+ const authManager = new AuthManager(webAppUrl, apiUrl);
6707
+ authManager.setGQLClient(inGqlClient);
6708
+ try {
6709
+ const isAuthenticated = await authManager.isAuthenticated();
6710
+ if (isAuthenticated) {
6711
+ createSpinner().start().success({
6712
+ text: `\u{1F513} Login to Mobb succeeded. Already authenticated`
6713
+ });
6714
+ return authManager.getGQLClient();
6715
+ }
6716
+ } catch (error) {
6717
+ debug7("Authentication check failed:", error);
6553
6718
  }
6554
- createSpinner().start().success({
6555
- text: `\u{1F513} Connection to Mobb: succeeded`
6556
- });
6557
- const userVerify = await inGqlClient.validateUserToken();
6558
- if (userVerify) {
6559
- createSpinner().start().success({
6560
- text: `\u{1F513} Login to Mobb succeeded. ${typeof userVerify === "string" ? `Logged in as ${userVerify}` : ""}`
6561
- });
6562
- return inGqlClient;
6563
- } else if (apiKey) {
6719
+ if (apiKey) {
6564
6720
  createSpinner().start().error({
6565
6721
  text: "\u{1F513} Login to Mobb failed: The provided API key does not match any configured API key on the system"
6566
6722
  });
@@ -6576,57 +6732,32 @@ async function handleMobbLogin({
6576
6732
  loginSpinner.update({
6577
6733
  text: "\u{1F513} Waiting for Mobb login..."
6578
6734
  });
6579
- const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
6580
- modulusLength: 2048
6581
- });
6582
- const loginId = await inGqlClient.createCliLogin({
6583
- publicKey: publicKey.export({ format: "pem", type: "pkcs1" }).toString()
6584
- });
6585
- const webLoginUrl = `${resolvedWebAppUrl}/cli-login`;
6586
- const browserUrl = loginContext ? buildLoginUrl(webLoginUrl, loginId, os.hostname(), loginContext) : `${webLoginUrl}/${loginId}?hostname=${os.hostname()}`;
6587
- !skipPrompts && console.log(
6588
- `If the page does not open automatically, kindly access it through ${browserUrl}.`
6589
- );
6590
- await open(browserUrl);
6591
- let newApiToken = null;
6592
- for (let i = 0; i < LOGIN_MAX_WAIT / LOGIN_CHECK_DELAY; i++) {
6593
- const encryptedApiToken = await inGqlClient.getEncryptedApiToken({
6594
- loginId
6595
- });
6596
- loginSpinner.spin();
6597
- if (encryptedApiToken) {
6598
- debug7("encrypted API token received %s", encryptedApiToken);
6599
- newApiToken = crypto.privateDecrypt(privateKey, Buffer.from(encryptedApiToken, "base64")).toString("utf-8");
6600
- debug7("API token decrypted");
6601
- break;
6735
+ try {
6736
+ const loginUrl = await authManager.generateLoginUrl(loginContext);
6737
+ if (!loginUrl) {
6738
+ loginSpinner.error({
6739
+ text: "Failed to generate login URL"
6740
+ });
6741
+ throw new CliError("Failed to generate login URL");
6742
+ }
6743
+ !skipPrompts && console.log(
6744
+ `If the page does not open automatically, kindly access it through ${loginUrl}.`
6745
+ );
6746
+ authManager.openUrlInBrowser();
6747
+ const authSuccess = await authManager.waitForAuthentication();
6748
+ if (!authSuccess) {
6749
+ loginSpinner.error({
6750
+ text: "Login timeout error"
6751
+ });
6752
+ throw new CliError("Login timeout error");
6602
6753
  }
6603
- await sleep(LOGIN_CHECK_DELAY);
6604
- }
6605
- if (!newApiToken) {
6606
- loginSpinner.error({
6607
- text: "Login timeout error"
6608
- });
6609
- throw new CliError();
6610
- }
6611
- const newGqlClient = new GQLClient({
6612
- apiKey: newApiToken,
6613
- type: "apiKey",
6614
- apiUrl: resolvedApiUrl
6615
- });
6616
- const loginSuccess = await newGqlClient.validateUserToken();
6617
- if (loginSuccess) {
6618
- debug7(`set api token ${newApiToken}`);
6619
- configStore.set("apiToken", newApiToken);
6620
6754
  loginSpinner.success({
6621
- text: `\u{1F513} Login to Mobb successful! ${typeof loginSpinner === "string" ? `Logged in as ${loginSuccess}` : ""}`
6622
- });
6623
- } else {
6624
- loginSpinner.error({
6625
- text: "Something went wrong, API token is invalid."
6755
+ text: `\u{1F513} Login to Mobb successful!`
6626
6756
  });
6627
- throw new CliError();
6757
+ return authManager.getGQLClient();
6758
+ } finally {
6759
+ authManager.cleanup();
6628
6760
  }
6629
- return newGqlClient;
6630
6761
  }
6631
6762
 
6632
6763
  // src/args/commands/upload_ai_blame.ts
package/dist/index.mjs CHANGED
@@ -11587,10 +11587,12 @@ import tmp2 from "tmp";
11587
11587
  import { z as z29 } from "zod";
11588
11588
 
11589
11589
  // src/commands/handleMobbLogin.ts
11590
- import crypto from "crypto";
11591
- import os from "os";
11592
11590
  import chalk3 from "chalk";
11593
11591
  import Debug6 from "debug";
11592
+
11593
+ // src/commands/AuthManager.ts
11594
+ import crypto from "crypto";
11595
+ import os from "os";
11594
11596
  import open from "open";
11595
11597
 
11596
11598
  // src/features/analysis/graphql/gql.ts
@@ -12346,10 +12348,174 @@ function getConfigStore() {
12346
12348
  }
12347
12349
  var configStore = getConfigStore();
12348
12350
 
12349
- // src/commands/handleMobbLogin.ts
12350
- var debug7 = Debug6("mobbdev:commands");
12351
+ // src/commands/AuthManager.ts
12351
12352
  var LOGIN_MAX_WAIT = 10 * 60 * 1e3;
12352
12353
  var LOGIN_CHECK_DELAY = 5 * 1e3;
12354
+ var AuthManager = class {
12355
+ constructor(webAppUrl, apiUrl) {
12356
+ __publicField(this, "publicKey");
12357
+ __publicField(this, "privateKey");
12358
+ __publicField(this, "loginId");
12359
+ __publicField(this, "gqlClient");
12360
+ __publicField(this, "currentBrowserUrl");
12361
+ __publicField(this, "authenticated", null);
12362
+ __publicField(this, "resolvedWebAppUrl");
12363
+ __publicField(this, "resolvedApiUrl");
12364
+ this.resolvedWebAppUrl = webAppUrl || WEB_APP_URL;
12365
+ this.resolvedApiUrl = apiUrl || API_URL;
12366
+ }
12367
+ openUrlInBrowser() {
12368
+ if (this.currentBrowserUrl) {
12369
+ open(this.currentBrowserUrl);
12370
+ return true;
12371
+ }
12372
+ return false;
12373
+ }
12374
+ async waitForAuthentication() {
12375
+ let newApiToken = null;
12376
+ for (let i = 0; i < LOGIN_MAX_WAIT / LOGIN_CHECK_DELAY; i++) {
12377
+ newApiToken = await this.getApiToken();
12378
+ if (newApiToken) {
12379
+ break;
12380
+ }
12381
+ await sleep(LOGIN_CHECK_DELAY);
12382
+ }
12383
+ if (!newApiToken) {
12384
+ return false;
12385
+ }
12386
+ this.gqlClient = new GQLClient({
12387
+ apiKey: newApiToken,
12388
+ type: "apiKey",
12389
+ apiUrl: this.resolvedApiUrl
12390
+ });
12391
+ const loginSuccess = await this.gqlClient.validateUserToken();
12392
+ if (loginSuccess) {
12393
+ configStore.set("apiToken", newApiToken);
12394
+ this.authenticated = true;
12395
+ return true;
12396
+ }
12397
+ return false;
12398
+ }
12399
+ /**
12400
+ * Checks if the user is already authenticated
12401
+ */
12402
+ async isAuthenticated() {
12403
+ if (this.authenticated === null) {
12404
+ const result = await this.checkAuthentication();
12405
+ this.authenticated = result.isAuthenticated;
12406
+ }
12407
+ return this.authenticated;
12408
+ }
12409
+ /**
12410
+ * Private function to check if the user is authenticated with the server
12411
+ */
12412
+ async checkAuthentication(apiKey) {
12413
+ try {
12414
+ if (!this.gqlClient) {
12415
+ this.gqlClient = this.getGQLClient(apiKey);
12416
+ }
12417
+ const isConnected = await this.gqlClient.verifyApiConnection();
12418
+ if (!isConnected) {
12419
+ return {
12420
+ isAuthenticated: false,
12421
+ message: "Failed to connect to Mobb server"
12422
+ };
12423
+ }
12424
+ const userVerify = await this.gqlClient.validateUserToken();
12425
+ if (!userVerify) {
12426
+ return {
12427
+ isAuthenticated: false,
12428
+ message: "User token validation failed"
12429
+ };
12430
+ }
12431
+ } catch (error) {
12432
+ return {
12433
+ isAuthenticated: false,
12434
+ message: error instanceof Error ? error.message : "Unknown authentication error"
12435
+ };
12436
+ }
12437
+ return { isAuthenticated: true, message: "Successfully authenticated" };
12438
+ }
12439
+ /**
12440
+ * Generates a login URL for manual authentication
12441
+ */
12442
+ async generateLoginUrl(loginContext) {
12443
+ try {
12444
+ if (!this.gqlClient) {
12445
+ this.gqlClient = this.getGQLClient();
12446
+ }
12447
+ const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
12448
+ modulusLength: 2048
12449
+ });
12450
+ this.publicKey = publicKey;
12451
+ this.privateKey = privateKey;
12452
+ this.loginId = await this.gqlClient.createCliLogin({
12453
+ publicKey: this.publicKey.export({ format: "pem", type: "pkcs1" }).toString()
12454
+ });
12455
+ const webLoginUrl = `${this.resolvedWebAppUrl}/cli-login`;
12456
+ const browserUrl = loginContext ? buildLoginUrl(webLoginUrl, this.loginId, os.hostname(), loginContext) : `${webLoginUrl}/${this.loginId}?hostname=${os.hostname()}`;
12457
+ this.currentBrowserUrl = browserUrl;
12458
+ return browserUrl;
12459
+ } catch (error) {
12460
+ console.error("Failed to generate login URL:", error);
12461
+ return null;
12462
+ }
12463
+ }
12464
+ /**
12465
+ * Retrieves and decrypts the API token after authentication
12466
+ */
12467
+ async getApiToken() {
12468
+ if (!this.gqlClient || !this.loginId || !this.privateKey) {
12469
+ return null;
12470
+ }
12471
+ const encryptedApiToken = await this.gqlClient.getEncryptedApiToken({
12472
+ loginId: this.loginId
12473
+ });
12474
+ if (encryptedApiToken) {
12475
+ return crypto.privateDecrypt(
12476
+ this.privateKey,
12477
+ Buffer.from(encryptedApiToken, "base64")
12478
+ ).toString("utf-8");
12479
+ }
12480
+ return null;
12481
+ }
12482
+ /**
12483
+ * Gets the current GQL client (if authenticated)
12484
+ */
12485
+ getGQLClient(inputApiKey) {
12486
+ if (this.gqlClient === void 0) {
12487
+ this.gqlClient = new GQLClient({
12488
+ apiKey: inputApiKey || configStore.get("apiToken") || "",
12489
+ type: "apiKey",
12490
+ apiUrl: this.resolvedApiUrl
12491
+ });
12492
+ }
12493
+ return this.gqlClient;
12494
+ }
12495
+ /**
12496
+ * Assigns a GQL client instance to the AuthManager, and resets auth state
12497
+ * @param gqlClient The GQL client instance to set
12498
+ */
12499
+ setGQLClient(gqlClient) {
12500
+ this.gqlClient = gqlClient;
12501
+ this.cleanup();
12502
+ }
12503
+ /**
12504
+ * Cleans up any active login session
12505
+ */
12506
+ cleanup() {
12507
+ this.publicKey = void 0;
12508
+ this.privateKey = void 0;
12509
+ this.loginId = void 0;
12510
+ this.authenticated = null;
12511
+ this.currentBrowserUrl = null;
12512
+ }
12513
+ };
12514
+
12515
+ // src/commands/handleMobbLogin.ts
12516
+ var debug7 = Debug6("mobbdev:commands");
12517
+ var LOGIN_MAX_WAIT2 = 10 * 60 * 1e3;
12518
+ var LOGIN_CHECK_DELAY2 = 5 * 1e3;
12353
12519
  var MOBB_LOGIN_REQUIRED_MSG = `\u{1F513} Login to Mobb is Required, you will be redirected to our login page, once the authorization is complete return to this prompt, ${chalk3.bgBlue(
12354
12520
  "press any key to continue"
12355
12521
  )};`;
@@ -12364,11 +12530,8 @@ async function getAuthenticatedGQLClient({
12364
12530
  apiUrl || "undefined",
12365
12531
  webAppUrl || "undefined"
12366
12532
  );
12367
- let gqlClient = new GQLClient({
12368
- apiKey: inputApiKey || configStore.get("apiToken") || "",
12369
- type: "apiKey",
12370
- apiUrl
12371
- });
12533
+ const authManager = new AuthManager(webAppUrl, apiUrl);
12534
+ let gqlClient = authManager.getGQLClient(inputApiKey);
12372
12535
  gqlClient = await handleMobbLogin({
12373
12536
  inGqlClient: gqlClient,
12374
12537
  skipPrompts: isSkipPrompts,
@@ -12385,35 +12548,28 @@ async function handleMobbLogin({
12385
12548
  webAppUrl,
12386
12549
  loginContext
12387
12550
  }) {
12388
- const resolvedWebAppUrl = webAppUrl || WEB_APP_URL;
12389
- const resolvedApiUrl = apiUrl || API_URL;
12390
12551
  debug7(
12391
12552
  "handleMobbLogin: resolved URLs - apiUrl=%s (from param: %s), webAppUrl=%s (from param: %s)",
12392
- resolvedApiUrl,
12393
12553
  apiUrl || "fallback",
12394
- resolvedWebAppUrl,
12554
+ apiUrl || "fallback",
12555
+ webAppUrl || "fallback",
12395
12556
  webAppUrl || "fallback"
12396
12557
  );
12397
12558
  const { createSpinner: createSpinner5 } = Spinner({ ci: skipPrompts });
12398
- const isConnected = await inGqlClient.verifyApiConnection();
12399
- if (!isConnected) {
12400
- createSpinner5().start().error({
12401
- text: "\u{1F513} Connection to Mobb: failed to connect to the Mobb server"
12402
- });
12403
- throw new CliError(
12404
- "Connection to Mobb: failed to connect to the Mobb server"
12405
- );
12559
+ const authManager = new AuthManager(webAppUrl, apiUrl);
12560
+ authManager.setGQLClient(inGqlClient);
12561
+ try {
12562
+ const isAuthenticated = await authManager.isAuthenticated();
12563
+ if (isAuthenticated) {
12564
+ createSpinner5().start().success({
12565
+ text: `\u{1F513} Login to Mobb succeeded. Already authenticated`
12566
+ });
12567
+ return authManager.getGQLClient();
12568
+ }
12569
+ } catch (error) {
12570
+ debug7("Authentication check failed:", error);
12406
12571
  }
12407
- createSpinner5().start().success({
12408
- text: `\u{1F513} Connection to Mobb: succeeded`
12409
- });
12410
- const userVerify = await inGqlClient.validateUserToken();
12411
- if (userVerify) {
12412
- createSpinner5().start().success({
12413
- text: `\u{1F513} Login to Mobb succeeded. ${typeof userVerify === "string" ? `Logged in as ${userVerify}` : ""}`
12414
- });
12415
- return inGqlClient;
12416
- } else if (apiKey) {
12572
+ if (apiKey) {
12417
12573
  createSpinner5().start().error({
12418
12574
  text: "\u{1F513} Login to Mobb failed: The provided API key does not match any configured API key on the system"
12419
12575
  });
@@ -12429,57 +12585,32 @@ async function handleMobbLogin({
12429
12585
  loginSpinner.update({
12430
12586
  text: "\u{1F513} Waiting for Mobb login..."
12431
12587
  });
12432
- const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
12433
- modulusLength: 2048
12434
- });
12435
- const loginId = await inGqlClient.createCliLogin({
12436
- publicKey: publicKey.export({ format: "pem", type: "pkcs1" }).toString()
12437
- });
12438
- const webLoginUrl = `${resolvedWebAppUrl}/cli-login`;
12439
- const browserUrl = loginContext ? buildLoginUrl(webLoginUrl, loginId, os.hostname(), loginContext) : `${webLoginUrl}/${loginId}?hostname=${os.hostname()}`;
12440
- !skipPrompts && console.log(
12441
- `If the page does not open automatically, kindly access it through ${browserUrl}.`
12442
- );
12443
- await open(browserUrl);
12444
- let newApiToken = null;
12445
- for (let i = 0; i < LOGIN_MAX_WAIT / LOGIN_CHECK_DELAY; i++) {
12446
- const encryptedApiToken = await inGqlClient.getEncryptedApiToken({
12447
- loginId
12448
- });
12449
- loginSpinner.spin();
12450
- if (encryptedApiToken) {
12451
- debug7("encrypted API token received %s", encryptedApiToken);
12452
- newApiToken = crypto.privateDecrypt(privateKey, Buffer.from(encryptedApiToken, "base64")).toString("utf-8");
12453
- debug7("API token decrypted");
12454
- break;
12588
+ try {
12589
+ const loginUrl = await authManager.generateLoginUrl(loginContext);
12590
+ if (!loginUrl) {
12591
+ loginSpinner.error({
12592
+ text: "Failed to generate login URL"
12593
+ });
12594
+ throw new CliError("Failed to generate login URL");
12595
+ }
12596
+ !skipPrompts && console.log(
12597
+ `If the page does not open automatically, kindly access it through ${loginUrl}.`
12598
+ );
12599
+ authManager.openUrlInBrowser();
12600
+ const authSuccess = await authManager.waitForAuthentication();
12601
+ if (!authSuccess) {
12602
+ loginSpinner.error({
12603
+ text: "Login timeout error"
12604
+ });
12605
+ throw new CliError("Login timeout error");
12455
12606
  }
12456
- await sleep(LOGIN_CHECK_DELAY);
12457
- }
12458
- if (!newApiToken) {
12459
- loginSpinner.error({
12460
- text: "Login timeout error"
12461
- });
12462
- throw new CliError();
12463
- }
12464
- const newGqlClient = new GQLClient({
12465
- apiKey: newApiToken,
12466
- type: "apiKey",
12467
- apiUrl: resolvedApiUrl
12468
- });
12469
- const loginSuccess = await newGqlClient.validateUserToken();
12470
- if (loginSuccess) {
12471
- debug7(`set api token ${newApiToken}`);
12472
- configStore.set("apiToken", newApiToken);
12473
12607
  loginSpinner.success({
12474
- text: `\u{1F513} Login to Mobb successful! ${typeof loginSpinner === "string" ? `Logged in as ${loginSuccess}` : ""}`
12475
- });
12476
- } else {
12477
- loginSpinner.error({
12478
- text: "Something went wrong, API token is invalid."
12608
+ text: `\u{1F513} Login to Mobb successful!`
12479
12609
  });
12480
- throw new CliError();
12610
+ return authManager.getGQLClient();
12611
+ } finally {
12612
+ authManager.cleanup();
12481
12613
  }
12482
- return newGqlClient;
12483
12614
  }
12484
12615
 
12485
12616
  // src/features/analysis/add_fix_comments_for_pr/add_fix_comments_for_pr.ts
@@ -14046,7 +14177,7 @@ async function _scan(params, { skipPrompts = false } = {}) {
14046
14177
  `If the page does not open automatically, kindly access it through ${scmAuthUrl2}.`
14047
14178
  );
14048
14179
  await open3(scmAuthUrl2);
14049
- for (let i = 0; i < LOGIN_MAX_WAIT / LOGIN_CHECK_DELAY; i++) {
14180
+ for (let i = 0; i < LOGIN_MAX_WAIT2 / LOGIN_CHECK_DELAY2; i++) {
14050
14181
  const userInfo2 = await gqlClient.getUserInfo();
14051
14182
  if (!userInfo2) {
14052
14183
  throw new CliError2("User info not found");
@@ -14065,7 +14196,7 @@ async function _scan(params, { skipPrompts = false } = {}) {
14065
14196
  return tokenInfo2.accessToken;
14066
14197
  }
14067
14198
  scmSpinner.spin();
14068
- await sleep(LOGIN_CHECK_DELAY);
14199
+ await sleep(LOGIN_CHECK_DELAY2);
14069
14200
  }
14070
14201
  scmSpinner.error({
14071
14202
  text: `${scmName} login timeout error`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobbdev",
3
- "version": "1.1.40",
3
+ "version": "1.1.41",
4
4
  "description": "Automated secure code remediation tool",
5
5
  "repository": "git+https://github.com/mobb-dev/bugsy.git",
6
6
  "main": "dist/index.mjs",