@uipath/auth 1.1.0 → 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,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) {
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, {
@@ -19455,32 +19551,7 @@ class InvalidBaseUrlError extends Error {
19455
19551
  this.name = "InvalidBaseUrlError";
19456
19552
  }
19457
19553
  }
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
- ];
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,
@@ -20668,6 +20740,129 @@ function normalizeTokenRefreshFailure() {
20668
20740
  function normalizeTokenRefreshUnavailableFailure() {
20669
20741
  return "token refresh failed before authentication completed";
20670
20742
  }
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
+ };
20777
+ }
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
+ }
20671
20866
  var getLoginStatusWithDeps = async (options = {}, deps = {}) => {
20672
20867
  const {
20673
20868
  resolveEnvFilePath = resolveEnvFilePathAsync,
@@ -20742,73 +20937,103 @@ var getLoginStatusWithDeps = async (options = {}, deps = {}) => {
20742
20937
  let refreshToken = credentials.UIPATH_REFRESH_TOKEN;
20743
20938
  let expiration = getTokenExpiration(accessToken);
20744
20939
  let persistenceWarning;
20940
+ let lockReleaseFailed = false;
20745
20941
  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;
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;
20750
20950
  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
- };
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;
20775
20960
  }
20776
- const refreshedExp = getTokenExpiration(refreshedAccess);
20777
- 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
+ }
20778
20989
  return {
20779
20990
  loginStatus: "Refresh Failed",
20780
- 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.",
20781
20992
  tokenRefresh: {
20782
- attempted: true,
20993
+ attempted: false,
20783
20994
  success: false,
20784
- errorMessage: "refreshed token has no valid expiration claim"
20995
+ errorMessage: `lock acquisition failed: ${msg}`
20785
20996
  }
20786
20997
  };
20787
20998
  }
20788
- accessToken = refreshedAccess;
20789
- refreshToken = refreshedRefresh;
20790
- expiration = refreshedExp;
20999
+ let lockedFailure;
20791
21000
  try {
20792
- await saveEnvFile({
20793
- envPath: absolutePath,
20794
- data: {
20795
- UIPATH_ACCESS_TOKEN: accessToken,
20796
- UIPATH_REFRESH_TOKEN: refreshToken
20797
- },
20798
- 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
20799
21010
  });
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
- };
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;
20812
21037
  }
20813
21038
  }
20814
21039
  const result = {
@@ -20823,23 +21048,13 @@ var getLoginStatusWithDeps = async (options = {}, deps = {}) => {
20823
21048
  expiration,
20824
21049
  source: "file" /* File */,
20825
21050
  ...persistenceWarning ? { hint: persistenceWarning, persistenceFailed: true } : {},
21051
+ ...lockReleaseFailed ? { lockReleaseFailed: true } : {},
20826
21052
  ...tokenRefresh ? { tokenRefresh } : {}
20827
21053
  };
20828
21054
  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 {}
21055
+ const globalHint = await tryGlobalCredsHint();
21056
+ if (globalHint) {
21057
+ result.hint = globalHint;
20843
21058
  }
20844
21059
  }
20845
21060
  return result;
@@ -20899,9 +21114,11 @@ var clientCredentialsLogin = async ({
20899
21114
  const params = {
20900
21115
  grant_type: "client_credentials",
20901
21116
  client_id: config.clientId,
20902
- client_secret: config.clientSecret ?? "",
20903
- scope: config.scopes.join(" ")
21117
+ client_secret: config.clientSecret ?? ""
20904
21118
  };
21119
+ if (config.scopes.length > 0) {
21120
+ params.scope = config.scopes.join(" ");
21121
+ }
20905
21122
  const tokenResponse = await fetch(config.tokenEndpoint, {
20906
21123
  method: "POST",
20907
21124
  headers: {
@@ -21109,11 +21326,25 @@ var interactiveLoginWithDeps = async (options, deps) => {
21109
21326
  searchDir = parentDir;
21110
21327
  }
21111
21328
  }
21112
- await saveEnvFile({
21113
- envPath: savePath,
21114
- data: credentials,
21115
- merge: true
21116
- });
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
+ }
21117
21348
  const reportedPath = fs7.path.isAbsolute(savePath) ? savePath : fs7.path.join(fs7.env.homedir(), savePath);
21118
21349
  emit({
21119
21350
  type: "saved",
@@ -21135,7 +21366,21 @@ async function logoutWithDeps(options, deps = {}) {
21135
21366
  const fs7 = getFs();
21136
21367
  const { absolutePath } = await resolveEnvFilePath(options.file);
21137
21368
  if (absolutePath && await fs7.exists(absolutePath)) {
21138
- 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
+ }
21139
21384
  return {
21140
21385
  success: true,
21141
21386
  message: `Logged out successfully. Removed ${absolutePath}`,
@@ -21153,6 +21398,7 @@ async function logout(options) {
21153
21398
  }
21154
21399
 
21155
21400
  // src/index.ts
21401
+ init_server();
21156
21402
  var authenticate = async ({
21157
21403
  baseUrl,
21158
21404
  clientId,
@@ -21252,5 +21498,6 @@ export {
21252
21498
  ENV_AUTH_ENABLE_VAR,
21253
21499
  ENFORCE_ROBOT_AUTH_VAR,
21254
21500
  DEFAULT_ENV_FILENAME,
21255
- DEFAULT_AUTH_FILENAME
21501
+ DEFAULT_AUTH_FILENAME,
21502
+ AUTH_TIMEOUT_ERROR_CODE
21256
21503
  };