@uipath/auth 1.0.4 → 1.195.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -728,6 +728,7 @@ var init_open = __esm(() => {
728
728
  });
729
729
 
730
730
  // ../filesystem/src/node.ts
731
+ import { randomUUID } from "node:crypto";
731
732
  import { existsSync } from "node:fs";
732
733
  import * as fs6 from "node:fs/promises";
733
734
  import * as os2 from "node:os";
@@ -809,6 +810,90 @@ class NodeFileSystem {
809
810
  async mkdir(dirPath) {
810
811
  await fs6.mkdir(dirPath, { recursive: true });
811
812
  }
813
+ async acquireLock(lockPath) {
814
+ const canonicalPath = await this.canonicalizeLockTarget(lockPath);
815
+ const lockFile = `${canonicalPath}.lock`;
816
+ const ownerId = randomUUID();
817
+ const start = Date.now();
818
+ while (true) {
819
+ try {
820
+ await fs6.writeFile(lockFile, ownerId, { flag: "wx" });
821
+ return this.createLockRelease(lockFile, ownerId);
822
+ } catch (error) {
823
+ if (!this.hasErrnoCode(error, "EEXIST")) {
824
+ throw error;
825
+ }
826
+ const stats = await fs6.stat(lockFile).catch(() => null);
827
+ if (stats && Date.now() - stats.mtimeMs > LOCK_STALE_MS) {
828
+ const reclaimed = await fs6.rm(lockFile, { force: true }).then(() => true).catch(() => false);
829
+ if (reclaimed)
830
+ continue;
831
+ }
832
+ if (Date.now() - start > LOCK_MAX_WAIT_MS) {
833
+ throw new Error(`ELOCKED: timed out waiting for lock on ${canonicalPath}`);
834
+ }
835
+ await new Promise((resolve2) => setTimeout(resolve2, LOCK_RETRY_MIN_MS + Math.random() * LOCK_RETRY_JITTER_MS));
836
+ }
837
+ }
838
+ }
839
+ async canonicalizeLockTarget(lockPath) {
840
+ const absolute = path2.resolve(lockPath);
841
+ const fullReal = await fs6.realpath(absolute).catch(() => null);
842
+ if (fullReal)
843
+ return fullReal;
844
+ const parent = path2.dirname(absolute);
845
+ const base = path2.basename(absolute);
846
+ const canonicalParent = await fs6.realpath(parent).catch(() => parent);
847
+ return path2.join(canonicalParent, base);
848
+ }
849
+ createLockRelease(lockFile, ownerId) {
850
+ const heartbeatStart = Date.now();
851
+ let heartbeatTimer;
852
+ let stopped = false;
853
+ const stopHeartbeat = () => {
854
+ stopped = true;
855
+ if (heartbeatTimer)
856
+ clearTimeout(heartbeatTimer);
857
+ };
858
+ const scheduleNextHeartbeat = () => {
859
+ if (stopped)
860
+ return;
861
+ if (Date.now() - heartbeatStart >= LOCK_MAX_HOLD_MS) {
862
+ stopped = true;
863
+ return;
864
+ }
865
+ heartbeatTimer = setTimeout(() => {
866
+ runHeartbeat();
867
+ }, LOCK_HEARTBEAT_MS);
868
+ heartbeatTimer.unref?.();
869
+ };
870
+ const runHeartbeat = async () => {
871
+ if (stopped)
872
+ return;
873
+ const current = await fs6.readFile(lockFile, "utf-8").catch(() => null);
874
+ if (stopped)
875
+ return;
876
+ if (current !== ownerId) {
877
+ stopped = true;
878
+ return;
879
+ }
880
+ const now = Date.now() / 1000;
881
+ await fs6.utimes(lockFile, now, now).catch(() => {});
882
+ scheduleNextHeartbeat();
883
+ };
884
+ scheduleNextHeartbeat();
885
+ let released = false;
886
+ return async () => {
887
+ if (released)
888
+ return;
889
+ released = true;
890
+ stopHeartbeat();
891
+ const current = await fs6.readFile(lockFile, "utf-8").catch(() => null);
892
+ if (current === ownerId) {
893
+ await fs6.rm(lockFile, { force: true });
894
+ }
895
+ };
896
+ }
812
897
  async rm(filePath) {
813
898
  await fs6.rm(filePath, { recursive: true, force: true });
814
899
  }
@@ -854,16 +939,18 @@ class NodeFileSystem {
854
939
  }
855
940
  }
856
941
  isEnoent(error) {
857
- return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
942
+ return this.hasErrnoCode(error, "ENOENT");
943
+ }
944
+ hasErrnoCode(error, code) {
945
+ return typeof error === "object" && error !== null && "code" in error && error.code === code;
858
946
  }
859
947
  }
948
+ var LOCK_HEARTBEAT_MS = 5000, LOCK_STALE_MS = 15000, LOCK_MAX_WAIT_MS = 20000, LOCK_MAX_HOLD_MS = 60000, LOCK_RETRY_MIN_MS = 100, LOCK_RETRY_JITTER_MS = 200;
860
949
  var init_node = __esm(() => {
861
950
  init_open();
862
951
  });
863
952
  // ../filesystem/src/index.ts
864
- var fsInstance, getFileSystem = () => {
865
- return fsInstance;
866
- };
953
+ var fsInstance, getFileSystem = () => fsInstance;
867
954
  var init_src = __esm(() => {
868
955
  init_node();
869
956
  init_node();
@@ -872,7 +959,7 @@ var init_src = __esm(() => {
872
959
 
873
960
  // ../../node_modules/@uipath/coreipc/index.js
874
961
  var require_coreipc = __commonJS((exports, module) => {
875
- var __dirname = "/Users/alexandru.oltean/github/cli/node_modules/@uipath/coreipc";
962
+ var __dirname = "/home/runner/work/cli/cli/node_modules/@uipath/coreipc";
876
963
  /*! For license information please see index.js.LICENSE.txt */
877
964
  (function(e, t) {
878
965
  typeof exports == "object" && typeof module == "object" ? module.exports = t() : typeof define == "function" && define.amd ? define([], t) : typeof exports == "object" ? exports.ipc = t() : e.ipc = t();
@@ -19065,81 +19152,6 @@ var require_dist = __commonJS((exports) => {
19065
19152
  __exportStar(require_agent(), exports);
19066
19153
  });
19067
19154
 
19068
- // src/strategies/browser-strategy.ts
19069
- var exports_browser_strategy = {};
19070
- __export(exports_browser_strategy, {
19071
- BrowserAuthStrategy: () => BrowserAuthStrategy
19072
- });
19073
-
19074
- class BrowserAuthStrategy {
19075
- async execute(url, _redirectUri, expectedState) {
19076
- const global2 = getGlobalThis();
19077
- if (!global2?.window) {
19078
- throw new Error("Browser environment required for authentication");
19079
- }
19080
- const screenWidth = global2.window.screen?.width ?? 1024;
19081
- const screenHeight = global2.window.screen?.height ?? 768;
19082
- const width = 600;
19083
- const height = 700;
19084
- const left = screenWidth / 2 - width / 2;
19085
- const top = screenHeight / 2 - height / 2;
19086
- if (!global2.window.open) {
19087
- throw new Error("window.open is not available");
19088
- }
19089
- const popupResult = global2.window.open(url, "uip_auth", `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes,status=yes`);
19090
- const popup = popupResult;
19091
- if (!popup) {
19092
- throw new Error(`Authentication popup was blocked by your browser.
19093
-
19094
- ` + `To continue:
19095
- ` + `1. Look for a popup blocker icon in your address bar
19096
- ` + `2. Allow popups for this site
19097
- ` + `3. Try logging in again
19098
-
19099
- ` + "If using an ad blocker, you may need to temporarily disable it.");
19100
- }
19101
- return new Promise((resolve2, reject) => {
19102
- const messageHandler = (event) => {
19103
- if (event.data?.type === "UIP_AUTH_CODE" && event.data.code) {
19104
- if (event.data.state !== expectedState) {
19105
- cleanup();
19106
- reject(new Error("OAuth state mismatch — the callback state does not match the expected value. " + "This may indicate a CSRF attack. Please try signing in again."));
19107
- popup.close();
19108
- return;
19109
- }
19110
- cleanup();
19111
- resolve2(event.data.code);
19112
- popup.close();
19113
- } else if (event.data?.type === "UIP_AUTH_ERROR") {
19114
- cleanup();
19115
- const errorMsg = event.data.error || "Authentication failed";
19116
- reject(new Error(`Authentication failed: ${errorMsg}
19117
-
19118
- ` + "Please check your credentials and try again. " + "If the problem persists, verify your UiPath account is active."));
19119
- popup.close();
19120
- }
19121
- };
19122
- const cleanup = () => {
19123
- global2.window?.removeEventListener?.("message", messageHandler);
19124
- if (timer)
19125
- clearInterval(timer);
19126
- };
19127
- if (global2.window?.addEventListener) {
19128
- global2.window.addEventListener("message", messageHandler);
19129
- }
19130
- const timer = setInterval(() => {
19131
- if (popup.closed) {
19132
- cleanup();
19133
- reject(new Error(`Authentication was cancelled.
19134
-
19135
- ` + "The authentication popup was closed before completing the login process. " + "Please try again and complete the authentication flow."));
19136
- }
19137
- }, 1000);
19138
- });
19139
- }
19140
- }
19141
- var init_browser_strategy = () => {};
19142
-
19143
19155
  // src/getBaseHtml.ts
19144
19156
  var getBaseHtml = ({ title, message, type }) => {
19145
19157
  const icon = type === "success" ? "✓" : "✕";
@@ -19286,7 +19298,7 @@ var getBaseHtml = ({ title, message, type }) => {
19286
19298
  };
19287
19299
 
19288
19300
  // src/server.ts
19289
- var startServer = async ({
19301
+ var AUTH_TIMEOUT_ERROR_CODE = "EAUTHTIMEOUT", startServer = async ({
19290
19302
  redirectUri,
19291
19303
  timeoutMs = DEFAULT_AUTH_TIMEOUT_MS,
19292
19304
  onListening
@@ -19357,7 +19369,18 @@ var startServer = async ({
19357
19369
  reject(new Error("No authorization code received"));
19358
19370
  return;
19359
19371
  });
19360
- server.listen(Number(redirectUri.port), redirectUri.hostname, () => {
19372
+ const timeoutHandle = setTimeout(() => {
19373
+ server.close();
19374
+ const err = new Error("Authentication timeout");
19375
+ err.code = AUTH_TIMEOUT_ERROR_CODE;
19376
+ reject(err);
19377
+ }, timeoutMs);
19378
+ const bindHost = redirectUri.hostname === "localhost" ? "127.0.0.1" : redirectUri.hostname;
19379
+ server.on("error", (err) => {
19380
+ clearTimeout(timeoutHandle);
19381
+ reject(err);
19382
+ });
19383
+ server.listen(Number(redirectUri.port), bindHost, () => {
19361
19384
  if (onListening) {
19362
19385
  Promise.resolve(onListening()).catch((err) => {
19363
19386
  server.close();
@@ -19366,10 +19389,6 @@ var startServer = async ({
19366
19389
  });
19367
19390
  }
19368
19391
  });
19369
- const timeoutHandle = setTimeout(() => {
19370
- server.close();
19371
- reject(new Error("Authentication timeout"));
19372
- }, timeoutMs);
19373
19392
  server.on("close", () => {
19374
19393
  clearTimeout(timeoutHandle);
19375
19394
  });
@@ -19379,6 +19398,81 @@ var init_server = __esm(() => {
19379
19398
  init_constants();
19380
19399
  });
19381
19400
 
19401
+ // src/strategies/browser-strategy.ts
19402
+ var exports_browser_strategy = {};
19403
+ __export(exports_browser_strategy, {
19404
+ BrowserAuthStrategy: () => BrowserAuthStrategy
19405
+ });
19406
+
19407
+ class BrowserAuthStrategy {
19408
+ async execute(url, _redirectUri, expectedState) {
19409
+ const global2 = getGlobalThis();
19410
+ if (!global2?.window) {
19411
+ throw new Error("Browser environment required for authentication");
19412
+ }
19413
+ const screenWidth = global2.window.screen?.width ?? 1024;
19414
+ const screenHeight = global2.window.screen?.height ?? 768;
19415
+ const width = 600;
19416
+ const height = 700;
19417
+ const left = screenWidth / 2 - width / 2;
19418
+ const top = screenHeight / 2 - height / 2;
19419
+ if (!global2.window.open) {
19420
+ throw new Error("window.open is not available");
19421
+ }
19422
+ const popupResult = global2.window.open(url, "uip_auth", `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes,status=yes`);
19423
+ const popup = popupResult;
19424
+ if (!popup) {
19425
+ throw new Error(`Authentication popup was blocked by your browser.
19426
+
19427
+ ` + `To continue:
19428
+ ` + `1. Look for a popup blocker icon in your address bar
19429
+ ` + `2. Allow popups for this site
19430
+ ` + `3. Try logging in again
19431
+
19432
+ ` + "If using an ad blocker, you may need to temporarily disable it.");
19433
+ }
19434
+ return new Promise((resolve2, reject) => {
19435
+ const messageHandler = (event) => {
19436
+ if (event.data?.type === "UIP_AUTH_CODE" && event.data.code) {
19437
+ if (event.data.state !== expectedState) {
19438
+ cleanup();
19439
+ reject(new Error("OAuth state mismatch — the callback state does not match the expected value. " + "This may indicate a CSRF attack. Please try signing in again."));
19440
+ popup.close();
19441
+ return;
19442
+ }
19443
+ cleanup();
19444
+ resolve2(event.data.code);
19445
+ popup.close();
19446
+ } else if (event.data?.type === "UIP_AUTH_ERROR") {
19447
+ cleanup();
19448
+ const errorMsg = event.data.error || "Authentication failed";
19449
+ reject(new Error(`Authentication failed: ${errorMsg}
19450
+
19451
+ ` + "Please check your credentials and try again. " + "If the problem persists, verify your UiPath account is active."));
19452
+ popup.close();
19453
+ }
19454
+ };
19455
+ const cleanup = () => {
19456
+ global2.window?.removeEventListener?.("message", messageHandler);
19457
+ if (timer)
19458
+ clearInterval(timer);
19459
+ };
19460
+ if (global2.window?.addEventListener) {
19461
+ global2.window.addEventListener("message", messageHandler);
19462
+ }
19463
+ const timer = setInterval(() => {
19464
+ if (popup.closed) {
19465
+ cleanup();
19466
+ reject(new Error(`Authentication was cancelled.
19467
+
19468
+ ` + "The authentication popup was closed before completing the login process. " + "Please try again and complete the authentication flow."));
19469
+ }
19470
+ }, 1000);
19471
+ });
19472
+ }
19473
+ }
19474
+ var init_browser_strategy = () => {};
19475
+
19382
19476
  // src/strategies/node-strategy.ts
19383
19477
  var exports_node_strategy = {};
19384
19478
  __export(exports_node_strategy, {
@@ -19457,30 +19551,7 @@ class InvalidBaseUrlError extends Error {
19457
19551
  this.name = "InvalidBaseUrlError";
19458
19552
  }
19459
19553
  }
19460
- var DEFAULT_SCOPES = [
19461
- "offline_access",
19462
- "ProcessMining",
19463
- "OrchestratorApiUserAccess",
19464
- "StudioWebBackend",
19465
- "IdentityServerApi",
19466
- "ConnectionService",
19467
- "DataService",
19468
- "DataServiceApiUserAccess",
19469
- "DocumentUnderstanding",
19470
- "EnterpriseContextService",
19471
- "Directory",
19472
- "JamJamApi",
19473
- "LLMGateway",
19474
- "LLMOps",
19475
- "OMS",
19476
- "RCS.FolderAuthorization",
19477
- "RCS.TagsManagement",
19478
- "TestmanagerApiUserAccess",
19479
- "AutomationSolutions",
19480
- "StudioWebTypeCacheService",
19481
- "Docs.GPT.Search",
19482
- "Insights"
19483
- ];
19554
+ var DEFAULT_SCOPES = ["openid", "profile", "offline_access"];
19484
19555
  var normalizeAndValidateBaseUrl = (rawUrl) => {
19485
19556
  let baseUrl = rawUrl;
19486
19557
  if (baseUrl.endsWith("/identity_/")) {
@@ -19530,7 +19601,8 @@ var resolveConfigAsync = async ({
19530
19601
  if (!clientSecret && fileAuth.clientSecret) {
19531
19602
  clientSecret = fileAuth.clientSecret;
19532
19603
  }
19533
- const scopes = customScopes && customScopes.length > 0 ? customScopes : fileAuth.scopes && fileAuth.scopes.length > 0 ? fileAuth.scopes : DEFAULT_SCOPES;
19604
+ const isExternalAppAuth = clientId !== DEFAULT_CLIENT_ID && Boolean(clientSecret);
19605
+ const scopes = customScopes && customScopes.length > 0 ? customScopes : fileAuth.scopes && fileAuth.scopes.length > 0 ? fileAuth.scopes : isExternalAppAuth ? [] : DEFAULT_SCOPES;
19534
19606
  return {
19535
19607
  clientId,
19536
19608
  clientSecret,
@@ -20250,6 +20322,7 @@ var getTokenExpiration = (accessToken) => {
20250
20322
 
20251
20323
  // src/envAuth.ts
20252
20324
  var ENV_AUTH_ENABLE_VAR = "UIPATH_CLI_ENABLE_ENV_AUTH";
20325
+ var ENFORCE_ROBOT_AUTH_VAR = "UIPATH_CLI_ENFORCE_ROBOT_AUTH";
20253
20326
  var ENV_AUTH_VARS = {
20254
20327
  token: "UIPATH_CLI_AUTH_TOKEN",
20255
20328
  organizationName: "UIPATH_CLI_ORGANIZATION_NAME",
@@ -20265,6 +20338,7 @@ class EnvAuthConfigError extends Error {
20265
20338
  }
20266
20339
  }
20267
20340
  var isEnvAuthEnabled = () => process.env[ENV_AUTH_ENABLE_VAR] === "true";
20341
+ var isRobotAuthEnforced = () => process.env[ENFORCE_ROBOT_AUTH_VAR] === "true";
20268
20342
  var requireEnv = (name) => {
20269
20343
  const value = process.env[name];
20270
20344
  if (!value) {
@@ -20306,7 +20380,9 @@ var readAuthFromEnv = () => {
20306
20380
  expiration
20307
20381
  };
20308
20382
  };
20383
+
20309
20384
  // src/robotClientFallback.ts
20385
+ init_src();
20310
20386
  var DEFAULT_TIMEOUT_MS = 1000;
20311
20387
  var CLOSE_TIMEOUT_MS = 500;
20312
20388
  var NOTICE_SENTINEL = Symbol.for("@uipath/auth/robotFallbackNoticePrinted");
@@ -20318,6 +20394,35 @@ var printNoticeOnce = () => {
20318
20394
  catchError(() => process.stderr.write(`Using UiPath Robot credentials. Run 'uip login' for a dedicated session.
20319
20395
  `));
20320
20396
  };
20397
+ var ROBOT_USER_SERVICES_PIPE = "UiPathUserServices";
20398
+ var ROBOT_USER_SERVICES_ALTERNATE_PIPE = `${ROBOT_USER_SERVICES_PIPE}Alternate`;
20399
+ var PIPE_NAME_MAX_LENGTH = 103;
20400
+ var getRobotIpcPipeNames = async () => {
20401
+ const fs7 = getFileSystem();
20402
+ const username = fs7.env.getenv("USER") ?? fs7.env.getenv("USERNAME");
20403
+ if (!username) {
20404
+ throw new Error("Unable to determine current username");
20405
+ }
20406
+ const tempPath = fs7.env.getenv("TMPDIR") ?? "/tmp/";
20407
+ return [ROBOT_USER_SERVICES_PIPE, ROBOT_USER_SERVICES_ALTERNATE_PIPE].map((baseName) => fs7.path.join(tempPath, `${baseName}_${username}`).substring(0, PIPE_NAME_MAX_LENGTH));
20408
+ };
20409
+ var defaultIsRobotIpcAvailable = async () => {
20410
+ if (process.platform === "win32") {
20411
+ return true;
20412
+ }
20413
+ const [pipeNamesError, pipeNames] = await catchError(getRobotIpcPipeNames());
20414
+ if (pipeNamesError || !pipeNames) {
20415
+ return false;
20416
+ }
20417
+ const fs7 = getFileSystem();
20418
+ for (const pipeName of pipeNames) {
20419
+ const [existsError, exists] = await catchError(fs7.exists(pipeName));
20420
+ if (!existsError && exists === true) {
20421
+ return true;
20422
+ }
20423
+ }
20424
+ return false;
20425
+ };
20321
20426
  var withTimeout = (promise, timeoutMs) => new Promise((resolve2, reject) => {
20322
20427
  const timer = setTimeout(() => reject(new Error(`Robot IPC call timed out after ${timeoutMs}ms`)), timeoutMs);
20323
20428
  promise.then((value) => {
@@ -20349,14 +20454,20 @@ var defaultLoadModule = async () => {
20349
20454
  var tryRobotClientFallback = async (options = {}) => {
20350
20455
  if (isBrowser())
20351
20456
  return;
20352
- if (process.env.CI || process.env.GITHUB_ACTIONS) {
20353
- return;
20354
- }
20355
- if (process.env.UIPATH_URL) {
20356
- return;
20457
+ if (!options.force) {
20458
+ if (process.env.CI || process.env.GITHUB_ACTIONS) {
20459
+ return;
20460
+ }
20461
+ if (process.env.UIPATH_URL) {
20462
+ return;
20463
+ }
20357
20464
  }
20358
20465
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
20466
+ const isRobotIpcAvailable = options.isRobotIpcAvailable ?? defaultIsRobotIpcAvailable;
20359
20467
  const loadModule = options.loadModule ?? defaultLoadModule;
20468
+ if (!await isRobotIpcAvailable()) {
20469
+ return;
20470
+ }
20360
20471
  const mod = await loadModule();
20361
20472
  if (!mod)
20362
20473
  return;
@@ -20629,11 +20740,130 @@ function normalizeTokenRefreshFailure() {
20629
20740
  function normalizeTokenRefreshUnavailableFailure() {
20630
20741
  return "token refresh failed before authentication completed";
20631
20742
  }
20632
- var getLoginStatusWithDeps = async (options = {}, deps = {}) => {
20633
- if (isEnvAuthEnabled()) {
20634
- return readAuthFromEnv();
20743
+ function errorMessage(error) {
20744
+ return error instanceof Error ? error.message : String(error);
20745
+ }
20746
+ function computeExpirationThreshold(ensureTokenValidityMinutes) {
20747
+ return new Date(Date.now() + (ensureTokenValidityMinutes ?? 0) * 60 * 1000);
20748
+ }
20749
+ async function runRefreshLocked(inputs) {
20750
+ const {
20751
+ absolutePath,
20752
+ refreshToken: callerRefreshToken,
20753
+ customAuthority,
20754
+ ensureTokenValidityMinutes,
20755
+ loadEnvFile,
20756
+ saveEnvFile,
20757
+ refreshFn,
20758
+ resolveConfig
20759
+ } = inputs;
20760
+ const expirationThreshold = computeExpirationThreshold(ensureTokenValidityMinutes);
20761
+ let fresh;
20762
+ try {
20763
+ fresh = await loadEnvFile({ envPath: absolutePath });
20764
+ } catch (error) {
20765
+ return {
20766
+ kind: "fail",
20767
+ status: {
20768
+ loginStatus: "Refresh Failed",
20769
+ hint: "Could not read the auth file while refreshing. Retry, or run 'uip login' to re-authenticate.",
20770
+ tokenRefresh: {
20771
+ attempted: false,
20772
+ success: false,
20773
+ errorMessage: `auth file read failed: ${errorMessage(error)}`
20774
+ }
20775
+ }
20776
+ };
20635
20777
  }
20636
- const { envFilePath = DEFAULT_ENV_FILENAME, ensureTokenValidityMinutes } = options;
20778
+ const freshAccess = fresh.UIPATH_ACCESS_TOKEN;
20779
+ const freshExp = freshAccess ? getTokenExpiration(freshAccess) : undefined;
20780
+ if (freshAccess && freshExp && freshExp > expirationThreshold) {
20781
+ return {
20782
+ kind: "ok",
20783
+ accessToken: freshAccess,
20784
+ refreshToken: fresh.UIPATH_REFRESH_TOKEN ?? callerRefreshToken,
20785
+ expiration: freshExp,
20786
+ tokenRefresh: { attempted: false, success: true }
20787
+ };
20788
+ }
20789
+ const tokenForIdP = fresh.UIPATH_REFRESH_TOKEN ?? callerRefreshToken;
20790
+ let refreshedAccess;
20791
+ let refreshedRefresh;
20792
+ try {
20793
+ const config = await resolveConfig({ customAuthority });
20794
+ const refreshed = await refreshFn({
20795
+ refreshToken: tokenForIdP,
20796
+ tokenEndpoint: config.tokenEndpoint,
20797
+ clientId: config.clientId,
20798
+ expectedAuthority: customAuthority
20799
+ });
20800
+ refreshedAccess = refreshed.accessToken;
20801
+ refreshedRefresh = refreshed.refreshToken;
20802
+ } catch (error) {
20803
+ const isOAuthFailure = isTokenRefreshOAuthFailure(error);
20804
+ const hint = isOAuthFailure ? "Run 'uip login' to re-authenticate — the stored refresh token is invalid or expired." : "Token refresh failed. Check your network connection, then retry or run 'uip login' to re-authenticate.";
20805
+ const message = isOAuthFailure ? normalizeTokenRefreshFailure() : normalizeTokenRefreshUnavailableFailure();
20806
+ return {
20807
+ kind: "fail",
20808
+ status: {
20809
+ loginStatus: "Refresh Failed",
20810
+ hint,
20811
+ tokenRefresh: {
20812
+ attempted: true,
20813
+ success: false,
20814
+ errorMessage: message
20815
+ }
20816
+ }
20817
+ };
20818
+ }
20819
+ const refreshedExp = getTokenExpiration(refreshedAccess);
20820
+ if (!refreshedExp || refreshedExp <= new Date) {
20821
+ return {
20822
+ kind: "fail",
20823
+ status: {
20824
+ loginStatus: "Refresh Failed",
20825
+ hint: "The identity server returned an unusable token. Run 'uip login' to re-authenticate.",
20826
+ tokenRefresh: {
20827
+ attempted: true,
20828
+ success: false,
20829
+ errorMessage: "refreshed token has no valid expiration claim"
20830
+ }
20831
+ }
20832
+ };
20833
+ }
20834
+ try {
20835
+ await saveEnvFile({
20836
+ envPath: absolutePath,
20837
+ data: {
20838
+ UIPATH_ACCESS_TOKEN: refreshedAccess,
20839
+ UIPATH_REFRESH_TOKEN: refreshedRefresh
20840
+ },
20841
+ merge: true
20842
+ });
20843
+ return {
20844
+ kind: "ok",
20845
+ accessToken: refreshedAccess,
20846
+ refreshToken: refreshedRefresh,
20847
+ expiration: refreshedExp,
20848
+ tokenRefresh: { attempted: true, success: true }
20849
+ };
20850
+ } catch (error) {
20851
+ const msg = errorMessage(error);
20852
+ return {
20853
+ kind: "ok",
20854
+ accessToken: refreshedAccess,
20855
+ refreshToken: refreshedRefresh,
20856
+ expiration: refreshedExp,
20857
+ persistenceWarning: `Access token refreshed in memory but could not be written to ${absolutePath}: ${msg}. The next CLI invocation will fail until the file can be updated — run 'uip login' to re-authenticate.`,
20858
+ tokenRefresh: {
20859
+ attempted: true,
20860
+ success: true,
20861
+ errorMessage: `persistence failed: ${msg}`
20862
+ }
20863
+ };
20864
+ }
20865
+ }
20866
+ var getLoginStatusWithDeps = async (options = {}, deps = {}) => {
20637
20867
  const {
20638
20868
  resolveEnvFilePath = resolveEnvFilePathAsync,
20639
20869
  loadEnvFile = loadEnvFileAsync,
@@ -20643,6 +20873,34 @@ var getLoginStatusWithDeps = async (options = {}, deps = {}) => {
20643
20873
  resolveConfig = resolveConfigAsync,
20644
20874
  robotFallback = tryRobotClientFallback
20645
20875
  } = deps;
20876
+ if (isRobotAuthEnforced()) {
20877
+ if (isEnvAuthEnabled()) {
20878
+ throw new EnvAuthConfigError(`${ENV_AUTH_ENABLE_VAR}=true and ${ENFORCE_ROBOT_AUTH_VAR}=true ` + `are mutually exclusive. Unset one of them and re-run.`);
20879
+ }
20880
+ const robotCreds = await robotFallback({ force: true });
20881
+ if (!robotCreds) {
20882
+ return {
20883
+ loginStatus: "Not logged in",
20884
+ hint: `${ENFORCE_ROBOT_AUTH_VAR}=true but the UiPath Robot ` + `session is unavailable. Start and sign in to the Assistant, ` + `or unset ${ENFORCE_ROBOT_AUTH_VAR} to fall back to file or ` + `env-var authentication.`
20885
+ };
20886
+ }
20887
+ const expiration2 = getTokenExpiration(robotCreds.accessToken);
20888
+ return {
20889
+ loginStatus: "Logged in",
20890
+ accessToken: robotCreds.accessToken,
20891
+ baseUrl: robotCreds.baseUrl,
20892
+ organizationName: robotCreds.organizationName,
20893
+ organizationId: robotCreds.organizationId,
20894
+ tenantName: robotCreds.tenantName,
20895
+ tenantId: robotCreds.tenantId,
20896
+ expiration: expiration2,
20897
+ source: "robot" /* Robot */
20898
+ };
20899
+ }
20900
+ if (isEnvAuthEnabled()) {
20901
+ return readAuthFromEnv();
20902
+ }
20903
+ const { envFilePath = DEFAULT_ENV_FILENAME, ensureTokenValidityMinutes } = options;
20646
20904
  const { absolutePath } = await resolveEnvFilePath(envFilePath);
20647
20905
  if (absolutePath === undefined) {
20648
20906
  const robotCreds = await robotFallback();
@@ -20679,73 +20937,103 @@ var getLoginStatusWithDeps = async (options = {}, deps = {}) => {
20679
20937
  let refreshToken = credentials.UIPATH_REFRESH_TOKEN;
20680
20938
  let expiration = getTokenExpiration(accessToken);
20681
20939
  let persistenceWarning;
20940
+ let lockReleaseFailed = false;
20682
20941
  let tokenRefresh;
20683
- const expirationThreshold = new Date(Date.now() + (ensureTokenValidityMinutes ?? 0) * 60 * 1000);
20684
- if (expiration && expiration <= expirationThreshold && refreshToken) {
20685
- let refreshedAccess;
20686
- let refreshedRefresh;
20942
+ const outerThreshold = computeExpirationThreshold(ensureTokenValidityMinutes);
20943
+ const tryGlobalCredsHint = async () => {
20944
+ const fs7 = getFs();
20945
+ const globalPath = fs7.path.join(fs7.env.homedir(), envFilePath);
20946
+ if (absolutePath === globalPath)
20947
+ return;
20948
+ if (!await fs7.exists(globalPath))
20949
+ return;
20687
20950
  try {
20688
- const config = await resolveConfig({
20689
- customAuthority: credentials.UIPATH_URL
20690
- });
20691
- const refreshed = await refreshTokenFn({
20692
- refreshToken,
20693
- tokenEndpoint: config.tokenEndpoint,
20694
- clientId: config.clientId,
20695
- expectedAuthority: credentials.UIPATH_URL
20696
- });
20697
- refreshedAccess = refreshed.accessToken;
20698
- refreshedRefresh = refreshed.refreshToken;
20699
- } catch (error) {
20700
- const isOAuthFailure = isTokenRefreshOAuthFailure(error);
20701
- const hint = isOAuthFailure ? "Run 'uip login' to re-authenticate — the stored refresh token is invalid or expired." : "Token refresh failed. Check your network connection, then retry or run 'uip login' to re-authenticate.";
20702
- const errorMessage = isOAuthFailure ? normalizeTokenRefreshFailure() : normalizeTokenRefreshUnavailableFailure();
20703
- return {
20704
- loginStatus: "Refresh Failed",
20705
- hint,
20706
- tokenRefresh: {
20707
- attempted: true,
20708
- success: false,
20709
- errorMessage
20710
- }
20711
- };
20951
+ const globalCreds = await loadEnvFile({ envPath: globalPath });
20952
+ if (!globalCreds.UIPATH_ACCESS_TOKEN)
20953
+ return;
20954
+ const globalExp = getTokenExpiration(globalCreds.UIPATH_ACCESS_TOKEN);
20955
+ if (globalExp && globalExp <= new Date)
20956
+ return;
20957
+ return `Local credentials file at ${absolutePath} has expired credentials. Valid credentials exist in ${globalPath}. Remove the local file or run 'uip login' to re-authenticate.`;
20958
+ } catch {
20959
+ return;
20712
20960
  }
20713
- const refreshedExp = getTokenExpiration(refreshedAccess);
20714
- if (!refreshedExp || refreshedExp <= new Date) {
20961
+ };
20962
+ if (expiration && expiration <= outerThreshold && refreshToken) {
20963
+ let release;
20964
+ try {
20965
+ release = await getFs().acquireLock(absolutePath);
20966
+ } catch (error) {
20967
+ const msg = errorMessage(error);
20968
+ const globalHint = await tryGlobalCredsHint();
20969
+ if (globalHint) {
20970
+ return {
20971
+ loginStatus: "Expired",
20972
+ accessToken,
20973
+ refreshToken,
20974
+ baseUrl: credentials.UIPATH_URL,
20975
+ organizationName: credentials.UIPATH_ORGANIZATION_NAME,
20976
+ organizationId: credentials.UIPATH_ORGANIZATION_ID,
20977
+ tenantName: credentials.UIPATH_TENANT_NAME,
20978
+ tenantId: credentials.UIPATH_TENANT_ID,
20979
+ expiration,
20980
+ source: "file" /* File */,
20981
+ hint: globalHint,
20982
+ tokenRefresh: {
20983
+ attempted: false,
20984
+ success: false,
20985
+ errorMessage: `lock acquisition failed: ${msg}`
20986
+ }
20987
+ };
20988
+ }
20715
20989
  return {
20716
20990
  loginStatus: "Refresh Failed",
20717
- hint: "The identity server returned an unusable token. Run 'uip login' to re-authenticate.",
20991
+ hint: "Could not acquire the auth-file lock — too many concurrent `uip` processes, or a permission issue on the auth directory. Retry, or run 'uip login' to re-authenticate.",
20718
20992
  tokenRefresh: {
20719
- attempted: true,
20993
+ attempted: false,
20720
20994
  success: false,
20721
- errorMessage: "refreshed token has no valid expiration claim"
20995
+ errorMessage: `lock acquisition failed: ${msg}`
20722
20996
  }
20723
20997
  };
20724
20998
  }
20725
- accessToken = refreshedAccess;
20726
- refreshToken = refreshedRefresh;
20727
- expiration = refreshedExp;
20999
+ let lockedFailure;
20728
21000
  try {
20729
- await saveEnvFile({
20730
- envPath: absolutePath,
20731
- data: {
20732
- UIPATH_ACCESS_TOKEN: accessToken,
20733
- UIPATH_REFRESH_TOKEN: refreshToken
20734
- },
20735
- merge: true
21001
+ const outcome = await runRefreshLocked({
21002
+ absolutePath,
21003
+ refreshToken,
21004
+ customAuthority: credentials.UIPATH_URL,
21005
+ ensureTokenValidityMinutes,
21006
+ loadEnvFile,
21007
+ saveEnvFile,
21008
+ refreshFn: refreshTokenFn,
21009
+ resolveConfig
20736
21010
  });
20737
- tokenRefresh = {
20738
- attempted: true,
20739
- success: true
20740
- };
20741
- } catch (error) {
20742
- const msg = error instanceof Error ? error.message : String(error);
20743
- persistenceWarning = `Access token refreshed in memory but could not be written to ${absolutePath}: ${msg}. The next CLI invocation will fail until the file can be updated — run 'uip login' to re-authenticate.`;
20744
- tokenRefresh = {
20745
- attempted: true,
20746
- success: true,
20747
- errorMessage: `persistence failed: ${msg}`
20748
- };
21011
+ if (outcome.kind === "fail") {
21012
+ lockedFailure = outcome.status;
21013
+ } else {
21014
+ accessToken = outcome.accessToken;
21015
+ refreshToken = outcome.refreshToken;
21016
+ expiration = outcome.expiration;
21017
+ tokenRefresh = outcome.tokenRefresh;
21018
+ if (outcome.persistenceWarning) {
21019
+ persistenceWarning = outcome.persistenceWarning;
21020
+ }
21021
+ }
21022
+ } finally {
21023
+ try {
21024
+ await release();
21025
+ } catch {
21026
+ lockReleaseFailed = true;
21027
+ }
21028
+ }
21029
+ if (lockedFailure) {
21030
+ const globalHint = await tryGlobalCredsHint();
21031
+ const base = globalHint ? {
21032
+ ...lockedFailure,
21033
+ loginStatus: "Expired",
21034
+ hint: globalHint
21035
+ } : lockedFailure;
21036
+ return lockReleaseFailed ? { ...base, lockReleaseFailed: true } : base;
20749
21037
  }
20750
21038
  }
20751
21039
  const result = {
@@ -20760,23 +21048,13 @@ var getLoginStatusWithDeps = async (options = {}, deps = {}) => {
20760
21048
  expiration,
20761
21049
  source: "file" /* File */,
20762
21050
  ...persistenceWarning ? { hint: persistenceWarning, persistenceFailed: true } : {},
21051
+ ...lockReleaseFailed ? { lockReleaseFailed: true } : {},
20763
21052
  ...tokenRefresh ? { tokenRefresh } : {}
20764
21053
  };
20765
21054
  if (result.loginStatus === "Expired") {
20766
- const fs7 = getFs();
20767
- const globalPath = fs7.path.join(fs7.env.homedir(), envFilePath);
20768
- if (absolutePath !== globalPath && await fs7.exists(globalPath)) {
20769
- try {
20770
- const globalCreds = await loadEnvFile({
20771
- envPath: globalPath
20772
- });
20773
- if (globalCreds.UIPATH_ACCESS_TOKEN) {
20774
- const globalExp = getTokenExpiration(globalCreds.UIPATH_ACCESS_TOKEN);
20775
- if (!globalExp || globalExp > new Date) {
20776
- result.hint = `Local credentials file at ${absolutePath} has expired credentials. Valid credentials exist in ${globalPath}. Remove the local file or run 'uip login' to re-authenticate.`;
20777
- }
20778
- }
20779
- } catch {}
21055
+ const globalHint = await tryGlobalCredsHint();
21056
+ if (globalHint) {
21057
+ result.hint = globalHint;
20780
21058
  }
20781
21059
  }
20782
21060
  return result;
@@ -20809,7 +21087,7 @@ var getAuthContext = async (options = {}) => {
20809
21087
  throw new Error("Tenant ID not available. Ensure UIPATH_TENANT_ID is set.");
20810
21088
  }
20811
21089
  if (options.requireTenantName && tenantName === undefined) {
20812
- throw new Error("Tenant not provided and UIPATH_TENANT_NAME not set. Please provide a tenant argument or set UIPATH_TENANT_NAME.");
21090
+ throw new Error("Tenant not provided and UIPATH_TENANT_NAME not set. Run 'uip login' to select a tenant, or use 'uip login tenant set <tenant>' to switch tenants.");
20813
21091
  }
20814
21092
  return {
20815
21093
  baseUrl: status.baseUrl,
@@ -20836,9 +21114,11 @@ var clientCredentialsLogin = async ({
20836
21114
  const params = {
20837
21115
  grant_type: "client_credentials",
20838
21116
  client_id: config.clientId,
20839
- client_secret: config.clientSecret ?? "",
20840
- scope: config.scopes.join(" ")
21117
+ client_secret: config.clientSecret ?? ""
20841
21118
  };
21119
+ if (config.scopes.length > 0) {
21120
+ params.scope = config.scopes.join(" ");
21121
+ }
20842
21122
  const tokenResponse = await fetch(config.tokenEndpoint, {
20843
21123
  method: "POST",
20844
21124
  headers: {
@@ -21001,29 +21281,24 @@ var interactiveLoginWithDeps = async (options, deps) => {
21001
21281
  try {
21002
21282
  const tokenData = jwtParser(tokens.UIPATH_ACCESS_TOKEN);
21003
21283
  const orgId = tokenData.prtId || tokenData.organizationId || tokenData.prt_id;
21004
- if (typeof orgId === "string") {
21005
- credentials.UIPATH_ORGANIZATION_ID = orgId;
21006
- if (organization) {
21007
- credentials.UIPATH_ORGANIZATION_NAME = organization;
21008
- }
21009
- try {
21010
- const [tenantName, tenantId, orgName] = await tenantSelector(config.baseUrl, tokens.UIPATH_ACCESS_TOKEN, orgId, tenant, interactive);
21011
- credentials.UIPATH_ORGANIZATION_NAME = orgName;
21012
- credentials.UIPATH_TENANT_NAME = tenantName;
21013
- credentials.UIPATH_TENANT_ID = tenantId;
21014
- } catch (error) {
21015
- credentials.UIPATH_TENANT_NAME = undefined;
21016
- credentials.UIPATH_TENANT_ID = undefined;
21017
- if (!organization) {
21018
- credentials.UIPATH_ORGANIZATION_NAME = undefined;
21019
- }
21020
- emit({
21021
- type: "tenant-fetch-failed",
21022
- message: error instanceof Error ? error.message : String(error)
21023
- });
21024
- }
21284
+ if (typeof orgId !== "string" || orgId.length === 0) {
21285
+ throw new Error("Organization ID not available from login token. Cannot establish an active tenant.");
21025
21286
  }
21026
- } catch {}
21287
+ credentials.UIPATH_ORGANIZATION_ID = orgId;
21288
+ if (organization) {
21289
+ credentials.UIPATH_ORGANIZATION_NAME = organization;
21290
+ }
21291
+ const [tenantName, tenantId, orgName] = await tenantSelector(config.baseUrl, tokens.UIPATH_ACCESS_TOKEN, orgId, tenant, interactive);
21292
+ credentials.UIPATH_ORGANIZATION_NAME = orgName;
21293
+ credentials.UIPATH_TENANT_NAME = tenantName;
21294
+ credentials.UIPATH_TENANT_ID = tenantId;
21295
+ } catch (error) {
21296
+ emit({
21297
+ type: "tenant-fetch-failed",
21298
+ message: error instanceof Error ? error.message : String(error)
21299
+ });
21300
+ throw error;
21301
+ }
21027
21302
  const fs7 = getFs();
21028
21303
  const requestedPath = envFilePath ?? DEFAULT_ENV_FILENAME;
21029
21304
  let savePath = requestedPath;
@@ -21051,11 +21326,25 @@ var interactiveLoginWithDeps = async (options, deps) => {
21051
21326
  searchDir = parentDir;
21052
21327
  }
21053
21328
  }
21054
- await saveEnvFile({
21055
- envPath: savePath,
21056
- data: credentials,
21057
- merge: true
21058
- });
21329
+ let saveRelease;
21330
+ try {
21331
+ if (typeof fs7.acquireLock === "function") {
21332
+ saveRelease = await fs7.acquireLock(savePath);
21333
+ }
21334
+ } catch {
21335
+ saveRelease = undefined;
21336
+ }
21337
+ try {
21338
+ await saveEnvFile({
21339
+ envPath: savePath,
21340
+ data: credentials,
21341
+ merge: true
21342
+ });
21343
+ } finally {
21344
+ if (saveRelease) {
21345
+ await saveRelease().catch(() => {});
21346
+ }
21347
+ }
21059
21348
  const reportedPath = fs7.path.isAbsolute(savePath) ? savePath : fs7.path.join(fs7.env.homedir(), savePath);
21060
21349
  emit({
21061
21350
  type: "saved",
@@ -21077,7 +21366,21 @@ async function logoutWithDeps(options, deps = {}) {
21077
21366
  const fs7 = getFs();
21078
21367
  const { absolutePath } = await resolveEnvFilePath(options.file);
21079
21368
  if (absolutePath && await fs7.exists(absolutePath)) {
21080
- await fs7.rm(absolutePath);
21369
+ let release;
21370
+ try {
21371
+ if (typeof fs7.acquireLock === "function") {
21372
+ release = await fs7.acquireLock(absolutePath);
21373
+ }
21374
+ } catch {
21375
+ release = undefined;
21376
+ }
21377
+ try {
21378
+ await fs7.rm(absolutePath);
21379
+ } finally {
21380
+ if (release) {
21381
+ await release().catch(() => {});
21382
+ }
21383
+ }
21081
21384
  return {
21082
21385
  success: true,
21083
21386
  message: `Logged out successfully. Removed ${absolutePath}`,
@@ -21095,6 +21398,7 @@ async function logout(options) {
21095
21398
  }
21096
21399
 
21097
21400
  // src/index.ts
21401
+ init_server();
21098
21402
  var authenticate = async ({
21099
21403
  baseUrl,
21100
21404
  clientId,
@@ -21174,6 +21478,7 @@ export {
21174
21478
  logout,
21175
21479
  loadEnvFileAsync,
21176
21480
  isTokenRefreshOAuthFailure,
21481
+ isRobotAuthEnforced,
21177
21482
  isNode,
21178
21483
  isEnvAuthEnabled,
21179
21484
  isBrowser,
@@ -21191,6 +21496,8 @@ export {
21191
21496
  EnvAuthConfigError,
21192
21497
  ENV_AUTH_VARS,
21193
21498
  ENV_AUTH_ENABLE_VAR,
21499
+ ENFORCE_ROBOT_AUTH_VAR,
21194
21500
  DEFAULT_ENV_FILENAME,
21195
- DEFAULT_AUTH_FILENAME
21501
+ DEFAULT_AUTH_FILENAME,
21502
+ AUTH_TIMEOUT_ERROR_CODE
21196
21503
  };