@agentxm/registry-auth 0.28.11 → 0.28.13
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/src/auth-client.d.ts +13 -5
- package/dist/src/auth-client.js +80 -39
- package/dist/src/credential-store.d.ts +39 -2
- package/dist/src/credential-store.js +50 -46
- package/dist/src/device-login.d.ts +20 -8
- package/dist/src/device-login.js +9 -4
- package/dist/src/errors.d.ts +37 -12
- package/dist/src/errors.js +20 -0
- package/dist/src/identity.d.ts +46 -0
- package/dist/src/identity.js +49 -0
- package/dist/src/index.d.ts +12 -2
- package/dist/src/index.js +14 -1
- package/dist/src/internal/environment.d.ts +23 -5
- package/dist/src/internal/environment.js +59 -9
- package/dist/src/live.d.ts +1 -0
- package/dist/src/live.js +1 -0
- package/dist/src/login-presenter.d.ts +55 -0
- package/dist/src/login-presenter.js +22 -0
- package/dist/src/login-suggestion.d.ts +18 -0
- package/dist/src/login-suggestion.js +34 -0
- package/dist/src/login.d.ts +80 -0
- package/dist/src/login.js +155 -0
- package/dist/src/logout.d.ts +35 -0
- package/dist/src/logout.js +37 -0
- package/dist/src/loopback-login.js +2 -1
- package/dist/src/loopback-server.d.ts +3 -1
- package/dist/src/loopback-server.js +51 -7
- package/dist/src/pending-publish-authorization-store.d.ts +33 -0
- package/dist/src/pending-publish-authorization-store.js +90 -0
- package/dist/src/publish-authorization-polling.d.ts +13 -0
- package/dist/src/publish-authorization-polling.js +176 -0
- package/dist/src/publish-authorization.d.ts +4 -1
- package/dist/src/publish-authorization.js +24 -5
- package/dist/src/selected-registry.d.ts +25 -0
- package/dist/src/selected-registry.js +18 -0
- package/dist/src/step-up.d.ts +49 -0
- package/dist/src/step-up.js +174 -0
- package/dist/src/testing.d.ts +1 -0
- package/dist/src/testing.js +1 -0
- package/dist/src/tokens.d.ts +57 -0
- package/dist/src/tokens.js +78 -0
- package/package.json +7 -6
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The authenticated identity of the selected Registry, and the credential
|
|
3
|
+
* lifecycle rule that governs reading it: a stored credential the Registry
|
|
4
|
+
* rejects is refreshed once and the read is retried, while a rejected ambient
|
|
5
|
+
* credential is reported as sign-in required.
|
|
6
|
+
*
|
|
7
|
+
* @experimental This API is unstable and may change without notice.
|
|
8
|
+
*/
|
|
9
|
+
import type * as DateTime from "effect/DateTime";
|
|
10
|
+
import * as Effect from "effect/Effect";
|
|
11
|
+
import type { Handle } from "@agentxm/extension-model/unstable/extensions/handle";
|
|
12
|
+
import { AuthClient } from "./auth-client.js";
|
|
13
|
+
import { CredentialStore } from "./credential-store.js";
|
|
14
|
+
/** The Registry's canonical answer to "who is this credential". */
|
|
15
|
+
export interface RegistryIdentity {
|
|
16
|
+
readonly user: Handle;
|
|
17
|
+
readonly registry: string;
|
|
18
|
+
readonly credentialType: string;
|
|
19
|
+
readonly scopes: ReadonlyArray<string>;
|
|
20
|
+
readonly resourceRestrictions: {
|
|
21
|
+
readonly extensions: ReadonlyArray<string> | null;
|
|
22
|
+
};
|
|
23
|
+
readonly expiresAt: DateTime.Utc | null;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Read the canonical Registry identity for the resolved credential.
|
|
27
|
+
*
|
|
28
|
+
* Only a credential this workspace stores can be refreshed: an ambient token
|
|
29
|
+
* the Registry rejects is the caller's to replace, so it maps to sign-in
|
|
30
|
+
* required rather than a silent refresh attempt.
|
|
31
|
+
*/
|
|
32
|
+
export declare const currentIdentity: (registryUrl: string) => Effect.Effect<{
|
|
33
|
+
user: string & import("effect/Brand").Brand<"Handle">;
|
|
34
|
+
registry: string;
|
|
35
|
+
credentialType: string;
|
|
36
|
+
scopes: readonly string[];
|
|
37
|
+
resourceRestrictions: {
|
|
38
|
+
readonly extensions: ReadonlyArray<string> | null;
|
|
39
|
+
};
|
|
40
|
+
expiresAt: DateTime.Utc | null;
|
|
41
|
+
}, import("./errors.js").AuthError, AuthClient | CredentialStore>;
|
|
42
|
+
/** The token an invocation would present to the selected Registry. */
|
|
43
|
+
export declare const currentToken: (registryUrl: string) => Effect.Effect<string, import("./errors.js").AuthError, CredentialStore>;
|
|
44
|
+
/** Both members keep `CredentialStore` in `R`; declared for the reader. */
|
|
45
|
+
export type IdentityRequirements = AuthClient | CredentialStore;
|
|
46
|
+
//# sourceMappingURL=identity.d.ts.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The authenticated identity of the selected Registry, and the credential
|
|
3
|
+
* lifecycle rule that governs reading it: a stored credential the Registry
|
|
4
|
+
* rejects is refreshed once and the read is retried, while a rejected ambient
|
|
5
|
+
* credential is reported as sign-in required.
|
|
6
|
+
*
|
|
7
|
+
* @experimental This API is unstable and may change without notice.
|
|
8
|
+
*/
|
|
9
|
+
import * as Effect from "effect/Effect";
|
|
10
|
+
import { isRegistryClientFailure } from "@agentxm/registry-client";
|
|
11
|
+
import { AuthClient } from "./auth-client.js";
|
|
12
|
+
import { CredentialStore } from "./credential-store.js";
|
|
13
|
+
import { authLoginRequired } from "./errors.js";
|
|
14
|
+
import { refreshStoredToken, resolveRequiredToken } from "./token-resolution.js";
|
|
15
|
+
const isRejectedCredential = (error) => isRegistryClientFailure(error) && error.metadata?.response?.status === 401;
|
|
16
|
+
/**
|
|
17
|
+
* Read the canonical Registry identity for the resolved credential.
|
|
18
|
+
*
|
|
19
|
+
* Only a credential this workspace stores can be refreshed: an ambient token
|
|
20
|
+
* the Registry rejects is the caller's to replace, so it maps to sign-in
|
|
21
|
+
* required rather than a silent refresh attempt.
|
|
22
|
+
*/
|
|
23
|
+
export const currentIdentity = Effect.fn("Identity.current")(function* (registryUrl) {
|
|
24
|
+
const authClient = yield* AuthClient;
|
|
25
|
+
const token = yield* resolveRequiredToken(registryUrl, {
|
|
26
|
+
missingTokenError: authLoginRequired("Not authenticated"),
|
|
27
|
+
});
|
|
28
|
+
const identity = yield* authClient.getMe(token.token).pipe(Effect.catch((error) => token._tag === "CredentialStore" && isRejectedCredential(error)
|
|
29
|
+
? refreshStoredToken(token).pipe(Effect.flatMap((refreshed) => authClient.getMe(refreshed.token)))
|
|
30
|
+
: Effect.fail(error)), Effect.mapError((error) => isRejectedCredential(error)
|
|
31
|
+
? authLoginRequired("Invalid or expired credential. Authenticate again.", error)
|
|
32
|
+
: error));
|
|
33
|
+
return {
|
|
34
|
+
user: identity.userHandle,
|
|
35
|
+
registry: registryUrl,
|
|
36
|
+
credentialType: identity.tokenType,
|
|
37
|
+
scopes: identity.scopes,
|
|
38
|
+
resourceRestrictions: identity.resourceRestrictions,
|
|
39
|
+
expiresAt: identity.expiresAt,
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
/** The token an invocation would present to the selected Registry. */
|
|
43
|
+
export const currentToken = Effect.fn("Identity.currentToken")(function* (registryUrl) {
|
|
44
|
+
const token = yield* resolveRequiredToken(registryUrl, {
|
|
45
|
+
missingTokenError: authLoginRequired("No token available"),
|
|
46
|
+
});
|
|
47
|
+
return token.token;
|
|
48
|
+
});
|
|
49
|
+
//# sourceMappingURL=identity.js.map
|
package/dist/src/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* @experimental This API is unstable and may change without notice.
|
|
11
11
|
* @packageDocumentation
|
|
12
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";
|
|
13
|
+
export { AuthExchangeFailed, AuthLoginRequired, AuthTokenPolicyRequired, authLoginRequired, DeviceAuthorizationPending, PublishAuthorizationPending, DeviceLoginCodeExpired, DeviceLoginDenied, AuthInteractionAbandoned, isAuthError, isRegistryAuthFailure, REGISTRY_AUTH_ERROR_CATEGORIES, RegistryAuthFailed, StepUpRequired, StepUpVerificationPending, type AuthError, type RegistryAuthErrorCategory, type RegistryAuthFailure, type StepUpRequest, } from "./errors.js";
|
|
14
14
|
export type { CredentialEntry, CredentialFile, StorageTier, StoredCredentials, TokenSource, } from "./schema.js";
|
|
15
15
|
export { CredentialEntrySchema, CredentialFileSchema, CredentialStoreTokenSource, EnvVarTokenSource, FileTokenSource, FlagTokenSource, RegistryAccountsSchema, } from "./schema.js";
|
|
16
16
|
export type { CredentialStoreService, EnvironmentInfo } from "./credential-store.js";
|
|
@@ -18,7 +18,7 @@ export { canUsePersistedCredentials, CredentialStore, detectEnvironment, makePer
|
|
|
18
18
|
export type { PendingDeviceLogin, PendingDeviceLoginStoreService, } from "./pending-device-login-store.js";
|
|
19
19
|
export { PendingDeviceLoginSchema, PendingDeviceLoginStore } from "./pending-device-login-store.js";
|
|
20
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";
|
|
21
|
+
export type { AuthClientService, CreateTokenOptions, CreatePublishAuthorizationRequestParams, DeviceFlowResponse, ExchangePublishAuthorizationCodeParams, MeResponse, PollResult, PublishAuthorizationRequestResponse, PublishAuthorizationExchangeResponse, PublishCapabilityResponse, } from "./auth-client.js";
|
|
22
22
|
export { AuthClient, pollOnce, readStepUpRequest } from "./auth-client.js";
|
|
23
23
|
export type { NormalizedTokenResponse } from "./oauth-contract.js";
|
|
24
24
|
export type { DeviceLoginPendingResult, DeviceLoginInteractionService, ResumeDeviceLoginOptions, } from "./device-login.js";
|
|
@@ -28,9 +28,19 @@ export { LoopbackCallbackRejected, LoopbackLoginFallback, startLoopbackServer, }
|
|
|
28
28
|
export { LoginDocumentSchema, LoginResultSchema, makeLoginResult, type LoginDocument, type LoginResult, } from "./login-output.js";
|
|
29
29
|
export type { AuthLoginPresenterService, AuthLoginProgress, DeviceFlowPresentation, } from "./login-presenter.js";
|
|
30
30
|
export { AuthLoginPresenter } from "./login-presenter.js";
|
|
31
|
+
export type { DeviceCodeFallbackReason, SessionReplacementDecision, StepUpChallengePresentation, } from "./login-presenter.js";
|
|
32
|
+
export { runWithStepUp, type StepUpOptions, type StepUpPresentation, type VerifiedWrite, } from "./step-up.js";
|
|
33
|
+
export { selectedRegistry, type SelectedRegistry } from "./selected-registry.js";
|
|
34
|
+
export { classifyLoopbackFailure, deviceLoginOptions, login, resumeLoginOptions, type LoginOutcome, type LoginRequest, } from "./login.js";
|
|
35
|
+
export { logout, type LogoutOutcome } from "./logout.js";
|
|
36
|
+
export { currentIdentity, currentToken, type RegistryIdentity } from "./identity.js";
|
|
37
|
+
export { createToken, listTokens, parseExpiresInSeconds, revokeToken, tokenPermissions, validateExpiresInSeconds, MAX_TOKEN_LIFETIME_SECONDS, MIN_TOKEN_LIFETIME_SECONDS, type CreateTokenRequest, type CreatedToken, type TokenAuthorityRequest, } from "./tokens.js";
|
|
38
|
+
export { hasCredentialsForAll } from "./login-suggestion.js";
|
|
39
|
+
export { AuthEnvironment } from "./internal/environment.js";
|
|
31
40
|
export { runPublishAuthorization, type PublishAuthorizationInput, } from "./publish-authorization.js";
|
|
32
41
|
export { selectLoginStrategy, type LoginStrategy, type LoginStrategyEnvironment, type LoginStrategyOptions, } from "./login-strategy.js";
|
|
33
42
|
export type { AuthLoginInteractionService } from "./login-interaction.js";
|
|
34
43
|
export { AuthLoginInteraction } from "./login-interaction.js";
|
|
35
44
|
export { withAuthGuard } from "./guard.js";
|
|
45
|
+
export { PendingPublishAuthorizationStore, PendingPublishAuthorizationSchema, type PendingPublishAuthorization, type PendingPublishAuthorizationStoreService, } from "./pending-publish-authorization-store.js";
|
|
36
46
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/src/index.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* @packageDocumentation
|
|
12
12
|
*/
|
|
13
13
|
// Typed failures
|
|
14
|
-
export { AuthExchangeFailed, AuthLoginRequired, AuthTokenPolicyRequired, authLoginRequired, DeviceAuthorizationPending, DeviceLoginCodeExpired, DeviceLoginDenied, isAuthError, isRegistryAuthFailure, REGISTRY_AUTH_ERROR_CATEGORIES, RegistryAuthFailed, StepUpRequired, } from "./errors.js";
|
|
14
|
+
export { AuthExchangeFailed, AuthLoginRequired, AuthTokenPolicyRequired, authLoginRequired, DeviceAuthorizationPending, PublishAuthorizationPending, DeviceLoginCodeExpired, DeviceLoginDenied, AuthInteractionAbandoned, isAuthError, isRegistryAuthFailure, REGISTRY_AUTH_ERROR_CATEGORIES, RegistryAuthFailed, StepUpRequired, StepUpVerificationPending, } from "./errors.js";
|
|
15
15
|
export { CredentialEntrySchema, CredentialFileSchema, CredentialStoreTokenSource, EnvVarTokenSource, FileTokenSource, FlagTokenSource, RegistryAccountsSchema, } from "./schema.js";
|
|
16
16
|
export { canUsePersistedCredentials, CredentialStore, detectEnvironment, makePersistedCredentialsUnsupportedError, selectTier, } from "./credential-store.js";
|
|
17
17
|
export { PendingDeviceLoginSchema, PendingDeviceLoginStore } from "./pending-device-login-store.js";
|
|
@@ -23,9 +23,22 @@ export { makeOAuthState, makePkceChallenge, makePkceVerifier, runLoopbackLogin,
|
|
|
23
23
|
export { LoopbackCallbackRejected, LoopbackLoginFallback, startLoopbackServer, } from "./loopback-server.js";
|
|
24
24
|
export { LoginDocumentSchema, LoginResultSchema, makeLoginResult, } from "./login-output.js";
|
|
25
25
|
export { AuthLoginPresenter } from "./login-presenter.js";
|
|
26
|
+
// Step-up verification protocol
|
|
27
|
+
export { runWithStepUp, } from "./step-up.js";
|
|
28
|
+
// The Registry this invocation authenticates against
|
|
29
|
+
export { selectedRegistry } from "./selected-registry.js";
|
|
30
|
+
// Sign-in, sign-out, identity, and token policy use cases
|
|
31
|
+
export { classifyLoopbackFailure, deviceLoginOptions, login, resumeLoginOptions, } from "./login.js";
|
|
32
|
+
export { logout } from "./logout.js";
|
|
33
|
+
export { currentIdentity, currentToken } from "./identity.js";
|
|
34
|
+
export { createToken, listTokens, parseExpiresInSeconds, revokeToken, tokenPermissions, validateExpiresInSeconds, MAX_TOKEN_LIFETIME_SECONDS, MIN_TOKEN_LIFETIME_SECONDS, } from "./tokens.js";
|
|
35
|
+
export { hasCredentialsForAll } from "./login-suggestion.js";
|
|
36
|
+
// Environment source for auth policy decisions (default: the process environment)
|
|
37
|
+
export { AuthEnvironment } from "./internal/environment.js";
|
|
26
38
|
export { runPublishAuthorization, } from "./publish-authorization.js";
|
|
27
39
|
export { selectLoginStrategy, } from "./login-strategy.js";
|
|
28
40
|
export { AuthLoginInteraction } from "./login-interaction.js";
|
|
29
41
|
// Auth guard combinator
|
|
30
42
|
export { withAuthGuard } from "./guard.js";
|
|
43
|
+
export { PendingPublishAuthorizationStore, PendingPublishAuthorizationSchema, } from "./pending-publish-authorization-store.js";
|
|
31
44
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Runtime environment detection for credential policy and login flows.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Environment values are read through Effect's `ConfigProvider`, whose default
|
|
5
|
+
* is the process environment. Tests and embedders replace the provider instead
|
|
6
|
+
* of mutating `process.env`, so no auth policy specification stubs globals.
|
|
7
7
|
*/
|
|
8
|
+
import * as ConfigProvider from "effect/ConfigProvider";
|
|
9
|
+
import * as ServiceMap from "effect/Context";
|
|
8
10
|
import * as FileSystem from "effect/FileSystem";
|
|
9
11
|
import * as Effect from "effect/Effect";
|
|
10
12
|
import * as Option from "effect/Option";
|
|
11
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* The configuration source every auth environment decision reads. Defaults to
|
|
15
|
+
* the process environment; tests and embedders provide any `ConfigProvider`
|
|
16
|
+
* — `ConfigProvider.fromEnvRecord({ ... })` — instead of mutating globals.
|
|
17
|
+
*/
|
|
18
|
+
export declare const AuthEnvironment: ServiceMap.Reference<ConfigProvider.ConfigProvider>;
|
|
19
|
+
/**
|
|
20
|
+
* Read an optional environment value. The single access point: every auth
|
|
21
|
+
* environment decision resolves through the ambient `ConfigProvider`.
|
|
22
|
+
*/
|
|
12
23
|
export declare const envOption: (name: string) => Effect.Effect<Option.Option<string>>;
|
|
13
|
-
/** Returns true if SSH_CLIENT or SSH_TTY
|
|
24
|
+
/** Returns true if SSH_CLIENT or SSH_TTY is set. */
|
|
14
25
|
export declare const isSSH: Effect.Effect<boolean>;
|
|
15
26
|
/** Returns true if running as root (uid 0). */
|
|
16
27
|
export declare const isRoot: () => boolean;
|
|
@@ -20,4 +31,11 @@ export declare const isContainer: Effect.Effect<boolean, never, FileSystem.FileS
|
|
|
20
31
|
export declare const isWSL: Effect.Effect<boolean, never, FileSystem.FileSystem>;
|
|
21
32
|
/** Returns true if CI env var is set. */
|
|
22
33
|
export declare const isCI: Effect.Effect<boolean>;
|
|
34
|
+
/**
|
|
35
|
+
* The environment facts login-strategy selection reads. Assembled here so the
|
|
36
|
+
* strategy decision never touches globals.
|
|
37
|
+
*/
|
|
38
|
+
export declare const loginStrategyEnvironment: Effect.Effect<{
|
|
39
|
+
[k: string]: string;
|
|
40
|
+
}, never, never>;
|
|
23
41
|
//# sourceMappingURL=environment.d.ts.map
|
|
@@ -1,19 +1,53 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Runtime environment detection for credential policy and login flows.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Environment values are read through Effect's `ConfigProvider`, whose default
|
|
5
|
+
* is the process environment. Tests and embedders replace the provider instead
|
|
6
|
+
* of mutating `process.env`, so no auth policy specification stubs globals.
|
|
7
7
|
*/
|
|
8
|
+
import * as ConfigProvider from "effect/ConfigProvider";
|
|
9
|
+
import * as ServiceMap from "effect/Context";
|
|
8
10
|
import * as FileSystem from "effect/FileSystem";
|
|
9
11
|
import * as Effect from "effect/Effect";
|
|
10
12
|
import * as Option from "effect/Option";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Reads the live process environment on every lookup. Effect's own
|
|
15
|
+
* `ConfigProvider` default snapshots the environment the first time any
|
|
16
|
+
* program reads configuration; auth reads must observe the environment the
|
|
17
|
+
* invocation actually runs under.
|
|
18
|
+
*/
|
|
19
|
+
const processEnvironmentProvider = ConfigProvider.make((path) =>
|
|
20
|
+
// eslint-disable-next-line no-restricted-properties -- the one process-environment read in registry-auth
|
|
21
|
+
ConfigProvider.fromEnvRecord(process.env).load(path));
|
|
22
|
+
/**
|
|
23
|
+
* The configuration source every auth environment decision reads. Defaults to
|
|
24
|
+
* the process environment; tests and embedders provide any `ConfigProvider`
|
|
25
|
+
* — `ConfigProvider.fromEnvRecord({ ... })` — instead of mutating globals.
|
|
26
|
+
*/
|
|
27
|
+
export const AuthEnvironment = ServiceMap.Reference("@agentxm/registry-auth/AuthEnvironment", {
|
|
28
|
+
defaultValue: () => processEnvironmentProvider,
|
|
29
|
+
});
|
|
30
|
+
/**
|
|
31
|
+
* The raw string a provider node carries. A `Record` or `Array` node still
|
|
32
|
+
* carries the value of the exact key when the environment also defines
|
|
33
|
+
* longer keys under it (`AXM_TOKEN` beside `AXM_TOKEN_FILE`).
|
|
34
|
+
*/
|
|
35
|
+
const nodeValue = (node) => node === undefined
|
|
36
|
+
? Option.none()
|
|
37
|
+
: node._tag === "Value"
|
|
38
|
+
? Option.some(node.value)
|
|
39
|
+
: Option.fromUndefinedOr(node.value);
|
|
40
|
+
/**
|
|
41
|
+
* Read an optional environment value. The single access point: every auth
|
|
42
|
+
* environment decision resolves through the ambient `ConfigProvider`.
|
|
43
|
+
*/
|
|
44
|
+
export const envOption = (name) => Effect.gen(function* () {
|
|
45
|
+
const provider = yield* AuthEnvironment;
|
|
46
|
+
const node = yield* provider.load([name]).pipe(Effect.orElseSucceed(() => undefined));
|
|
47
|
+
return nodeValue(node);
|
|
48
|
+
});
|
|
49
|
+
/** Returns true if SSH_CLIENT or SSH_TTY is set. */
|
|
50
|
+
export const isSSH = Effect.map(Effect.all([envOption("SSH_CLIENT"), envOption("SSH_TTY")]), ([client, tty]) => Option.isSome(client) || Option.isSome(tty));
|
|
17
51
|
/** Returns true if running as root (uid 0). */
|
|
18
52
|
export const isRoot = () => process.getuid?.() === 0;
|
|
19
53
|
/** Returns true if /.dockerenv or /.containerenv exists. Requires FileSystem. */
|
|
@@ -42,4 +76,20 @@ export const isWSL = Effect.gen(function* () {
|
|
|
42
76
|
});
|
|
43
77
|
/** Returns true if CI env var is set. */
|
|
44
78
|
export const isCI = Effect.map(envOption("CI"), (value) => Option.exists(value, (raw) => raw.length > 0 && raw !== "0" && raw.toLowerCase() !== "false"));
|
|
79
|
+
/**
|
|
80
|
+
* The environment facts login-strategy selection reads. Assembled here so the
|
|
81
|
+
* strategy decision never touches globals.
|
|
82
|
+
*/
|
|
83
|
+
export const loginStrategyEnvironment = Effect.gen(function* () {
|
|
84
|
+
const env = yield* Effect.all({
|
|
85
|
+
SSH_CONNECTION: envOption("SSH_CONNECTION"),
|
|
86
|
+
SSH_CLIENT: envOption("SSH_CLIENT"),
|
|
87
|
+
SSH_TTY: envOption("SSH_TTY"),
|
|
88
|
+
DISPLAY: envOption("DISPLAY"),
|
|
89
|
+
WAYLAND_DISPLAY: envOption("WAYLAND_DISPLAY"),
|
|
90
|
+
CI: envOption("CI"),
|
|
91
|
+
CODESPACES: envOption("CODESPACES"),
|
|
92
|
+
});
|
|
93
|
+
return Object.fromEntries(Object.entries(env).flatMap(([key, value]) => Option.isSome(value) ? [[key, value.value]] : []));
|
|
94
|
+
});
|
|
45
95
|
//# sourceMappingURL=environment.js.map
|
package/dist/src/live.d.ts
CHANGED
|
@@ -10,4 +10,5 @@ export { AuthMiddlewareLive, makeAuthMiddlewareLive } from "./auth-middleware.js
|
|
|
10
10
|
export { CredentialStoreLive, CredentialStoreSessionLive } from "./credential-store.js";
|
|
11
11
|
export { AuthLoginInteractionLive } from "./login-interaction.js";
|
|
12
12
|
export { PendingDeviceLoginStoreLive } from "./pending-device-login-store.js";
|
|
13
|
+
export { PendingPublishAuthorizationStoreLive } from "./pending-publish-authorization-store.js";
|
|
13
14
|
//# sourceMappingURL=live.d.ts.map
|
package/dist/src/live.js
CHANGED
|
@@ -10,4 +10,5 @@ export { AuthMiddlewareLive, makeAuthMiddlewareLive } from "./auth-middleware.js
|
|
|
10
10
|
export { CredentialStoreLive, CredentialStoreSessionLive } from "./credential-store.js";
|
|
11
11
|
export { AuthLoginInteractionLive } from "./login-interaction.js";
|
|
12
12
|
export { PendingDeviceLoginStoreLive } from "./pending-device-login-store.js";
|
|
13
|
+
export { PendingPublishAuthorizationStoreLive } from "./pending-publish-authorization-store.js";
|
|
13
14
|
//# sourceMappingURL=live.js.map
|
|
@@ -13,6 +13,7 @@ import * as Effect from "effect/Effect";
|
|
|
13
13
|
import * as Layer from "effect/Layer";
|
|
14
14
|
import * as ServiceMap from "effect/Context";
|
|
15
15
|
import type { DeviceLoginPendingResult } from "./device-login.js";
|
|
16
|
+
import type { AuthInteractionAbandoned } from "./errors.js";
|
|
16
17
|
import type { LoginResult } from "./login-output.js";
|
|
17
18
|
export type AuthLoginProgress = {
|
|
18
19
|
readonly _tag: "StartingDeviceAuthorization";
|
|
@@ -30,7 +31,42 @@ export type AuthLoginProgress = {
|
|
|
30
31
|
} | {
|
|
31
32
|
readonly _tag: "CompletingSignIn";
|
|
32
33
|
readonly registryHost: string;
|
|
34
|
+
} | {
|
|
35
|
+
readonly _tag: "CheckingRegistrySession";
|
|
36
|
+
readonly registryHost: string;
|
|
37
|
+
} | {
|
|
38
|
+
readonly _tag: "RevokingRegistrySession";
|
|
39
|
+
readonly registryHost: string;
|
|
40
|
+
} | {
|
|
41
|
+
readonly _tag: "ListingRegistryTokens";
|
|
42
|
+
}
|
|
43
|
+
/** A Registry write that the Registry may challenge for human verification. */
|
|
44
|
+
| {
|
|
45
|
+
readonly _tag: "RunningVerifiedWrite";
|
|
46
|
+
readonly operation: string;
|
|
47
|
+
}
|
|
48
|
+
/** The bounded wait while a person completes verification. */
|
|
49
|
+
| {
|
|
50
|
+
readonly _tag: "WaitingForHumanVerification";
|
|
51
|
+
readonly operation: string;
|
|
52
|
+
}
|
|
53
|
+
/** The one retry of the challenged write, after verification. */
|
|
54
|
+
| {
|
|
55
|
+
readonly _tag: "RetryingVerifiedWrite";
|
|
56
|
+
readonly operation: string;
|
|
33
57
|
};
|
|
58
|
+
/** What a person decided about replacing a session that is still valid. */
|
|
59
|
+
export type SessionReplacementDecision = "replace" | "keep";
|
|
60
|
+
/** Why sign-in fell back to the device-code flow. */
|
|
61
|
+
export type DeviceCodeFallbackReason = "remote-or-headless" | "loopback-bind-failed";
|
|
62
|
+
/** One step-up challenge, as a person needs to see it. */
|
|
63
|
+
export interface StepUpChallengePresentation {
|
|
64
|
+
readonly action: string;
|
|
65
|
+
readonly target: string;
|
|
66
|
+
readonly verificationUrl: string;
|
|
67
|
+
readonly expiresAt: string;
|
|
68
|
+
readonly browserOpened: boolean;
|
|
69
|
+
}
|
|
34
70
|
export interface DeviceFlowPresentation {
|
|
35
71
|
readonly verificationUri: string;
|
|
36
72
|
readonly verificationUriComplete: string;
|
|
@@ -64,6 +100,19 @@ export interface AuthLoginPresenterService {
|
|
|
64
100
|
readonly candidateCount: number;
|
|
65
101
|
readonly authorizationUrl: string;
|
|
66
102
|
}) => Effect.Effect<void>;
|
|
103
|
+
/** A still-valid session was found for the selected Registry. */
|
|
104
|
+
readonly noteExistingSession: (handle: string) => Effect.Effect<void>;
|
|
105
|
+
/** Stored credentials were rejected, so a new sign-in starts. */
|
|
106
|
+
readonly noteRejectedStoredCredentials: Effect.Effect<void>;
|
|
107
|
+
/** Sign-in fell back to the device-code flow for the carried reason. */
|
|
108
|
+
readonly noteDeviceCodeFallback: (reason: DeviceCodeFallbackReason) => Effect.Effect<void>;
|
|
109
|
+
/**
|
|
110
|
+
* Ask whether to replace a session that is still valid. Abandoning the
|
|
111
|
+
* question fails with `AuthInteractionAbandoned`; declining returns "keep".
|
|
112
|
+
*/
|
|
113
|
+
readonly confirmSessionReplacement: (message: string) => Effect.Effect<SessionReplacementDecision, AuthInteractionAbandoned>;
|
|
114
|
+
/** Guidance for one pending step-up verification. */
|
|
115
|
+
readonly presentStepUpChallenge: (challenge: StepUpChallengePresentation) => Effect.Effect<void>;
|
|
67
116
|
}
|
|
68
117
|
declare const AuthLoginPresenter_base: ServiceMap.ServiceClass<AuthLoginPresenter, "@agentxm/registry-auth/login-presenter/AuthLoginPresenter", AuthLoginPresenterService>;
|
|
69
118
|
export declare class AuthLoginPresenter extends AuthLoginPresenter_base {
|
|
@@ -84,9 +133,15 @@ export interface AuthLoginPresenterTestState {
|
|
|
84
133
|
readonly candidateCount: number;
|
|
85
134
|
readonly authorizationUrl: string;
|
|
86
135
|
}>;
|
|
136
|
+
readonly existingSessions: Array<string>;
|
|
137
|
+
readonly rejectedStoredCredentials: Array<true>;
|
|
138
|
+
readonly deviceCodeFallbacks: Array<DeviceCodeFallbackReason>;
|
|
139
|
+
readonly sessionReplacementPrompts: Array<string>;
|
|
140
|
+
readonly stepUpChallenges: Array<StepUpChallengePresentation>;
|
|
87
141
|
}
|
|
88
142
|
export declare const AuthLoginPresenterTest: (overrides?: {
|
|
89
143
|
readonly tryEmitPendingDeviceLogin?: (result: DeviceLoginPendingResult) => Effect.Effect<boolean>;
|
|
144
|
+
readonly confirmSessionReplacement?: (message: string) => Effect.Effect<SessionReplacementDecision, AuthInteractionAbandoned>;
|
|
90
145
|
}) => {
|
|
91
146
|
layer: Layer.Layer<AuthLoginPresenter, never, never>;
|
|
92
147
|
state: AuthLoginPresenterTestState;
|
|
@@ -24,6 +24,11 @@ export const AuthLoginPresenterTest = (overrides) => {
|
|
|
24
24
|
loopbackStarts: [],
|
|
25
25
|
loopbackBrowserOutcomes: [],
|
|
26
26
|
publishReviews: [],
|
|
27
|
+
existingSessions: [],
|
|
28
|
+
rejectedStoredCredentials: [],
|
|
29
|
+
deviceCodeFallbacks: [],
|
|
30
|
+
sessionReplacementPrompts: [],
|
|
31
|
+
stepUpChallenges: [],
|
|
27
32
|
};
|
|
28
33
|
const layer = Layer.succeed(AuthLoginPresenter, {
|
|
29
34
|
withProgress: (progress, run) => Effect.suspend(() => {
|
|
@@ -52,6 +57,23 @@ export const AuthLoginPresenterTest = (overrides) => {
|
|
|
52
57
|
notePublishReview: (review) => Effect.sync(() => {
|
|
53
58
|
state.publishReviews.push(review);
|
|
54
59
|
}),
|
|
60
|
+
noteExistingSession: (handle) => Effect.sync(() => {
|
|
61
|
+
state.existingSessions.push(handle);
|
|
62
|
+
}),
|
|
63
|
+
noteRejectedStoredCredentials: Effect.sync(() => {
|
|
64
|
+
state.rejectedStoredCredentials.push(true);
|
|
65
|
+
}),
|
|
66
|
+
noteDeviceCodeFallback: (reason) => Effect.sync(() => {
|
|
67
|
+
state.deviceCodeFallbacks.push(reason);
|
|
68
|
+
}),
|
|
69
|
+
confirmSessionReplacement: (message) => Effect.gen(function* () {
|
|
70
|
+
state.sessionReplacementPrompts.push(message);
|
|
71
|
+
return yield* overrides?.confirmSessionReplacement?.(message) ??
|
|
72
|
+
Effect.succeed("replace");
|
|
73
|
+
}),
|
|
74
|
+
presentStepUpChallenge: (challenge) => Effect.sync(() => {
|
|
75
|
+
state.stepUpChallenges.push(challenge);
|
|
76
|
+
}),
|
|
55
77
|
});
|
|
56
78
|
return { layer, state };
|
|
57
79
|
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Offline credential-presence probe.
|
|
3
|
+
*
|
|
4
|
+
* Answers "could this invocation be missing a private item because it is not
|
|
5
|
+
* signed in to that origin?" without contacting any Registry. A successful
|
|
6
|
+
* ambient or stored token suppresses the hint even when that identity cannot
|
|
7
|
+
* see the requested item.
|
|
8
|
+
*
|
|
9
|
+
* @experimental This API is unstable and may change without notice.
|
|
10
|
+
*/
|
|
11
|
+
import * as Effect from "effect/Effect";
|
|
12
|
+
import { CredentialStore } from "./credential-store.js";
|
|
13
|
+
/**
|
|
14
|
+
* Whether every remote origin among `locations` already has a credential this
|
|
15
|
+
* invocation would present. False when at least one does not.
|
|
16
|
+
*/
|
|
17
|
+
export declare const hasCredentialsForAll: (locations: ReadonlyArray<string>, defaultRegistryUrl: string) => Effect.Effect<boolean, never, CredentialStore>;
|
|
18
|
+
//# sourceMappingURL=login-suggestion.d.ts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Offline credential-presence probe.
|
|
3
|
+
*
|
|
4
|
+
* Answers "could this invocation be missing a private item because it is not
|
|
5
|
+
* signed in to that origin?" without contacting any Registry. A successful
|
|
6
|
+
* ambient or stored token suppresses the hint even when that identity cannot
|
|
7
|
+
* see the requested item.
|
|
8
|
+
*
|
|
9
|
+
* @experimental This API is unstable and may change without notice.
|
|
10
|
+
*/
|
|
11
|
+
import * as Effect from "effect/Effect";
|
|
12
|
+
import * as Option from "effect/Option";
|
|
13
|
+
import { CredentialStore } from "./credential-store.js";
|
|
14
|
+
import { resolveRequestToken } from "./token-resolution.js";
|
|
15
|
+
const remoteOrigins = (locations) => {
|
|
16
|
+
const origins = new Set();
|
|
17
|
+
for (const location of locations) {
|
|
18
|
+
try {
|
|
19
|
+
const url = new URL(location);
|
|
20
|
+
if (url.protocol === "http:" || url.protocol === "https:")
|
|
21
|
+
origins.add(url.origin);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// Invalid source locations are reported by their owning resolution path.
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return [...origins];
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Whether every remote origin among `locations` already has a credential this
|
|
31
|
+
* invocation would present. False when at least one does not.
|
|
32
|
+
*/
|
|
33
|
+
export const hasCredentialsForAll = (locations, defaultRegistryUrl) => Effect.forEach(remoteOrigins(locations), (origin) => resolveRequestToken(origin, defaultRegistryUrl).pipe(Effect.map(Option.isSome), Effect.catch(() => Effect.succeed(false)))).pipe(Effect.map((present) => present.every((hasCredential) => hasCredential)));
|
|
34
|
+
//# sourceMappingURL=login-suggestion.js.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sign-in orchestration: flag coherence, persisted-credential policy, the
|
|
3
|
+
* existing-session decision, strategy selection, and the loopback →
|
|
4
|
+
* device-code fallback chain.
|
|
5
|
+
*
|
|
6
|
+
* The capability decides; the application supplies presentation through
|
|
7
|
+
* `AuthLoginPresenter` and platform integration through
|
|
8
|
+
* `AuthLoginInteraction`. No sign-in decision is taken outside this module.
|
|
9
|
+
*
|
|
10
|
+
* @experimental This API is unstable and may change without notice.
|
|
11
|
+
*/
|
|
12
|
+
import * as Effect from "effect/Effect";
|
|
13
|
+
import { AuthClient } from "./auth-client.js";
|
|
14
|
+
import { CredentialStore } from "./credential-store.js";
|
|
15
|
+
import { type RunDeviceLoginOptions } from "./device-login.js";
|
|
16
|
+
import { RegistryAuthFailed } from "./errors.js";
|
|
17
|
+
import type { LoopbackCallbackRejected, LoopbackLoginFallback } from "./loopback-server.js";
|
|
18
|
+
import { AuthLoginPresenter } from "./login-presenter.js";
|
|
19
|
+
/** The invocation's sign-in inputs, parsed from the command line. */
|
|
20
|
+
export interface LoginRequest {
|
|
21
|
+
/** Preapproval: start a new sign-in without asking about a valid session. */
|
|
22
|
+
readonly yes: boolean;
|
|
23
|
+
readonly deviceCode: boolean;
|
|
24
|
+
readonly restart: boolean;
|
|
25
|
+
/** Resume and wait for a pending device sign-in instead of starting one. */
|
|
26
|
+
readonly wait: boolean;
|
|
27
|
+
readonly timeoutSeconds?: number;
|
|
28
|
+
readonly scopes: ReadonlyArray<string>;
|
|
29
|
+
/** No terminal is available to run a sign-in flow interactively. */
|
|
30
|
+
readonly nonInteractive: boolean;
|
|
31
|
+
/** The invocation reports through a machine document rather than prose. */
|
|
32
|
+
readonly machineOutput: boolean;
|
|
33
|
+
}
|
|
34
|
+
/** What one `login` invocation settled on. */
|
|
35
|
+
export type LoginOutcome =
|
|
36
|
+
/** A valid session was found and kept; nothing was written. */
|
|
37
|
+
{
|
|
38
|
+
readonly _tag: "SessionRetained";
|
|
39
|
+
readonly registryHost: string;
|
|
40
|
+
readonly handle: string;
|
|
41
|
+
}
|
|
42
|
+
/** A sign-in flow ran; the presenter reported its result. */
|
|
43
|
+
| {
|
|
44
|
+
readonly _tag: "SignInAttempted";
|
|
45
|
+
}
|
|
46
|
+
/** A pending device sign-in was resumed. */
|
|
47
|
+
| {
|
|
48
|
+
readonly _tag: "PendingSignInResumed";
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* The device-login options one sign-in request means. Exported so the option
|
|
52
|
+
* shaping is provable without substituting the flow it is handed to.
|
|
53
|
+
*/
|
|
54
|
+
export declare const deviceLoginOptions: (request: Pick<LoginRequest, "restart" | "scopes">, openBrowser: boolean) => RunDeviceLoginOptions;
|
|
55
|
+
/** The resume options one `--wait` request means. */
|
|
56
|
+
export declare const resumeLoginOptions: (request: Pick<LoginRequest, "timeoutSeconds">) => {
|
|
57
|
+
readonly timeoutSeconds?: number;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* How a loopback sign-in that could not complete is classified.
|
|
61
|
+
*
|
|
62
|
+
* A failed bind is recoverable — device-code sign-in replaces it — while an
|
|
63
|
+
* expired wait or a rejected callback is terminal and leaves credentials
|
|
64
|
+
* untouched.
|
|
65
|
+
*/
|
|
66
|
+
export declare const classifyLoopbackFailure: (error: LoopbackLoginFallback | LoopbackCallbackRejected) => RegistryAuthFailed;
|
|
67
|
+
export declare const login: (request: LoginRequest, registryUrl: string) => Effect.Effect<{
|
|
68
|
+
readonly _tag: "PendingSignInResumed";
|
|
69
|
+
readonly registryHost?: never;
|
|
70
|
+
readonly handle?: never;
|
|
71
|
+
} | {
|
|
72
|
+
readonly _tag: "SessionRetained";
|
|
73
|
+
readonly registryHost: string;
|
|
74
|
+
readonly handle: string & import("effect/Brand").Brand<"Handle">;
|
|
75
|
+
} | {
|
|
76
|
+
readonly _tag: "SignInAttempted";
|
|
77
|
+
readonly registryHost?: never;
|
|
78
|
+
readonly handle?: never;
|
|
79
|
+
}, RegistryAuthFailed | import("./errors.js").AuthLoginRequired | import("./errors.js").AuthTokenPolicyRequired | import("./errors.js").DeviceLoginDenied | import("./errors.js").DeviceLoginCodeExpired | import("./errors.js").DeviceAuthorizationPending | import("./errors.js").StepUpRequired | import("@agentxm/registry-client").RegistryProblem | import("@agentxm/registry-client").RegistryRequestFailed | import("@agentxm/registry-client").RegistryOperationFailed | import("./errors.js").AuthExchangeFailed | import("./errors.js").PublishAuthorizationPending | import("./errors.js").StepUpVerificationPending | import("./errors.js").AuthInteractionAbandoned, AuthClient | CredentialStore | AuthLoginPresenter | import("./pending-device-login-store.ts").PendingDeviceLoginStore | import("./device-login.js").DeviceLoginInteraction>;
|
|
80
|
+
//# sourceMappingURL=login.d.ts.map
|