@gjermundgaraba/clankerauth-sdk 0.11.1 → 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/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 `session` contract and builds the issuer's sign-out URL,
4
- * so a page imports it directly and a server answers it with `resource.session`.
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
- import * as ActionGroup from "@gjermundgaraba/effect-actions/ActionGroup";
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 `whoami` is its
17
- * own group and never an MCP tool. Bind it with `Http.layer([app, resource.session])`.
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 Session = ActionGroup.make({ name: "session" }, Action.make("whoami", {
29
+ export const Whoami = Action.make("whoami", {
20
30
  description: "The verified subject, issuer and scopes behind this request's credential.",
21
- access: "read",
31
+ readOnly: true,
32
+ caller: CurrentPrincipal,
22
33
  success: Principal,
23
- mcp: false,
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/issuer/keyList" || request.method !== "POST")
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({ message: "Authentication required", cause });
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/issuer/keyList", options.issuer).href,
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.11.1",
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,17 +46,17 @@
46
46
  "jose": "6.2.12"
47
47
  },
48
48
  "devDependencies": {
49
- "@gjermundgaraba/effect-actions": "0.8.0",
49
+ "@gjermundgaraba/effect-actions": "0.10.0",
50
50
  "@modelcontextprotocol/client": "2.0.0",
51
51
  "@types/node": "26.6.2",
52
- "effect": "4.0.0-rc.118",
52
+ "effect": "4.0.0",
53
53
  "typescript": "7.0.2",
54
54
  "vite": "npm:@voidzero-dev/vite-plus-core@1.0.0",
55
55
  "vite-plus": "1.0.0"
56
56
  },
57
57
  "peerDependencies": {
58
- "@gjermundgaraba/effect-actions": "^0.8.0",
59
- "effect": "^4.0.0-rc.118"
58
+ "@gjermundgaraba/effect-actions": "^0.10.0",
59
+ "effect": "^4.0.0"
60
60
  },
61
61
  "peerDependenciesMeta": {
62
62
  "@gjermundgaraba/effect-actions": {