@north-light/identity 0.3.439 → 0.3.441

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/index.d.ts CHANGED
@@ -22,7 +22,7 @@ export interface IdTokenClaims extends JWTPayload {
22
22
  family_name?: string;
23
23
  picture?: string;
24
24
  }
25
- export type Scope = 'crtr:llm' | 'crtr:act' | 'crtr:schedule' | `crtr:memory:${'read' | 'rw'}:user${'' | `:${string}`}` | 'crtr:memory:manage:user' | `crtr:memory:${'read' | 'rw'}:app:${string}` | `crtr:files:${'read' | 'rw'}:${'user' | `app:${string}`}:${string}` | 'crtr:files:share' | `crtr:${'conversations' | 'objects'}:read:app:${string}` | `crtr:peer:${string}` | `${string}:${string}`;
25
+ export type Scope = 'crtr:llm' | 'crtr:act' | 'crtr:schedule' | `crtr:memory:${'read' | 'rw'}:user${'' | `:${string}`}` | 'crtr:memory:manage:user' | `crtr:memory:${'read' | 'rw'}:app:${string}` | `crtr:files:${'read' | 'rw'}:${'user' | `app:${string}`}:${string}` | 'crtr:files:share' | `crtr:${'conversations' | 'objects'}:read:app:${string}` | `crtr:objects:${'message' | 'spawn'}:app:${string}` | `crtr:peer:${string}` | `${string}:${string}`;
26
26
  /** App-memory form keys omit the app id. */
27
27
  export declare const ENFORCED: ReadonlySet<string>;
28
28
  export declare function parseScope(value: string, grantee?: string): Scope;
@@ -73,6 +73,34 @@ export declare function verifyAccessToken(options: {
73
73
  issuer: string;
74
74
  audience: string;
75
75
  }): Promise<AccessTokenClaims>;
76
+ /** The claims of a machine token: one machine of an account calling another (or the account relay). It names the
77
+ * person and the calling machine and carries no grant, so it authorizes nothing on its own. */
78
+ export interface MachineTokenClaims extends JWTPayload {
79
+ iss: string;
80
+ /** The single audience: a peer machine URL or the relay origin. */
81
+ aud: string;
82
+ /** The account's person, `user:<id>`. */
83
+ sub: string;
84
+ /** Always equal to `sub`. */
85
+ grantee: string;
86
+ client_id: string;
87
+ /** The calling machine, `runtime:<id>`. */
88
+ machine_id: string;
89
+ exp: number;
90
+ }
91
+ /**
92
+ * Verifies a machine token: ES256 with typ `at+jwt`, signature against `jwks`, `iss` = `issuer`, `aud` a string equal
93
+ * to `audience` (the sole audience), and `exp`. `sub` must be `user:<id>`, `grantee` must equal `sub`, `machine_id`
94
+ * must be `runtime:<id>` and `client_id` a non-empty string. A token carrying a `grant`, `rev` or `scope` claim is
95
+ * refused, so an app grant token never verifies here; `verifyAccessToken` requires an `app:` grantee, so a machine
96
+ * token never verifies there.
97
+ */
98
+ export declare function verifyMachineToken(options: {
99
+ token: string;
100
+ jwks: JSONWebKeySet | JwksCache;
101
+ issuer: string;
102
+ audience: string;
103
+ }): Promise<MachineTokenClaims>;
76
104
  /** One level of the outbound token's RFC 8693 `act` nesting: the actor, and the actor it acted for. */
77
105
  export interface OutboundActor {
78
106
  sub: string;
package/dist/index.js CHANGED
@@ -31,9 +31,10 @@ function selector(value) {
31
31
  match = /^crtr:files:(read|rw):(user|app:[A-Za-z0-9_-]{1,64}):(.+)$/.exec(value);
32
32
  if (match && validPath(match[3], true))
33
33
  return { kind: 'files', access: match[1], holder: match[2], path: match[3] };
34
- match = /^crtr:(conversations|objects):read:app:([A-Za-z0-9_-]{1,64})$/.exec(value);
34
+ // read, message and spawn are three unrelated scopes: `covers` is exact equality for them.
35
+ match = /^crtr:(?:(conversations):read|(objects):(?:read|message|spawn)):app:([A-Za-z0-9_-]{1,64})$/.exec(value);
35
36
  if (match)
36
- return { kind: match[1], value };
37
+ return { kind: (match[1] ?? match[2]), value };
37
38
  match = /^crtr:peer:([A-Za-z0-9_-]{1,64})$/.exec(value);
38
39
  if (match)
39
40
  return { kind: 'peer', value };
@@ -226,6 +227,28 @@ export async function verifyAccessToken(options) {
226
227
  throw new Error('Invalid access token claims');
227
228
  return payload;
228
229
  }
230
+ /**
231
+ * Verifies a machine token: ES256 with typ `at+jwt`, signature against `jwks`, `iss` = `issuer`, `aud` a string equal
232
+ * to `audience` (the sole audience), and `exp`. `sub` must be `user:<id>`, `grantee` must equal `sub`, `machine_id`
233
+ * must be `runtime:<id>` and `client_id` a non-empty string. A token carrying a `grant`, `rev` or `scope` claim is
234
+ * refused, so an app grant token never verifies here; `verifyAccessToken` requires an `app:` grantee, so a machine
235
+ * token never verifies there.
236
+ */
237
+ export async function verifyMachineToken(options) {
238
+ const header = decodeProtectedHeader(options.token);
239
+ if (header.typ !== 'at+jwt' || header.alg !== 'ES256')
240
+ throw new Error('Invalid machine token header');
241
+ const keys = await keySet(options.jwks, header.kid);
242
+ const { payload } = await jwtVerify(options.token, createLocalJWKSet(keys), {
243
+ issuer: options.issuer, audience: options.audience, algorithms: ['ES256'], typ: 'at+jwt', requiredClaims: ['exp'],
244
+ });
245
+ if (payload.aud !== options.audience || !validatePrincipal(payload.sub, 'user') || payload.grantee !== payload.sub ||
246
+ !validatePrincipal(payload.machine_id, 'runtime') || !nonEmptyString(payload.client_id) || typeof payload.exp !== 'number' ||
247
+ payload.grant !== undefined || payload.rev !== undefined || payload.scope !== undefined) {
248
+ throw new Error('Invalid machine token claims');
249
+ }
250
+ return payload;
251
+ }
229
252
  function nonEmptyString(value) {
230
253
  return typeof value === 'string' && value !== '';
231
254
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/identity",
3
- "version": "0.3.439",
3
+ "version": "0.3.441",
4
4
  "description": "Northlight access-token verification and scope rules",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",