@agentxm/registry-auth 0.28.4-bootstrap.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/LICENSE +110 -0
- package/dist/src/auth-client.d.ts +199 -0
- package/dist/src/auth-client.js +638 -0
- package/dist/src/auth-middleware.d.ts +28 -0
- package/dist/src/auth-middleware.js +105 -0
- package/dist/src/credential-store.d.ts +77 -0
- package/dist/src/credential-store.js +443 -0
- package/dist/src/device-login.d.ts +114 -0
- package/dist/src/device-login.js +300 -0
- package/dist/src/errors.d.ts +141 -0
- package/dist/src/errors.js +84 -0
- package/dist/src/guard.d.ts +24 -0
- package/dist/src/guard.js +62 -0
- package/dist/src/index.d.ts +36 -0
- package/dist/src/index.js +31 -0
- package/dist/src/internal/environment.d.ts +23 -0
- package/dist/src/internal/environment.js +45 -0
- package/dist/src/live.d.ts +13 -0
- package/dist/src/live.js +13 -0
- package/dist/src/login-interaction.d.ts +41 -0
- package/dist/src/login-interaction.js +108 -0
- package/dist/src/login-output.d.ts +22 -0
- package/dist/src/login-output.js +32 -0
- package/dist/src/login-presenter.d.ts +95 -0
- package/dist/src/login-presenter.js +58 -0
- package/dist/src/login-strategy.d.ts +21 -0
- package/dist/src/login-strategy.js +25 -0
- package/dist/src/loopback-login.d.ts +19 -0
- package/dist/src/loopback-login.js +91 -0
- package/dist/src/loopback-server.d.ts +40 -0
- package/dist/src/loopback-server.js +168 -0
- package/dist/src/oauth-contract.d.ts +7 -0
- package/dist/src/oauth-contract.js +2 -0
- package/dist/src/pending-device-login-store.d.ts +37 -0
- package/dist/src/pending-device-login-store.js +122 -0
- package/dist/src/publish-authorization.d.ts +11 -0
- package/dist/src/publish-authorization.js +79 -0
- package/dist/src/schema.d.ts +78 -0
- package/dist/src/schema.js +55 -0
- package/dist/src/testing.d.ts +14 -0
- package/dist/src/testing.js +14 -0
- package/dist/src/token-resolution.d.ts +79 -0
- package/dist/src/token-resolution.js +190 -0
- package/package.json +62 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth guard combinator for commands that require authentication.
|
|
3
|
+
*
|
|
4
|
+
* Wraps an Effect with a pre-check for authentication. If no token is
|
|
5
|
+
* resolvable, fails immediately with an actionable error message directing
|
|
6
|
+
* the user to `axm login`.
|
|
7
|
+
*
|
|
8
|
+
* @experimental This API is unstable and may change without notice.
|
|
9
|
+
*/
|
|
10
|
+
import * as Effect from "effect/Effect";
|
|
11
|
+
import * as Option from "effect/Option";
|
|
12
|
+
import { RegistryUrl } from "@agentxm/registry-client";
|
|
13
|
+
import { authLoginRequired, RegistryAuthFailed } from "./errors.js";
|
|
14
|
+
import { CredentialStore, makePersistedCredentialsUnsupportedError } from "./credential-store.js";
|
|
15
|
+
import { resolveRequestToken } from "./token-resolution.js";
|
|
16
|
+
// -----------------------------------------------------------------------------
|
|
17
|
+
// Constants
|
|
18
|
+
// -----------------------------------------------------------------------------
|
|
19
|
+
const AUTH_LOGIN_REQUIRED = authLoginRequired();
|
|
20
|
+
// -----------------------------------------------------------------------------
|
|
21
|
+
// Auth guard combinator
|
|
22
|
+
// -----------------------------------------------------------------------------
|
|
23
|
+
const isRemoteRegistryUrl = (registryUrl) => Effect.try({
|
|
24
|
+
try: () => {
|
|
25
|
+
const protocol = new URL(registryUrl).protocol;
|
|
26
|
+
return protocol === "http:" || protocol === "https:";
|
|
27
|
+
},
|
|
28
|
+
catch: (error) => new RegistryAuthFailed({
|
|
29
|
+
category: "validation",
|
|
30
|
+
detail: `Invalid registry URL: ${registryUrl}`,
|
|
31
|
+
suggestions: [{ description: "Check the registry URL in your settings." }],
|
|
32
|
+
cause: error,
|
|
33
|
+
}),
|
|
34
|
+
});
|
|
35
|
+
/**
|
|
36
|
+
* Wraps an Effect with an auth guard.
|
|
37
|
+
*
|
|
38
|
+
* - If the target registry is local, runs the inner effect directly.
|
|
39
|
+
* - If a token is already resolvable, runs the inner effect directly.
|
|
40
|
+
* - If no token: fails with `AUTH_LOGIN_REQUIRED`.
|
|
41
|
+
*/
|
|
42
|
+
export const withAuthGuard = (effect, options) => Effect.gen(function* () {
|
|
43
|
+
const defaultRegistryUrl = yield* RegistryUrl;
|
|
44
|
+
const targetRegistryUrl = options?.registryUrl ?? defaultRegistryUrl;
|
|
45
|
+
// Local registries do not require HTTP auth.
|
|
46
|
+
const isRemote = yield* isRemoteRegistryUrl(targetRegistryUrl);
|
|
47
|
+
if (!isRemote) {
|
|
48
|
+
return yield* effect;
|
|
49
|
+
}
|
|
50
|
+
const token = yield* resolveRequestToken(targetRegistryUrl, defaultRegistryUrl);
|
|
51
|
+
// Token available — proceed directly
|
|
52
|
+
if (Option.isSome(token)) {
|
|
53
|
+
return yield* effect;
|
|
54
|
+
}
|
|
55
|
+
const credStore = yield* CredentialStore;
|
|
56
|
+
if (!credStore.allowsPersistedCredentials) {
|
|
57
|
+
return yield* makePersistedCredentialsUnsupportedError();
|
|
58
|
+
}
|
|
59
|
+
// No token — fail fast
|
|
60
|
+
return yield* AUTH_LOGIN_REQUIRED;
|
|
61
|
+
});
|
|
62
|
+
//# sourceMappingURL=guard.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry-auth feature: login, logout, token, identity inspection, device
|
|
3
|
+
* and loopback flows, and credential lifecycle.
|
|
4
|
+
*
|
|
5
|
+
* Provides credential storage, environment detection, token resolution,
|
|
6
|
+
* auth middleware, and device login orchestration for authentication.
|
|
7
|
+
* Environment-backed Layers live behind `./live`; deterministic in-memory
|
|
8
|
+
* ports live behind `./testing`.
|
|
9
|
+
*
|
|
10
|
+
* @experimental This API is unstable and may change without notice.
|
|
11
|
+
* @packageDocumentation
|
|
12
|
+
*/
|
|
13
|
+
export { AuthExchangeFailed, AuthLoginRequired, AuthTokenPolicyRequired, authLoginRequired, DeviceAuthorizationPending, DeviceLoginCodeExpired, DeviceLoginDenied, isAuthError, isRegistryAuthFailure, REGISTRY_AUTH_ERROR_CATEGORIES, RegistryAuthFailed, StepUpRequired, type AuthError, type RegistryAuthErrorCategory, type RegistryAuthFailure, type StepUpRequest, } from "./errors.js";
|
|
14
|
+
export type { CredentialEntry, CredentialFile, StorageTier, StoredCredentials, TokenSource, } from "./schema.js";
|
|
15
|
+
export { CredentialEntrySchema, CredentialFileSchema, CredentialStoreTokenSource, EnvVarTokenSource, FileTokenSource, FlagTokenSource, RegistryAccountsSchema, } from "./schema.js";
|
|
16
|
+
export type { CredentialStoreService, EnvironmentInfo } from "./credential-store.js";
|
|
17
|
+
export { canUsePersistedCredentials, CredentialStore, detectEnvironment, makePersistedCredentialsUnsupportedError, selectTier, } from "./credential-store.js";
|
|
18
|
+
export type { PendingDeviceLogin, PendingDeviceLoginStoreService, } from "./pending-device-login-store.js";
|
|
19
|
+
export { PendingDeviceLoginSchema, PendingDeviceLoginStore } from "./pending-device-login-store.js";
|
|
20
|
+
export { getCurrentUserHandle, resolveRequiredToken, resolveToken, resolveStoredToken, refreshStoredToken, resolveAmbientToken, resolveRequestToken, } from "./token-resolution.js";
|
|
21
|
+
export type { AuthClientService, CreateTokenOptions, CreatePublishAuthorizationRequestParams, DeviceFlowResponse, ExchangePublishAuthorizationCodeParams, MeResponse, PollResult, PublishAuthorizationRequestResponse, PublishCapabilityResponse, } from "./auth-client.js";
|
|
22
|
+
export { AuthClient, pollOnce, readStepUpRequest } from "./auth-client.js";
|
|
23
|
+
export type { NormalizedTokenResponse } from "./oauth-contract.js";
|
|
24
|
+
export type { DeviceLoginPendingResult, DeviceLoginInteractionService, ResumeDeviceLoginOptions, } from "./device-login.js";
|
|
25
|
+
export { DeviceLoginPendingDocumentSchema, DeviceLoginPendingResultSchema, DeviceLoginInteraction, initiateDeviceLogin, resumeDeviceLogin, runDeviceLogin, type RunDeviceLoginOptions, } from "./device-login.js";
|
|
26
|
+
export { makeOAuthState, makePkceChallenge, makePkceVerifier, runLoopbackLogin, type RunLoopbackLoginOptions, } from "./loopback-login.js";
|
|
27
|
+
export { LoopbackCallbackRejected, LoopbackLoginFallback, startLoopbackServer, } from "./loopback-server.js";
|
|
28
|
+
export { LoginDocumentSchema, LoginResultSchema, makeLoginResult, type LoginDocument, type LoginResult, } from "./login-output.js";
|
|
29
|
+
export type { AuthLoginPresenterService, AuthLoginProgress, DeviceFlowPresentation, } from "./login-presenter.js";
|
|
30
|
+
export { AuthLoginPresenter } from "./login-presenter.js";
|
|
31
|
+
export { runPublishAuthorization, type PublishAuthorizationInput, } from "./publish-authorization.js";
|
|
32
|
+
export { selectLoginStrategy, type LoginStrategy, type LoginStrategyEnvironment, type LoginStrategyOptions, } from "./login-strategy.js";
|
|
33
|
+
export type { AuthLoginInteractionService } from "./login-interaction.js";
|
|
34
|
+
export { AuthLoginInteraction } from "./login-interaction.js";
|
|
35
|
+
export { withAuthGuard } from "./guard.js";
|
|
36
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry-auth feature: login, logout, token, identity inspection, device
|
|
3
|
+
* and loopback flows, and credential lifecycle.
|
|
4
|
+
*
|
|
5
|
+
* Provides credential storage, environment detection, token resolution,
|
|
6
|
+
* auth middleware, and device login orchestration for authentication.
|
|
7
|
+
* Environment-backed Layers live behind `./live`; deterministic in-memory
|
|
8
|
+
* ports live behind `./testing`.
|
|
9
|
+
*
|
|
10
|
+
* @experimental This API is unstable and may change without notice.
|
|
11
|
+
* @packageDocumentation
|
|
12
|
+
*/
|
|
13
|
+
// Typed failures
|
|
14
|
+
export { AuthExchangeFailed, AuthLoginRequired, AuthTokenPolicyRequired, authLoginRequired, DeviceAuthorizationPending, DeviceLoginCodeExpired, DeviceLoginDenied, isAuthError, isRegistryAuthFailure, REGISTRY_AUTH_ERROR_CATEGORIES, RegistryAuthFailed, StepUpRequired, } from "./errors.js";
|
|
15
|
+
export { CredentialEntrySchema, CredentialFileSchema, CredentialStoreTokenSource, EnvVarTokenSource, FileTokenSource, FlagTokenSource, RegistryAccountsSchema, } from "./schema.js";
|
|
16
|
+
export { canUsePersistedCredentials, CredentialStore, detectEnvironment, makePersistedCredentialsUnsupportedError, selectTier, } from "./credential-store.js";
|
|
17
|
+
export { PendingDeviceLoginSchema, PendingDeviceLoginStore } from "./pending-device-login-store.js";
|
|
18
|
+
// Token resolution
|
|
19
|
+
export { getCurrentUserHandle, resolveRequiredToken, resolveToken, resolveStoredToken, refreshStoredToken, resolveAmbientToken, resolveRequestToken, } from "./token-resolution.js";
|
|
20
|
+
export { AuthClient, pollOnce, readStepUpRequest } from "./auth-client.js";
|
|
21
|
+
export { DeviceLoginPendingDocumentSchema, DeviceLoginPendingResultSchema, DeviceLoginInteraction, initiateDeviceLogin, resumeDeviceLogin, runDeviceLogin, } from "./device-login.js";
|
|
22
|
+
export { makeOAuthState, makePkceChallenge, makePkceVerifier, runLoopbackLogin, } from "./loopback-login.js";
|
|
23
|
+
export { LoopbackCallbackRejected, LoopbackLoginFallback, startLoopbackServer, } from "./loopback-server.js";
|
|
24
|
+
export { LoginDocumentSchema, LoginResultSchema, makeLoginResult, } from "./login-output.js";
|
|
25
|
+
export { AuthLoginPresenter } from "./login-presenter.js";
|
|
26
|
+
export { runPublishAuthorization, } from "./publish-authorization.js";
|
|
27
|
+
export { selectLoginStrategy, } from "./login-strategy.js";
|
|
28
|
+
export { AuthLoginInteraction } from "./login-interaction.js";
|
|
29
|
+
// Auth guard combinator
|
|
30
|
+
export { withAuthGuard } from "./guard.js";
|
|
31
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime environment detection for credential policy and login flows.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions for env var / process checks. Effect-based for filesystem
|
|
5
|
+
* checks. Mirrors the shared helpers the extracted kernels carry; a generic
|
|
6
|
+
* utils package is deliberately not created.
|
|
7
|
+
*/
|
|
8
|
+
import * as FileSystem from "effect/FileSystem";
|
|
9
|
+
import * as Effect from "effect/Effect";
|
|
10
|
+
import * as Option from "effect/Option";
|
|
11
|
+
/** Read an optional env var. Centralized access point for process.env. */
|
|
12
|
+
export declare const envOption: (name: string) => Effect.Effect<Option.Option<string>>;
|
|
13
|
+
/** Returns true if SSH_CLIENT or SSH_TTY env var is set. */
|
|
14
|
+
export declare const isSSH: Effect.Effect<boolean>;
|
|
15
|
+
/** Returns true if running as root (uid 0). */
|
|
16
|
+
export declare const isRoot: () => boolean;
|
|
17
|
+
/** Returns true if /.dockerenv or /.containerenv exists. Requires FileSystem. */
|
|
18
|
+
export declare const isContainer: Effect.Effect<boolean, never, FileSystem.FileSystem>;
|
|
19
|
+
/** Returns true if /proc/version contains "microsoft". Requires FileSystem. */
|
|
20
|
+
export declare const isWSL: Effect.Effect<boolean, never, FileSystem.FileSystem>;
|
|
21
|
+
/** Returns true if CI env var is set. */
|
|
22
|
+
export declare const isCI: Effect.Effect<boolean>;
|
|
23
|
+
//# sourceMappingURL=environment.d.ts.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime environment detection for credential policy and login flows.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions for env var / process checks. Effect-based for filesystem
|
|
5
|
+
* checks. Mirrors the shared helpers the extracted kernels carry; a generic
|
|
6
|
+
* utils package is deliberately not created.
|
|
7
|
+
*/
|
|
8
|
+
import * as FileSystem from "effect/FileSystem";
|
|
9
|
+
import * as Effect from "effect/Effect";
|
|
10
|
+
import * as Option from "effect/Option";
|
|
11
|
+
// eslint-disable-next-line no-restricted-properties -- Centralized env var access point; all callers use these helpers
|
|
12
|
+
const readEnv = (name) => process.env[name];
|
|
13
|
+
/** Read an optional env var. Centralized access point for process.env. */
|
|
14
|
+
export const envOption = (name) => Effect.sync(() => Option.fromUndefinedOr(readEnv(name)));
|
|
15
|
+
/** Returns true if SSH_CLIENT or SSH_TTY env var is set. */
|
|
16
|
+
export const isSSH = Effect.sync(() => readEnv("SSH_CLIENT") !== undefined || readEnv("SSH_TTY") !== undefined);
|
|
17
|
+
/** Returns true if running as root (uid 0). */
|
|
18
|
+
export const isRoot = () => process.getuid?.() === 0;
|
|
19
|
+
/** Returns true if /.dockerenv or /.containerenv exists. Requires FileSystem. */
|
|
20
|
+
export const isContainer = Effect.gen(function* () {
|
|
21
|
+
const fs = yield* FileSystem.FileSystem;
|
|
22
|
+
const dockerExists = yield* fs
|
|
23
|
+
.exists("/.dockerenv")
|
|
24
|
+
.pipe(Effect.catch(() => Effect.succeed(false)));
|
|
25
|
+
if (dockerExists)
|
|
26
|
+
return true;
|
|
27
|
+
const containerExists = yield* fs
|
|
28
|
+
.exists("/.containerenv")
|
|
29
|
+
.pipe(Effect.catch(() => Effect.succeed(false)));
|
|
30
|
+
return containerExists;
|
|
31
|
+
});
|
|
32
|
+
/** Returns true if /proc/version contains "microsoft". Requires FileSystem. */
|
|
33
|
+
export const isWSL = Effect.gen(function* () {
|
|
34
|
+
const fs = yield* FileSystem.FileSystem;
|
|
35
|
+
const exists = yield* fs.exists("/proc/version").pipe(Effect.catch(() => Effect.succeed(false)));
|
|
36
|
+
if (!exists)
|
|
37
|
+
return false;
|
|
38
|
+
const content = yield* fs
|
|
39
|
+
.readFileString("/proc/version")
|
|
40
|
+
.pipe(Effect.catch(() => Effect.succeed("")));
|
|
41
|
+
return /microsoft/i.test(content);
|
|
42
|
+
});
|
|
43
|
+
/** Returns true if CI env var is set. */
|
|
44
|
+
export const isCI = Effect.map(envOption("CI"), (value) => Option.exists(value, (raw) => raw.length > 0 && raw !== "0" && raw.toLowerCase() !== "false"));
|
|
45
|
+
//# sourceMappingURL=environment.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment-backed Layers for the registry-auth feature. Only application
|
|
3
|
+
* composition imports this module; feature logic keeps service requirements
|
|
4
|
+
* in its Effect environment.
|
|
5
|
+
*
|
|
6
|
+
* @experimental This API is unstable and may change without notice.
|
|
7
|
+
*/
|
|
8
|
+
export { AuthClientLive } from "./auth-client.js";
|
|
9
|
+
export { AuthMiddlewareLive, makeAuthMiddlewareLive } from "./auth-middleware.js";
|
|
10
|
+
export { CredentialStoreLive, CredentialStoreSessionLive } from "./credential-store.js";
|
|
11
|
+
export { AuthLoginInteractionLive } from "./login-interaction.js";
|
|
12
|
+
export { PendingDeviceLoginStoreLive } from "./pending-device-login-store.js";
|
|
13
|
+
//# sourceMappingURL=live.d.ts.map
|
package/dist/src/live.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment-backed Layers for the registry-auth feature. Only application
|
|
3
|
+
* composition imports this module; feature logic keeps service requirements
|
|
4
|
+
* in its Effect environment.
|
|
5
|
+
*
|
|
6
|
+
* @experimental This API is unstable and may change without notice.
|
|
7
|
+
*/
|
|
8
|
+
export { AuthClientLive } from "./auth-client.js";
|
|
9
|
+
export { AuthMiddlewareLive, makeAuthMiddlewareLive } from "./auth-middleware.js";
|
|
10
|
+
export { CredentialStoreLive, CredentialStoreSessionLive } from "./credential-store.js";
|
|
11
|
+
export { AuthLoginInteractionLive } from "./login-interaction.js";
|
|
12
|
+
export { PendingDeviceLoginStoreLive } from "./pending-device-login-store.js";
|
|
13
|
+
//# sourceMappingURL=live.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth login interaction service.
|
|
3
|
+
*
|
|
4
|
+
* Best-effort platform integration for browser launch and clipboard copy.
|
|
5
|
+
* Provides both the CLI-specific AuthLoginInteraction and the core
|
|
6
|
+
* DeviceLoginInteraction service (used by core's runDeviceLogin).
|
|
7
|
+
*
|
|
8
|
+
* @experimental This API is unstable and may change without notice.
|
|
9
|
+
*/
|
|
10
|
+
import * as Effect from "effect/Effect";
|
|
11
|
+
import * as Layer from "effect/Layer";
|
|
12
|
+
import * as ServiceMap from "effect/Context";
|
|
13
|
+
import type { ChildProcessSpawner as ChildProcessSpawnerService } from "effect/unstable/process/ChildProcessSpawner";
|
|
14
|
+
import { DeviceLoginInteraction } from "./device-login.js";
|
|
15
|
+
interface CommandInvocation {
|
|
16
|
+
readonly command: string;
|
|
17
|
+
readonly args: ReadonlyArray<string>;
|
|
18
|
+
readonly stdinText?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface AuthLoginInteractionService {
|
|
21
|
+
readonly openBrowser: (url: string) => Effect.Effect<boolean>;
|
|
22
|
+
readonly copyToClipboard: (text: string) => Effect.Effect<boolean>;
|
|
23
|
+
}
|
|
24
|
+
declare const AuthLoginInteraction_base: ServiceMap.ServiceClass<AuthLoginInteraction, "@agentxm/registry-auth/login-interaction/AuthLoginInteraction", AuthLoginInteractionService>;
|
|
25
|
+
export declare class AuthLoginInteraction extends AuthLoginInteraction_base {
|
|
26
|
+
}
|
|
27
|
+
export declare const browserCommands: (url: string, platform?: NodeJS.Platform) => ReadonlyArray<CommandInvocation>;
|
|
28
|
+
export declare const AuthLoginInteractionLive: Layer.Layer<DeviceLoginInteraction | AuthLoginInteraction, never, ChildProcessSpawnerService>;
|
|
29
|
+
export interface AuthLoginInteractionTestState {
|
|
30
|
+
readonly openBrowserCalls: Array<string>;
|
|
31
|
+
readonly copyToClipboardCalls: Array<string>;
|
|
32
|
+
}
|
|
33
|
+
export declare const AuthLoginInteractionTest: (overrides?: {
|
|
34
|
+
readonly openBrowser?: (url: string) => Effect.Effect<boolean>;
|
|
35
|
+
readonly copyToClipboard?: (text: string) => Effect.Effect<boolean>;
|
|
36
|
+
}) => {
|
|
37
|
+
layer: Layer.Layer<DeviceLoginInteraction | AuthLoginInteraction, never, never>;
|
|
38
|
+
state: AuthLoginInteractionTestState;
|
|
39
|
+
};
|
|
40
|
+
export {};
|
|
41
|
+
//# sourceMappingURL=login-interaction.d.ts.map
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth login interaction service.
|
|
3
|
+
*
|
|
4
|
+
* Best-effort platform integration for browser launch and clipboard copy.
|
|
5
|
+
* Provides both the CLI-specific AuthLoginInteraction and the core
|
|
6
|
+
* DeviceLoginInteraction service (used by core's runDeviceLogin).
|
|
7
|
+
*
|
|
8
|
+
* @experimental This API is unstable and may change without notice.
|
|
9
|
+
*/
|
|
10
|
+
import * as Effect from "effect/Effect";
|
|
11
|
+
import * as Layer from "effect/Layer";
|
|
12
|
+
import * as ServiceMap from "effect/Context";
|
|
13
|
+
import * as Stream from "effect/Stream";
|
|
14
|
+
import * as ChildProcess from "effect/unstable/process/ChildProcess";
|
|
15
|
+
import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner";
|
|
16
|
+
import { DeviceLoginInteraction } from "./device-login.js";
|
|
17
|
+
export class AuthLoginInteraction extends ServiceMap.Service()("@agentxm/registry-auth/login-interaction/AuthLoginInteraction") {
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Best-effort command execution: `true` only when the process exits with code
|
|
21
|
+
* 0; every failure (spawn, stdin write, signal termination) collapses to
|
|
22
|
+
* `false`.
|
|
23
|
+
*
|
|
24
|
+
* The previous `node:child_process` implementation passed `windowsHide: true`;
|
|
25
|
+
* `ChildProcess.CommandOptions` has no equivalent, so a transient console
|
|
26
|
+
* window may appear on Windows.
|
|
27
|
+
*/
|
|
28
|
+
const runCommand = (spawner, invocation) => Effect.gen(function* () {
|
|
29
|
+
const handle = yield* spawner.spawn(ChildProcess.make(invocation.command, invocation.args, {
|
|
30
|
+
// Browser and clipboard helpers have no cwd-dependent behavior. Core
|
|
31
|
+
// deliberately does not depend on the CLI execution-directory service.
|
|
32
|
+
stdin: invocation.stdinText === undefined ? "ignore" : "pipe",
|
|
33
|
+
stdout: "ignore",
|
|
34
|
+
stderr: "ignore",
|
|
35
|
+
}));
|
|
36
|
+
if (invocation.stdinText !== undefined) {
|
|
37
|
+
// Write the payload, then close stdin (the handle's sink ends the pipe
|
|
38
|
+
// when the stream completes) so clipboard commands finish reading.
|
|
39
|
+
yield* Stream.run(Stream.make(new TextEncoder().encode(invocation.stdinText)), handle.stdin);
|
|
40
|
+
}
|
|
41
|
+
return (yield* handle.exitCode) === 0;
|
|
42
|
+
}).pipe(Effect.scoped, Effect.catch(() => Effect.succeed(false)));
|
|
43
|
+
const tryCommands = (spawner, invocations) => Effect.gen(function* () {
|
|
44
|
+
for (const invocation of invocations) {
|
|
45
|
+
const succeeded = yield* runCommand(spawner, invocation);
|
|
46
|
+
if (succeeded) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
});
|
|
52
|
+
export const browserCommands = (url, platform = process.platform) => {
|
|
53
|
+
switch (platform) {
|
|
54
|
+
case "darwin":
|
|
55
|
+
return [{ command: "open", args: [url] }];
|
|
56
|
+
case "win32":
|
|
57
|
+
return [
|
|
58
|
+
{
|
|
59
|
+
command: "rundll32",
|
|
60
|
+
args: ["url.dll,FileProtocolHandler", url],
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
default:
|
|
64
|
+
return [{ command: "xdg-open", args: [url] }];
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const clipboardCommands = (text) => {
|
|
68
|
+
switch (process.platform) {
|
|
69
|
+
case "darwin":
|
|
70
|
+
return [{ command: "pbcopy", args: [], stdinText: text }];
|
|
71
|
+
case "win32":
|
|
72
|
+
return [{ command: "clip", args: [], stdinText: text }];
|
|
73
|
+
default:
|
|
74
|
+
return [
|
|
75
|
+
{ command: "wl-copy", args: [], stdinText: text },
|
|
76
|
+
{ command: "xclip", args: ["-selection", "clipboard"], stdinText: text },
|
|
77
|
+
{ command: "xsel", args: ["--clipboard", "--input"], stdinText: text },
|
|
78
|
+
];
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const makeInteraction = Effect.gen(function* () {
|
|
82
|
+
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
83
|
+
const impl = {
|
|
84
|
+
openBrowser: (url) => tryCommands(spawner, browserCommands(url)),
|
|
85
|
+
copyToClipboard: (text) => tryCommands(spawner, clipboardCommands(text)),
|
|
86
|
+
};
|
|
87
|
+
return impl;
|
|
88
|
+
});
|
|
89
|
+
export const AuthLoginInteractionLive = Layer.mergeAll(Layer.effect(AuthLoginInteraction, Effect.map(makeInteraction, (impl) => impl)), Layer.effect(DeviceLoginInteraction, makeInteraction));
|
|
90
|
+
export const AuthLoginInteractionTest = (overrides) => {
|
|
91
|
+
const state = {
|
|
92
|
+
openBrowserCalls: [],
|
|
93
|
+
copyToClipboardCalls: [],
|
|
94
|
+
};
|
|
95
|
+
const impl = {
|
|
96
|
+
openBrowser: (url) => Effect.gen(function* () {
|
|
97
|
+
state.openBrowserCalls.push(url);
|
|
98
|
+
return yield* overrides?.openBrowser?.(url) ?? Effect.succeed(false);
|
|
99
|
+
}),
|
|
100
|
+
copyToClipboard: (text) => Effect.gen(function* () {
|
|
101
|
+
state.copyToClipboardCalls.push(text);
|
|
102
|
+
return yield* overrides?.copyToClipboard?.(text) ?? Effect.succeed(false);
|
|
103
|
+
}),
|
|
104
|
+
};
|
|
105
|
+
const layer = Layer.mergeAll(Layer.succeed(AuthLoginInteraction, impl), Layer.succeed(DeviceLoginInteraction, impl));
|
|
106
|
+
return { layer, state };
|
|
107
|
+
};
|
|
108
|
+
//# sourceMappingURL=login-interaction.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Option from "effect/Option";
|
|
3
|
+
import * as Schema from "effect/Schema";
|
|
4
|
+
import type { Handle } from "@agentxm/extension-model/unstable/extensions/handle";
|
|
5
|
+
import { AuthLoginPresenter } from "./login-presenter.js";
|
|
6
|
+
export declare const LoginResultSchema: Schema.Struct<{
|
|
7
|
+
readonly status: Schema.Literal<"logged-in">;
|
|
8
|
+
readonly registryHost: Schema.String;
|
|
9
|
+
readonly handle: Schema.optional<Schema.String>;
|
|
10
|
+
}>;
|
|
11
|
+
export declare const LoginDocumentSchema: Schema.Struct<{
|
|
12
|
+
result: Schema.Struct<{
|
|
13
|
+
readonly status: Schema.Literal<"logged-in">;
|
|
14
|
+
readonly registryHost: Schema.String;
|
|
15
|
+
readonly handle: Schema.optional<Schema.String>;
|
|
16
|
+
}>;
|
|
17
|
+
}>;
|
|
18
|
+
export type LoginResult = typeof LoginResultSchema.Type;
|
|
19
|
+
export type LoginDocument = typeof LoginDocumentSchema.Type;
|
|
20
|
+
export declare const makeLoginResult: (registryUrl: string, handle: Option.Option<Handle>) => LoginResult;
|
|
21
|
+
export declare const emitLoginSuccess: (registryUrl: string, handle: Option.Option<Handle>) => Effect.Effect<void, never, AuthLoginPresenter>;
|
|
22
|
+
//# sourceMappingURL=login-output.d.ts.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Option from "effect/Option";
|
|
3
|
+
import * as Schema from "effect/Schema";
|
|
4
|
+
import { AuthLoginPresenter } from "./login-presenter.js";
|
|
5
|
+
export const LoginResultSchema = Schema.Struct({
|
|
6
|
+
status: Schema.Literal("logged-in"),
|
|
7
|
+
registryHost: Schema.String,
|
|
8
|
+
handle: Schema.optional(Schema.String),
|
|
9
|
+
});
|
|
10
|
+
const LoginDocumentFields = {
|
|
11
|
+
result: LoginResultSchema,
|
|
12
|
+
};
|
|
13
|
+
export const LoginDocumentSchema = Schema.Struct(LoginDocumentFields);
|
|
14
|
+
export const makeLoginResult = (registryUrl, handle) => {
|
|
15
|
+
const registryHost = new URL(registryUrl).host;
|
|
16
|
+
return Option.match(handle, {
|
|
17
|
+
onNone: () => ({
|
|
18
|
+
status: "logged-in",
|
|
19
|
+
registryHost,
|
|
20
|
+
}),
|
|
21
|
+
onSome: (userHandle) => ({
|
|
22
|
+
status: "logged-in",
|
|
23
|
+
registryHost,
|
|
24
|
+
handle: userHandle,
|
|
25
|
+
}),
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
export const emitLoginSuccess = (registryUrl, handle) => Effect.gen(function* () {
|
|
29
|
+
const presenter = yield* AuthLoginPresenter;
|
|
30
|
+
yield* presenter.emitLoginSuccess(makeLoginResult(registryUrl, handle));
|
|
31
|
+
});
|
|
32
|
+
//# sourceMappingURL=login-output.js.map
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth login presentation seam.
|
|
3
|
+
*
|
|
4
|
+
* The device, loopback, and publish-authorization flows report progress and
|
|
5
|
+
* present sign-in guidance exclusively through this service. The CLI runtime
|
|
6
|
+
* provides the renderer-backed implementation; wording, suggestion sets, and
|
|
7
|
+
* machine-mode document emission belong to that implementation, never to the
|
|
8
|
+
* auth feature.
|
|
9
|
+
*
|
|
10
|
+
* @experimental This API is unstable and may change without notice.
|
|
11
|
+
*/
|
|
12
|
+
import * as Effect from "effect/Effect";
|
|
13
|
+
import * as Layer from "effect/Layer";
|
|
14
|
+
import * as ServiceMap from "effect/Context";
|
|
15
|
+
import type { DeviceLoginPendingResult } from "./device-login.js";
|
|
16
|
+
import type { LoginResult } from "./login-output.js";
|
|
17
|
+
export type AuthLoginProgress = {
|
|
18
|
+
readonly _tag: "StartingDeviceAuthorization";
|
|
19
|
+
readonly registryHost: string;
|
|
20
|
+
} | {
|
|
21
|
+
readonly _tag: "WaitingForDeviceAuthorization";
|
|
22
|
+
readonly registryHost: string;
|
|
23
|
+
} | {
|
|
24
|
+
readonly _tag: "SavingCredentials";
|
|
25
|
+
readonly registryHost: string;
|
|
26
|
+
} | {
|
|
27
|
+
readonly _tag: "WaitingForLoopbackAuthorization";
|
|
28
|
+
readonly registryHost: string;
|
|
29
|
+
readonly timeoutMinutes: number;
|
|
30
|
+
} | {
|
|
31
|
+
readonly _tag: "CompletingSignIn";
|
|
32
|
+
readonly registryHost: string;
|
|
33
|
+
};
|
|
34
|
+
export interface DeviceFlowPresentation {
|
|
35
|
+
readonly verificationUri: string;
|
|
36
|
+
readonly verificationUriComplete: string;
|
|
37
|
+
readonly userCode: string;
|
|
38
|
+
readonly expiresInSeconds: number;
|
|
39
|
+
readonly browserOpened: boolean;
|
|
40
|
+
readonly copiedToClipboard: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface AuthLoginPresenterService {
|
|
43
|
+
/** Progress envelope for one login phase. */
|
|
44
|
+
readonly withProgress: <A, E, R>(progress: AuthLoginProgress, run: () => Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
|
|
45
|
+
/**
|
|
46
|
+
* Machine-mode pending-document emission. Returns true when machine output
|
|
47
|
+
* consumed the result — the caller must then skip browser/clipboard side
|
|
48
|
+
* effects and human presentation.
|
|
49
|
+
*/
|
|
50
|
+
readonly tryEmitPendingDeviceLogin: (result: DeviceLoginPendingResult) => Effect.Effect<boolean>;
|
|
51
|
+
/** Human presentation of the device flow after side effects have settled. */
|
|
52
|
+
readonly presentDeviceFlow: (presentation: DeviceFlowPresentation) => Effect.Effect<void>;
|
|
53
|
+
/** Human-path tail: sign-in is waiting for approval, with resume guidance. */
|
|
54
|
+
readonly notePendingApproval: (result: DeviceLoginPendingResult) => Effect.Effect<void>;
|
|
55
|
+
/** Machine login-document emission with human success fallback. */
|
|
56
|
+
readonly emitLoginSuccess: (result: LoginResult) => Effect.Effect<void>;
|
|
57
|
+
readonly presentLoopbackStart: (start: {
|
|
58
|
+
readonly redirectUri: string;
|
|
59
|
+
readonly authorizeUrl: string;
|
|
60
|
+
}) => Effect.Effect<void>;
|
|
61
|
+
readonly noteLoopbackBrowserOutcome: (opened: boolean) => Effect.Effect<void>;
|
|
62
|
+
readonly notePublishReview: (review: {
|
|
63
|
+
readonly browserOpened: boolean;
|
|
64
|
+
readonly candidateCount: number;
|
|
65
|
+
readonly authorizationUrl: string;
|
|
66
|
+
}) => Effect.Effect<void>;
|
|
67
|
+
}
|
|
68
|
+
declare const AuthLoginPresenter_base: ServiceMap.ServiceClass<AuthLoginPresenter, "@agentxm/registry-auth/login-presenter/AuthLoginPresenter", AuthLoginPresenterService>;
|
|
69
|
+
export declare class AuthLoginPresenter extends AuthLoginPresenter_base {
|
|
70
|
+
}
|
|
71
|
+
export interface AuthLoginPresenterTestState {
|
|
72
|
+
readonly progress: Array<AuthLoginProgress>;
|
|
73
|
+
readonly pendingEmissions: Array<DeviceLoginPendingResult>;
|
|
74
|
+
readonly deviceFlowPresentations: Array<DeviceFlowPresentation>;
|
|
75
|
+
readonly pendingApprovals: Array<DeviceLoginPendingResult>;
|
|
76
|
+
readonly loginSuccesses: Array<LoginResult>;
|
|
77
|
+
readonly loopbackStarts: Array<{
|
|
78
|
+
readonly redirectUri: string;
|
|
79
|
+
readonly authorizeUrl: string;
|
|
80
|
+
}>;
|
|
81
|
+
readonly loopbackBrowserOutcomes: Array<boolean>;
|
|
82
|
+
readonly publishReviews: Array<{
|
|
83
|
+
readonly browserOpened: boolean;
|
|
84
|
+
readonly candidateCount: number;
|
|
85
|
+
readonly authorizationUrl: string;
|
|
86
|
+
}>;
|
|
87
|
+
}
|
|
88
|
+
export declare const AuthLoginPresenterTest: (overrides?: {
|
|
89
|
+
readonly tryEmitPendingDeviceLogin?: (result: DeviceLoginPendingResult) => Effect.Effect<boolean>;
|
|
90
|
+
}) => {
|
|
91
|
+
layer: Layer.Layer<AuthLoginPresenter, never, never>;
|
|
92
|
+
state: AuthLoginPresenterTestState;
|
|
93
|
+
};
|
|
94
|
+
export {};
|
|
95
|
+
//# sourceMappingURL=login-presenter.d.ts.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth login presentation seam.
|
|
3
|
+
*
|
|
4
|
+
* The device, loopback, and publish-authorization flows report progress and
|
|
5
|
+
* present sign-in guidance exclusively through this service. The CLI runtime
|
|
6
|
+
* provides the renderer-backed implementation; wording, suggestion sets, and
|
|
7
|
+
* machine-mode document emission belong to that implementation, never to the
|
|
8
|
+
* auth feature.
|
|
9
|
+
*
|
|
10
|
+
* @experimental This API is unstable and may change without notice.
|
|
11
|
+
*/
|
|
12
|
+
import * as Effect from "effect/Effect";
|
|
13
|
+
import * as Layer from "effect/Layer";
|
|
14
|
+
import * as ServiceMap from "effect/Context";
|
|
15
|
+
export class AuthLoginPresenter extends ServiceMap.Service()("@agentxm/registry-auth/login-presenter/AuthLoginPresenter") {
|
|
16
|
+
}
|
|
17
|
+
export const AuthLoginPresenterTest = (overrides) => {
|
|
18
|
+
const state = {
|
|
19
|
+
progress: [],
|
|
20
|
+
pendingEmissions: [],
|
|
21
|
+
deviceFlowPresentations: [],
|
|
22
|
+
pendingApprovals: [],
|
|
23
|
+
loginSuccesses: [],
|
|
24
|
+
loopbackStarts: [],
|
|
25
|
+
loopbackBrowserOutcomes: [],
|
|
26
|
+
publishReviews: [],
|
|
27
|
+
};
|
|
28
|
+
const layer = Layer.succeed(AuthLoginPresenter, {
|
|
29
|
+
withProgress: (progress, run) => Effect.suspend(() => {
|
|
30
|
+
state.progress.push(progress);
|
|
31
|
+
return run();
|
|
32
|
+
}),
|
|
33
|
+
tryEmitPendingDeviceLogin: (result) => Effect.gen(function* () {
|
|
34
|
+
state.pendingEmissions.push(result);
|
|
35
|
+
return yield* overrides?.tryEmitPendingDeviceLogin?.(result) ?? Effect.succeed(false);
|
|
36
|
+
}),
|
|
37
|
+
presentDeviceFlow: (presentation) => Effect.sync(() => {
|
|
38
|
+
state.deviceFlowPresentations.push(presentation);
|
|
39
|
+
}),
|
|
40
|
+
notePendingApproval: (result) => Effect.sync(() => {
|
|
41
|
+
state.pendingApprovals.push(result);
|
|
42
|
+
}),
|
|
43
|
+
emitLoginSuccess: (result) => Effect.sync(() => {
|
|
44
|
+
state.loginSuccesses.push(result);
|
|
45
|
+
}),
|
|
46
|
+
presentLoopbackStart: (start) => Effect.sync(() => {
|
|
47
|
+
state.loopbackStarts.push(start);
|
|
48
|
+
}),
|
|
49
|
+
noteLoopbackBrowserOutcome: (opened) => Effect.sync(() => {
|
|
50
|
+
state.loopbackBrowserOutcomes.push(opened);
|
|
51
|
+
}),
|
|
52
|
+
notePublishReview: (review) => Effect.sync(() => {
|
|
53
|
+
state.publishReviews.push(review);
|
|
54
|
+
}),
|
|
55
|
+
});
|
|
56
|
+
return { layer, state };
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=login-presenter.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Login strategy selection for interactive auth commands.
|
|
3
|
+
*
|
|
4
|
+
* @experimental This API is unstable and may change without notice.
|
|
5
|
+
*/
|
|
6
|
+
export type LoginStrategy = "loopback" | "device-code";
|
|
7
|
+
export interface LoginStrategyOptions {
|
|
8
|
+
readonly deviceCode: boolean;
|
|
9
|
+
readonly nonInteractive: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface LoginStrategyEnvironment {
|
|
12
|
+
readonly SSH_CONNECTION?: string;
|
|
13
|
+
readonly SSH_CLIENT?: string;
|
|
14
|
+
readonly SSH_TTY?: string;
|
|
15
|
+
readonly DISPLAY?: string;
|
|
16
|
+
readonly WAYLAND_DISPLAY?: string;
|
|
17
|
+
readonly CI?: string;
|
|
18
|
+
readonly CODESPACES?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare const selectLoginStrategy: (options: LoginStrategyOptions, env: LoginStrategyEnvironment) => LoginStrategy;
|
|
21
|
+
//# sourceMappingURL=login-strategy.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Login strategy selection for interactive auth commands.
|
|
3
|
+
*
|
|
4
|
+
* @experimental This API is unstable and may change without notice.
|
|
5
|
+
*/
|
|
6
|
+
const isTruthyEnvValue = (value) => value !== undefined && value !== "" && value !== "0" && value.toLowerCase() !== "false";
|
|
7
|
+
const isSshWithoutDisplay = (env) => {
|
|
8
|
+
const hasSsh = isTruthyEnvValue(env.SSH_CONNECTION) ||
|
|
9
|
+
isTruthyEnvValue(env.SSH_CLIENT) ||
|
|
10
|
+
isTruthyEnvValue(env.SSH_TTY);
|
|
11
|
+
const hasDisplay = isTruthyEnvValue(env.DISPLAY) || isTruthyEnvValue(env.WAYLAND_DISPLAY);
|
|
12
|
+
return hasSsh && !hasDisplay;
|
|
13
|
+
};
|
|
14
|
+
export const selectLoginStrategy = (options, env) => {
|
|
15
|
+
if (options.deviceCode || options.nonInteractive)
|
|
16
|
+
return "device-code";
|
|
17
|
+
if (isSshWithoutDisplay(env))
|
|
18
|
+
return "device-code";
|
|
19
|
+
if (isTruthyEnvValue(env.CI))
|
|
20
|
+
return "device-code";
|
|
21
|
+
if (isTruthyEnvValue(env.CODESPACES))
|
|
22
|
+
return "device-code";
|
|
23
|
+
return "loopback";
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=login-strategy.js.map
|