@uipath/auth 1.196.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;
@@ -51,6 +54,14 @@ interface InteractiveLoginOptions {
51
54
  * authorization-code (browser) path uses it.
52
55
  */
53
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;
54
65
  }
55
66
  export interface InteractiveLoginDependencies {
56
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;
@@ -53,6 +55,10 @@ export interface LoginStatus {
53
55
  */
54
56
  lockReleaseFailed?: boolean;
55
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;
56
62
  }
57
63
  export interface GetLoginStatusOptions {
58
64
  envFilePath?: string;
@@ -66,11 +72,10 @@ export interface LoginStatusDependencies {
66
72
  refreshToken?: typeof refreshAccessToken;
67
73
  resolveConfig?: typeof resolveConfigAsync;
68
74
  robotFallback?: typeof tryRobotClientFallback;
75
+ loadBreaker?: typeof loadRefreshBreaker;
76
+ saveBreaker?: typeof saveRefreshBreaker;
77
+ clearBreaker?: typeof clearRefreshBreaker;
69
78
  }
70
- /**
71
- * Get the current login status by reading credentials from the credentials file (with DI for testing)
72
- */
73
- export declare const getLoginStatusWithDeps: (options?: GetLoginStatusOptions, deps?: LoginStatusDependencies) => Promise<LoginStatus>;
74
79
  /**
75
80
  * Get the current login status by reading credentials from the credentials file
76
81
  * @param envFilePath - Path to the credentials file (defaults to ".uipath/.auth")
@@ -78,3 +83,7 @@ export declare const getLoginStatusWithDeps: (options?: GetLoginStatusOptions, d
78
83
  * @returns LoginStatus object with authentication information
79
84
  */
80
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;
@@ -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,7 +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,
3
- /** Optional controls; `timeoutMs` bounds the wait for the callback. */
4
- opts?: {
5
- timeoutMs?: number;
6
- }): Promise<string>;
17
+ execute(url: string, redirectUri: URL, expectedState: string, opts?: AuthExecuteOptions): Promise<string>;
7
18
  }
@@ -1,6 +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, opts?: {
4
- timeoutMs?: number;
5
- }): Promise<string>;
3
+ execute(url: string, redirectUri: URL, expectedState: string, opts?: AuthExecuteOptions): Promise<string>;
6
4
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@uipath/auth",
3
3
  "license": "MIT",
4
- "version": "1.196.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": "94d71f9c52214980a1f0ae62b3f5372095788553"
40
+ "gitHead": "df0e2b8140cced13f463b487214927c82bc0f85b"
41
41
  }