@c9up/visa 0.1.3 → 0.1.4

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.
Files changed (47) hide show
  1. package/README.md +82 -11
  2. package/dist/VisaManager.d.ts +47 -0
  3. package/dist/VisaManager.d.ts.map +1 -1
  4. package/dist/VisaManager.js +169 -7
  5. package/dist/VisaManager.js.map +1 -1
  6. package/dist/VisaProvider.d.ts +4 -0
  7. package/dist/VisaProvider.d.ts.map +1 -1
  8. package/dist/VisaProvider.js +87 -5
  9. package/dist/VisaProvider.js.map +1 -1
  10. package/dist/configure.d.ts.map +1 -1
  11. package/dist/configure.js +16 -1
  12. package/dist/configure.js.map +1 -1
  13. package/dist/guard.d.ts +2 -1
  14. package/dist/guard.d.ts.map +1 -1
  15. package/dist/guard.js +12 -13
  16. package/dist/guard.js.map +1 -1
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js.map +1 -1
  20. package/dist/register.d.ts.map +1 -1
  21. package/dist/register.js +9 -4
  22. package/dist/register.js.map +1 -1
  23. package/dist/store.d.ts +10 -0
  24. package/dist/store.d.ts.map +1 -1
  25. package/dist/stores/atlas.d.ts +1 -1
  26. package/dist/stores/atlas.d.ts.map +1 -1
  27. package/dist/stores/atlas.js +6 -1
  28. package/dist/stores/atlas.js.map +1 -1
  29. package/dist/stores/memory.d.ts +1 -2
  30. package/dist/stores/memory.d.ts.map +1 -1
  31. package/dist/stores/memory.js +1 -3
  32. package/dist/stores/memory.js.map +1 -1
  33. package/dist/token.js +17 -5
  34. package/dist/token.js.map +1 -1
  35. package/package.json +1 -1
  36. package/src/VisaManager.ts +212 -11
  37. package/src/VisaProvider.ts +108 -6
  38. package/src/configure.ts +17 -1
  39. package/src/guard.ts +12 -13
  40. package/src/index.ts +1 -0
  41. package/src/register.ts +11 -4
  42. package/src/store.ts +11 -0
  43. package/src/stores/atlas.ts +6 -1
  44. package/src/stores/memory.ts +1 -3
  45. package/src/token.ts +30 -11
  46. package/stubs/database/migrations/9000_create_visa_tables.stub +2 -0
  47. package/stubs/database/migrations/9001_add_visa_client_secret_expiry.stub +29 -0
package/dist/token.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"token.js","sourceRoot":"","sources":["../src/token.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"token.js","sourceRoot":"","sources":["../src/token.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c9up/visa",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Visa — OAuth 2.1 authorization server for the Ream framework: authorization code with PKCE, refresh rotation, revocation and introspection, on a store contract",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -21,7 +21,7 @@ import {
21
21
  readCredentials,
22
22
  redirectUriMatches,
23
23
  } from "./clients.js";
24
- import { hashSecret, randomToken } from "./crypto.js";
24
+ import { hashMatches, hashSecret, randomToken, sha256 } from "./crypto.js";
25
25
  import { OAuthError, VisaError } from "./errors.js";
26
26
  import { introspect, revoke, verifyAccessToken } from "./introspect.js";
27
27
  import {
@@ -49,8 +49,33 @@ export interface VisaConfig extends AuthorizeOptions, TokenOptions {
49
49
  store: VisaStore;
50
50
  /** Whether clients may register themselves, and on what terms (RFC 7591). */
51
51
  registration?: RegistrationOptions;
52
+ /**
53
+ * How long a consent page may stay open before its answer is refused, in
54
+ * seconds. Ten minutes by default — see {@link VisaManager.consentTicket}.
55
+ */
56
+ consentTicketTtlSeconds?: number;
57
+ }
58
+
59
+ /**
60
+ * What the application keeps server-side between the consent page and the
61
+ * POST that answers it — in the session, never in the form.
62
+ *
63
+ * Plain values only, so any session store can hold it: the request as it was
64
+ * validated, not the client record (which carries the secret's hash).
65
+ */
66
+ export interface ConsentTicket {
67
+ /** Random, 256 bits. The consent form posts it back; nothing else does. */
68
+ id: string;
69
+ /** Who was asked. Another user answering it is refused. */
70
+ userId: string;
71
+ /** Milliseconds since the epoch. */
72
+ expiresAt: number;
73
+ /** The authorization request, rebuilt from what was validated. */
74
+ request: AuthorizationRequest;
52
75
  }
53
76
 
77
+ const DEFAULT_CONSENT_TICKET_TTL_SECONDS = 600;
78
+
54
79
  /** What `authorize()` decided. */
55
80
  export type AuthorizationOutcome =
56
81
  /** Send the user here. */
@@ -71,6 +96,7 @@ export class VisaManager {
71
96
  // with a broken issuer answers every metadata fetch with something no
72
97
  // client can use, and says so nowhere.
73
98
  this.#config = { ...config, issuer: normalizeIssuer(config.issuer) };
99
+ assertRegistration(config);
74
100
  }
75
101
 
76
102
  get issuer(): string {
@@ -122,16 +148,10 @@ export class VisaManager {
122
148
  : { secretExpiresAt: input.secretExpiresAt }),
123
149
  };
124
150
  const store = this.#config.store;
125
- if (!("addClient" in store) || typeof store.addClient !== "function") {
126
- throw new VisaError(
127
- "E_VISA_STORE_READ_ONLY",
128
- "This store cannot register clients; create them where they live.",
129
- {
130
- hint: "MemoryStore.addClient exists; a database store registers rows.",
131
- },
132
- );
133
- }
134
- store.addClient(client);
151
+ if (store.saveClient === undefined) throw readOnlyStore();
152
+ // Awaited: the secret below is handed out once, and handing it out for a
153
+ // client whose write failed is a credential nothing will ever accept.
154
+ await store.saveClient(client);
135
155
  return secret === undefined ? { client } : { client, secret };
136
156
  }
137
157
 
@@ -245,6 +265,92 @@ export class VisaManager {
245
265
  return successRedirect(validated, code);
246
266
  }
247
267
 
268
+ /**
269
+ * Remember what the consent page asks, so its answer cannot change it.
270
+ *
271
+ * The application puts the ticket in the user's session, renders its `id`
272
+ * as a hidden field, and on the POST hands both to
273
+ * {@link redeemConsentTicket}. What gets granted is then the request the
274
+ * user was shown — not whatever parameters the POST carries — and the
275
+ * session makes it single use (`session.pull`) and bound to this browser.
276
+ */
277
+ consentTicket(
278
+ validated: ValidatedRequest,
279
+ userId: string,
280
+ now: Date = new Date(),
281
+ ): ConsentTicket {
282
+ const ttl =
283
+ this.#config.consentTicketTtlSeconds ??
284
+ DEFAULT_CONSENT_TICKET_TTL_SECONDS;
285
+ return {
286
+ id: randomToken(),
287
+ userId,
288
+ expiresAt: now.getTime() + ttl * 1000,
289
+ request: {
290
+ response_type: "code",
291
+ client_id: validated.client.id,
292
+ redirect_uri: validated.redirectUri,
293
+ // Joined from what was resolved, so an omitted `scope` that
294
+ // defaulted to the client's list is the list the user saw, even
295
+ // if the client registers more by the time the user answers.
296
+ scope: validated.scopes.join(" "),
297
+ code_challenge: validated.codeChallenge,
298
+ code_challenge_method: validated.codeChallengeMethod,
299
+ ...(validated.state === undefined ? {} : { state: validated.state }),
300
+ ...(validated.nonce === undefined ? {} : { nonce: validated.nonce }),
301
+ ...(validated.resources.length === 0
302
+ ? {}
303
+ : { resource: [...validated.resources] }),
304
+ },
305
+ };
306
+ }
307
+
308
+ /**
309
+ * The consent page's answer, checked against the ticket it was rendered with.
310
+ *
311
+ * `ticket` is what the session held — pulled, not read, so a second POST
312
+ * of the same form finds nothing. `presentedId` is the form's hidden field:
313
+ * it must name THIS ticket, which is what stops a form left open in one tab
314
+ * from approving the request another tab put in the session since. The
315
+ * request is validated again — the client may have lost a scope or a
316
+ * redirect URI meanwhile — and no code is minted: that is `grant`'s job,
317
+ * once the user said yes.
318
+ *
319
+ * `null` for anything wrong: no ticket, another ticket, another user,
320
+ * expired, or a request that no longer validates. The user starts again.
321
+ */
322
+ async redeemConsentTicket(
323
+ ticket: unknown,
324
+ presentedId: unknown,
325
+ userId: string,
326
+ now: Date = new Date(),
327
+ ): Promise<ValidatedRequest | null> {
328
+ const read = readConsentTicket(ticket);
329
+ if (read === null || typeof presentedId !== "string") return null;
330
+ if (!hashMatches(sha256(presentedId), sha256(read.id))) return null;
331
+ if (read.userId !== userId) return null;
332
+ if (read.expiresAt <= now.getTime()) return null;
333
+ try {
334
+ const validated = await validateAuthorizationRequest(
335
+ read.request,
336
+ this.#config.store,
337
+ this.#config,
338
+ );
339
+ // An empty `scope` resolves to the client's registered list, which
340
+ // may have grown since the page was shown: the user agreed to the
341
+ // list on the page, and to nothing it has become.
342
+ if (validated.scopes.join(" ") !== (read.request.scope ?? "")) {
343
+ return null;
344
+ }
345
+ return validated;
346
+ } catch (error) {
347
+ if (error instanceof UnredirectableError || error instanceof OAuthError) {
348
+ return null;
349
+ }
350
+ throw error;
351
+ }
352
+ }
353
+
248
354
  /** The user said no. */
249
355
  deny(validated: ValidatedRequest): string {
250
356
  return errorRedirect(
@@ -426,6 +532,101 @@ function normalizeIssuer(issuer: string): string {
426
532
  return issuer.replace(/\/+$/, "");
427
533
  }
428
534
 
535
+ /**
536
+ * A ticket as a session store hands it back: untyped, so read field by field.
537
+ *
538
+ * A session serialises to JSON and back, and a store an application wrote
539
+ * itself may hand back anything — a value that is not exactly a ticket is no
540
+ * ticket.
541
+ */
542
+ function readConsentTicket(value: unknown): ConsentTicket | null {
543
+ if (typeof value !== "object" || value === null) return null;
544
+ if (!("id" in value) || typeof value.id !== "string" || value.id === "")
545
+ return null;
546
+ if (!("userId" in value) || typeof value.userId !== "string") return null;
547
+ if (!("expiresAt" in value) || typeof value.expiresAt !== "number")
548
+ return null;
549
+ if (!("request" in value)) return null;
550
+ const request = readTicketRequest(value.request);
551
+ if (request === null) return null;
552
+ return {
553
+ id: value.id,
554
+ userId: value.userId,
555
+ expiresAt: value.expiresAt,
556
+ request,
557
+ };
558
+ }
559
+
560
+ /** `prompt` is left out on purpose: the ticket IS the consent being asked. */
561
+ const TICKET_STRING_FIELDS: readonly Exclude<
562
+ keyof AuthorizationRequest,
563
+ "resource" | "prompt"
564
+ >[] = [
565
+ "response_type",
566
+ "client_id",
567
+ "redirect_uri",
568
+ "scope",
569
+ "state",
570
+ "code_challenge",
571
+ "code_challenge_method",
572
+ "nonce",
573
+ ];
574
+
575
+ function readTicketRequest(value: unknown): AuthorizationRequest | null {
576
+ if (typeof value !== "object" || value === null) return null;
577
+ const request: AuthorizationRequest = {};
578
+ for (const key of TICKET_STRING_FIELDS) {
579
+ const field: unknown = Reflect.get(value, key);
580
+ if (field === undefined) continue;
581
+ if (typeof field !== "string") return null;
582
+ request[key] = field;
583
+ }
584
+ const resource: unknown = Reflect.get(value, "resource");
585
+ if (resource !== undefined) {
586
+ if (
587
+ !Array.isArray(resource) ||
588
+ !resource.every((entry) => typeof entry === "string")
589
+ ) {
590
+ return null;
591
+ }
592
+ request.resource = resource;
593
+ }
594
+ return request;
595
+ }
596
+
597
+ /**
598
+ * Registration that can actually register, checked at boot.
599
+ *
600
+ * Both failures would otherwise surface on the first `POST /register`: a store
601
+ * with nowhere to write a client answers every MCP client a 500, and an empty
602
+ * initial access token is matched by `Authorization: Bearer ` — an endpoint
603
+ * the deployment meant to close, open to anyone who sends the header.
604
+ */
605
+ function assertRegistration(config: VisaConfig): void {
606
+ const registration = config.registration;
607
+ if (registration?.enabled !== true) return;
608
+ if (config.store.saveClient === undefined) throw readOnlyStore();
609
+ if (registration.initialAccessToken === "") {
610
+ throw new VisaError(
611
+ "E_VISA_INVALID_INITIAL_ACCESS_TOKEN",
612
+ "registration.initialAccessToken is empty, which would accept an empty bearer token.",
613
+ {
614
+ hint: "Set a long random value, or leave it out for open registration.",
615
+ },
616
+ );
617
+ }
618
+ }
619
+
620
+ function readOnlyStore(): VisaError {
621
+ return new VisaError(
622
+ "E_VISA_STORE_READ_ONLY",
623
+ "This store cannot register clients; create them where they live.",
624
+ {
625
+ hint: "Implement saveClient(client) on the store — MemoryStore and AtlasStore both do.",
626
+ },
627
+ );
628
+ }
629
+
429
630
  /**
430
631
  * Every redirect URI a client registers, checked once.
431
632
  *
@@ -14,6 +14,10 @@
14
14
  * decision; the page around it is yours. The README shows the ten lines.
15
15
  *
16
16
  * Duck-typed throughout: visa must not import its host's HTTP types.
17
+ *
18
+ * NAMED DEVIATION from the usual `request.all()`: these endpoints read the
19
+ * body only, and refuse a request that puts an OAuth parameter in the query
20
+ * string — see `readBody`.
17
21
  */
18
22
 
19
23
  import "./augmentations.js";
@@ -44,7 +48,10 @@ export interface VisaAppContext {
44
48
  interface VisaHttpContext {
45
49
  request: {
46
50
  header(name: string): string | undefined;
47
- all(): Record<string, unknown>;
51
+ /** The parsed body — the only place an OAuth parameter is read from. */
52
+ body(): unknown;
53
+ /** Read only to refuse a request that put parameters in the URL. */
54
+ qs(): Record<string, unknown>;
48
55
  };
49
56
  response: {
50
57
  status(code: number): unknown;
@@ -96,7 +103,7 @@ export default class VisaProvider {
96
103
 
97
104
  router.post(`${prefix}/token`, async (ctx) => {
98
105
  await this.#answer(ctx, async () => {
99
- const body = ctx.request.all();
106
+ const body = readBody(ctx);
100
107
  const credentials = manager.readCredentials({
101
108
  body,
102
109
  ...readAuthorization(ctx),
@@ -113,7 +120,7 @@ export default class VisaProvider {
113
120
 
114
121
  router.post(`${prefix}/introspect`, async (ctx) => {
115
122
  await this.#answer(ctx, async () => {
116
- const body = ctx.request.all();
123
+ const body = readBody(ctx);
117
124
  const credentials = manager.readCredentials({
118
125
  body,
119
126
  ...readAuthorization(ctx),
@@ -125,7 +132,7 @@ export default class VisaProvider {
125
132
 
126
133
  router.post(`${prefix}/revoke`, async (ctx) => {
127
134
  await this.#answer(ctx, async () => {
128
- const body = ctx.request.all();
135
+ const body = readBody(ctx);
129
136
  const credentials = manager.readCredentials({
130
137
  body,
131
138
  ...readAuthorization(ctx),
@@ -144,7 +151,10 @@ export default class VisaProvider {
144
151
  router.post(`${prefix}/register`, async (ctx) => {
145
152
  await this.#answer(ctx, async () => {
146
153
  const bearer = readBearer(ctx);
147
- const created = await manager.register(ctx.request.all(), bearer);
154
+ const created = await manager.register(
155
+ readBody(ctx, REGISTRATION_PARAMETERS),
156
+ bearer,
157
+ );
148
158
  // §3.2.1: 201, and the body carries a secret — no cache may
149
159
  // keep it.
150
160
  ctx.response.status(201);
@@ -245,12 +255,104 @@ export default class VisaProvider {
245
255
  }
246
256
  }
247
257
 
258
+ /**
259
+ * What may never travel in the URL of these endpoints.
260
+ *
261
+ * Every credential and every parameter the spec defines for them: the client's
262
+ * own (§2.3.1, RFC 7523), each grant's (§4), introspection's and revocation's
263
+ * (RFC 7662 §2.1, RFC 7009 §2.1), resource indicators (RFC 8707), a bearer
264
+ * token in its RFC 6750 §2.3 form, and the removed password grant's — a
265
+ * client still sending those sends a password.
266
+ */
267
+ const OAUTH_PARAMETERS: ReadonlySet<string> = new Set([
268
+ "client_id",
269
+ "client_secret",
270
+ "client_assertion",
271
+ "client_assertion_type",
272
+ "grant_type",
273
+ "code",
274
+ "code_verifier",
275
+ "redirect_uri",
276
+ "refresh_token",
277
+ "scope",
278
+ "resource",
279
+ "token",
280
+ "token_type_hint",
281
+ "access_token",
282
+ "username",
283
+ "password",
284
+ ]);
285
+
286
+ /** The same, plus the client metadata a registration carries (RFC 7591 §2). */
287
+ const REGISTRATION_PARAMETERS: ReadonlySet<string> = new Set([
288
+ ...OAUTH_PARAMETERS,
289
+ "redirect_uris",
290
+ "token_endpoint_auth_method",
291
+ "grant_types",
292
+ "response_types",
293
+ "client_name",
294
+ "client_uri",
295
+ "logo_uri",
296
+ "contacts",
297
+ "tos_uri",
298
+ "policy_uri",
299
+ "jwks_uri",
300
+ "jwks",
301
+ "software_id",
302
+ "software_version",
303
+ "software_statement",
304
+ ]);
305
+
306
+ /**
307
+ * The parameters of a machine endpoint, read from the body and nowhere else.
308
+ *
309
+ * Every one of these requests carries a credential — a client secret, a code,
310
+ * a refresh or access token. RFC 6749 §2.3.1 says client credentials "MUST NOT
311
+ * be included in the request URI", and §3.2, RFC 7009 §2.1, RFC 7662 §2.1 and
312
+ * RFC 7591 §3.1 all define the parameters as a POST body. A URL is kept by
313
+ * access logs, proxies and traces where a body is not.
314
+ *
315
+ * So the query string is never read, and a request that puts an OAuth
316
+ * parameter there is refused rather than answered: accepting it would teach a
317
+ * client that the URL works, and the secret it carries is already in a log.
318
+ * Anything else in the query (a tracing or cache-busting parameter a proxy
319
+ * added) is ignored, not refused.
320
+ */
321
+ function readBody(
322
+ ctx: VisaHttpContext,
323
+ forbidden: ReadonlySet<string> = OAUTH_PARAMETERS,
324
+ ): Record<string, unknown> {
325
+ const inUrl = Object.keys(ctx.request.qs()).filter((key) =>
326
+ forbidden.has(key),
327
+ );
328
+ if (inUrl.length > 0) {
329
+ throw new OAuthError(
330
+ "invalid_request",
331
+ `OAuth parameters go in the request body, never in the URL: ${inUrl.join(", ")}.`,
332
+ );
333
+ }
334
+ const body = ctx.request.body();
335
+ if (body === undefined || body === null) return {};
336
+ if (!isRecord(body)) {
337
+ throw new OAuthError(
338
+ "invalid_request",
339
+ "The request body must be a form or a JSON object.",
340
+ );
341
+ }
342
+ return body;
343
+ }
344
+
345
+ function isRecord(value: unknown): value is Record<string, unknown> {
346
+ return typeof value === "object" && value !== null && !Array.isArray(value);
347
+ }
348
+
248
349
  /** The initial access token a registration may carry (RFC 7591 §3). */
249
350
  function readBearer(ctx: VisaHttpContext): string | undefined {
250
351
  const header = ctx.request.header("authorization");
251
352
  if (header === undefined) return undefined;
252
353
  const [scheme, value] = header.split(" ");
253
- if (scheme?.toLowerCase() !== "bearer" || value === undefined)
354
+ // An empty value is no token at all, not a token that is the empty string.
355
+ if (scheme?.toLowerCase() !== "bearer" || value === undefined || value === "")
254
356
  return undefined;
255
357
  return value;
256
358
  }
package/src/configure.ts CHANGED
@@ -25,6 +25,17 @@ import { stubsRoot } from "./stubs.js";
25
25
  */
26
26
  const MIGRATION_STUB = "database/migrations/9000_create_visa_tables.stub";
27
27
 
28
+ /**
29
+ * Columns added after the tables first shipped, one migration each.
30
+ *
31
+ * Written on every run: `makeUsingStub` leaves a file that already exists
32
+ * alone, so an application that configured an earlier version keeps its 9000
33
+ * and gains only what it is missing by running `ream configure` again.
34
+ */
35
+ const UPGRADE_STUBS = [
36
+ "database/migrations/9001_add_visa_client_secret_expiry.stub",
37
+ ];
38
+
28
39
  interface Codemods {
29
40
  addProvider(importPath: string): Promise<void>;
30
41
  addEnvVars(vars: Record<string, string>): Promise<void>;
@@ -45,7 +56,9 @@ export async function configure(codemods: Codemods): Promise<void> {
45
56
  // Fail fast BEFORE touching the project: a pruned tarball or an unreadable
46
57
  // stub must not leave an application with a provider registered, a config
47
58
  // written and no tables to put anything in.
48
- await access(resolve(stubsRoot, MIGRATION_STUB), constants.R_OK);
59
+ for (const stub of [MIGRATION_STUB, ...UPGRADE_STUBS]) {
60
+ await access(resolve(stubsRoot, stub), constants.R_OK);
61
+ }
49
62
 
50
63
  // The config below reads this, so it is declared here. Writing the file
51
64
  // without it leaves an application whose config asks the environment for
@@ -60,4 +73,7 @@ export async function configure(codemods: Codemods): Promise<void> {
60
73
  // data layer deletes the file and keeps its own store — which is why this
61
74
  // is a migration and not a schema the package owns.
62
75
  await codemods.makeUsingStub(stubsRoot, MIGRATION_STUB);
76
+ for (const stub of UPGRADE_STUBS) {
77
+ await codemods.makeUsingStub(stubsRoot, stub);
78
+ }
63
79
  }
package/src/guard.ts CHANGED
@@ -9,7 +9,8 @@
9
9
  * upstream rule read off `@adonisjs/auth`'s build rather than recalled.
10
10
  *
11
11
  * Two things this fixes for every application at once. A route gate no longer
12
- * has to introspect the token itself — the scopes are on the guard's token. And
12
+ * has to introspect the token itself — the scopes are on the guard's token,
13
+ * and only there: `user.permissions` stays the user's own. And
13
14
  * the user is fetched through the application's own provider on every request,
14
15
  * so an account disabled after the token was issued is refused here instead of
15
16
  * in each application that remembers to check.
@@ -410,19 +411,17 @@ export class VisaGuard {
410
411
 
411
412
  // A copy: the provider's object is the application's, and a guard that
412
413
  // mutated it would leak one request's token into the next.
414
+ //
415
+ // The scopes go on `currentAccessToken` and NOWHERE else — upstream's
416
+ // access-tokens guard leaves the user model untouched the same way. They
417
+ // are never merged into `permissions`: a scope is chosen by the client's
418
+ // registration and granted by consent, which never checks that the user
419
+ // holds a permission of the same name, so a merge would hand any user a
420
+ // route permission they lack the moment a scope is named like one
421
+ // (`orders:write`). And a token limited to `profile` must not carry the
422
+ // user's other rights to a third-party client either: the two axes meet
423
+ // only as an intersection, `permission && currentAccessToken.allows(...)`.
413
424
  const user: GuardUser = { ...subject, currentAccessToken: token };
414
- if (verified.scopes.length > 0) {
415
- // Scopes surface on `permissions` as well, the way warden's access-token
416
- // guard does it, so `ctx.auth.user.permissions` reads the same whichever
417
- // guard authenticated.
418
- //
419
- // They remain a SEPARATE axis from the route-level rights gate, exactly
420
- // as upstream: a token ability is checked with
421
- // `currentAccessToken.allows(...)`, and Bouncer does not consult it.
422
- user.permissions = [
423
- ...new Set([...(subject.permissions ?? []), ...verified.scopes]),
424
- ];
425
- }
426
425
  return { authenticated: true, user };
427
426
  }
428
427
  }
package/src/index.ts CHANGED
@@ -103,6 +103,7 @@ export type {
103
103
  } from "./types.js";
104
104
  export {
105
105
  type AuthorizationOutcome,
106
+ type ConsentTicket,
106
107
  type VisaConfig,
107
108
  VisaManager,
108
109
  } from "./VisaManager.js";
package/src/register.ts CHANGED
@@ -20,6 +20,7 @@
20
20
  * it decide what it may do.
21
21
  */
22
22
 
23
+ import { hashMatches, sha256 } from "./crypto.js";
23
24
  import { OAuthError, VisaError } from "./errors.js";
24
25
  import type { Client, ClientAuthMethod, GrantType } from "./types.js";
25
26
 
@@ -113,10 +114,16 @@ export function validateRegistration(
113
114
  );
114
115
  }
115
116
  if (options.initialAccessToken !== undefined) {
116
- // Constant-time is not the point here: the token is compared once per
117
- // registration, and a registration endpoint is not a guessing oracle
118
- // worth the machinery. Absent or wrong reads the same to the caller.
119
- if (presentedToken !== options.initialAccessToken) {
117
+ // Constant time, like every other secret here. This one is a static,
118
+ // operator-chosen string — often shorter than a minted token — that an
119
+ // anonymous caller may test once per request, as often as they like.
120
+ // Both sides are hashed first so the comparison never depends on the
121
+ // configured length either. Absent or wrong reads the same to the caller.
122
+ if (
123
+ presentedToken === undefined ||
124
+ presentedToken === "" ||
125
+ !hashMatches(sha256(presentedToken), sha256(options.initialAccessToken))
126
+ ) {
120
127
  throw new OAuthError(
121
128
  "invalid_client_metadata",
122
129
  "Registration on this server needs an initial access token.",
package/src/store.ts CHANGED
@@ -20,6 +20,17 @@ import type {
20
20
  export interface VisaStore {
21
21
  findClient(id: string): Promise<Client | null>;
22
22
 
23
+ /**
24
+ * Persist a client — what `registerClient`, and so dynamic registration
25
+ * (RFC 7591), writes through. Awaited: a 201 carrying a secret for a
26
+ * client whose insert failed would hand out a credential nothing accepts.
27
+ *
28
+ * Optional, for a store whose clients live in a table another service
29
+ * owns. Without it `registerClient` refuses, and a server with
30
+ * registration turned on refuses to boot.
31
+ */
32
+ saveClient?(client: Client): Promise<void>;
33
+
23
34
  saveAuthorizationCode(code: AuthorizationCode): Promise<void>;
24
35
  findAuthorizationCode(codeHash: string): Promise<AuthorizationCode | null>;
25
36
  /**
@@ -166,10 +166,12 @@ export class AtlasStore implements VisaStore {
166
166
  const row = await this.#db.from(this.#tables.clients).where({ id }).first();
167
167
  if (row === null) return null;
168
168
  const secretHash = optionalString(row.secret_hash);
169
+ const secretExpiresAt = decodeDate(row.secret_expires_at);
169
170
  return {
170
171
  id: requireString(row.id, "id"),
171
172
  name: requireString(row.name, "name"),
172
173
  ...(secretHash === undefined ? {} : { secretHash }),
174
+ ...(secretExpiresAt === undefined ? {} : { secretExpiresAt }),
173
175
  redirectUris: decodeList(row.redirect_uris, "redirect_uris"),
174
176
  grantTypes: decodeList(row.grant_types, "grant_types").filter(
175
177
  isGrantType,
@@ -180,12 +182,15 @@ export class AtlasStore implements VisaStore {
180
182
  };
181
183
  }
182
184
 
183
- /** Persist a registered client. Not on the contract — what `registerClient` writes through. */
185
+ /** Persist a registered client — what `registerClient` writes through. */
184
186
  async saveClient(client: Client): Promise<void> {
185
187
  await this.#db.table(this.#tables.clients).insert({
186
188
  id: client.id,
187
189
  name: client.name,
188
190
  secret_hash: client.secretHash ?? null,
191
+ // Without it an expiry announced at registration is never enforced:
192
+ // authentication reads it off the client and nowhere else.
193
+ secret_expires_at: client.secretExpiresAt ?? null,
189
194
  redirect_uris: encodeList(client.redirectUris),
190
195
  grant_types: encodeList(client.grantTypes),
191
196
  scopes: encodeList(client.scopes),
@@ -30,10 +30,8 @@ export class MemoryStore implements VisaStore {
30
30
  for (const client of clients) this.#clients.set(client.id, client);
31
31
  }
32
32
 
33
- /** Register a client. Returns it, so a caller can keep the reference. */
34
- addClient(client: Client): Client {
33
+ async saveClient(client: Client): Promise<void> {
35
34
  this.#clients.set(client.id, client);
36
- return client;
37
35
  }
38
36
 
39
37
  async findClient(id: string): Promise<Client | null> {