@agentxm/registry-auth 0.28.12 → 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 +1 -1
- package/dist/src/auth-client.js +1 -1
- package/dist/src/credential-store.d.ts +39 -2
- package/dist/src/credential-store.js +50 -46
- package/dist/src/device-login.d.ts +2 -2
- package/dist/src/errors.d.ts +26 -2
- package/dist/src/errors.js +17 -0
- package/dist/src/identity.d.ts +46 -0
- package/dist/src/identity.js +49 -0
- package/dist/src/index.d.ts +10 -1
- package/dist/src/index.js +13 -1
- package/dist/src/internal/environment.d.ts +23 -5
- package/dist/src/internal/environment.js +59 -9
- 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/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/tokens.d.ts +57 -0
- package/dist/src/tokens.js +78 -0
- package/package.json +7 -6
|
@@ -17,7 +17,7 @@ import * as ServiceMap from "effect/Context";
|
|
|
17
17
|
import * as Effect from "effect/Effect";
|
|
18
18
|
import * as Layer from "effect/Layer";
|
|
19
19
|
import { type Handle } from "@agentxm/extension-model/unstable/extensions/handle";
|
|
20
|
-
import { type PublishVisibility } from "@agentxm/registry-protocol/unstable/publish
|
|
20
|
+
import { type PublishVisibility } from "@agentxm/registry-protocol/unstable/publish";
|
|
21
21
|
import { type PreviewPublicationSetRequest, type PreviewPublicationSetResponse, type Sha256Hex } from "@agentxm/registry-protocol/unstable/registry/publication-set";
|
|
22
22
|
import { type NormalizedTokenResponse } from "./oauth-contract.js";
|
|
23
23
|
import { GeneratedRegistryClient, RegistryUrl } from "@agentxm/registry-client";
|
package/dist/src/auth-client.js
CHANGED
|
@@ -23,7 +23,7 @@ import * as Schedule from "effect/Schedule";
|
|
|
23
23
|
import * as Schema from "effect/Schema";
|
|
24
24
|
import { DateTimeUtcSchema } from "@agentxm/extension-model/unstable/date-time";
|
|
25
25
|
import { normalizeHandle } from "@agentxm/extension-model/unstable/extensions/handle";
|
|
26
|
-
import { PublishVisibilitySchema, } from "@agentxm/registry-protocol/unstable/publish
|
|
26
|
+
import { PublishVisibilitySchema, } from "@agentxm/registry-protocol/unstable/publish";
|
|
27
27
|
import { PreviewPublicationSetResponseSchema, } from "@agentxm/registry-protocol/unstable/registry/publication-set";
|
|
28
28
|
import {} from "./oauth-contract.js";
|
|
29
29
|
import { GeneratedRegistryClient, executeRegistryRequest, RegistryUrl, captureRegistryErrorResponseBodies, getString, isHttpClientError, isRegistryClientError, isRegistryClientFailure, isTransientHttpClientError, mapRegistryFailure, } from "@agentxm/registry-client";
|
|
@@ -68,8 +68,45 @@ export declare const makePersistedCredentialsUnsupportedError: () => AuthTokenPo
|
|
|
68
68
|
export declare const CredentialStoreLive: Layer.Layer<CredentialStore, never, FileSystem.FileSystem | Path.Path>;
|
|
69
69
|
/**
|
|
70
70
|
* Decorates a credential store with a per-layer, per-origin read memo.
|
|
71
|
-
*
|
|
72
|
-
*
|
|
71
|
+
*
|
|
72
|
+
* Reads go through an `Effect.Cache` keyed by registry origin, which owns the
|
|
73
|
+
* concurrent-lookup sharing, per-key invalidation, and result-dependent expiry
|
|
74
|
+
* that this layer previously hand-rolled with a value map, a semaphore map, and
|
|
75
|
+
* a double-checked read.
|
|
76
|
+
*
|
|
77
|
+
* Capacity is `Number.POSITIVE_INFINITY`: the cache never evicts on size, which
|
|
78
|
+
* preserves the unbounded map this layer already used. The key space is the set
|
|
79
|
+
* of distinct registry origins a single process contacts — the configured
|
|
80
|
+
* registry plus any origins named by workspace extension sources — so there is
|
|
81
|
+
* no measured workload that justifies a numeric bound. The bound that matters
|
|
82
|
+
* is lifetime, and it is the layer's: the cache is created per
|
|
83
|
+
* `CredentialStoreSessionLive` construction and released with it, so a CLI
|
|
84
|
+
* session is its longest possible life.
|
|
85
|
+
*
|
|
86
|
+
* Expiry is result-dependent. Successful reads — including a successful empty
|
|
87
|
+
* read — live for the session, so repeated authentication paths pay one load
|
|
88
|
+
* per origin. Failed reads expire immediately, so a failure is never memoized
|
|
89
|
+
* and a later request retries against the store.
|
|
90
|
+
*
|
|
91
|
+
* Callers that arrive while a lookup is in flight share it rather than issuing
|
|
92
|
+
* their own; callers that arrive after a failed lookup resolves start a new
|
|
93
|
+
* one. That replaces the old per-origin semaphore, under which every waiter on
|
|
94
|
+
* a failing origin retried serially. Sharing is safe here because each auth
|
|
95
|
+
* path issues a single load and none of them depends on serial retry.
|
|
96
|
+
*
|
|
97
|
+
* `Effect.onInterrupt` is load-path scaffolding, not decoration. `Cache.get`
|
|
98
|
+
* runs its lookup on a detached daemon fiber and leaves the pending entry in
|
|
99
|
+
* the map when the only caller is interrupted, so without this guard the next
|
|
100
|
+
* read of that origin would await an abandoned lookup forever. Invalidating on
|
|
101
|
+
* interrupt restores the pre-cache behavior, where an interrupted read left
|
|
102
|
+
* nothing behind and the next read simply ran again.
|
|
103
|
+
*
|
|
104
|
+
* Known limitation, unchanged by this layer's move to `Cache` and not fixed by
|
|
105
|
+
* it: a `save` or `clear` whose invalidation lands while a read of the same
|
|
106
|
+
* origin is still in flight can be overwritten when that read populates the
|
|
107
|
+
* memo, leaving the pre-write value cached for the rest of the session. The
|
|
108
|
+
* previous value-map-and-semaphore memo lost the same invalidation through its
|
|
109
|
+
* own write-after-invalidate window.
|
|
73
110
|
*/
|
|
74
111
|
export declare const CredentialStoreSessionLive: Layer.Layer<CredentialStore, never, CredentialStore>;
|
|
75
112
|
export declare const CredentialStoreTest: (tier?: StorageTier, initialData?: CredentialFile, allowsPersistedCredentials?: boolean) => Layer.Layer<CredentialStore, never, never>;
|
|
@@ -20,12 +20,13 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
20
20
|
import * as FileSystem from "effect/FileSystem";
|
|
21
21
|
import * as Path from "effect/Path";
|
|
22
22
|
import * as ServiceMap from "effect/Context";
|
|
23
|
+
import * as Cache from "effect/Cache";
|
|
24
|
+
import * as Duration from "effect/Duration";
|
|
23
25
|
import * as Effect from "effect/Effect";
|
|
26
|
+
import * as Exit from "effect/Exit";
|
|
24
27
|
import * as Layer from "effect/Layer";
|
|
25
28
|
import * as Option from "effect/Option";
|
|
26
|
-
import * as Ref from "effect/Ref";
|
|
27
29
|
import * as Schema from "effect/Schema";
|
|
28
|
-
import * as Semaphore from "effect/Semaphore";
|
|
29
30
|
import * as lockfile from "proper-lockfile";
|
|
30
31
|
import { decodeHandleSync } from "@agentxm/extension-model/unstable/extensions/handle";
|
|
31
32
|
import { AuthTokenPolicyRequired, RegistryAuthFailed } from "./errors.js";
|
|
@@ -340,57 +341,60 @@ export const CredentialStoreLive = Layer.effect(CredentialStore, Effect.gen(func
|
|
|
340
341
|
}));
|
|
341
342
|
/**
|
|
342
343
|
* Decorates a credential store with a per-layer, per-origin read memo.
|
|
343
|
-
*
|
|
344
|
-
*
|
|
344
|
+
*
|
|
345
|
+
* Reads go through an `Effect.Cache` keyed by registry origin, which owns the
|
|
346
|
+
* concurrent-lookup sharing, per-key invalidation, and result-dependent expiry
|
|
347
|
+
* that this layer previously hand-rolled with a value map, a semaphore map, and
|
|
348
|
+
* a double-checked read.
|
|
349
|
+
*
|
|
350
|
+
* Capacity is `Number.POSITIVE_INFINITY`: the cache never evicts on size, which
|
|
351
|
+
* preserves the unbounded map this layer already used. The key space is the set
|
|
352
|
+
* of distinct registry origins a single process contacts — the configured
|
|
353
|
+
* registry plus any origins named by workspace extension sources — so there is
|
|
354
|
+
* no measured workload that justifies a numeric bound. The bound that matters
|
|
355
|
+
* is lifetime, and it is the layer's: the cache is created per
|
|
356
|
+
* `CredentialStoreSessionLive` construction and released with it, so a CLI
|
|
357
|
+
* session is its longest possible life.
|
|
358
|
+
*
|
|
359
|
+
* Expiry is result-dependent. Successful reads — including a successful empty
|
|
360
|
+
* read — live for the session, so repeated authentication paths pay one load
|
|
361
|
+
* per origin. Failed reads expire immediately, so a failure is never memoized
|
|
362
|
+
* and a later request retries against the store.
|
|
363
|
+
*
|
|
364
|
+
* Callers that arrive while a lookup is in flight share it rather than issuing
|
|
365
|
+
* their own; callers that arrive after a failed lookup resolves start a new
|
|
366
|
+
* one. That replaces the old per-origin semaphore, under which every waiter on
|
|
367
|
+
* a failing origin retried serially. Sharing is safe here because each auth
|
|
368
|
+
* path issues a single load and none of them depends on serial retry.
|
|
369
|
+
*
|
|
370
|
+
* `Effect.onInterrupt` is load-path scaffolding, not decoration. `Cache.get`
|
|
371
|
+
* runs its lookup on a detached daemon fiber and leaves the pending entry in
|
|
372
|
+
* the map when the only caller is interrupted, so without this guard the next
|
|
373
|
+
* read of that origin would await an abandoned lookup forever. Invalidating on
|
|
374
|
+
* interrupt restores the pre-cache behavior, where an interrupted read left
|
|
375
|
+
* nothing behind and the next read simply ran again.
|
|
376
|
+
*
|
|
377
|
+
* Known limitation, unchanged by this layer's move to `Cache` and not fixed by
|
|
378
|
+
* it: a `save` or `clear` whose invalidation lands while a read of the same
|
|
379
|
+
* origin is still in flight can be overwritten when that read populates the
|
|
380
|
+
* memo, leaving the pre-write value cached for the rest of the session. The
|
|
381
|
+
* previous value-map-and-semaphore memo lost the same invalidation through its
|
|
382
|
+
* own write-after-invalidate window.
|
|
345
383
|
*/
|
|
346
384
|
export const CredentialStoreSessionLive = Layer.effect(CredentialStore, Effect.gen(function* () {
|
|
347
385
|
const store = yield* CredentialStore;
|
|
348
|
-
const cache = yield*
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
const existing = current.get(registryUrl);
|
|
352
|
-
if (existing !== undefined)
|
|
353
|
-
return [existing, current];
|
|
354
|
-
const created = Semaphore.makeUnsafe(1);
|
|
355
|
-
const updated = new Map(current);
|
|
356
|
-
updated.set(registryUrl, created);
|
|
357
|
-
return [created, updated];
|
|
358
|
-
});
|
|
359
|
-
const getCached = (registryUrl) => Effect.map(Ref.get(cache), (current) => {
|
|
360
|
-
const cached = current.get(registryUrl);
|
|
361
|
-
return cached === undefined
|
|
362
|
-
? Option.none()
|
|
363
|
-
: Option.some(cached);
|
|
364
|
-
});
|
|
365
|
-
const invalidate = (registryUrl) => Ref.update(cache, (current) => {
|
|
366
|
-
const updated = new Map(current);
|
|
367
|
-
updated.delete(registryUrl);
|
|
368
|
-
return updated;
|
|
369
|
-
});
|
|
370
|
-
const load = (registryUrl) => Effect.gen(function* () {
|
|
371
|
-
const cached = yield* getCached(registryUrl);
|
|
372
|
-
if (Option.isSome(cached))
|
|
373
|
-
return cached.value;
|
|
374
|
-
const lock = yield* getLock(registryUrl);
|
|
375
|
-
return yield* lock.withPermits(1)(Effect.gen(function* () {
|
|
376
|
-
const afterWait = yield* getCached(registryUrl);
|
|
377
|
-
if (Option.isSome(afterWait))
|
|
378
|
-
return afterWait.value;
|
|
379
|
-
const loaded = yield* store.load(registryUrl);
|
|
380
|
-
yield* Ref.update(cache, (current) => {
|
|
381
|
-
const updated = new Map(current);
|
|
382
|
-
updated.set(registryUrl, loaded);
|
|
383
|
-
return updated;
|
|
384
|
-
});
|
|
385
|
-
return loaded;
|
|
386
|
-
}));
|
|
386
|
+
const cache = yield* Cache.makeWith((registryUrl) => store.load(registryUrl), {
|
|
387
|
+
capacity: Number.POSITIVE_INFINITY,
|
|
388
|
+
timeToLive: (exit) => Exit.isSuccess(exit) ? Duration.infinity : Duration.zero,
|
|
387
389
|
});
|
|
388
390
|
return {
|
|
389
391
|
tier: store.tier,
|
|
390
392
|
allowsPersistedCredentials: store.allowsPersistedCredentials,
|
|
391
|
-
load,
|
|
392
|
-
save: (registryUrl, handle, credentials) => store
|
|
393
|
-
|
|
393
|
+
load: (registryUrl) => Cache.get(cache, registryUrl).pipe(Effect.onInterrupt(() => Cache.invalidate(cache, registryUrl))),
|
|
394
|
+
save: (registryUrl, handle, credentials) => store
|
|
395
|
+
.save(registryUrl, handle, credentials)
|
|
396
|
+
.pipe(Effect.andThen(Cache.invalidate(cache, registryUrl))),
|
|
397
|
+
clear: (registryUrl) => store.clear(registryUrl).pipe(Effect.andThen(Cache.invalidate(cache, registryUrl))),
|
|
394
398
|
};
|
|
395
399
|
}));
|
|
396
400
|
// -----------------------------------------------------------------------------
|
|
@@ -120,7 +120,7 @@ export declare const initiateDeviceLogin: (registryUrl: string, options?: RunDev
|
|
|
120
120
|
readonly resume: string;
|
|
121
121
|
};
|
|
122
122
|
}, import("./errors.js").AuthError, AuthClient | CredentialStore | AuthLoginPresenter | PendingDeviceLoginStore | DeviceLoginInteraction>;
|
|
123
|
-
export declare const resumeDeviceLogin: (registryUrl: string, options?: ResumeDeviceLoginOptions) => Effect.Effect<undefined, RegistryAuthFailed | import("./errors.js").AuthLoginRequired | import("./errors.js").AuthTokenPolicyRequired | DeviceAuthorizationPending | import("./errors.js").StepUpRequired | import("@agentxm/registry-client").RegistryClientFailure | import("./errors.js").AuthExchangeFailed | import("./errors.js").PublishAuthorizationPending, AuthClient | CredentialStore | AuthLoginPresenter | PendingDeviceLoginStore>;
|
|
124
|
-
export declare const runDeviceLogin: (registryUrl: string, options?: RunDeviceLoginOptions) => Effect.Effect<void, RegistryAuthFailed | import("./errors.js").AuthLoginRequired | import("./errors.js").AuthTokenPolicyRequired | import("./errors.js").DeviceLoginDenied | import("./errors.js").DeviceLoginCodeExpired | 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, AuthClient | CredentialStore | AuthLoginPresenter | PendingDeviceLoginStore | DeviceLoginInteraction>;
|
|
123
|
+
export declare const resumeDeviceLogin: (registryUrl: string, options?: ResumeDeviceLoginOptions) => Effect.Effect<undefined, RegistryAuthFailed | import("./errors.js").AuthLoginRequired | import("./errors.js").AuthTokenPolicyRequired | DeviceAuthorizationPending | import("./errors.js").StepUpRequired | import("@agentxm/registry-client").RegistryClientFailure | import("./errors.js").AuthExchangeFailed | import("./errors.js").PublishAuthorizationPending | import("./errors.js").StepUpVerificationPending | import("./errors.js").AuthInteractionAbandoned, AuthClient | CredentialStore | AuthLoginPresenter | PendingDeviceLoginStore>;
|
|
124
|
+
export declare const runDeviceLogin: (registryUrl: string, options?: RunDeviceLoginOptions) => Effect.Effect<void, RegistryAuthFailed | import("./errors.js").AuthLoginRequired | import("./errors.js").AuthTokenPolicyRequired | import("./errors.js").DeviceLoginDenied | import("./errors.js").DeviceLoginCodeExpired | 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 | PendingDeviceLoginStore | DeviceLoginInteraction>;
|
|
125
125
|
export {};
|
|
126
126
|
//# sourceMappingURL=device-login.d.ts.map
|
package/dist/src/errors.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { HumanHandoffAction } from "@agentxm/registry-protocol/unstable/hum
|
|
|
2
2
|
import { type RegistryClientFailure } from "@agentxm/registry-client";
|
|
3
3
|
import type { SuggestedAction } from "@agentxm/registry-protocol/unstable/suggested-action";
|
|
4
4
|
/** Every category a registry-auth failure can carry. Identical strings to the CLI error codes. */
|
|
5
|
-
export declare const REGISTRY_AUTH_ERROR_CATEGORIES: readonly ["auth", "auth_denied", "auth_expired", "conflict", "internal", "not_found", "validation"];
|
|
5
|
+
export declare const REGISTRY_AUTH_ERROR_CATEGORIES: readonly ["auth", "auth_denied", "auth_expired", "conflict", "internal", "not_found", "timeout", "usage", "validation"];
|
|
6
6
|
export type RegistryAuthErrorCategory = (typeof REGISTRY_AUTH_ERROR_CATEGORIES)[number];
|
|
7
7
|
declare const RegistryAuthFailed_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
8
8
|
readonly _tag: "RegistryAuthFailed";
|
|
@@ -129,8 +129,32 @@ export declare class PublishAuthorizationPending extends PublishAuthorizationPen
|
|
|
129
129
|
readonly timedOut: boolean;
|
|
130
130
|
}> {
|
|
131
131
|
}
|
|
132
|
+
declare const StepUpVerificationPending_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
133
|
+
readonly _tag: "StepUpVerificationPending";
|
|
134
|
+
} & Readonly<A>;
|
|
135
|
+
/**
|
|
136
|
+
* A challenged Registry write is waiting on human verification. No challenged
|
|
137
|
+
* write has completed: the operation is retried exactly once, after the
|
|
138
|
+
* verification the carried handoff describes.
|
|
139
|
+
*/
|
|
140
|
+
export declare class StepUpVerificationPending extends StepUpVerificationPending_base<{
|
|
141
|
+
readonly action: HumanHandoffAction;
|
|
142
|
+
readonly timedOut: boolean;
|
|
143
|
+
}> {
|
|
144
|
+
}
|
|
145
|
+
declare const AuthInteractionAbandoned_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
146
|
+
readonly _tag: "AuthInteractionAbandoned";
|
|
147
|
+
} & Readonly<A>;
|
|
148
|
+
/**
|
|
149
|
+
* A person abandoned an auth interaction the capability asked the application
|
|
150
|
+
* to run (declining is not abandoning: it returns a decision).
|
|
151
|
+
*/
|
|
152
|
+
export declare class AuthInteractionAbandoned extends AuthInteractionAbandoned_base<{
|
|
153
|
+
readonly message: string;
|
|
154
|
+
}> {
|
|
155
|
+
}
|
|
132
156
|
/** Every typed failure the registry-auth feature constructs. */
|
|
133
|
-
export type RegistryAuthFailure = RegistryAuthFailed | AuthLoginRequired | AuthTokenPolicyRequired | DeviceLoginDenied | DeviceLoginCodeExpired | DeviceAuthorizationPending | PublishAuthorizationPending | StepUpRequired | AuthExchangeFailed;
|
|
157
|
+
export type RegistryAuthFailure = RegistryAuthFailed | AuthLoginRequired | AuthTokenPolicyRequired | DeviceLoginDenied | DeviceLoginCodeExpired | DeviceAuthorizationPending | PublishAuthorizationPending | StepUpVerificationPending | AuthInteractionAbandoned | StepUpRequired | AuthExchangeFailed;
|
|
134
158
|
export declare const isRegistryAuthFailure: (error: unknown) => error is RegistryAuthFailure;
|
|
135
159
|
/**
|
|
136
160
|
* Every failure a registry-auth use case can surface: the feature's own typed
|
package/dist/src/errors.js
CHANGED
|
@@ -18,6 +18,8 @@ export const REGISTRY_AUTH_ERROR_CATEGORIES = [
|
|
|
18
18
|
"conflict",
|
|
19
19
|
"internal",
|
|
20
20
|
"not_found",
|
|
21
|
+
"timeout",
|
|
22
|
+
"usage",
|
|
21
23
|
"validation",
|
|
22
24
|
];
|
|
23
25
|
/**
|
|
@@ -74,6 +76,19 @@ export class AuthExchangeFailed extends Data.TaggedError("AuthExchangeFailed") {
|
|
|
74
76
|
}
|
|
75
77
|
export class PublishAuthorizationPending extends Data.TaggedError("PublishAuthorizationPending") {
|
|
76
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* A challenged Registry write is waiting on human verification. No challenged
|
|
81
|
+
* write has completed: the operation is retried exactly once, after the
|
|
82
|
+
* verification the carried handoff describes.
|
|
83
|
+
*/
|
|
84
|
+
export class StepUpVerificationPending extends Data.TaggedError("StepUpVerificationPending") {
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* A person abandoned an auth interaction the capability asked the application
|
|
88
|
+
* to run (declining is not abandoning: it returns a decision).
|
|
89
|
+
*/
|
|
90
|
+
export class AuthInteractionAbandoned extends Data.TaggedError("AuthInteractionAbandoned") {
|
|
91
|
+
}
|
|
77
92
|
export const isRegistryAuthFailure = (error) => error instanceof RegistryAuthFailed ||
|
|
78
93
|
error instanceof AuthLoginRequired ||
|
|
79
94
|
error instanceof AuthTokenPolicyRequired ||
|
|
@@ -81,6 +96,8 @@ export const isRegistryAuthFailure = (error) => error instanceof RegistryAuthFai
|
|
|
81
96
|
error instanceof DeviceLoginCodeExpired ||
|
|
82
97
|
error instanceof DeviceAuthorizationPending ||
|
|
83
98
|
error instanceof PublishAuthorizationPending ||
|
|
99
|
+
error instanceof StepUpVerificationPending ||
|
|
100
|
+
error instanceof AuthInteractionAbandoned ||
|
|
84
101
|
error instanceof StepUpRequired ||
|
|
85
102
|
error instanceof AuthExchangeFailed;
|
|
86
103
|
export const isAuthError = (error) => isRegistryAuthFailure(error) || isRegistryClientFailure(error);
|
|
@@ -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, PublishAuthorizationPending, 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";
|
|
@@ -28,6 +28,15 @@ 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";
|
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, PublishAuthorizationPending, 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,6 +23,18 @@ 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";
|
|
@@ -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
|