@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.
@@ -0,0 +1,17 @@
1
+ export interface AuthContext {
2
+ baseUrl: string;
3
+ accessToken: string;
4
+ organizationId?: string;
5
+ organizationName?: string;
6
+ tenantId?: string;
7
+ tenantName?: string;
8
+ }
9
+ export interface GetAuthContextOptions {
10
+ tenant?: string;
11
+ ensureTokenValidityMinutes?: number;
12
+ requireOrganizationId?: boolean;
13
+ requireOrganizationName?: boolean;
14
+ requireTenantId?: boolean;
15
+ requireTenantName?: boolean;
16
+ }
17
+ export declare const getAuthContext: (options?: GetAuthContextOptions) => Promise<AuthContext>;
@@ -0,0 +1,5 @@
1
+ type CatchErrorResult<T> = [undefined, T] | [Error, undefined];
2
+ export declare function catchError<T>(fnOrPromise: Promise<T>): Promise<CatchErrorResult<T>>;
3
+ export declare function catchError<T>(fnOrPromise: () => Promise<T>): Promise<CatchErrorResult<T>>;
4
+ export declare function catchError<T>(fnOrPromise: () => T): CatchErrorResult<T>;
5
+ export {};
@@ -0,0 +1,9 @@
1
+ import type { BaseCredentials } from "./types";
2
+ interface ClientCredentialsLoginProps {
3
+ clientId: string;
4
+ clientSecret: string;
5
+ scope?: string[];
6
+ authority?: string;
7
+ }
8
+ export declare const clientCredentialsLogin: ({ clientId, clientSecret, scope, authority, }: ClientCredentialsLoginProps) => Promise<BaseCredentials>;
9
+ export {};
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Auth-relevant slice of the on-disk CLI config. Auth itself does no
3
+ * filesystem I/O — the CLI loads `.uipath/config.json`, picks the `auth`
4
+ * section, and pushes it here via {@link setAuthFileConfig}. Tools that
5
+ * bundle their own copy of `@uipath/auth` see the same value because the
6
+ * slot is keyed by `Symbol.for()` on `globalThis`.
7
+ */
8
+ export interface AuthFileConfig {
9
+ clientId?: string;
10
+ clientSecret?: string;
11
+ authority?: string;
12
+ scopes?: string[];
13
+ }
14
+ /**
15
+ * Push the resolved `auth.*` block of the on-disk config into auth.
16
+ * Called by the CLI host once at startup; safe to call repeatedly.
17
+ */
18
+ export declare const setAuthFileConfig: (cfg: AuthFileConfig | undefined) => void;
19
+ /**
20
+ * Error thrown when an invalid base URL is provided
21
+ */
22
+ export declare class InvalidBaseUrlError extends Error {
23
+ readonly url: string;
24
+ readonly reason: string;
25
+ constructor(url: string, reason: string);
26
+ }
27
+ /**
28
+ * Default OAuth scopes requested during authentication.
29
+ *
30
+ * Identity is expected to expand the issued token to every scope granted
31
+ * to the CLI client (the "assistant pattern"), so the CLI only asks for
32
+ * the OIDC base set. Adding product-specific scopes here re-introduces
33
+ * per-deployment configuration coupling on Automation Suite, where each
34
+ * scope must be granted to the client in the Identity database.
35
+ */
36
+ export declare const DEFAULT_SCOPES: string[];
37
+ interface EndpointsConfig {
38
+ clientId: string;
39
+ clientSecret?: string;
40
+ baseUrl: string;
41
+ authorizationEndpoint: string;
42
+ tokenEndpoint: string;
43
+ /**
44
+ * Effective scope list: caller-provided scope wins; file-config
45
+ * `auth.scopes` is the secondary fallback; {@link DEFAULT_SCOPES} the
46
+ * tertiary. Pre-resolved here so callers can trust a single source of
47
+ * truth instead of re-implementing the precedence at every call site.
48
+ */
49
+ scopes: string[];
50
+ }
51
+ interface ResolveConfigProps {
52
+ customAuthority?: string;
53
+ customClientId?: string;
54
+ customClientSecret?: string;
55
+ customScopes?: string[];
56
+ }
57
+ /**
58
+ * Normalize and validate a UiPath base URL.
59
+ *
60
+ * Applies the same rules used by the interactive login flow so every
61
+ * source of baseUrl (user config, env var, JWT `iss` claim) produces
62
+ * a canonical `https://<host>` string:
63
+ * • strips a trailing `/identity_` or `/identity_/` (users paste the
64
+ * full identity endpoint and we need just the authority);
65
+ * • strips trailing slashes;
66
+ * • validates the result as a parseable `https://` URL;
67
+ * • strips any path segments — organization is carried separately.
68
+ *
69
+ * Throws {@link InvalidBaseUrlError} on any failure.
70
+ */
71
+ export declare const normalizeAndValidateBaseUrl: (rawUrl: string) => string;
72
+ export declare const resolveConfigAsync: ({ customAuthority, customClientId, customClientSecret, customScopes, }?: ResolveConfigProps) => Promise<EndpointsConfig>;
73
+ export {};
@@ -0,0 +1,10 @@
1
+ /** Home directory for UiPath CLI auth data: ~/.uipath/ */
2
+ export declare const UIPATH_HOME_DIR = ".uipath";
3
+ /** Auth credentials filename within the UiPath home directory. */
4
+ export declare const AUTH_FILENAME = ".auth";
5
+ /** Default UiPath cloud base URL. */
6
+ export declare const DEFAULT_BASE_URL = "https://cloud.uipath.com";
7
+ /** Auth callback server timeout (5 minutes). */
8
+ export declare const DEFAULT_AUTH_TIMEOUT_MS: number;
9
+ /** Localhost OIDC redirect URI. */
10
+ export declare const DEFAULT_REDIRECT_URI = "http://localhost:8104/oidc/login";
@@ -0,0 +1,75 @@
1
+ import type { LoginStatus } from "./loginStatus";
2
+ /**
3
+ * Environment variable that opts the CLI into sourcing authentication
4
+ * data from env vars instead of the `.uipath/.auth` file. Must be set
5
+ * to the literal string `"true"`.
6
+ */
7
+ export declare const ENV_AUTH_ENABLE_VAR = "UIPATH_CLI_ENABLE_ENV_AUTH";
8
+ /**
9
+ * Environment variable that opts the CLI into Robot-IPC-only authentication.
10
+ * Must be set to the literal string `"true"`. When set:
11
+ * - `~/.uipath/.auth` is **not consulted** (file lookup is skipped entirely;
12
+ * a stale or expired file cannot block the Robot path).
13
+ * - `UIPATH_CLI_ENABLE_ENV_AUTH=true` set in parallel raises
14
+ * {@link EnvAuthConfigError} ("mutually exclusive flags") rather than
15
+ * being silently overridden — the two opt-ins contradict each other and
16
+ * the user must resolve the conflict.
17
+ * - The Robot fallback runs first and is required to succeed.
18
+ * - If the Robot is not running / not signed in, the CLI returns
19
+ * `loginStatus: "Not logged in"` with a hint pointing at this env var
20
+ * and the Assistant — no fallback to file or env-auth.
21
+ * Intended as a per-process opt-in (set on the spawned `uip` child only)
22
+ * for consumers like Studio Desktop that authenticate through the local
23
+ * Robot. See GH issue #2131.
24
+ */
25
+ export declare const ENFORCE_ROBOT_AUTH_VAR = "UIPATH_CLI_ENFORCE_ROBOT_AUTH";
26
+ /**
27
+ * Names of the env vars consumed when {@link ENV_AUTH_ENABLE_VAR} is
28
+ * enabled. The server URL (`baseUrl`) is not listed here — it is
29
+ * derived from the JWT's `iss` claim, which is the authoritative
30
+ * source for the authority that minted the token.
31
+ *
32
+ * NOTE: insertion order of these keys is the public ordering of the
33
+ * `Vars` array surfaced by `uip login which`. Reordering changes CLI
34
+ * output that programmatic consumers may parse positionally.
35
+ */
36
+ export declare const ENV_AUTH_VARS: {
37
+ readonly token: "UIPATH_CLI_AUTH_TOKEN";
38
+ readonly organizationName: "UIPATH_CLI_ORGANIZATION_NAME";
39
+ readonly organizationId: "UIPATH_CLI_ORGANIZATION_ID";
40
+ readonly tenantName: "UIPATH_CLI_TENANT_NAME";
41
+ readonly tenantId: "UIPATH_CLI_TENANT_ID";
42
+ };
43
+ /**
44
+ * Error thrown when env-var auth is enabled but the configuration is
45
+ * incomplete or invalid. Surfaces the specific variable at fault so
46
+ * the user can correct the CI setup without guessing.
47
+ */
48
+ export declare class EnvAuthConfigError extends Error {
49
+ constructor(message: string);
50
+ }
51
+ /**
52
+ * Whether env-var auth is active. Checked at the top of
53
+ * {@link getLoginStatusWithDeps} so that when the gate is off the
54
+ * existing file-based flow is entirely unaffected.
55
+ */
56
+ export declare const isEnvAuthEnabled: () => boolean;
57
+ /**
58
+ * Whether Robot-only enforcement is active. See {@link ENFORCE_ROBOT_AUTH_VAR}.
59
+ */
60
+ export declare const isRobotAuthEnforced: () => boolean;
61
+ /**
62
+ * Build a {@link LoginStatus} from environment variables, bypassing
63
+ * disk I/O and token refresh entirely.
64
+ *
65
+ * The access token is treated as opaque — whoever populated the env
66
+ * var is responsible for its freshness. If the token carries a JWT
67
+ * `exp` that has already passed, the status is reported as `Expired`
68
+ * (mirroring the file-based flow), but no refresh is attempted: there
69
+ * is no refresh token in the env-var contract and the CLI has no way
70
+ * to update a secret it did not mint.
71
+ *
72
+ * baseUrl is derived from the token's `iss` claim so the env-var set
73
+ * can stay aligned with the GH #1034 spec (five vars, no URL).
74
+ */
75
+ export declare const readAuthFromEnv: () => LoginStatus;
@@ -0,0 +1,7 @@
1
+ interface GetBaseHtmlProps {
2
+ title: string;
3
+ message: string;
4
+ type: "success" | "error";
5
+ }
6
+ export declare const getBaseHtml: ({ title, message, type }: GetBaseHtmlProps) => string;
7
+ export {};
@@ -0,0 +1,10 @@
1
+ export * from "./authContext";
2
+ export * from "./clientCredentials";
3
+ export { type AuthFileConfig, InvalidBaseUrlError, setAuthFileConfig, } from "./config";
4
+ export * from "./loginStatus";
5
+ export * from "./logout";
6
+ export { fetchTenantsAndOrganizations, type Tenant, type TenantsAndOrganizations, } from "./tenantSelection";
7
+ export * from "./tokenRefresh";
8
+ export type { BaseCredentials } from "./types";
9
+ export { DEFAULT_AUTH_FILENAME, DEFAULT_ENV_FILENAME, type EnvFileErrorCode, type EnvFileLocation, type EnvFileSource, type EnvFileUnusable, type EnvFileUnusableReason, loadEnvFileAsync, resolveEnvFileLocationAsync, resolveEnvFilePathAsync, saveEnvFileAsync, } from "./utils/envFile";
10
+ export { isBrowser, isNode } from "./utils/platform";
@@ -69,32 +69,7 @@ class InvalidBaseUrlError extends Error {
69
69
  this.name = "InvalidBaseUrlError";
70
70
  }
71
71
  }
72
- var DEFAULT_SCOPES = [
73
- "offline_access",
74
- "ProcessMining",
75
- "OrchestratorApiUserAccess",
76
- "StudioWebBackend",
77
- "IdentityServerApi",
78
- "ConnectionService",
79
- "DataService",
80
- "DataServiceApiUserAccess",
81
- "DocumentUnderstanding",
82
- "EnterpriseContextService",
83
- "Directory",
84
- "JamJamApi",
85
- "LLMGateway",
86
- "LLMOps",
87
- "OMS",
88
- "RCS.FolderAuthorization",
89
- "RCS.TagsManagement",
90
- "TestmanagerApiUserAccess",
91
- "AutomationSolutions",
92
- "StudioWebTypeCacheService",
93
- "Docs.GPT.Search",
94
- "Insights",
95
- "ReferenceToken",
96
- "Audit.Read"
97
- ];
72
+ var DEFAULT_SCOPES = ["openid", "profile", "offline_access"];
98
73
  var normalizeAndValidateBaseUrl = (rawUrl) => {
99
74
  let baseUrl = rawUrl;
100
75
  if (baseUrl.endsWith("/identity_/")) {
@@ -144,7 +119,8 @@ var resolveConfigAsync = async ({
144
119
  if (!clientSecret && fileAuth.clientSecret) {
145
120
  clientSecret = fileAuth.clientSecret;
146
121
  }
147
- const scopes = customScopes && customScopes.length > 0 ? customScopes : fileAuth.scopes && fileAuth.scopes.length > 0 ? fileAuth.scopes : DEFAULT_SCOPES;
122
+ const isExternalAppAuth = clientId !== DEFAULT_CLIENT_ID && Boolean(clientSecret);
123
+ const scopes = customScopes && customScopes.length > 0 ? customScopes : fileAuth.scopes && fileAuth.scopes.length > 0 ? fileAuth.scopes : isExternalAppAuth ? [] : DEFAULT_SCOPES;
148
124
  return {
149
125
  clientId,
150
126
  clientSecret,
@@ -653,6 +629,129 @@ function normalizeTokenRefreshFailure() {
653
629
  function normalizeTokenRefreshUnavailableFailure() {
654
630
  return "token refresh failed before authentication completed";
655
631
  }
632
+ function errorMessage(error) {
633
+ return error instanceof Error ? error.message : String(error);
634
+ }
635
+ function computeExpirationThreshold(ensureTokenValidityMinutes) {
636
+ return new Date(Date.now() + (ensureTokenValidityMinutes ?? 0) * 60 * 1000);
637
+ }
638
+ async function runRefreshLocked(inputs) {
639
+ const {
640
+ absolutePath,
641
+ refreshToken: callerRefreshToken,
642
+ customAuthority,
643
+ ensureTokenValidityMinutes,
644
+ loadEnvFile,
645
+ saveEnvFile,
646
+ refreshFn,
647
+ resolveConfig
648
+ } = inputs;
649
+ const expirationThreshold = computeExpirationThreshold(ensureTokenValidityMinutes);
650
+ let fresh;
651
+ try {
652
+ fresh = await loadEnvFile({ envPath: absolutePath });
653
+ } catch (error) {
654
+ return {
655
+ kind: "fail",
656
+ status: {
657
+ loginStatus: "Refresh Failed",
658
+ hint: "Could not read the auth file while refreshing. Retry, or run 'uip login' to re-authenticate.",
659
+ tokenRefresh: {
660
+ attempted: false,
661
+ success: false,
662
+ errorMessage: `auth file read failed: ${errorMessage(error)}`
663
+ }
664
+ }
665
+ };
666
+ }
667
+ const freshAccess = fresh.UIPATH_ACCESS_TOKEN;
668
+ const freshExp = freshAccess ? getTokenExpiration(freshAccess) : undefined;
669
+ if (freshAccess && freshExp && freshExp > expirationThreshold) {
670
+ return {
671
+ kind: "ok",
672
+ accessToken: freshAccess,
673
+ refreshToken: fresh.UIPATH_REFRESH_TOKEN ?? callerRefreshToken,
674
+ expiration: freshExp,
675
+ tokenRefresh: { attempted: false, success: true }
676
+ };
677
+ }
678
+ const tokenForIdP = fresh.UIPATH_REFRESH_TOKEN ?? callerRefreshToken;
679
+ let refreshedAccess;
680
+ let refreshedRefresh;
681
+ try {
682
+ const config = await resolveConfig({ customAuthority });
683
+ const refreshed = await refreshFn({
684
+ refreshToken: tokenForIdP,
685
+ tokenEndpoint: config.tokenEndpoint,
686
+ clientId: config.clientId,
687
+ expectedAuthority: customAuthority
688
+ });
689
+ refreshedAccess = refreshed.accessToken;
690
+ refreshedRefresh = refreshed.refreshToken;
691
+ } catch (error) {
692
+ const isOAuthFailure = isTokenRefreshOAuthFailure(error);
693
+ 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.";
694
+ const message = isOAuthFailure ? normalizeTokenRefreshFailure() : normalizeTokenRefreshUnavailableFailure();
695
+ return {
696
+ kind: "fail",
697
+ status: {
698
+ loginStatus: "Refresh Failed",
699
+ hint,
700
+ tokenRefresh: {
701
+ attempted: true,
702
+ success: false,
703
+ errorMessage: message
704
+ }
705
+ }
706
+ };
707
+ }
708
+ const refreshedExp = getTokenExpiration(refreshedAccess);
709
+ if (!refreshedExp || refreshedExp <= new Date) {
710
+ return {
711
+ kind: "fail",
712
+ status: {
713
+ loginStatus: "Refresh Failed",
714
+ hint: "The identity server returned an unusable token. Run 'uip login' to re-authenticate.",
715
+ tokenRefresh: {
716
+ attempted: true,
717
+ success: false,
718
+ errorMessage: "refreshed token has no valid expiration claim"
719
+ }
720
+ }
721
+ };
722
+ }
723
+ try {
724
+ await saveEnvFile({
725
+ envPath: absolutePath,
726
+ data: {
727
+ UIPATH_ACCESS_TOKEN: refreshedAccess,
728
+ UIPATH_REFRESH_TOKEN: refreshedRefresh
729
+ },
730
+ merge: true
731
+ });
732
+ return {
733
+ kind: "ok",
734
+ accessToken: refreshedAccess,
735
+ refreshToken: refreshedRefresh,
736
+ expiration: refreshedExp,
737
+ tokenRefresh: { attempted: true, success: true }
738
+ };
739
+ } catch (error) {
740
+ const msg = errorMessage(error);
741
+ return {
742
+ kind: "ok",
743
+ accessToken: refreshedAccess,
744
+ refreshToken: refreshedRefresh,
745
+ expiration: refreshedExp,
746
+ 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.`,
747
+ tokenRefresh: {
748
+ attempted: true,
749
+ success: true,
750
+ errorMessage: `persistence failed: ${msg}`
751
+ }
752
+ };
753
+ }
754
+ }
656
755
  var getLoginStatusWithDeps = async (options = {}, deps = {}) => {
657
756
  const {
658
757
  resolveEnvFilePath = resolveEnvFilePathAsync,
@@ -727,73 +826,103 @@ var getLoginStatusWithDeps = async (options = {}, deps = {}) => {
727
826
  let refreshToken = credentials.UIPATH_REFRESH_TOKEN;
728
827
  let expiration = getTokenExpiration(accessToken);
729
828
  let persistenceWarning;
829
+ let lockReleaseFailed = false;
730
830
  let tokenRefresh;
731
- const expirationThreshold = new Date(Date.now() + (ensureTokenValidityMinutes ?? 0) * 60 * 1000);
732
- if (expiration && expiration <= expirationThreshold && refreshToken) {
733
- let refreshedAccess;
734
- let refreshedRefresh;
831
+ const outerThreshold = computeExpirationThreshold(ensureTokenValidityMinutes);
832
+ const tryGlobalCredsHint = async () => {
833
+ const fs = getFs();
834
+ const globalPath = fs.path.join(fs.env.homedir(), envFilePath);
835
+ if (absolutePath === globalPath)
836
+ return;
837
+ if (!await fs.exists(globalPath))
838
+ return;
735
839
  try {
736
- const config = await resolveConfig({
737
- customAuthority: credentials.UIPATH_URL
738
- });
739
- const refreshed = await refreshTokenFn({
740
- refreshToken,
741
- tokenEndpoint: config.tokenEndpoint,
742
- clientId: config.clientId,
743
- expectedAuthority: credentials.UIPATH_URL
744
- });
745
- refreshedAccess = refreshed.accessToken;
746
- refreshedRefresh = refreshed.refreshToken;
747
- } catch (error) {
748
- const isOAuthFailure = isTokenRefreshOAuthFailure(error);
749
- 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.";
750
- const errorMessage = isOAuthFailure ? normalizeTokenRefreshFailure() : normalizeTokenRefreshUnavailableFailure();
751
- return {
752
- loginStatus: "Refresh Failed",
753
- hint,
754
- tokenRefresh: {
755
- attempted: true,
756
- success: false,
757
- errorMessage
758
- }
759
- };
840
+ const globalCreds = await loadEnvFile({ envPath: globalPath });
841
+ if (!globalCreds.UIPATH_ACCESS_TOKEN)
842
+ return;
843
+ const globalExp = getTokenExpiration(globalCreds.UIPATH_ACCESS_TOKEN);
844
+ if (globalExp && globalExp <= new Date)
845
+ return;
846
+ 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.`;
847
+ } catch {
848
+ return;
760
849
  }
761
- const refreshedExp = getTokenExpiration(refreshedAccess);
762
- if (!refreshedExp || refreshedExp <= new Date) {
850
+ };
851
+ if (expiration && expiration <= outerThreshold && refreshToken) {
852
+ let release;
853
+ try {
854
+ release = await getFs().acquireLock(absolutePath);
855
+ } catch (error) {
856
+ const msg = errorMessage(error);
857
+ const globalHint = await tryGlobalCredsHint();
858
+ if (globalHint) {
859
+ return {
860
+ loginStatus: "Expired",
861
+ accessToken,
862
+ refreshToken,
863
+ baseUrl: credentials.UIPATH_URL,
864
+ organizationName: credentials.UIPATH_ORGANIZATION_NAME,
865
+ organizationId: credentials.UIPATH_ORGANIZATION_ID,
866
+ tenantName: credentials.UIPATH_TENANT_NAME,
867
+ tenantId: credentials.UIPATH_TENANT_ID,
868
+ expiration,
869
+ source: "file" /* File */,
870
+ hint: globalHint,
871
+ tokenRefresh: {
872
+ attempted: false,
873
+ success: false,
874
+ errorMessage: `lock acquisition failed: ${msg}`
875
+ }
876
+ };
877
+ }
763
878
  return {
764
879
  loginStatus: "Refresh Failed",
765
- hint: "The identity server returned an unusable token. Run 'uip login' to re-authenticate.",
880
+ 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.",
766
881
  tokenRefresh: {
767
- attempted: true,
882
+ attempted: false,
768
883
  success: false,
769
- errorMessage: "refreshed token has no valid expiration claim"
884
+ errorMessage: `lock acquisition failed: ${msg}`
770
885
  }
771
886
  };
772
887
  }
773
- accessToken = refreshedAccess;
774
- refreshToken = refreshedRefresh;
775
- expiration = refreshedExp;
888
+ let lockedFailure;
776
889
  try {
777
- await saveEnvFile({
778
- envPath: absolutePath,
779
- data: {
780
- UIPATH_ACCESS_TOKEN: accessToken,
781
- UIPATH_REFRESH_TOKEN: refreshToken
782
- },
783
- merge: true
890
+ const outcome = await runRefreshLocked({
891
+ absolutePath,
892
+ refreshToken,
893
+ customAuthority: credentials.UIPATH_URL,
894
+ ensureTokenValidityMinutes,
895
+ loadEnvFile,
896
+ saveEnvFile,
897
+ refreshFn: refreshTokenFn,
898
+ resolveConfig
784
899
  });
785
- tokenRefresh = {
786
- attempted: true,
787
- success: true
788
- };
789
- } catch (error) {
790
- const msg = error instanceof Error ? error.message : String(error);
791
- 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.`;
792
- tokenRefresh = {
793
- attempted: true,
794
- success: true,
795
- errorMessage: `persistence failed: ${msg}`
796
- };
900
+ if (outcome.kind === "fail") {
901
+ lockedFailure = outcome.status;
902
+ } else {
903
+ accessToken = outcome.accessToken;
904
+ refreshToken = outcome.refreshToken;
905
+ expiration = outcome.expiration;
906
+ tokenRefresh = outcome.tokenRefresh;
907
+ if (outcome.persistenceWarning) {
908
+ persistenceWarning = outcome.persistenceWarning;
909
+ }
910
+ }
911
+ } finally {
912
+ try {
913
+ await release();
914
+ } catch {
915
+ lockReleaseFailed = true;
916
+ }
917
+ }
918
+ if (lockedFailure) {
919
+ const globalHint = await tryGlobalCredsHint();
920
+ const base = globalHint ? {
921
+ ...lockedFailure,
922
+ loginStatus: "Expired",
923
+ hint: globalHint
924
+ } : lockedFailure;
925
+ return lockReleaseFailed ? { ...base, lockReleaseFailed: true } : base;
797
926
  }
798
927
  }
799
928
  const result = {
@@ -808,23 +937,13 @@ var getLoginStatusWithDeps = async (options = {}, deps = {}) => {
808
937
  expiration,
809
938
  source: "file" /* File */,
810
939
  ...persistenceWarning ? { hint: persistenceWarning, persistenceFailed: true } : {},
940
+ ...lockReleaseFailed ? { lockReleaseFailed: true } : {},
811
941
  ...tokenRefresh ? { tokenRefresh } : {}
812
942
  };
813
943
  if (result.loginStatus === "Expired") {
814
- const fs = getFs();
815
- const globalPath = fs.path.join(fs.env.homedir(), envFilePath);
816
- if (absolutePath !== globalPath && await fs.exists(globalPath)) {
817
- try {
818
- const globalCreds = await loadEnvFile({
819
- envPath: globalPath
820
- });
821
- if (globalCreds.UIPATH_ACCESS_TOKEN) {
822
- const globalExp = getTokenExpiration(globalCreds.UIPATH_ACCESS_TOKEN);
823
- if (!globalExp || globalExp > new Date) {
824
- 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.`;
825
- }
826
- }
827
- } catch {}
944
+ const globalHint = await tryGlobalCredsHint();
945
+ if (globalHint) {
946
+ result.hint = globalHint;
828
947
  }
829
948
  }
830
949
  return result;
@@ -884,9 +1003,11 @@ var clientCredentialsLogin = async ({
884
1003
  const params = {
885
1004
  grant_type: "client_credentials",
886
1005
  client_id: config.clientId,
887
- client_secret: config.clientSecret ?? "",
888
- scope: config.scopes.join(" ")
1006
+ client_secret: config.clientSecret ?? ""
889
1007
  };
1008
+ if (config.scopes.length > 0) {
1009
+ params.scope = config.scopes.join(" ");
1010
+ }
890
1011
  const tokenResponse = await fetch(config.tokenEndpoint, {
891
1012
  method: "POST",
892
1013
  headers: {
@@ -933,7 +1054,21 @@ async function logoutWithDeps(options, deps = {}) {
933
1054
  const fs = getFs();
934
1055
  const { absolutePath } = await resolveEnvFilePath(options.file);
935
1056
  if (absolutePath && await fs.exists(absolutePath)) {
936
- await fs.rm(absolutePath);
1057
+ let release;
1058
+ try {
1059
+ if (typeof fs.acquireLock === "function") {
1060
+ release = await fs.acquireLock(absolutePath);
1061
+ }
1062
+ } catch {
1063
+ release = undefined;
1064
+ }
1065
+ try {
1066
+ await fs.rm(absolutePath);
1067
+ } finally {
1068
+ if (release) {
1069
+ await release().catch(() => {});
1070
+ }
1071
+ }
937
1072
  return {
938
1073
  success: true,
939
1074
  message: `Logged out successfully. Removed ${absolutePath}`,
@@ -0,0 +1,27 @@
1
+ export * from "./authContext";
2
+ export * from "./clientCredentials";
3
+ export { type AuthFileConfig, InvalidBaseUrlError, setAuthFileConfig, } from "./config";
4
+ export { ENFORCE_ROBOT_AUTH_VAR, ENV_AUTH_ENABLE_VAR, ENV_AUTH_VARS, EnvAuthConfigError, isEnvAuthEnabled, isRobotAuthEnforced, readAuthFromEnv, } from "./envAuth";
5
+ export * from "./interactive";
6
+ export * from "./loginStatus";
7
+ export * from "./logout";
8
+ export { type SelectFromList, selectTenantWithDeps } from "./selectTenant";
9
+ export { AUTH_TIMEOUT_ERROR_CODE } from "./server";
10
+ export { fetchTenantsAndOrganizations, type Tenant, type TenantsAndOrganizations, } from "./tenantSelection";
11
+ export * from "./tokenRefresh";
12
+ export type { BaseCredentials } from "./types";
13
+ export { DEFAULT_AUTH_FILENAME, DEFAULT_ENV_FILENAME, type EnvFileErrorCode, type EnvFileLocation, type EnvFileSource, type EnvFileUnusable, type EnvFileUnusableReason, loadEnvFileAsync, resolveEnvFileLocationAsync, resolveEnvFilePathAsync, saveEnvFileAsync, } from "./utils/envFile";
14
+ export { parseJWT } from "./utils/jwt";
15
+ export { isBrowser, isNode } from "./utils/platform";
16
+ interface AuthenticateOptions {
17
+ baseUrl?: string;
18
+ clientId?: string;
19
+ clientSecret?: string;
20
+ redirectUri?: URL;
21
+ scope?: string[];
22
+ organization?: string;
23
+ }
24
+ export declare const authenticate: ({ baseUrl, clientId, clientSecret, redirectUri, scope, organization, }: AuthenticateOptions) => Promise<{
25
+ accessToken: string;
26
+ refreshToken: string;
27
+ }>;