@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.
- package/README.md +82 -11
- package/dist/VisaManager.d.ts +47 -0
- package/dist/VisaManager.d.ts.map +1 -1
- package/dist/VisaManager.js +169 -7
- package/dist/VisaManager.js.map +1 -1
- package/dist/VisaProvider.d.ts +4 -0
- package/dist/VisaProvider.d.ts.map +1 -1
- package/dist/VisaProvider.js +87 -5
- package/dist/VisaProvider.js.map +1 -1
- package/dist/configure.d.ts.map +1 -1
- package/dist/configure.js +16 -1
- package/dist/configure.js.map +1 -1
- package/dist/guard.d.ts +2 -1
- package/dist/guard.d.ts.map +1 -1
- package/dist/guard.js +12 -13
- package/dist/guard.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/register.d.ts.map +1 -1
- package/dist/register.js +9 -4
- package/dist/register.js.map +1 -1
- package/dist/store.d.ts +10 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/stores/atlas.d.ts +1 -1
- package/dist/stores/atlas.d.ts.map +1 -1
- package/dist/stores/atlas.js +6 -1
- package/dist/stores/atlas.js.map +1 -1
- package/dist/stores/memory.d.ts +1 -2
- package/dist/stores/memory.d.ts.map +1 -1
- package/dist/stores/memory.js +1 -3
- package/dist/stores/memory.js.map +1 -1
- package/dist/token.js +17 -5
- package/dist/token.js.map +1 -1
- package/package.json +1 -1
- package/src/VisaManager.ts +212 -11
- package/src/VisaProvider.ts +108 -6
- package/src/configure.ts +17 -1
- package/src/guard.ts +12 -13
- package/src/index.ts +1 -0
- package/src/register.ts +11 -4
- package/src/store.ts +11 -0
- package/src/stores/atlas.ts +6 -1
- package/src/stores/memory.ts +1 -3
- package/src/token.ts +30 -11
- package/stubs/database/migrations/9000_create_visa_tables.stub +2 -0
- 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
|
+
"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",
|
package/src/VisaManager.ts
CHANGED
|
@@ -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 (
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
*
|
package/src/VisaProvider.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
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
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
|
|
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
|
/**
|
package/src/stores/atlas.ts
CHANGED
|
@@ -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
|
|
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),
|
package/src/stores/memory.ts
CHANGED
|
@@ -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
|
-
|
|
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> {
|