@uipath/auth 1.195.0 → 1.197.0-preview.59

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.
@@ -24,6 +24,9 @@ export type InteractiveLoginEvent = {
24
24
  } | {
25
25
  type: "tenant-fetch-failed";
26
26
  message: string;
27
+ } | {
28
+ type: "auth-url";
29
+ url: string;
27
30
  };
28
31
  interface InteractiveLoginOptions {
29
32
  envFilePath?: string;
@@ -42,6 +45,23 @@ interface InteractiveLoginOptions {
42
45
  selectFromList?: SelectFromList;
43
46
  /** Optional subscriber for user-facing progress events. */
44
47
  onEvent?: (event: InteractiveLoginEvent) => void;
48
+ /**
49
+ * Max ms to wait for the browser callback before the local server closes
50
+ * itself and the login rejects with `AUTH_TIMEOUT_ERROR_CODE`. Lets an
51
+ * in-process host (e.g. the VS Code extension) bound a stuck login and
52
+ * free the callback port — parity with the CLI killing its `uip`
53
+ * subprocess on timeout. Defaults to the server's 5-min cap. Only the
54
+ * authorization-code (browser) path uses it.
55
+ */
56
+ timeoutMs?: number;
57
+ /**
58
+ * Headless / automation-driven login: skip launching the system browser
59
+ * on the authorization-code path. The authorize URL is surfaced via an
60
+ * `auth-url` event so an external automation can open it; the existing
61
+ * local callback still completes the flow. No effect with `clientSecret`
62
+ * (client-credentials opens no browser).
63
+ */
64
+ noBrowser?: boolean;
45
65
  }
46
66
  export interface InteractiveLoginDependencies {
47
67
  resolveConfig?: typeof resolveConfigAsync;
@@ -1,5 +1,6 @@
1
1
  import { getFileSystem } from "@uipath/filesystem";
2
2
  import { resolveConfigAsync } from "./config";
3
+ import { clearRefreshBreaker, loadRefreshBreaker, saveRefreshBreaker } from "./refreshCircuitBreaker";
3
4
  import { tryRobotClientFallback } from "./robotClientFallback";
4
5
  import { refreshAccessToken } from "./tokenRefresh";
5
6
  import { loadEnvFileAsync, resolveEnvFilePathAsync, saveEnvFileAsync } from "./utils/envFile";
@@ -11,7 +12,8 @@ import { loadEnvFileAsync, resolveEnvFilePathAsync, saveEnvFileAsync } from "./u
11
12
  export type LoginStatusValue = "Logged in" | "Not logged in" | "Expired" | "Refresh Failed";
12
13
  export declare enum LoginStatusSource {
13
14
  File = "file",
14
- Robot = "robot"
15
+ Robot = "robot",
16
+ Env = "env"
15
17
  }
16
18
  export interface TokenRefreshTelemetry {
17
19
  attempted: boolean;
@@ -27,6 +29,13 @@ export interface LoginStatus {
27
29
  organizationId?: string;
28
30
  tenantName?: string;
29
31
  tenantId?: string;
32
+ /**
33
+ * Identity authority derived from the access token's `iss` claim.
34
+ * Authoritative for both cloud (`…/identity_`) and on-prem (`…/identity`),
35
+ * so consumers should build identity URLs from this rather than hardcoding
36
+ * the gateway path. Currently populated only on the Robot-borrow path.
37
+ */
38
+ issuer?: string;
30
39
  expiration?: Date;
31
40
  hint?: string;
32
41
  source?: LoginStatusSource;
@@ -46,6 +55,10 @@ export interface LoginStatus {
46
55
  */
47
56
  lockReleaseFailed?: boolean;
48
57
  tokenRefresh?: TokenRefreshTelemetry;
58
+ /** Refresh was short-circuited by the breaker; no IdP call was made. */
59
+ refreshCircuitOpen?: boolean;
60
+ /** Suppress the duplicate `uip.error` telemetry; still show the error. */
61
+ refreshTelemetrySuppressed?: boolean;
49
62
  }
50
63
  export interface GetLoginStatusOptions {
51
64
  envFilePath?: string;
@@ -59,11 +72,10 @@ export interface LoginStatusDependencies {
59
72
  refreshToken?: typeof refreshAccessToken;
60
73
  resolveConfig?: typeof resolveConfigAsync;
61
74
  robotFallback?: typeof tryRobotClientFallback;
75
+ loadBreaker?: typeof loadRefreshBreaker;
76
+ saveBreaker?: typeof saveRefreshBreaker;
77
+ clearBreaker?: typeof clearRefreshBreaker;
62
78
  }
63
- /**
64
- * Get the current login status by reading credentials from the credentials file (with DI for testing)
65
- */
66
- export declare const getLoginStatusWithDeps: (options?: GetLoginStatusOptions, deps?: LoginStatusDependencies) => Promise<LoginStatus>;
67
79
  /**
68
80
  * Get the current login status by reading credentials from the credentials file
69
81
  * @param envFilePath - Path to the credentials file (defaults to ".uipath/.auth")
@@ -71,3 +83,7 @@ export declare const getLoginStatusWithDeps: (options?: GetLoginStatusOptions, d
71
83
  * @returns LoginStatus object with authentication information
72
84
  */
73
85
  export declare const getLoginStatusAsync: (options?: GetLoginStatusOptions) => Promise<LoginStatus>;
86
+ /**
87
+ * Get the current login status by reading credentials from the credentials file (with DI for testing)
88
+ */
89
+ export declare const getLoginStatusWithDeps: (options?: GetLoginStatusOptions, deps?: LoginStatusDependencies) => Promise<LoginStatus>;
package/dist/logout.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import { getFileSystem } from "@uipath/filesystem";
2
+ import { getActiveAuthProfileFilePath } from "./authProfile";
3
+ import { clearRefreshBreaker } from "./refreshCircuitBreaker";
2
4
  import { resolveEnvFilePathAsync } from "./utils/envFile";
3
5
  export interface LogoutOptions {
4
6
  file?: string;
@@ -22,6 +24,8 @@ export interface LogoutFileSystem {
22
24
  export interface LogoutDependencies {
23
25
  resolveEnvFilePath?: typeof resolveEnvFilePathAsync;
24
26
  getFileSystem?: typeof getFileSystem;
27
+ clearBreaker?: typeof clearRefreshBreaker;
28
+ getActiveProfileFilePath?: typeof getActiveAuthProfileFilePath;
25
29
  }
26
30
  /**
27
31
  * Logout from UiPath Cloud by removing the credentials file.
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Persistent circuit-breaker state for the token-refresh path, stored as a
3
+ * JSON sibling of the credentials file. Each `uip` call is a separate process,
4
+ * so an in-memory breaker can't stop a non-interactive caller from re-hammering
5
+ * the IdP with a refresh we already know will fail — the state must live on
6
+ * disk. Keyed by a fingerprint of the refresh token, so re-login rotates the
7
+ * token and self-heals the breaker with no explicit reset.
8
+ */
9
+ export interface RefreshBreakerState {
10
+ /** Fingerprint of a terminally-failed (`invalid_grant`) refresh token. */
11
+ deadTokenFp?: string;
12
+ /** Epoch ms before which transient failures should not be retried. */
13
+ backoffUntilMs?: number;
14
+ /** Consecutive transient-failure count; drives the backoff curve. */
15
+ attempts?: number;
16
+ /** Epoch ms a circuit-open failure was last surfaced to telemetry. */
17
+ lastSurfacedAtMs?: number;
18
+ }
19
+ /** Surface a circuit-open failure to telemetry at most once per hour. */
20
+ export declare const SURFACE_WINDOW_MS: number;
21
+ /**
22
+ * Non-reversible, truncated fingerprint — never persists the token itself.
23
+ * Uses WebCrypto so it works in the browser bundle, where `node:crypto` is
24
+ * shimmed to throw; falls back to `node:crypto` only on runtimes without a
25
+ * global `crypto.subtle`.
26
+ */
27
+ export declare function refreshTokenFingerprint(refreshToken: string): Promise<string>;
28
+ /** A missing or unparseable file is treated as "no breaker" (empty state). */
29
+ export declare function loadRefreshBreaker(authPath: string): Promise<RefreshBreakerState>;
30
+ /**
31
+ * Persist atomically (temp file + rename). Best-effort and self-protecting
32
+ * (like {@link clearRefreshBreaker}): a breaker we can't write degrades to
33
+ * today's retry-every-time behavior, no worse than before — so callers don't
34
+ * need their own `.catch`.
35
+ */
36
+ export declare function saveRefreshBreaker(authPath: string, state: RefreshBreakerState): Promise<void>;
37
+ /** Remove the breaker file. Best-effort; a missing file is success. */
38
+ export declare function clearRefreshBreaker(authPath: string): Promise<void>;
39
+ /** Backoff for the Nth consecutive failure: 1 min, doubling, capped at 1h. */
40
+ export declare function nextBackoffMs(attempts: number): number;
41
+ /** First failure always surfaces; afterwards at most once per window. */
42
+ export declare function shouldSurface(state: RefreshBreakerState, nowMs: number): boolean;
@@ -21,10 +21,11 @@ interface RobotClientModule {
21
21
  export interface RobotClientFallbackResult {
22
22
  accessToken: string;
23
23
  baseUrl: string;
24
- organizationName: string;
25
- organizationId: string;
26
- tenantName: string;
24
+ organizationName?: string;
25
+ organizationId?: string;
26
+ tenantName?: string;
27
27
  tenantId?: string;
28
+ issuer?: string;
28
29
  }
29
30
  export interface RobotClientFallbackOptions {
30
31
  timeoutMs?: number;
@@ -1,5 +1,40 @@
1
1
  import { type TenantsAndOrganizations } from "./tenantSelection";
2
2
  export type SelectFromList = (options: string[], message: string) => Promise<number>;
3
+ export declare const TENANT_SELECTION_REQUIRED_CODE = "TENANT_SELECTION_REQUIRED";
4
+ export declare const INVALID_TENANT_CODE = "INVALID_TENANT";
5
+ /**
6
+ * Auth succeeded, but we cannot resolve a single tenant to use — either none
7
+ * was chosen for a multi-tenant org we can't prompt in, or the requested
8
+ * `--tenant` doesn't exist. Both carry the org's tenant names so the host can
9
+ * render one actionable, non-zero-exit error that lists the valid choices.
10
+ *
11
+ * Recognized across bundle boundaries by `code` (see `isTenantSelectionError`),
12
+ * never `instanceof` — each tool bundles its own copy of this class, so class
13
+ * identity does not survive the boundary.
14
+ */
15
+ export declare abstract class TenantSelectionError extends Error {
16
+ abstract readonly code: string;
17
+ readonly availableTenants: string[];
18
+ readonly organizationName: string;
19
+ constructor(message: string, organizationName: string, availableTenants: string[]);
20
+ }
21
+ /** Multiple tenants exist, none was chosen, and we can't prompt. */
22
+ export declare class TenantSelectionRequiredError extends TenantSelectionError {
23
+ readonly code = "TENANT_SELECTION_REQUIRED";
24
+ constructor(organizationName: string, availableTenants: string[]);
25
+ }
26
+ /** A `--tenant <name>` was given that matches no tenant in the org. */
27
+ export declare class InvalidTenantError extends TenantSelectionError {
28
+ readonly code = "INVALID_TENANT";
29
+ readonly requestedTenant: string;
30
+ constructor(requestedTenant: string, organizationName: string, availableTenants: string[]);
31
+ }
32
+ /**
33
+ * Duck-typed across bundles: true for either tenant-selection error. Checks
34
+ * `code` and the carried `availableTenants` rather than `instanceof`, so it
35
+ * works on the CLI side where the error crossed a bundle boundary.
36
+ */
37
+ export declare function isTenantSelectionError(error: unknown): error is TenantSelectionError;
3
38
  interface TenantSelectionDependencies {
4
39
  fetchTenantsAndOrgs?: (baseUrl: string, accessToken: string, organizationId: string) => Promise<TenantsAndOrganizations>;
5
40
  selectFromList?: SelectFromList;
@@ -1,3 +1,18 @@
1
+ /** Optional controls for {@link IAuthStrategy.execute}. Named once and shared
2
+ * with the concrete strategies so the three copies can't drift. */
3
+ export interface AuthExecuteOptions {
4
+ /** Bounds the wait for the callback. */
5
+ timeoutMs?: number;
6
+ /** When true, do not launch the system browser. Requires `onAuthUrl` so
7
+ * the URL is surfaced to the caller instead of being lost. */
8
+ noBrowser?: boolean;
9
+ /**
10
+ * Invoked with the authorize URL when `noBrowser` is set, in place of
11
+ * opening a browser. Called once, when the callback server starts
12
+ * listening. Mandatory whenever `noBrowser` is true.
13
+ */
14
+ onAuthUrl?: (url: string) => void;
15
+ }
1
16
  export interface IAuthStrategy {
2
- execute(url: string, redirectUri: URL, expectedState: string): Promise<string>;
17
+ execute(url: string, redirectUri: URL, expectedState: string, opts?: AuthExecuteOptions): Promise<string>;
3
18
  }
@@ -1,4 +1,6 @@
1
1
  import type { IAuthStrategy } from "./auth-strategy";
2
2
  export declare class BrowserAuthStrategy implements IAuthStrategy {
3
- execute(url: string, _redirectUri: URL, expectedState: string): Promise<string>;
3
+ execute(url: string, _redirectUri: URL, expectedState: string, _opts?: {
4
+ timeoutMs?: number;
5
+ }): Promise<string>;
4
6
  }
@@ -1,4 +1,4 @@
1
- import type { IAuthStrategy } from "./auth-strategy";
1
+ import type { AuthExecuteOptions, IAuthStrategy } from "./auth-strategy";
2
2
  export declare class NodeAuthStrategy implements IAuthStrategy {
3
- execute(url: string, redirectUri: URL, expectedState: string): Promise<string>;
3
+ execute(url: string, redirectUri: URL, expectedState: string, opts?: AuthExecuteOptions): Promise<string>;
4
4
  }
@@ -74,7 +74,9 @@ export type EnvFileLocation = {
74
74
  * "not found" and the search continues; only the final candidate (the
75
75
  * one actually returned) carries the `unusable` block.
76
76
  */
77
- export declare const resolveEnvFileLocationAsync: (envFilePath?: string) => Promise<EnvFileLocation>;
77
+ export declare const resolveEnvFileLocationAsync: (envFilePath?: string, opts?: {
78
+ cwd?: string;
79
+ }) => Promise<EnvFileLocation>;
78
80
  /**
79
81
  * Resolve the absolute path to an environment file, returning an
80
82
  * `errorMessage` if no existing usable file was found. Delegates the
@@ -83,8 +85,11 @@ export declare const resolveEnvFileLocationAsync: (envFilePath?: string) => Prom
83
85
  * semantics including the unreadable-vs-missing distinction.
84
86
  *
85
87
  * @param envFilePath - Path to the credentials file (defaults to ".uipath/.auth")
88
+ * @param opts.cwd - Directory to start the relative-path walk-up from (defaults to the process cwd)
86
89
  */
87
- export declare const resolveEnvFilePathAsync: (envFilePath?: string) => Promise<ResolveEnvFilePathResult>;
90
+ export declare const resolveEnvFilePathAsync: (envFilePath?: string, opts?: {
91
+ cwd?: string;
92
+ }) => Promise<ResolveEnvFilePathResult>;
88
93
  /**
89
94
  * Load environment variables from a credentials file
90
95
  * @param envPath - Path to the credentials file (relative to cwd or absolute)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@uipath/auth",
3
3
  "license": "MIT",
4
- "version": "1.195.0",
4
+ "version": "1.197.0-preview.59",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/UiPath/cli.git",
@@ -37,5 +37,5 @@
37
37
  "mihaigirleanu",
38
38
  "vlad-uipath"
39
39
  ],
40
- "gitHead": "65fabb84552758b2710d8ca68470e70a9b1d19bc"
40
+ "gitHead": "df0e2b8140cced13f463b487214927c82bc0f85b"
41
41
  }