@gjermundgaraba/clankerauth-sdk 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +136 -73
- package/dist/effect-actions.d.ts +2 -1
- package/dist/effect-actions.js +0 -1
- package/dist/errors.d.ts +41 -22
- package/dist/errors.js +28 -16
- package/dist/resource.d.ts +133 -54
- package/dist/resource.js +170 -126
- package/dist/session.d.ts +34 -8
- package/dist/session.js +20 -10
- package/dist/testing.js +1 -1
- package/dist/verify.d.ts +5 -18
- package/dist/verify.js +4 -12
- package/package.json +3 -3
package/dist/session.js
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The identity a browser page needs, without any verification code. This entry point is
|
|
3
|
-
* browser-safe: it declares the
|
|
4
|
-
*
|
|
3
|
+
* browser-safe: it declares the identity a protected contract states, the `whoami` contract
|
|
4
|
+
* and the issuer's sign-out URL, so contracts, bindings and pages import it directly, and a
|
|
5
|
+
* server answers `Whoami` with its resource's `session`.
|
|
5
6
|
*/
|
|
6
|
-
import { Schema } from "effect";
|
|
7
|
+
import { Context, Schema } from "effect";
|
|
7
8
|
import * as Action from "@gjermundgaraba/effect-actions/Action";
|
|
8
|
-
|
|
9
|
+
/**
|
|
10
|
+
* The caller: what a protected contract states, `caller: CurrentPrincipal`, and what an
|
|
11
|
+
* authentication descriptor proves,
|
|
12
|
+
* `Authentication.make("notes.Login", CurrentPrincipal, { error: ProviderUnavailable })`.
|
|
13
|
+
* Remotely, the resource's provider gives it per request, from the verified credential; on
|
|
14
|
+
* a trusted local surface, the host gives it the process, `Notes.local(subject)`.
|
|
15
|
+
*/
|
|
16
|
+
export class CurrentPrincipal extends Context.Service()("@clankerauth/CurrentPrincipal") {
|
|
17
|
+
}
|
|
9
18
|
/** The verified credential behind a request, as the resource server read it. */
|
|
10
19
|
export const Principal = Schema.Struct({
|
|
11
20
|
subject: Schema.NonEmptyString,
|
|
@@ -13,15 +22,16 @@ export const Principal = Schema.Struct({
|
|
|
13
22
|
scopes: Schema.Array(Schema.String),
|
|
14
23
|
});
|
|
15
24
|
/**
|
|
16
|
-
* The credential is a transport concern: a process surface has none, so `
|
|
17
|
-
*
|
|
25
|
+
* The credential is a transport concern: a process surface has none, so bind `Whoami` to
|
|
26
|
+
* HTTP alone, `ActionHttp.layer(Http, [app, Notes.session])`, and leave `Notes.session` out
|
|
27
|
+
* of MCP.
|
|
18
28
|
*/
|
|
19
|
-
export const
|
|
29
|
+
export const Whoami = Action.make("whoami", {
|
|
20
30
|
description: "The verified subject, issuer and scopes behind this request's credential.",
|
|
21
|
-
|
|
31
|
+
readOnly: true,
|
|
32
|
+
caller: CurrentPrincipal,
|
|
22
33
|
success: Principal,
|
|
23
|
-
|
|
24
|
-
}));
|
|
34
|
+
});
|
|
25
35
|
/**
|
|
26
36
|
* Sign out at the issuer, which ends its session and every app's forward cookie, then
|
|
27
37
|
* return to `returnTo`. It is a plain link: any page under the cookie domain may use it.
|
package/dist/testing.js
CHANGED
|
@@ -29,7 +29,7 @@ export const startFakeIssuer = async (options) => {
|
|
|
29
29
|
};
|
|
30
30
|
if (request.url === "/api/auth/jwks")
|
|
31
31
|
return send(failure ?? 200, JSON.stringify({ keys: [jwk] }));
|
|
32
|
-
if (request.url !== "/api/
|
|
32
|
+
if (request.url !== "/api/keyList" || request.method !== "POST")
|
|
33
33
|
return send(404, "{}");
|
|
34
34
|
lists++;
|
|
35
35
|
if (failure !== undefined)
|
package/dist/verify.d.ts
CHANGED
|
@@ -2,6 +2,11 @@ import { Effect } from "effect";
|
|
|
2
2
|
import { HttpClient } from "effect/http";
|
|
3
3
|
import { ConfigurationError, InsufficientScope, ProviderUnavailable, Unauthorized } from "./errors.ts";
|
|
4
4
|
import type { AuthenticationError } from "./errors.ts";
|
|
5
|
+
/**
|
|
6
|
+
* What a verified credential proves: an OAuth client's access token or an API key. A request's
|
|
7
|
+
* caller, `CurrentPrincipal`, is this or, on a trusted local surface, the process itself, which
|
|
8
|
+
* no verifier produces: `Caller` of the `/session` entry.
|
|
9
|
+
*/
|
|
5
10
|
export interface Principal {
|
|
6
11
|
readonly subject: string;
|
|
7
12
|
readonly scopes: readonly string[];
|
|
@@ -28,7 +33,6 @@ export interface Options {
|
|
|
28
33
|
readonly apiKeys?: boolean;
|
|
29
34
|
}
|
|
30
35
|
export interface Verifier {
|
|
31
|
-
readonly verify: (authorization: string | null | undefined) => Effect.Effect<Principal, AuthenticationError>;
|
|
32
36
|
readonly verifyToken: (token: string) => Effect.Effect<Principal, AuthenticationError>;
|
|
33
37
|
}
|
|
34
38
|
/**
|
|
@@ -55,21 +59,4 @@ export declare const make: (options: Options) => Effect.Effect<{
|
|
|
55
59
|
};
|
|
56
60
|
expiresAt: number | undefined;
|
|
57
61
|
}, InsufficientScope | ProviderUnavailable | Unauthorized, never>;
|
|
58
|
-
verify: (authorization: string | null | undefined) => Effect.Effect<{
|
|
59
|
-
subject: string;
|
|
60
|
-
scopes: string[];
|
|
61
|
-
actor: {
|
|
62
|
-
kind: "client";
|
|
63
|
-
clientId: string;
|
|
64
|
-
};
|
|
65
|
-
expiresAt: number;
|
|
66
|
-
} | {
|
|
67
|
-
subject: string;
|
|
68
|
-
scopes: readonly string[];
|
|
69
|
-
actor: {
|
|
70
|
-
kind: "key";
|
|
71
|
-
keyId: string;
|
|
72
|
-
};
|
|
73
|
-
expiresAt: number | undefined;
|
|
74
|
-
}, InsufficientScope | ProviderUnavailable | Unauthorized, never>;
|
|
75
62
|
}, ConfigurationError, HttpClient.HttpClient>;
|
package/dist/verify.js
CHANGED
|
@@ -26,7 +26,7 @@ const Claims = Schema.Struct({
|
|
|
26
26
|
/** The list document as the issuer's `keyList` action answers it. */
|
|
27
27
|
const KeyListResponse = Schema.Struct({ list: Schema.String });
|
|
28
28
|
const KeyListClaims = KeyList.Claims.pipe(Schema.fieldsAssign({ exp: Schema.Finite }));
|
|
29
|
-
const unauthorized = (cause) => new Unauthorized({
|
|
29
|
+
const unauthorized = (cause) => new Unauthorized({ cause });
|
|
30
30
|
/** How long a JWKS document answers verification before it is read again. */
|
|
31
31
|
const documentLifetime = "1 minute";
|
|
32
32
|
/**
|
|
@@ -50,7 +50,7 @@ export const make = Effect.fn("Verifier.make")(function* (options) {
|
|
|
50
50
|
const endpoints = yield* Effect.try({
|
|
51
51
|
try: () => ({
|
|
52
52
|
jwks: new URL(`${options.issuer}/jwks`).href,
|
|
53
|
-
keyList: new URL("/api/
|
|
53
|
+
keyList: new URL("/api/keyList", options.issuer).href,
|
|
54
54
|
}),
|
|
55
55
|
catch: () => new ConfigurationError({ message: "Invalid issuer URL" }),
|
|
56
56
|
});
|
|
@@ -207,16 +207,8 @@ export const make = Effect.fn("Verifier.make")(function* (options) {
|
|
|
207
207
|
: verifyJwt(token);
|
|
208
208
|
const missing = options.requiredScopes?.find((scope) => !principal.scopes.includes(scope));
|
|
209
209
|
if (missing !== undefined)
|
|
210
|
-
return yield* new InsufficientScope({ scope: missing });
|
|
210
|
+
return yield* new InsufficientScope({ scope: missing, actor: principal.actor });
|
|
211
211
|
return principal;
|
|
212
212
|
});
|
|
213
|
-
return {
|
|
214
|
-
verifyToken,
|
|
215
|
-
verify: Effect.fn("Verifier.verify")(function* (authorization) {
|
|
216
|
-
const token = /^Bearer ([^\s,]+)$/iu.exec(authorization ?? "")?.[1];
|
|
217
|
-
if (!token)
|
|
218
|
-
return yield* unauthorized();
|
|
219
|
-
return yield* verifyToken(token);
|
|
220
|
-
}),
|
|
221
|
-
};
|
|
213
|
+
return { verifyToken };
|
|
222
214
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjermundgaraba/clankerauth-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
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,7 +46,7 @@
|
|
|
46
46
|
"jose": "6.2.12"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@gjermundgaraba/effect-actions": "0.
|
|
49
|
+
"@gjermundgaraba/effect-actions": "0.10.0",
|
|
50
50
|
"@modelcontextprotocol/client": "2.0.0",
|
|
51
51
|
"@types/node": "26.6.2",
|
|
52
52
|
"effect": "4.0.0",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"vite-plus": "1.0.0"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
|
-
"@gjermundgaraba/effect-actions": "^0.
|
|
58
|
+
"@gjermundgaraba/effect-actions": "^0.10.0",
|
|
59
59
|
"effect": "^4.0.0"
|
|
60
60
|
},
|
|
61
61
|
"peerDependenciesMeta": {
|