@gjermundgaraba/clankerauth-sdk 0.22.0 → 0.23.1
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/README.md +9 -9
- package/dist/key-list.d.ts +2 -0
- package/dist/key-list.js +2 -0
- package/dist/request-policy.d.ts +1 -1
- package/dist/request-policy.js +1 -2
- package/dist/resource.js +0 -19
- package/dist/session.d.ts +4 -0
- package/dist/session.js +6 -2
- package/dist/testing.js +1 -3
- package/dist/transport.js +0 -2
- package/dist/verify.js +2 -19
- package/package.json +7 -5
package/README.md
CHANGED
|
@@ -17,19 +17,19 @@ vp add effect
|
|
|
17
17
|
The `/session` and `/effect-actions` entry points also need effect-actions, an optional peer. Install this range: the SDK's declarations are built against it, and under `skipLibCheck` they silently degrade to `any` with another.
|
|
18
18
|
|
|
19
19
|
```sh
|
|
20
|
-
vp add @gjermundgaraba/effect-actions@^0.
|
|
20
|
+
vp add @gjermundgaraba/effect-actions@^0.17.0
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
## Entry points
|
|
24
24
|
|
|
25
|
-
| Import | Needs effect-actions | Browser safe | What it holds
|
|
26
|
-
| ---------------------------------- | -------------------- | ------------ |
|
|
27
|
-
| `…/clankerauth-sdk` | no | no | `Verifier`, `RequestPolicy`
|
|
28
|
-
| `…/clankerauth-sdk/errors` | no | **yes** | the verifier's errors; `ProviderUnavailable` is the one a descriptor declares
|
|
29
|
-
| `…/clankerauth-sdk/session` | yes | **yes** | `CurrentPrincipal` and its `Caller`, the `Whoami` contract, `Principal`, `signOutUrl` |
|
|
30
|
-
| `…/clankerauth-sdk/effect-actions` | yes | no | `Resource`
|
|
31
|
-
| `…/clankerauth-sdk/key-list` | no | no | the key list format
|
|
32
|
-
| `…/clankerauth-sdk/testing` | no | no | `startFakeIssuer`, for tests (Node only)
|
|
25
|
+
| Import | Needs effect-actions | Browser safe | What it holds |
|
|
26
|
+
| ---------------------------------- | -------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------- |
|
|
27
|
+
| `…/clankerauth-sdk` | no | no | `Verifier`, `RequestPolicy` |
|
|
28
|
+
| `…/clankerauth-sdk/errors` | no | **yes** | the verifier's errors; `ProviderUnavailable` is the one a descriptor declares |
|
|
29
|
+
| `…/clankerauth-sdk/session` | yes | **yes** | `CurrentPrincipal` and its `Caller`, the `Whoami` contract, `Principal`, `signOutUrl`, `signOutPath`, `returnToParameter` |
|
|
30
|
+
| `…/clankerauth-sdk/effect-actions` | yes | no | `Resource` |
|
|
31
|
+
| `…/clankerauth-sdk/key-list` | no | no | the key list format and `keyPrefix`, for issuers and fakes |
|
|
32
|
+
| `…/clankerauth-sdk/testing` | no | no | `startFakeIssuer`, for tests (Node only) |
|
|
33
33
|
|
|
34
34
|
The package is free of side effects, so a bundler drops what a page does not use. Errors are **not** re-exported from the root: a shared contract imports them from `/errors`, and nothing about that import pulls token verification into a browser bundle.
|
|
35
35
|
|
package/dist/key-list.d.ts
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
import { Schema } from "effect";
|
|
14
14
|
/** The list's JWT `typ`, so neither a list nor an access token can pass for the other. */
|
|
15
15
|
export declare const type = "key-list+jwt";
|
|
16
|
+
/** Every API key the issuer creates begins with this, which tells it apart from a JWT. */
|
|
17
|
+
export declare const keyPrefix = "clankerauth_";
|
|
16
18
|
/**
|
|
17
19
|
* How long a list decides, in seconds from its `iat`: for as long as a resource server
|
|
18
20
|
* cannot read a newer one, it keeps accepting the keys this one grants. So it is also the
|
package/dist/key-list.js
CHANGED
|
@@ -14,6 +14,8 @@ import { Schema } from "effect";
|
|
|
14
14
|
import { base64url } from "jose";
|
|
15
15
|
/** The list's JWT `typ`, so neither a list nor an access token can pass for the other. */
|
|
16
16
|
export const type = "key-list+jwt";
|
|
17
|
+
/** Every API key the issuer creates begins with this, which tells it apart from a JWT. */
|
|
18
|
+
export const keyPrefix = "clankerauth_";
|
|
17
19
|
/**
|
|
18
20
|
* How long a list decides, in seconds from its `iat`: for as long as a resource server
|
|
19
21
|
* cannot read a newer one, it keeps accepting the keys this one grants. So it is also the
|
package/dist/request-policy.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export interface Request {
|
|
|
25
25
|
*/
|
|
26
26
|
export declare const make: (options: Options) => {
|
|
27
27
|
allows: (request: Request) => boolean;
|
|
28
|
-
/** Refuse anything `allows` rejects with
|
|
28
|
+
/** Refuse anything `allows` rejects with a 403 (body `Forbidden`) before any handler. */
|
|
29
29
|
middleware: HttpRouter.Middleware<{
|
|
30
30
|
provides: never;
|
|
31
31
|
handles: never;
|
package/dist/request-policy.js
CHANGED
|
@@ -36,14 +36,13 @@ export const make = (options) => {
|
|
|
36
36
|
const allows = (request) => {
|
|
37
37
|
if (pathOf(request.target) === probePath)
|
|
38
38
|
return true;
|
|
39
|
-
// A request with no Host is not a browser request for this app's origin.
|
|
40
39
|
if (request.host !== host)
|
|
41
40
|
return false;
|
|
42
41
|
return request.origin === undefined || origins.has(request.origin);
|
|
43
42
|
};
|
|
44
43
|
return {
|
|
45
44
|
allows,
|
|
46
|
-
/** Refuse anything `allows` rejects with
|
|
45
|
+
/** Refuse anything `allows` rejects with a 403 (body `Forbidden`) before any handler. */
|
|
47
46
|
middleware: HttpRouter.middleware((httpEffect) => Effect.gen(function* () {
|
|
48
47
|
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
49
48
|
const permitted = allows({
|
package/dist/resource.js
CHANGED
|
@@ -45,11 +45,7 @@ const refusalOf = (error) => {
|
|
|
45
45
|
*/
|
|
46
46
|
export const authenticate = (verifier, token) => Effect.mapError(verifier.verifyToken(Redacted.value(token)), refusalOf);
|
|
47
47
|
const build = Effect.fn("Resource.layer")(function* (authentication, { scopes, required }, options) {
|
|
48
|
-
// One resource per application, at the public origin root, trailing slash and all.
|
|
49
|
-
// An MCP client sends `new URL(metadata.resource).href`, and the token's audience is
|
|
50
|
-
// that string, so deriving it here is what keeps the two in agreement.
|
|
51
48
|
const identifier = new URL("/", options.publicUrl).href;
|
|
52
|
-
// effect-actions checks every scope here is an OAuth scope token, when the provider builds.
|
|
53
49
|
const metadata = {
|
|
54
50
|
resource: identifier,
|
|
55
51
|
authorizationServers: [options.issuer],
|
|
@@ -63,9 +59,6 @@ const build = Effect.fn("Resource.layer")(function* (authentication, { scopes, r
|
|
|
63
59
|
requiredScopes: [required],
|
|
64
60
|
apiKeys: options.apiKeys,
|
|
65
61
|
});
|
|
66
|
-
// A request must not wait on an issuer that does not answer: the credential is refused
|
|
67
|
-
// as unavailable, and a read no other request waits on is interrupted. A key list read
|
|
68
|
-
// gives up sooner, so a held list still decides a key.
|
|
69
62
|
const deadline = (effect) => Effect.timeoutOrElse(effect, {
|
|
70
63
|
duration: "5 seconds",
|
|
71
64
|
orElse: () => Effect.fail(new ProviderUnavailable({ operation: "verify.timeout" })),
|
|
@@ -80,7 +73,6 @@ const build = Effect.fn("Resource.layer")(function* (authentication, { scopes, r
|
|
|
80
73
|
*/
|
|
81
74
|
const principalOf = Effect.fn("Resource.principal")(function* (authorization, scope) {
|
|
82
75
|
const token = Authentication.bearerTokenOf(authorization);
|
|
83
|
-
// As a protected route refuses a request without one, before its verifier runs.
|
|
84
76
|
if (Option.isNone(token))
|
|
85
77
|
return yield* new Action.Unauthenticated({ message: "A bearer token is required." });
|
|
86
78
|
const principal = yield* authenticate(verifier, token.value);
|
|
@@ -97,7 +89,6 @@ const build = Effect.fn("Resource.layer")(function* (authentication, { scopes, r
|
|
|
97
89
|
const verified = yield* Effect.result(principalOf(authorization, scope));
|
|
98
90
|
if (Result.isSuccess(verified))
|
|
99
91
|
return { ok: true, principal: verified.success };
|
|
100
|
-
// A refusal, or the `ProviderUnavailable` the descriptor declares, as its routes send it.
|
|
101
92
|
const web = HttpServerResponse.toWeb(Authentication.refusalResponse(verified.failure, {
|
|
102
93
|
authentication,
|
|
103
94
|
protectedResource,
|
|
@@ -121,12 +112,8 @@ const build = Effect.fn("Resource.layer")(function* (authentication, { scopes, r
|
|
|
121
112
|
const principal = yield* principalOf(authorization, scope);
|
|
122
113
|
const now = yield* Clock.currentTimeMillis;
|
|
123
114
|
const remaining = Math.max(0, (principal.expiresAt ?? Infinity) - now);
|
|
124
|
-
// A key is checked again against each refreshed list, until it expires.
|
|
125
115
|
if (principal.actor.kind === "key")
|
|
126
116
|
return yield* Effect.sleep(Math.min(remaining, Duration.toMillis(keyListRefresh)));
|
|
127
|
-
// An access token cannot be revoked and never becomes valid again, so its expiry is
|
|
128
|
-
// all that ends it: no second JWKS read that an issuer outage could fail. It ends as
|
|
129
|
-
// the verifier refuses an expired token.
|
|
130
117
|
yield* Effect.sleep(remaining);
|
|
131
118
|
return yield* refusalOf(new Unauthorized());
|
|
132
119
|
}));
|
|
@@ -156,17 +143,11 @@ export const make = (authentication, declaration) => {
|
|
|
156
143
|
const { scopes } = declaration;
|
|
157
144
|
const service = Context.Service(`@clankerauth/Resource/${authentication.name}`);
|
|
158
145
|
const layer = (options) => Layer.effect(service, build(authentication, declaration, options));
|
|
159
|
-
// The descriptor as the verifier fails: `DeclaresUnavailable` holds that every alternative of
|
|
160
|
-
// `E` declares `ProviderUnavailable`, which a generic `E` cannot show the types by itself.
|
|
161
146
|
const declaring = authentication;
|
|
162
|
-
// The descriptor's verifier: a token to its principal, a refusal, or `ProviderUnavailable`.
|
|
163
147
|
const provider = Authentication.layer(declaring, Effect.gen(function* () {
|
|
164
148
|
const { verifier, resource, protectedResource } = yield* service;
|
|
165
149
|
const host = new URL(resource).host;
|
|
166
150
|
const warned = yield* Ref.make(protectedResource === undefined);
|
|
167
|
-
// RFC 9728 has a client refuse metadata naming another resource than the one it
|
|
168
|
-
// reached, so a listener publishing discovery at another host fails every OAuth login
|
|
169
|
-
// while keys keep working. A proxy may rewrite the host, so this warns and refuses nothing.
|
|
170
151
|
const noteHost = Effect.gen(function* () {
|
|
171
152
|
if (yield* Ref.get(warned))
|
|
172
153
|
return;
|
package/dist/session.d.ts
CHANGED
|
@@ -47,6 +47,10 @@ export declare const Whoami: Action.Action<"whoami", Schema.$Record<Schema.Strin
|
|
|
47
47
|
readonly issuer: Schema.NonEmptyString;
|
|
48
48
|
readonly scopes: Schema.$Array<Schema.String>;
|
|
49
49
|
}>, readonly never[], true, typeof CurrentPrincipal>;
|
|
50
|
+
/** The query parameter naming where the issuer sends the owner after signing in or out. */
|
|
51
|
+
export declare const returnToParameter = "rd";
|
|
52
|
+
/** Where the issuer signs the owner out and clears the forward cookie. */
|
|
53
|
+
export declare const signOutPath = "/forward-auth/logout";
|
|
50
54
|
/**
|
|
51
55
|
* Sign out at the issuer, which ends its session and every app's forward cookie, then
|
|
52
56
|
* return to `returnTo`. It is a plain link: any page under the cookie domain may use it.
|
package/dist/session.js
CHANGED
|
@@ -32,12 +32,16 @@ export const Whoami = Action.make("whoami", {
|
|
|
32
32
|
caller: CurrentPrincipal,
|
|
33
33
|
success: Principal,
|
|
34
34
|
});
|
|
35
|
+
/** The query parameter naming where the issuer sends the owner after signing in or out. */
|
|
36
|
+
export const returnToParameter = "rd";
|
|
37
|
+
/** Where the issuer signs the owner out and clears the forward cookie. */
|
|
38
|
+
export const signOutPath = "/forward-auth/logout";
|
|
35
39
|
/**
|
|
36
40
|
* Sign out at the issuer, which ends its session and every app's forward cookie, then
|
|
37
41
|
* return to `returnTo`. It is a plain link: any page under the cookie domain may use it.
|
|
38
42
|
*/
|
|
39
43
|
export const signOutUrl = (issuer, returnTo) => {
|
|
40
|
-
const url = new URL(
|
|
41
|
-
url.searchParams.set(
|
|
44
|
+
const url = new URL(signOutPath, issuer);
|
|
45
|
+
url.searchParams.set(returnToParameter, returnTo);
|
|
42
46
|
return url.href;
|
|
43
47
|
};
|
package/dist/testing.js
CHANGED
|
@@ -41,8 +41,6 @@ export const startFakeIssuer = async (options) => {
|
|
|
41
41
|
if (Option.isNone(asked))
|
|
42
42
|
return send(400, '{"error":"Invalid request"}');
|
|
43
43
|
const { resource } = asked.value;
|
|
44
|
-
// Like the real issuer, a list holds only what is granted on the resource it names,
|
|
45
|
-
// and only what has not expired.
|
|
46
44
|
const now = Date.now();
|
|
47
45
|
const entries = await Promise.all([...keys].flatMap(([key, { keyId, permissions, expiresAt }]) => {
|
|
48
46
|
const scopes = permissions[resource] ?? [];
|
|
@@ -85,7 +83,7 @@ export const startFakeIssuer = async (options) => {
|
|
|
85
83
|
.sign(pair.privateKey);
|
|
86
84
|
},
|
|
87
85
|
apiKey: (permissions = { [options.resource]: options.scopes }, expiresAt) => {
|
|
88
|
-
const key =
|
|
86
|
+
const key = `${KeyList.keyPrefix}${randomBytes(48).toString("base64url")}`;
|
|
89
87
|
keys.set(key, {
|
|
90
88
|
keyId: `key-${++minted}`,
|
|
91
89
|
permissions,
|
package/dist/transport.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { Context, Effect } from "effect";
|
|
2
2
|
import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/http";
|
|
3
3
|
import { ProviderUnavailable } from "./errors.js";
|
|
4
|
-
// Node transports do not follow redirects by default. Also pin FetchHttpClient's policy;
|
|
5
|
-
// forwarding credentials through a redirect is not an authentication fallback.
|
|
6
4
|
export const execute = (client, request) => Effect.contextWith((context) => HttpClient.withScope(client)
|
|
7
5
|
.execute(request)
|
|
8
6
|
.pipe(Effect.provideService(FetchHttpClient.RequestInit, {
|
package/dist/verify.js
CHANGED
|
@@ -66,15 +66,11 @@ export const make = Effect.fn("Verifier.make")(function* (options) {
|
|
|
66
66
|
keys: jwks.keys.map(({ key_ops, ...key }) => key_ops ? { ...key, key_ops: [...key_ops] } : key),
|
|
67
67
|
});
|
|
68
68
|
}, Effect.scoped);
|
|
69
|
-
// One read serves every key identifier until it expires, or for the cooldown if it
|
|
70
|
-
// failed, so an unknown `kid` in an unauthenticated request never becomes traffic at
|
|
71
|
-
// the issuer. A read every caller gave up on is dropped, and the next caller reads again.
|
|
72
69
|
const keys = yield* Effect.cachedWithTTL(document(), (exit) => Exit.isSuccess(exit) ? documentLifetime : cooldown);
|
|
73
70
|
const signingKey = Effect.fn("Verifier.signingKey")(function* (kid) {
|
|
74
71
|
const resolve = yield* keys;
|
|
75
72
|
return yield* Effect.tryPromise({
|
|
76
73
|
try: () => resolve({ alg: "EdDSA", kid }),
|
|
77
|
-
// Selecting from fetched keys does no I/O: every failure is the credential's.
|
|
78
74
|
catch: unauthorized,
|
|
79
75
|
});
|
|
80
76
|
});
|
|
@@ -89,8 +85,7 @@ export const make = Effect.fn("Verifier.make")(function* (options) {
|
|
|
89
85
|
try: () => decodeProtectedHeader(jws),
|
|
90
86
|
catch: unauthorized,
|
|
91
87
|
});
|
|
92
|
-
//
|
|
93
|
-
// documents before doing provider I/O; JOSE still enforces its algorithm allowlist.
|
|
88
|
+
// why: refused before any JWKS read, so an unauthenticated request causes no issuer traffic.
|
|
94
89
|
if (header.alg !== "EdDSA" || header.kid === undefined)
|
|
95
90
|
return yield* unauthorized();
|
|
96
91
|
const key = yield* signingKey(header.kid);
|
|
@@ -103,7 +98,6 @@ export const make = Effect.fn("Verifier.make")(function* (options) {
|
|
|
103
98
|
typ,
|
|
104
99
|
currentDate: new Date(now),
|
|
105
100
|
}),
|
|
106
|
-
// Verification against a resolved key does no I/O either.
|
|
107
101
|
catch: unauthorized,
|
|
108
102
|
});
|
|
109
103
|
return payload;
|
|
@@ -117,7 +111,6 @@ export const make = Effect.fn("Verifier.make")(function* (options) {
|
|
|
117
111
|
subject: claims.sub,
|
|
118
112
|
scopes: claims.scope.split(" ").filter(Boolean),
|
|
119
113
|
actor: { kind: "client", clientId: claims.client_id },
|
|
120
|
-
// `jwtVerify` already checked this claim against the current time.
|
|
121
114
|
expiresAt: claims.exp * 1000,
|
|
122
115
|
};
|
|
123
116
|
});
|
|
@@ -130,7 +123,6 @@ export const make = Effect.fn("Verifier.make")(function* (options) {
|
|
|
130
123
|
cause: { status: response.status },
|
|
131
124
|
});
|
|
132
125
|
const { list } = yield* HttpClientResponse.schemaBodyJson(KeyListResponse)(response).pipe(Effect.mapError((cause) => new ProviderUnavailable({ operation: "key-list.decode", cause })));
|
|
133
|
-
// A list that does not verify is no list: the held one keeps deciding.
|
|
134
126
|
const rejected = (cause) => new ProviderUnavailable({ operation: "key-list.verify", cause });
|
|
135
127
|
const payload = yield* verifySigned(list, KeyList.type).pipe(Effect.catchTag("Unauthorized", (cause) => Effect.fail(rejected(cause))));
|
|
136
128
|
const claims = yield* Schema.decodeUnknownEffect(KeyListClaims)(payload).pipe(Effect.mapError(rejected));
|
|
@@ -140,8 +132,6 @@ export const make = Effect.fn("Verifier.make")(function* (options) {
|
|
|
140
132
|
entries: new Map(claims.keys.map((entry) => [entry.id, entry.sealed])),
|
|
141
133
|
};
|
|
142
134
|
}, Effect.scoped);
|
|
143
|
-
// While a list is held, a failing issuer is otherwise invisible until that list runs
|
|
144
|
-
// out. Reads are logged when they start failing and when they recover, not per retry.
|
|
145
135
|
const failing = yield* Ref.make(false);
|
|
146
136
|
const readList = yield* Effect.cachedWithTTL(listDocument().pipe(Effect.timeoutOrElse({
|
|
147
137
|
duration: listReadTimeout,
|
|
@@ -149,8 +139,6 @@ export const make = Effect.fn("Verifier.make")(function* (options) {
|
|
|
149
139
|
}), Effect.tapError(({ operation }) => Effect.flatMap(Ref.getAndSet(failing, true), (was) => was
|
|
150
140
|
? Effect.void
|
|
151
141
|
: Effect.logWarning("clankerauth key list reads are failing; a held list decides until it expires", { operation }))), Effect.tap(() => Effect.flatMap(Ref.getAndSet(failing, false), (was) => was ? Effect.logInfo("clankerauth key list reads have recovered") : Effect.void))), cooldown);
|
|
152
|
-
// The list is verified once, when it is read. Holding its entries, not the document,
|
|
153
|
-
// is what lets keys verify through an outage that has already taken JWKS with it.
|
|
154
142
|
const held = yield* Ref.make(undefined);
|
|
155
143
|
/** The list read now, or within the cooldown, or else the held one, while inside its window. */
|
|
156
144
|
const latest = Effect.gen(function* () {
|
|
@@ -158,7 +146,6 @@ export const make = Effect.fn("Verifier.make")(function* (options) {
|
|
|
158
146
|
if (Result.isSuccess(fetched))
|
|
159
147
|
yield* Ref.set(held, fetched.success);
|
|
160
148
|
const list = yield* Ref.get(held);
|
|
161
|
-
// A list is held past its own check, so the window is checked on every use.
|
|
162
149
|
if (list !== undefined && list.expiresAt > (yield* Clock.currentTimeMillis))
|
|
163
150
|
return list;
|
|
164
151
|
return yield* Result.isFailure(fetched)
|
|
@@ -172,17 +159,13 @@ export const make = Effect.fn("Verifier.make")(function* (options) {
|
|
|
172
159
|
});
|
|
173
160
|
const now = yield* Clock.currentTimeMillis;
|
|
174
161
|
const current = yield* Ref.get(held);
|
|
175
|
-
// One read decides the keys it names for a minute, without the issuer.
|
|
176
162
|
const list = current !== undefined &&
|
|
177
163
|
now - current.readAt < Duration.toMillis(keyListRefresh) &&
|
|
178
164
|
current.expiresAt > now
|
|
179
165
|
? current
|
|
180
166
|
: yield* latest;
|
|
181
|
-
// A key it does not name may have been created since, so the list is read again,
|
|
182
|
-
// unless a read answered within the cooldown and there is nothing new to look in.
|
|
183
167
|
const grant = (yield* open(list)) ??
|
|
184
168
|
(yield* Effect.flatMap(latest, (next) => next === list ? Effect.succeed(undefined) : open(next)));
|
|
185
|
-
// No entry is the same case as a token for another audience: not this resource's.
|
|
186
169
|
if (grant === undefined)
|
|
187
170
|
return yield* unauthorized();
|
|
188
171
|
if (grant.expiresAt !== null && grant.expiresAt <= (yield* Clock.currentTimeMillis))
|
|
@@ -195,7 +178,7 @@ export const make = Effect.fn("Verifier.make")(function* (options) {
|
|
|
195
178
|
};
|
|
196
179
|
});
|
|
197
180
|
const verifyToken = Effect.fn("Verifier.verifyToken")(function* (token) {
|
|
198
|
-
const principal = yield* token.startsWith(
|
|
181
|
+
const principal = yield* token.startsWith(KeyList.keyPrefix)
|
|
199
182
|
? options.apiKeys === false
|
|
200
183
|
? Effect.fail(unauthorized())
|
|
201
184
|
: verifyKey(token)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjermundgaraba/clankerauth-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.1",
|
|
4
4
|
"description": "Effect-native clankerauth access-token and offline API-key verification, with optional effect-actions integration",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -46,16 +46,18 @@
|
|
|
46
46
|
"jose": "6.2.12"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@
|
|
49
|
+
"@effect/vitest": "4.0.4",
|
|
50
|
+
"@gjermundgaraba/effect-actions": "0.17.0",
|
|
50
51
|
"@modelcontextprotocol/client": "2.3.1",
|
|
51
52
|
"@types/node": "26.6.4",
|
|
52
53
|
"effect": "4.0.4",
|
|
53
54
|
"typescript": "7.0.2",
|
|
54
|
-
"vite": "npm:@voidzero-dev/vite-plus-core@1.
|
|
55
|
-
"vite-plus": "1.
|
|
55
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@1.2.0",
|
|
56
|
+
"vite-plus": "1.2.0",
|
|
57
|
+
"vitest": "5.0.4"
|
|
56
58
|
},
|
|
57
59
|
"peerDependencies": {
|
|
58
|
-
"@gjermundgaraba/effect-actions": "^0.
|
|
60
|
+
"@gjermundgaraba/effect-actions": "^0.17.0",
|
|
59
61
|
"effect": "^4.0.4"
|
|
60
62
|
},
|
|
61
63
|
"peerDependenciesMeta": {
|