@uipath/auth 1.1.0 → 1.196.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,9 +939,13 @@ 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
  });
@@ -870,7 +959,7 @@ var init_src = __esm(() => {
870
959
 
871
960
  // ../../node_modules/@uipath/coreipc/index.js
872
961
  var require_coreipc = __commonJS((exports, module) => {
873
- var __dirname = "/Users/alexandru.oltean/github/cli/node_modules/@uipath/coreipc";
962
+ var __dirname = "/home/runner/work/cli/cli/node_modules/@uipath/coreipc";
874
963
  /*! For license information please see index.js.LICENSE.txt */
875
964
  (function(e, t) {
876
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();
@@ -19063,81 +19152,6 @@ var require_dist = __commonJS((exports) => {
19063
19152
  __exportStar(require_agent(), exports);
19064
19153
  });
19065
19154
 
19066
- // src/strategies/browser-strategy.ts
19067
- var exports_browser_strategy = {};
19068
- __export(exports_browser_strategy, {
19069
- BrowserAuthStrategy: () => BrowserAuthStrategy
19070
- });
19071
-
19072
- class BrowserAuthStrategy {
19073
- async execute(url, _redirectUri, expectedState) {
19074
- const global2 = getGlobalThis();
19075
- if (!global2?.window) {
19076
- throw new Error("Browser environment required for authentication");
19077
- }
19078
- const screenWidth = global2.window.screen?.width ?? 1024;
19079
- const screenHeight = global2.window.screen?.height ?? 768;
19080
- const width = 600;
19081
- const height = 700;
19082
- const left = screenWidth / 2 - width / 2;
19083
- const top = screenHeight / 2 - height / 2;
19084
- if (!global2.window.open) {
19085
- throw new Error("window.open is not available");
19086
- }
19087
- const popupResult = global2.window.open(url, "uip_auth", `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes,status=yes`);
19088
- const popup = popupResult;
19089
- if (!popup) {
19090
- throw new Error(`Authentication popup was blocked by your browser.
19091
-
19092
- ` + `To continue:
19093
- ` + `1. Look for a popup blocker icon in your address bar
19094
- ` + `2. Allow popups for this site
19095
- ` + `3. Try logging in again
19096
-
19097
- ` + "If using an ad blocker, you may need to temporarily disable it.");
19098
- }
19099
- return new Promise((resolve2, reject) => {
19100
- const messageHandler = (event) => {
19101
- if (event.data?.type === "UIP_AUTH_CODE" && event.data.code) {
19102
- if (event.data.state !== expectedState) {
19103
- cleanup();
19104
- 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."));
19105
- popup.close();
19106
- return;
19107
- }
19108
- cleanup();
19109
- resolve2(event.data.code);
19110
- popup.close();
19111
- } else if (event.data?.type === "UIP_AUTH_ERROR") {
19112
- cleanup();
19113
- const errorMsg = event.data.error || "Authentication failed";
19114
- reject(new Error(`Authentication failed: ${errorMsg}
19115
-
19116
- ` + "Please check your credentials and try again. " + "If the problem persists, verify your UiPath account is active."));
19117
- popup.close();
19118
- }
19119
- };
19120
- const cleanup = () => {
19121
- global2.window?.removeEventListener?.("message", messageHandler);
19122
- if (timer)
19123
- clearInterval(timer);
19124
- };
19125
- if (global2.window?.addEventListener) {
19126
- global2.window.addEventListener("message", messageHandler);
19127
- }
19128
- const timer = setInterval(() => {
19129
- if (popup.closed) {
19130
- cleanup();
19131
- reject(new Error(`Authentication was cancelled.
19132
-
19133
- ` + "The authentication popup was closed before completing the login process. " + "Please try again and complete the authentication flow."));
19134
- }
19135
- }, 1000);
19136
- });
19137
- }
19138
- }
19139
- var init_browser_strategy = () => {};
19140
-
19141
19155
  // src/getBaseHtml.ts
19142
19156
  var getBaseHtml = ({ title, message, type }) => {
19143
19157
  const icon = type === "success" ? "✓" : "✕";
@@ -19284,7 +19298,7 @@ var getBaseHtml = ({ title, message, type }) => {
19284
19298
  };
19285
19299
 
19286
19300
  // src/server.ts
19287
- var startServer = async ({
19301
+ var AUTH_TIMEOUT_ERROR_CODE = "EAUTHTIMEOUT", startServer = async ({
19288
19302
  redirectUri,
19289
19303
  timeoutMs = DEFAULT_AUTH_TIMEOUT_MS,
19290
19304
  onListening
@@ -19355,7 +19369,18 @@ var startServer = async ({
19355
19369
  reject(new Error("No authorization code received"));
19356
19370
  return;
19357
19371
  });
19358
- 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, () => {
19359
19384
  if (onListening) {
19360
19385
  Promise.resolve(onListening()).catch((err) => {
19361
19386
  server.close();
@@ -19364,10 +19389,6 @@ var startServer = async ({
19364
19389
  });
19365
19390
  }
19366
19391
  });
19367
- const timeoutHandle = setTimeout(() => {
19368
- server.close();
19369
- reject(new Error("Authentication timeout"));
19370
- }, timeoutMs);
19371
19392
  server.on("close", () => {
19372
19393
  clearTimeout(timeoutHandle);
19373
19394
  });
@@ -19377,6 +19398,81 @@ var init_server = __esm(() => {
19377
19398
  init_constants();
19378
19399
  });
19379
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, _opts) {
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
+
19380
19476
  // src/strategies/node-strategy.ts
19381
19477
  var exports_node_strategy = {};
19382
19478
  __export(exports_node_strategy, {
@@ -19384,10 +19480,11 @@ __export(exports_node_strategy, {
19384
19480
  });
19385
19481
 
19386
19482
  class NodeAuthStrategy {
19387
- async execute(url, redirectUri, expectedState) {
19483
+ async execute(url, redirectUri, expectedState, opts) {
19388
19484
  const fs7 = getFileSystem();
19389
19485
  const callbackUrl = await startServer({
19390
19486
  redirectUri,
19487
+ timeoutMs: opts?.timeoutMs,
19391
19488
  onListening: async () => {
19392
19489
  let safeUrl = "";
19393
19490
  for (const ch of url) {
@@ -19455,32 +19552,7 @@ class InvalidBaseUrlError extends Error {
19455
19552
  this.name = "InvalidBaseUrlError";
19456
19553
  }
19457
19554
  }
19458
- var DEFAULT_SCOPES = [
19459
- "offline_access",
19460
- "ProcessMining",
19461
- "OrchestratorApiUserAccess",
19462
- "StudioWebBackend",
19463
- "IdentityServerApi",
19464
- "ConnectionService",
19465
- "DataService",
19466
- "DataServiceApiUserAccess",
19467
- "DocumentUnderstanding",
19468
- "EnterpriseContextService",
19469
- "Directory",
19470
- "JamJamApi",
19471
- "LLMGateway",
19472
- "LLMOps",
19473
- "OMS",
19474
- "RCS.FolderAuthorization",
19475
- "RCS.TagsManagement",
19476
- "TestmanagerApiUserAccess",
19477
- "AutomationSolutions",
19478
- "StudioWebTypeCacheService",
19479
- "Docs.GPT.Search",
19480
- "Insights",
19481
- "ReferenceToken",
19482
- "Audit.Read"
19483
- ];
19555
+ var DEFAULT_SCOPES = ["openid", "profile", "offline_access"];
19484
19556
  var normalizeAndValidateBaseUrl = (rawUrl) => {
19485
19557
  let baseUrl = rawUrl;
19486
19558
  if (baseUrl.endsWith("/identity_/")) {
@@ -19530,7 +19602,8 @@ var resolveConfigAsync = async ({
19530
19602
  if (!clientSecret && fileAuth.clientSecret) {
19531
19603
  clientSecret = fileAuth.clientSecret;
19532
19604
  }
19533
- const scopes = customScopes && customScopes.length > 0 ? customScopes : fileAuth.scopes && fileAuth.scopes.length > 0 ? fileAuth.scopes : DEFAULT_SCOPES;
19605
+ const isExternalAppAuth = clientId !== DEFAULT_CLIENT_ID && Boolean(clientSecret);
19606
+ const scopes = customScopes && customScopes.length > 0 ? customScopes : fileAuth.scopes && fileAuth.scopes.length > 0 ? fileAuth.scopes : isExternalAppAuth ? [] : DEFAULT_SCOPES;
19534
19607
  return {
19535
19608
  clientId,
19536
19609
  clientSecret,
@@ -20366,11 +20439,11 @@ var parseResourceUrl = (url) => {
20366
20439
  if (error || !parsed)
20367
20440
  return;
20368
20441
  const segments = parsed.pathname.split("/").filter(Boolean);
20369
- const organizationName = segments[0];
20370
- const tenantName = segments[1];
20371
- if (!organizationName || !tenantName)
20372
- return;
20373
- return { baseUrl: parsed.origin, organizationName, tenantName };
20442
+ return {
20443
+ baseUrl: parsed.origin,
20444
+ organizationName: segments[0],
20445
+ tenantName: segments[1]
20446
+ };
20374
20447
  };
20375
20448
  var defaultLoadModule = async () => {
20376
20449
  const [error, mod] = await catchError(() => Promise.resolve().then(() => __toESM(require_dist(), 1)));
@@ -20421,6 +20494,7 @@ var tryRobotClientFallback = async (options = {}) => {
20421
20494
  }
20422
20495
  let organizationIdFromToken;
20423
20496
  let tenantIdFromToken;
20497
+ let issuerFromToken;
20424
20498
  const [jwtError, claims] = catchError(() => parseJWT(accessToken));
20425
20499
  if (!jwtError && claims) {
20426
20500
  const rawOrgId = claims.prtId ?? claims.organizationId ?? claims.prt_id;
@@ -20431,6 +20505,10 @@ var tryRobotClientFallback = async (options = {}) => {
20431
20505
  if (typeof tenantClaim === "string" && tenantClaim.length > 0) {
20432
20506
  tenantIdFromToken = tenantClaim;
20433
20507
  }
20508
+ const issClaim = claims.iss;
20509
+ if (typeof issClaim === "string" && issClaim.length > 0) {
20510
+ issuerFromToken = issClaim;
20511
+ }
20434
20512
  }
20435
20513
  printNoticeOnce();
20436
20514
  return {
@@ -20439,7 +20517,8 @@ var tryRobotClientFallback = async (options = {}) => {
20439
20517
  organizationName: parsedUrl.organizationName,
20440
20518
  organizationId: organizationIdFromToken ?? parsedUrl.organizationName,
20441
20519
  tenantName: parsedUrl.tenantName,
20442
- tenantId: tenantIdFromToken
20520
+ tenantId: tenantIdFromToken,
20521
+ issuer: issuerFromToken
20443
20522
  };
20444
20523
  } catch {
20445
20524
  return;
@@ -20542,7 +20621,7 @@ var probeAsync = async (fs7, candidate) => {
20542
20621
  };
20543
20622
  }
20544
20623
  };
20545
- var resolveEnvFileLocationAsync = async (envFilePath = DEFAULT_ENV_FILENAME) => {
20624
+ var resolveEnvFileLocationAsync = async (envFilePath = DEFAULT_ENV_FILENAME, opts) => {
20546
20625
  const fs7 = getFileSystem();
20547
20626
  if (fs7.path.isAbsolute(envFilePath)) {
20548
20627
  const probe2 = await probeAsync(fs7, envFilePath);
@@ -20553,7 +20632,7 @@ var resolveEnvFileLocationAsync = async (envFilePath = DEFAULT_ENV_FILENAME) =>
20553
20632
  ...probe2.unusable ? { unusable: probe2.unusable } : {}
20554
20633
  };
20555
20634
  }
20556
- const cwd = fs7.env.cwd();
20635
+ const cwd = opts?.cwd ?? fs7.env.cwd();
20557
20636
  let searchDir = cwd;
20558
20637
  while (true) {
20559
20638
  const candidate = fs7.path.join(searchDir, envFilePath);
@@ -20583,8 +20662,8 @@ var resolveEnvFileLocationAsync = async (envFilePath = DEFAULT_ENV_FILENAME) =>
20583
20662
  ...probe.unusable ? { unusable: probe.unusable } : {}
20584
20663
  };
20585
20664
  };
20586
- var resolveEnvFilePathAsync = async (envFilePath = DEFAULT_ENV_FILENAME) => {
20587
- const location = await resolveEnvFileLocationAsync(envFilePath);
20665
+ var resolveEnvFilePathAsync = async (envFilePath = DEFAULT_ENV_FILENAME, opts) => {
20666
+ const location = await resolveEnvFileLocationAsync(envFilePath, opts);
20588
20667
  if (location.exists) {
20589
20668
  return { absolutePath: location.absolutePath };
20590
20669
  }
@@ -20668,6 +20747,129 @@ function normalizeTokenRefreshFailure() {
20668
20747
  function normalizeTokenRefreshUnavailableFailure() {
20669
20748
  return "token refresh failed before authentication completed";
20670
20749
  }
20750
+ function errorMessage(error) {
20751
+ return error instanceof Error ? error.message : String(error);
20752
+ }
20753
+ function computeExpirationThreshold(ensureTokenValidityMinutes) {
20754
+ return new Date(Date.now() + (ensureTokenValidityMinutes ?? 0) * 60 * 1000);
20755
+ }
20756
+ async function runRefreshLocked(inputs) {
20757
+ const {
20758
+ absolutePath,
20759
+ refreshToken: callerRefreshToken,
20760
+ customAuthority,
20761
+ ensureTokenValidityMinutes,
20762
+ loadEnvFile,
20763
+ saveEnvFile,
20764
+ refreshFn,
20765
+ resolveConfig
20766
+ } = inputs;
20767
+ const expirationThreshold = computeExpirationThreshold(ensureTokenValidityMinutes);
20768
+ let fresh;
20769
+ try {
20770
+ fresh = await loadEnvFile({ envPath: absolutePath });
20771
+ } catch (error) {
20772
+ return {
20773
+ kind: "fail",
20774
+ status: {
20775
+ loginStatus: "Refresh Failed",
20776
+ hint: "Could not read the auth file while refreshing. Retry, or run 'uip login' to re-authenticate.",
20777
+ tokenRefresh: {
20778
+ attempted: false,
20779
+ success: false,
20780
+ errorMessage: `auth file read failed: ${errorMessage(error)}`
20781
+ }
20782
+ }
20783
+ };
20784
+ }
20785
+ const freshAccess = fresh.UIPATH_ACCESS_TOKEN;
20786
+ const freshExp = freshAccess ? getTokenExpiration(freshAccess) : undefined;
20787
+ if (freshAccess && freshExp && freshExp > expirationThreshold) {
20788
+ return {
20789
+ kind: "ok",
20790
+ accessToken: freshAccess,
20791
+ refreshToken: fresh.UIPATH_REFRESH_TOKEN ?? callerRefreshToken,
20792
+ expiration: freshExp,
20793
+ tokenRefresh: { attempted: false, success: true }
20794
+ };
20795
+ }
20796
+ const tokenForIdP = fresh.UIPATH_REFRESH_TOKEN ?? callerRefreshToken;
20797
+ let refreshedAccess;
20798
+ let refreshedRefresh;
20799
+ try {
20800
+ const config = await resolveConfig({ customAuthority });
20801
+ const refreshed = await refreshFn({
20802
+ refreshToken: tokenForIdP,
20803
+ tokenEndpoint: config.tokenEndpoint,
20804
+ clientId: config.clientId,
20805
+ expectedAuthority: customAuthority
20806
+ });
20807
+ refreshedAccess = refreshed.accessToken;
20808
+ refreshedRefresh = refreshed.refreshToken;
20809
+ } catch (error) {
20810
+ const isOAuthFailure = isTokenRefreshOAuthFailure(error);
20811
+ 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.";
20812
+ const message = isOAuthFailure ? normalizeTokenRefreshFailure() : normalizeTokenRefreshUnavailableFailure();
20813
+ return {
20814
+ kind: "fail",
20815
+ status: {
20816
+ loginStatus: "Refresh Failed",
20817
+ hint,
20818
+ tokenRefresh: {
20819
+ attempted: true,
20820
+ success: false,
20821
+ errorMessage: message
20822
+ }
20823
+ }
20824
+ };
20825
+ }
20826
+ const refreshedExp = getTokenExpiration(refreshedAccess);
20827
+ if (!refreshedExp || refreshedExp <= new Date) {
20828
+ return {
20829
+ kind: "fail",
20830
+ status: {
20831
+ loginStatus: "Refresh Failed",
20832
+ hint: "The identity server returned an unusable token. Run 'uip login' to re-authenticate.",
20833
+ tokenRefresh: {
20834
+ attempted: true,
20835
+ success: false,
20836
+ errorMessage: "refreshed token has no valid expiration claim"
20837
+ }
20838
+ }
20839
+ };
20840
+ }
20841
+ try {
20842
+ await saveEnvFile({
20843
+ envPath: absolutePath,
20844
+ data: {
20845
+ UIPATH_ACCESS_TOKEN: refreshedAccess,
20846
+ UIPATH_REFRESH_TOKEN: refreshedRefresh
20847
+ },
20848
+ merge: true
20849
+ });
20850
+ return {
20851
+ kind: "ok",
20852
+ accessToken: refreshedAccess,
20853
+ refreshToken: refreshedRefresh,
20854
+ expiration: refreshedExp,
20855
+ tokenRefresh: { attempted: true, success: true }
20856
+ };
20857
+ } catch (error) {
20858
+ const msg = errorMessage(error);
20859
+ return {
20860
+ kind: "ok",
20861
+ accessToken: refreshedAccess,
20862
+ refreshToken: refreshedRefresh,
20863
+ expiration: refreshedExp,
20864
+ 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.`,
20865
+ tokenRefresh: {
20866
+ attempted: true,
20867
+ success: true,
20868
+ errorMessage: `persistence failed: ${msg}`
20869
+ }
20870
+ };
20871
+ }
20872
+ }
20671
20873
  var getLoginStatusWithDeps = async (options = {}, deps = {}) => {
20672
20874
  const {
20673
20875
  resolveEnvFilePath = resolveEnvFilePathAsync,
@@ -20698,6 +20900,7 @@ var getLoginStatusWithDeps = async (options = {}, deps = {}) => {
20698
20900
  organizationId: robotCreds.organizationId,
20699
20901
  tenantName: robotCreds.tenantName,
20700
20902
  tenantId: robotCreds.tenantId,
20903
+ issuer: robotCreds.issuer,
20701
20904
  expiration: expiration2,
20702
20905
  source: "robot" /* Robot */
20703
20906
  };
@@ -20719,6 +20922,7 @@ var getLoginStatusWithDeps = async (options = {}, deps = {}) => {
20719
20922
  organizationId: robotCreds.organizationId,
20720
20923
  tenantName: robotCreds.tenantName,
20721
20924
  tenantId: robotCreds.tenantId,
20925
+ issuer: robotCreds.issuer,
20722
20926
  expiration: expiration2,
20723
20927
  source: "robot" /* Robot */
20724
20928
  };
@@ -20742,73 +20946,103 @@ var getLoginStatusWithDeps = async (options = {}, deps = {}) => {
20742
20946
  let refreshToken = credentials.UIPATH_REFRESH_TOKEN;
20743
20947
  let expiration = getTokenExpiration(accessToken);
20744
20948
  let persistenceWarning;
20949
+ let lockReleaseFailed = false;
20745
20950
  let tokenRefresh;
20746
- const expirationThreshold = new Date(Date.now() + (ensureTokenValidityMinutes ?? 0) * 60 * 1000);
20747
- if (expiration && expiration <= expirationThreshold && refreshToken) {
20748
- let refreshedAccess;
20749
- let refreshedRefresh;
20951
+ const outerThreshold = computeExpirationThreshold(ensureTokenValidityMinutes);
20952
+ const tryGlobalCredsHint = async () => {
20953
+ const fs7 = getFs();
20954
+ const globalPath = fs7.path.join(fs7.env.homedir(), envFilePath);
20955
+ if (absolutePath === globalPath)
20956
+ return;
20957
+ if (!await fs7.exists(globalPath))
20958
+ return;
20750
20959
  try {
20751
- const config = await resolveConfig({
20752
- customAuthority: credentials.UIPATH_URL
20753
- });
20754
- const refreshed = await refreshTokenFn({
20755
- refreshToken,
20756
- tokenEndpoint: config.tokenEndpoint,
20757
- clientId: config.clientId,
20758
- expectedAuthority: credentials.UIPATH_URL
20759
- });
20760
- refreshedAccess = refreshed.accessToken;
20761
- refreshedRefresh = refreshed.refreshToken;
20762
- } catch (error) {
20763
- const isOAuthFailure = isTokenRefreshOAuthFailure(error);
20764
- 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.";
20765
- const errorMessage = isOAuthFailure ? normalizeTokenRefreshFailure() : normalizeTokenRefreshUnavailableFailure();
20766
- return {
20767
- loginStatus: "Refresh Failed",
20768
- hint,
20769
- tokenRefresh: {
20770
- attempted: true,
20771
- success: false,
20772
- errorMessage
20773
- }
20774
- };
20960
+ const globalCreds = await loadEnvFile({ envPath: globalPath });
20961
+ if (!globalCreds.UIPATH_ACCESS_TOKEN)
20962
+ return;
20963
+ const globalExp = getTokenExpiration(globalCreds.UIPATH_ACCESS_TOKEN);
20964
+ if (globalExp && globalExp <= new Date)
20965
+ return;
20966
+ 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.`;
20967
+ } catch {
20968
+ return;
20775
20969
  }
20776
- const refreshedExp = getTokenExpiration(refreshedAccess);
20777
- if (!refreshedExp || refreshedExp <= new Date) {
20970
+ };
20971
+ if (expiration && expiration <= outerThreshold && refreshToken) {
20972
+ let release;
20973
+ try {
20974
+ release = await getFs().acquireLock(absolutePath);
20975
+ } catch (error) {
20976
+ const msg = errorMessage(error);
20977
+ const globalHint = await tryGlobalCredsHint();
20978
+ if (globalHint) {
20979
+ return {
20980
+ loginStatus: "Expired",
20981
+ accessToken,
20982
+ refreshToken,
20983
+ baseUrl: credentials.UIPATH_URL,
20984
+ organizationName: credentials.UIPATH_ORGANIZATION_NAME,
20985
+ organizationId: credentials.UIPATH_ORGANIZATION_ID,
20986
+ tenantName: credentials.UIPATH_TENANT_NAME,
20987
+ tenantId: credentials.UIPATH_TENANT_ID,
20988
+ expiration,
20989
+ source: "file" /* File */,
20990
+ hint: globalHint,
20991
+ tokenRefresh: {
20992
+ attempted: false,
20993
+ success: false,
20994
+ errorMessage: `lock acquisition failed: ${msg}`
20995
+ }
20996
+ };
20997
+ }
20778
20998
  return {
20779
20999
  loginStatus: "Refresh Failed",
20780
- hint: "The identity server returned an unusable token. Run 'uip login' to re-authenticate.",
21000
+ 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.",
20781
21001
  tokenRefresh: {
20782
- attempted: true,
21002
+ attempted: false,
20783
21003
  success: false,
20784
- errorMessage: "refreshed token has no valid expiration claim"
21004
+ errorMessage: `lock acquisition failed: ${msg}`
20785
21005
  }
20786
21006
  };
20787
21007
  }
20788
- accessToken = refreshedAccess;
20789
- refreshToken = refreshedRefresh;
20790
- expiration = refreshedExp;
21008
+ let lockedFailure;
20791
21009
  try {
20792
- await saveEnvFile({
20793
- envPath: absolutePath,
20794
- data: {
20795
- UIPATH_ACCESS_TOKEN: accessToken,
20796
- UIPATH_REFRESH_TOKEN: refreshToken
20797
- },
20798
- merge: true
21010
+ const outcome = await runRefreshLocked({
21011
+ absolutePath,
21012
+ refreshToken,
21013
+ customAuthority: credentials.UIPATH_URL,
21014
+ ensureTokenValidityMinutes,
21015
+ loadEnvFile,
21016
+ saveEnvFile,
21017
+ refreshFn: refreshTokenFn,
21018
+ resolveConfig
20799
21019
  });
20800
- tokenRefresh = {
20801
- attempted: true,
20802
- success: true
20803
- };
20804
- } catch (error) {
20805
- const msg = error instanceof Error ? error.message : String(error);
20806
- 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.`;
20807
- tokenRefresh = {
20808
- attempted: true,
20809
- success: true,
20810
- errorMessage: `persistence failed: ${msg}`
20811
- };
21020
+ if (outcome.kind === "fail") {
21021
+ lockedFailure = outcome.status;
21022
+ } else {
21023
+ accessToken = outcome.accessToken;
21024
+ refreshToken = outcome.refreshToken;
21025
+ expiration = outcome.expiration;
21026
+ tokenRefresh = outcome.tokenRefresh;
21027
+ if (outcome.persistenceWarning) {
21028
+ persistenceWarning = outcome.persistenceWarning;
21029
+ }
21030
+ }
21031
+ } finally {
21032
+ try {
21033
+ await release();
21034
+ } catch {
21035
+ lockReleaseFailed = true;
21036
+ }
21037
+ }
21038
+ if (lockedFailure) {
21039
+ const globalHint = await tryGlobalCredsHint();
21040
+ const base = globalHint ? {
21041
+ ...lockedFailure,
21042
+ loginStatus: "Expired",
21043
+ hint: globalHint
21044
+ } : lockedFailure;
21045
+ return lockReleaseFailed ? { ...base, lockReleaseFailed: true } : base;
20812
21046
  }
20813
21047
  }
20814
21048
  const result = {
@@ -20823,23 +21057,13 @@ var getLoginStatusWithDeps = async (options = {}, deps = {}) => {
20823
21057
  expiration,
20824
21058
  source: "file" /* File */,
20825
21059
  ...persistenceWarning ? { hint: persistenceWarning, persistenceFailed: true } : {},
21060
+ ...lockReleaseFailed ? { lockReleaseFailed: true } : {},
20826
21061
  ...tokenRefresh ? { tokenRefresh } : {}
20827
21062
  };
20828
21063
  if (result.loginStatus === "Expired") {
20829
- const fs7 = getFs();
20830
- const globalPath = fs7.path.join(fs7.env.homedir(), envFilePath);
20831
- if (absolutePath !== globalPath && await fs7.exists(globalPath)) {
20832
- try {
20833
- const globalCreds = await loadEnvFile({
20834
- envPath: globalPath
20835
- });
20836
- if (globalCreds.UIPATH_ACCESS_TOKEN) {
20837
- const globalExp = getTokenExpiration(globalCreds.UIPATH_ACCESS_TOKEN);
20838
- if (!globalExp || globalExp > new Date) {
20839
- 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.`;
20840
- }
20841
- }
20842
- } catch {}
21064
+ const globalHint = await tryGlobalCredsHint();
21065
+ if (globalHint) {
21066
+ result.hint = globalHint;
20843
21067
  }
20844
21068
  }
20845
21069
  return result;
@@ -20856,7 +21080,8 @@ var getLoginStatusAsync = async (options = {}) => {
20856
21080
  // src/authContext.ts
20857
21081
  var getAuthContext = async (options = {}) => {
20858
21082
  const status = await getLoginStatusAsync({
20859
- ensureTokenValidityMinutes: options.ensureTokenValidityMinutes
21083
+ ensureTokenValidityMinutes: options.ensureTokenValidityMinutes,
21084
+ envFilePath: options.envFilePath
20860
21085
  });
20861
21086
  if (status.loginStatus !== "Logged in" || !status.baseUrl || !status.accessToken) {
20862
21087
  throw new Error(status.hint ? `Not logged in. ${status.hint}` : "Not logged in. Run 'uip login' first.");
@@ -20883,6 +21108,34 @@ var getAuthContext = async (options = {}) => {
20883
21108
  tenantName
20884
21109
  };
20885
21110
  };
21111
+ var getAuthEnv = async (options = {}) => {
21112
+ const authEnv = {};
21113
+ let status;
21114
+ try {
21115
+ status = await getLoginStatusAsync(options);
21116
+ } catch {
21117
+ return { authEnv };
21118
+ }
21119
+ if (status.loginStatus === "Logged in" && status.accessToken) {
21120
+ authEnv.UIPATH_ACCESS_TOKEN = status.accessToken;
21121
+ if (status.baseUrl) {
21122
+ const org = status.organizationName || status.organizationId;
21123
+ const tenant = status.tenantName || status.tenantId;
21124
+ if (org && tenant) {
21125
+ authEnv.UIPATH_URL = `${status.baseUrl.replace(/\/+$/, "")}/${org}/${tenant}`;
21126
+ }
21127
+ }
21128
+ if (status.organizationId)
21129
+ authEnv.UIPATH_ORGANIZATION_ID = status.organizationId;
21130
+ if (status.organizationName)
21131
+ authEnv.UIPATH_ORGANIZATION_NAME = status.organizationName;
21132
+ if (status.tenantId)
21133
+ authEnv.UIPATH_TENANT_ID = status.tenantId;
21134
+ if (status.tenantName)
21135
+ authEnv.UIPATH_TENANT_NAME = status.tenantName;
21136
+ }
21137
+ return { authEnv, loginStatus: status };
21138
+ };
20886
21139
  // src/clientCredentials.ts
20887
21140
  var clientCredentialsLogin = async ({
20888
21141
  clientId,
@@ -20899,9 +21152,11 @@ var clientCredentialsLogin = async ({
20899
21152
  const params = {
20900
21153
  grant_type: "client_credentials",
20901
21154
  client_id: config.clientId,
20902
- client_secret: config.clientSecret ?? "",
20903
- scope: config.scopes.join(" ")
21155
+ client_secret: config.clientSecret ?? ""
20904
21156
  };
21157
+ if (config.scopes.length > 0) {
21158
+ params.scope = config.scopes.join(" ");
21159
+ }
20905
21160
  const tokenResponse = await fetch(config.tokenEndpoint, {
20906
21161
  method: "POST",
20907
21162
  headers: {
@@ -21017,7 +21272,8 @@ var interactiveLoginWithDeps = async (options, deps) => {
21017
21272
  tenant,
21018
21273
  organization,
21019
21274
  interactive,
21020
- onEvent
21275
+ onEvent,
21276
+ timeoutMs
21021
21277
  } = options;
21022
21278
  const emit = (event) => {
21023
21279
  if (!onEvent)
@@ -21045,7 +21301,8 @@ var interactiveLoginWithDeps = async (options, deps) => {
21045
21301
  clientId,
21046
21302
  clientSecret,
21047
21303
  scope,
21048
- organization
21304
+ organization,
21305
+ timeoutMs
21049
21306
  });
21050
21307
  return {
21051
21308
  UIPATH_ACCESS_TOKEN: authTokens.accessToken,
@@ -21109,11 +21366,25 @@ var interactiveLoginWithDeps = async (options, deps) => {
21109
21366
  searchDir = parentDir;
21110
21367
  }
21111
21368
  }
21112
- await saveEnvFile({
21113
- envPath: savePath,
21114
- data: credentials,
21115
- merge: true
21116
- });
21369
+ let saveRelease;
21370
+ try {
21371
+ if (typeof fs7.acquireLock === "function") {
21372
+ saveRelease = await fs7.acquireLock(savePath);
21373
+ }
21374
+ } catch {
21375
+ saveRelease = undefined;
21376
+ }
21377
+ try {
21378
+ await saveEnvFile({
21379
+ envPath: savePath,
21380
+ data: credentials,
21381
+ merge: true
21382
+ });
21383
+ } finally {
21384
+ if (saveRelease) {
21385
+ await saveRelease().catch(() => {});
21386
+ }
21387
+ }
21117
21388
  const reportedPath = fs7.path.isAbsolute(savePath) ? savePath : fs7.path.join(fs7.env.homedir(), savePath);
21118
21389
  emit({
21119
21390
  type: "saved",
@@ -21135,7 +21406,21 @@ async function logoutWithDeps(options, deps = {}) {
21135
21406
  const fs7 = getFs();
21136
21407
  const { absolutePath } = await resolveEnvFilePath(options.file);
21137
21408
  if (absolutePath && await fs7.exists(absolutePath)) {
21138
- await fs7.rm(absolutePath);
21409
+ let release;
21410
+ try {
21411
+ if (typeof fs7.acquireLock === "function") {
21412
+ release = await fs7.acquireLock(absolutePath);
21413
+ }
21414
+ } catch {
21415
+ release = undefined;
21416
+ }
21417
+ try {
21418
+ await fs7.rm(absolutePath);
21419
+ } finally {
21420
+ if (release) {
21421
+ await release().catch(() => {});
21422
+ }
21423
+ }
21139
21424
  return {
21140
21425
  success: true,
21141
21426
  message: `Logged out successfully. Removed ${absolutePath}`,
@@ -21153,13 +21438,15 @@ async function logout(options) {
21153
21438
  }
21154
21439
 
21155
21440
  // src/index.ts
21441
+ init_server();
21156
21442
  var authenticate = async ({
21157
21443
  baseUrl,
21158
21444
  clientId,
21159
21445
  clientSecret,
21160
21446
  redirectUri,
21161
21447
  scope,
21162
- organization
21448
+ organization,
21449
+ timeoutMs
21163
21450
  }) => {
21164
21451
  const config = await resolveConfigAsync({
21165
21452
  customAuthority: baseUrl,
@@ -21209,7 +21496,7 @@ var authenticate = async ({
21209
21496
  const { NodeAuthStrategy: NodeAuthStrategy2 } = await Promise.resolve().then(() => (init_node_strategy(), exports_node_strategy));
21210
21497
  strategy = new NodeAuthStrategy2;
21211
21498
  }
21212
- const code = await strategy.execute(authUrl, effectiveRedirectUriUrl, state);
21499
+ const code = await strategy.execute(authUrl, effectiveRedirectUriUrl, state, { timeoutMs });
21213
21500
  return await exchangeCodeForTokens({
21214
21501
  code,
21215
21502
  codeVerifier: code_verifier,
@@ -21240,6 +21527,7 @@ export {
21240
21527
  interactiveLogin,
21241
21528
  getLoginStatusWithDeps,
21242
21529
  getLoginStatusAsync,
21530
+ getAuthEnv,
21243
21531
  getAuthContext,
21244
21532
  fetchTenantsAndOrganizations,
21245
21533
  clientCredentialsLogin,
@@ -21252,5 +21540,6 @@ export {
21252
21540
  ENV_AUTH_ENABLE_VAR,
21253
21541
  ENFORCE_ROBOT_AUTH_VAR,
21254
21542
  DEFAULT_ENV_FILENAME,
21255
- DEFAULT_AUTH_FILENAME
21543
+ DEFAULT_AUTH_FILENAME,
21544
+ AUTH_TIMEOUT_ERROR_CODE
21256
21545
  };