@boopkit/cap-oauth-server 0.1.4 → 0.3.1
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/capability.json +14 -2
- package/dist/authorization-code-service.d.ts +40 -11
- package/dist/authorization-code-service.d.ts.map +1 -1
- package/dist/authorization-code-service.js +93 -41
- package/dist/authorization-code-service.js.map +1 -1
- package/dist/bearer.d.ts +27 -4
- package/dist/bearer.d.ts.map +1 -1
- package/dist/bearer.js +4 -4
- package/dist/bearer.js.map +1 -1
- package/dist/client-prune.d.ts +102 -0
- package/dist/client-prune.d.ts.map +1 -0
- package/dist/client-prune.js +51 -0
- package/dist/client-prune.js.map +1 -0
- package/dist/client-registry-service.d.ts +85 -8
- package/dist/client-registry-service.d.ts.map +1 -1
- package/dist/client-registry-service.js +267 -64
- package/dist/client-registry-service.js.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/connections-controller.js +1 -1
- package/dist/connections-controller.js.map +1 -1
- package/dist/consent-controller.d.ts +80 -6
- package/dist/consent-controller.d.ts.map +1 -1
- package/dist/consent-controller.js +134 -18
- package/dist/consent-controller.js.map +1 -1
- package/dist/discovery-controller.d.ts +2 -1
- package/dist/discovery-controller.d.ts.map +1 -1
- package/dist/discovery-controller.js +14 -4
- package/dist/discovery-controller.js.map +1 -1
- package/dist/discovery.d.ts +26 -0
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +55 -1
- package/dist/discovery.js.map +1 -1
- package/dist/errors.d.ts +38 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +59 -0
- package/dist/errors.js.map +1 -1
- package/dist/grant-service.d.ts +11 -4
- package/dist/grant-service.d.ts.map +1 -1
- package/dist/grant-service.js +6 -3
- package/dist/grant-service.js.map +1 -1
- package/dist/index.d.ts +10 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -3
- package/dist/index.js.map +1 -1
- package/dist/metadata-document.d.ts +22 -0
- package/dist/metadata-document.d.ts.map +1 -1
- package/dist/metadata-document.js +44 -2
- package/dist/metadata-document.js.map +1 -1
- package/dist/outbound-target.d.ts +48 -0
- package/dist/outbound-target.d.ts.map +1 -0
- package/dist/outbound-target.js +211 -0
- package/dist/outbound-target.js.map +1 -0
- package/dist/rate-limit.d.ts +53 -11
- package/dist/rate-limit.d.ts.map +1 -1
- package/dist/rate-limit.js +27 -4
- package/dist/rate-limit.js.map +1 -1
- package/dist/redirect-uri.d.ts +17 -9
- package/dist/redirect-uri.d.ts.map +1 -1
- package/dist/redirect-uri.js +26 -10
- package/dist/redirect-uri.js.map +1 -1
- package/dist/registration-controller.d.ts +10 -0
- package/dist/registration-controller.d.ts.map +1 -1
- package/dist/registration-controller.js +10 -0
- package/dist/registration-controller.js.map +1 -1
- package/dist/request-client.d.ts +7 -1
- package/dist/request-client.d.ts.map +1 -1
- package/dist/request-client.js +31 -13
- package/dist/request-client.js.map +1 -1
- package/dist/revocation-controller.d.ts +5 -0
- package/dist/revocation-controller.d.ts.map +1 -1
- package/dist/revocation-controller.js +5 -1
- package/dist/revocation-controller.js.map +1 -1
- package/dist/same-site.d.ts +54 -0
- package/dist/same-site.d.ts.map +1 -0
- package/dist/same-site.js +124 -0
- package/dist/same-site.js.map +1 -0
- package/dist/token-controller.d.ts +26 -0
- package/dist/token-controller.d.ts.map +1 -1
- package/dist/token-controller.js +44 -19
- package/dist/token-controller.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/scaffold/reference/oauth-server.ref.md +359 -11
- package/scaffold/src/middleware/oauth-server.ts +33 -17
- package/scaffold/src/scripts/prune-oauth-clients.ts +86 -0
- package/scaffold/src/services/oauth-server.service.ts +24 -0
- package/scaffold/views/oauth-consent-error.hbs +10 -3
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a caller-chosen URL may point.
|
|
3
|
+
*
|
|
4
|
+
* One place in this capability fetches a URL a stranger supplied: the Client ID
|
|
5
|
+
* Metadata Document path, reached from an unauthenticated `GET /oauth/authorize`
|
|
6
|
+
* whose `client_id` is an https URL. Without a guard, that is an SSRF primitive
|
|
7
|
+
* pointed at whatever network the application runs in — the document body is
|
|
8
|
+
* discarded on a failure, but *existence* and *latency* are both readable
|
|
9
|
+
* through the request's response time, and the outbound request happens whatever
|
|
10
|
+
* the answer (boop-jd4x).
|
|
11
|
+
*
|
|
12
|
+
* ## What this can and cannot promise
|
|
13
|
+
*
|
|
14
|
+
* The literal tables below are exact: an address in one of these ranges is never
|
|
15
|
+
* fetched. A *name* is harder, and is handled in two layers.
|
|
16
|
+
*
|
|
17
|
+
* 1. Names reserved by convention for a local network — `localhost`, `.local`,
|
|
18
|
+
* `.internal`, `.home.arpa` — are refused outright.
|
|
19
|
+
* 2. Everything else needs DNS, which this package will not do: it has no
|
|
20
|
+
* business importing `node:dns`, and a capability that did could not be
|
|
21
|
+
* tested without a resolver. So the resolver is injected
|
|
22
|
+
* (`metadataResolveHost`), and when an application supplies one, every
|
|
23
|
+
* address a name resolves to is run through the same table.
|
|
24
|
+
*
|
|
25
|
+
* Even with a resolver this is not airtight, and the gap has a name: between the
|
|
26
|
+
* lookup here and the connection the HTTP client makes, the name can be
|
|
27
|
+
* re-resolved to a different address (DNS rebinding). Closing that needs a
|
|
28
|
+
* transport that connects to an address this code chose, which `fetch` does not
|
|
29
|
+
* offer. What is here raises the cost of the probe from "free" to "needs a
|
|
30
|
+
* rebinding setup", and refuses the whole class of literal and by-name targets
|
|
31
|
+
* that a probe actually uses.
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* Is this string a literal address we refuse to connect to?
|
|
35
|
+
*
|
|
36
|
+
* `false` for anything that is not an address at all, including every hostname —
|
|
37
|
+
* a name is `blockedHostReason`'s business, because deciding it needs DNS.
|
|
38
|
+
*/
|
|
39
|
+
export declare function isBlockedAddress(value: string): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Why this hostname may not be fetched, or `null` if it may.
|
|
42
|
+
*
|
|
43
|
+
* A sentence rather than a boolean, because the caller puts it in a refusal an
|
|
44
|
+
* operator has to read: "could not be fetched" tells them nothing, and a CIMD
|
|
45
|
+
* publisher who pointed at the wrong host needs to see which host.
|
|
46
|
+
*/
|
|
47
|
+
export declare function blockedHostReason(hostname: string): string | null;
|
|
48
|
+
//# sourceMappingURL=outbound-target.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbound-target.d.ts","sourceRoot":"","sources":["../src/outbound-target.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AA+GH;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAWvD;AASD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkBjE"}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Where a caller-chosen URL may point.
|
|
4
|
+
*
|
|
5
|
+
* One place in this capability fetches a URL a stranger supplied: the Client ID
|
|
6
|
+
* Metadata Document path, reached from an unauthenticated `GET /oauth/authorize`
|
|
7
|
+
* whose `client_id` is an https URL. Without a guard, that is an SSRF primitive
|
|
8
|
+
* pointed at whatever network the application runs in — the document body is
|
|
9
|
+
* discarded on a failure, but *existence* and *latency* are both readable
|
|
10
|
+
* through the request's response time, and the outbound request happens whatever
|
|
11
|
+
* the answer (boop-jd4x).
|
|
12
|
+
*
|
|
13
|
+
* ## What this can and cannot promise
|
|
14
|
+
*
|
|
15
|
+
* The literal tables below are exact: an address in one of these ranges is never
|
|
16
|
+
* fetched. A *name* is harder, and is handled in two layers.
|
|
17
|
+
*
|
|
18
|
+
* 1. Names reserved by convention for a local network — `localhost`, `.local`,
|
|
19
|
+
* `.internal`, `.home.arpa` — are refused outright.
|
|
20
|
+
* 2. Everything else needs DNS, which this package will not do: it has no
|
|
21
|
+
* business importing `node:dns`, and a capability that did could not be
|
|
22
|
+
* tested without a resolver. So the resolver is injected
|
|
23
|
+
* (`metadataResolveHost`), and when an application supplies one, every
|
|
24
|
+
* address a name resolves to is run through the same table.
|
|
25
|
+
*
|
|
26
|
+
* Even with a resolver this is not airtight, and the gap has a name: between the
|
|
27
|
+
* lookup here and the connection the HTTP client makes, the name can be
|
|
28
|
+
* re-resolved to a different address (DNS rebinding). Closing that needs a
|
|
29
|
+
* transport that connects to an address this code chose, which `fetch` does not
|
|
30
|
+
* offer. What is here raises the cost of the probe from "free" to "needs a
|
|
31
|
+
* rebinding setup", and refuses the whole class of literal and by-name targets
|
|
32
|
+
* that a probe actually uses.
|
|
33
|
+
*/
|
|
34
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
+
exports.isBlockedAddress = isBlockedAddress;
|
|
36
|
+
exports.blockedHostReason = blockedHostReason;
|
|
37
|
+
/** The four octets of a dotted-quad, or `null` if it is not one. */
|
|
38
|
+
function parseIpv4(value) {
|
|
39
|
+
const parts = value.split('.');
|
|
40
|
+
if (parts.length !== 4)
|
|
41
|
+
return null;
|
|
42
|
+
const octets = [];
|
|
43
|
+
for (const part of parts) {
|
|
44
|
+
// No octal, no hex, no leading zeros: `010.0.0.1` is a spelling of
|
|
45
|
+
// `8.0.0.1` in some resolvers and of `10.0.0.1` in others, and a table that
|
|
46
|
+
// has to guess which is a table with a hole in it. Anything ambiguous is
|
|
47
|
+
// not an address as far as this code is concerned, and a hostname that is
|
|
48
|
+
// not an address is decided by DNS instead.
|
|
49
|
+
if (!/^(0|[1-9]\d{0,2})$/.test(part))
|
|
50
|
+
return null;
|
|
51
|
+
const octet = Number(part);
|
|
52
|
+
if (octet > 255)
|
|
53
|
+
return null;
|
|
54
|
+
octets.push(octet);
|
|
55
|
+
}
|
|
56
|
+
return octets;
|
|
57
|
+
}
|
|
58
|
+
/** The sixteen bytes of an IPv6 address, or `null` if it is not one. */
|
|
59
|
+
function parseIpv6(value) {
|
|
60
|
+
// A zone index (`fe80::1%eth0`) names an interface, not a different address.
|
|
61
|
+
let text = value.toLowerCase().split('%')[0];
|
|
62
|
+
if (!text.includes(':'))
|
|
63
|
+
return null;
|
|
64
|
+
if (!/^[0-9a-f:.]*$/.test(text))
|
|
65
|
+
return null;
|
|
66
|
+
// A trailing dotted-quad (`::ffff:127.0.0.1`) is the same address as the two
|
|
67
|
+
// hextets it stands for, so rewrite it and carry on with one parser.
|
|
68
|
+
const lastColon = text.lastIndexOf(':');
|
|
69
|
+
const tail = text.slice(lastColon + 1);
|
|
70
|
+
if (tail.includes('.')) {
|
|
71
|
+
const quad = parseIpv4(tail);
|
|
72
|
+
if (!quad)
|
|
73
|
+
return null;
|
|
74
|
+
const high = ((quad[0] << 8) | quad[1]).toString(16);
|
|
75
|
+
const low = ((quad[2] << 8) | quad[3]).toString(16);
|
|
76
|
+
text = `${text.slice(0, lastColon + 1)}${high}:${low}`;
|
|
77
|
+
}
|
|
78
|
+
const halves = text.split('::');
|
|
79
|
+
if (halves.length > 2)
|
|
80
|
+
return null;
|
|
81
|
+
const head = halves[0] === '' ? [] : halves[0].split(':');
|
|
82
|
+
const rest = halves.length === 2 ? (halves[1] === '' ? [] : halves[1].split(':')) : [];
|
|
83
|
+
let groups;
|
|
84
|
+
if (halves.length === 2) {
|
|
85
|
+
const fill = 8 - head.length - rest.length;
|
|
86
|
+
if (fill < 1)
|
|
87
|
+
return null;
|
|
88
|
+
groups = [...head, ...Array(fill).fill('0'), ...rest];
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
groups = head;
|
|
92
|
+
}
|
|
93
|
+
if (groups.length !== 8)
|
|
94
|
+
return null;
|
|
95
|
+
const bytes = [];
|
|
96
|
+
for (const group of groups) {
|
|
97
|
+
if (!/^[0-9a-f]{1,4}$/.test(group))
|
|
98
|
+
return null;
|
|
99
|
+
const hextet = Number.parseInt(group, 16);
|
|
100
|
+
bytes.push(hextet >>> 8, hextet & 0xff);
|
|
101
|
+
}
|
|
102
|
+
return bytes;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Is this IPv4 address somewhere other than the public internet?
|
|
106
|
+
*
|
|
107
|
+
* The list is IANA's special-purpose registry, narrowed to the entries a probe
|
|
108
|
+
* would actually aim at. `169.254.0.0/16` is the one to notice: every major
|
|
109
|
+
* cloud puts its instance metadata service at `169.254.169.254`, and that
|
|
110
|
+
* endpoint hands out credentials.
|
|
111
|
+
*/
|
|
112
|
+
function isBlockedIpv4(octets) {
|
|
113
|
+
const [a, b] = octets;
|
|
114
|
+
if (a === 0)
|
|
115
|
+
return true; // "this network"
|
|
116
|
+
if (a === 10)
|
|
117
|
+
return true; // RFC 1918
|
|
118
|
+
if (a === 127)
|
|
119
|
+
return true; // loopback
|
|
120
|
+
if (a === 100 && b >= 64 && b <= 127)
|
|
121
|
+
return true; // RFC 6598 carrier-grade NAT
|
|
122
|
+
if (a === 169 && b === 254)
|
|
123
|
+
return true; // link-local, incl. cloud metadata
|
|
124
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
125
|
+
return true; // RFC 1918
|
|
126
|
+
if (a === 192 && b === 0)
|
|
127
|
+
return true; // IETF protocol assignments
|
|
128
|
+
if (a === 192 && b === 168)
|
|
129
|
+
return true; // RFC 1918
|
|
130
|
+
if (a === 198 && (b === 18 || b === 19))
|
|
131
|
+
return true; // RFC 2544 benchmarking
|
|
132
|
+
if (a >= 224)
|
|
133
|
+
return true; // multicast, reserved, and 255.255.255.255
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
/** The same question for IPv6, including addresses wearing an IPv4 coat. */
|
|
137
|
+
function isBlockedIpv6(bytes) {
|
|
138
|
+
const isZero = (from, to) => bytes.slice(from, to).every((byte) => byte === 0);
|
|
139
|
+
// `::ffff:0:0/96` — an IPv4 address in IPv6 clothing. Judged as the IPv4
|
|
140
|
+
// address it is, or `::ffff:127.0.0.1` would walk straight past the table.
|
|
141
|
+
if (isZero(0, 10) && bytes[10] === 0xff && bytes[11] === 0xff) {
|
|
142
|
+
return isBlockedIpv4(bytes.slice(12));
|
|
143
|
+
}
|
|
144
|
+
// `64:ff9b::/96` — the well-known NAT64 prefix, which is another way to spell
|
|
145
|
+
// an IPv4 destination for a network that has a translator. Same treatment.
|
|
146
|
+
if (bytes[0] === 0x00 && bytes[1] === 0x64 && bytes[2] === 0xff && bytes[3] === 0x9b &&
|
|
147
|
+
isZero(4, 12)) {
|
|
148
|
+
return isBlockedIpv4(bytes.slice(12));
|
|
149
|
+
}
|
|
150
|
+
// `::` (unspecified) and `::1` (loopback), and the rest of `::/64` with them:
|
|
151
|
+
// nothing in that range is a host on the public internet.
|
|
152
|
+
if (isZero(0, 8))
|
|
153
|
+
return true;
|
|
154
|
+
if ((bytes[0] & 0xfe) === 0xfc)
|
|
155
|
+
return true; // fc00::/7 unique local
|
|
156
|
+
if (bytes[0] === 0xfe && (bytes[1] & 0xc0) === 0x80)
|
|
157
|
+
return true; // fe80::/10 link-local
|
|
158
|
+
if (bytes[0] === 0xff)
|
|
159
|
+
return true; // ff00::/8 multicast
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Is this string a literal address we refuse to connect to?
|
|
164
|
+
*
|
|
165
|
+
* `false` for anything that is not an address at all, including every hostname —
|
|
166
|
+
* a name is `blockedHostReason`'s business, because deciding it needs DNS.
|
|
167
|
+
*/
|
|
168
|
+
function isBlockedAddress(value) {
|
|
169
|
+
const text = value.trim();
|
|
170
|
+
if (text === '')
|
|
171
|
+
return false;
|
|
172
|
+
const ipv4 = parseIpv4(text);
|
|
173
|
+
if (ipv4)
|
|
174
|
+
return isBlockedIpv4(ipv4);
|
|
175
|
+
const ipv6 = parseIpv6(text);
|
|
176
|
+
if (ipv6)
|
|
177
|
+
return isBlockedIpv6(ipv6);
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Names reserved for a local network. RFC 6761 (`localhost`), RFC 6762
|
|
182
|
+
* (`.local`), RFC 8375 (`.home.arpa`), and `.internal`, which is what cloud
|
|
183
|
+
* providers hand out for private zones and which ICANN has now reserved.
|
|
184
|
+
*/
|
|
185
|
+
const BLOCKED_SUFFIXES = ['.localhost', '.local', '.internal', '.home.arpa'];
|
|
186
|
+
/**
|
|
187
|
+
* Why this hostname may not be fetched, or `null` if it may.
|
|
188
|
+
*
|
|
189
|
+
* A sentence rather than a boolean, because the caller puts it in a refusal an
|
|
190
|
+
* operator has to read: "could not be fetched" tells them nothing, and a CIMD
|
|
191
|
+
* publisher who pointed at the wrong host needs to see which host.
|
|
192
|
+
*/
|
|
193
|
+
function blockedHostReason(hostname) {
|
|
194
|
+
// A URL spells an IPv6 literal in brackets; the address inside is what the
|
|
195
|
+
// table knows about.
|
|
196
|
+
const host = hostname.trim().toLowerCase().replace(/^\[|\]$/g, '');
|
|
197
|
+
if (host === '')
|
|
198
|
+
return 'the URL names no host';
|
|
199
|
+
if (isBlockedAddress(host)) {
|
|
200
|
+
return `${hostname} is a private, loopback, link-local or otherwise non-public address`;
|
|
201
|
+
}
|
|
202
|
+
// A trailing dot is a fully-qualified spelling of the same name.
|
|
203
|
+
const name = host.endsWith('.') ? host.slice(0, -1) : host;
|
|
204
|
+
if (name === 'localhost')
|
|
205
|
+
return `${hostname} names this machine`;
|
|
206
|
+
if (BLOCKED_SUFFIXES.some((suffix) => name.endsWith(suffix))) {
|
|
207
|
+
return `${hostname} is a name reserved for a local network`;
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=outbound-target.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbound-target.js","sourceRoot":"","sources":["../src/outbound-target.ts"],"names":[],"mappings":"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"}
|
package/dist/rate-limit.d.ts
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
import type { RequestHandler } from 'express';
|
|
1
|
+
import type { Request, RequestHandler } from 'express';
|
|
2
2
|
/**
|
|
3
|
-
* Per-IP rate limiting for the
|
|
3
|
+
* Per-IP rate limiting for the paths that let a stranger write a row.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* cannot have.
|
|
5
|
+
* There are two, and for a long time only one of them had this. `POST
|
|
6
|
+
* /oauth/register` is unauthenticated by design — RFC 7591 open registration has
|
|
7
|
+
* no caller to authenticate — so there is no account to hang a quota on and the
|
|
8
|
+
* source address is the only thing a limiter has to go on. Neither donor has
|
|
9
|
+
* anything like this: paywalls' registration endpoint required an authenticated
|
|
10
|
+
* `service-user`, which is a gate this endpoint cannot have.
|
|
11
|
+
*
|
|
12
|
+
* The second is **first-sight metadata-document resolution** at `GET
|
|
13
|
+
* /oauth/authorize`. It is equally unauthenticated, it writes a row per distinct
|
|
14
|
+
* URL, and it makes an outbound request besides — so it is limited by the same
|
|
15
|
+
* counter, mounted by the consent handlers rather than by a router, because only
|
|
16
|
+
* the handler knows whether a given request is about to resolve a new client or
|
|
17
|
+
* serve one that already has a row (boop-jd4x).
|
|
11
18
|
*
|
|
12
19
|
* ## What this is, and what it is not
|
|
13
20
|
*
|
|
@@ -27,6 +34,14 @@ import type { RequestHandler } from 'express';
|
|
|
27
34
|
* boundary. Named here rather than fixed, because the mitigation (a sliding
|
|
28
35
|
* window) buys precision this does not need against a ceiling that is already
|
|
29
36
|
* the real bound.
|
|
37
|
+
*
|
|
38
|
+
* ## The address has to be the caller's
|
|
39
|
+
*
|
|
40
|
+
* Both limiters key on `req.ip`, which behind a proxy is the proxy unless the
|
|
41
|
+
* application has set `trust proxy`. Left unset, every caller shares one bucket:
|
|
42
|
+
* one abuser exhausts it for everybody, and no individual abuser is ever
|
|
43
|
+
* stopped. That is worth checking in a deployment — it is the difference between
|
|
44
|
+
* a limiter and a decoration.
|
|
30
45
|
*/
|
|
31
46
|
export interface RateLimiterOptions {
|
|
32
47
|
/** Requests allowed per window, per address. */
|
|
@@ -43,14 +58,41 @@ export interface RateLimiterOptions {
|
|
|
43
58
|
}
|
|
44
59
|
export declare const DEFAULT_REGISTRATION_RATE_LIMIT = 10;
|
|
45
60
|
export declare const DEFAULT_REGISTRATION_RATE_WINDOW_MS: number;
|
|
46
|
-
|
|
61
|
+
/**
|
|
62
|
+
* First-sight metadata-document resolutions allowed per address per window.
|
|
63
|
+
*
|
|
64
|
+
* The same number as registration, and for the same reason: reaching it is not
|
|
65
|
+
* something an honest browser does. A resolution is charged only when the
|
|
66
|
+
* `client_id` is an https URL with **no row yet** — every later visit to the
|
|
67
|
+
* same client is served from the row and costs nothing — so ten distinct,
|
|
68
|
+
* never-before-seen connectors an hour from one address is already generous.
|
|
69
|
+
*/
|
|
70
|
+
export declare const DEFAULT_METADATA_RESOLUTION_RATE_LIMIT = 10;
|
|
71
|
+
export interface FixedWindowRateLimiter {
|
|
72
|
+
/** The `POST /oauth/register` mount. */
|
|
47
73
|
middleware: RequestHandler;
|
|
48
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* The counter itself, for a caller that decides *whether* a request counts —
|
|
76
|
+
* the consent handlers charge only a first-sight resolution — and for tests
|
|
77
|
+
* and operator scripts.
|
|
78
|
+
*/
|
|
49
79
|
check(address: string): {
|
|
50
80
|
allowed: boolean;
|
|
51
81
|
retryAfterSeconds: number;
|
|
52
82
|
};
|
|
53
83
|
reset(): void;
|
|
54
84
|
}
|
|
55
|
-
|
|
85
|
+
/** The old name, kept so an application that imported it still compiles. */
|
|
86
|
+
export type RegistrationRateLimiter = FixedWindowRateLimiter;
|
|
87
|
+
/**
|
|
88
|
+
* The address a limiter counts against.
|
|
89
|
+
*
|
|
90
|
+
* Exported so the middleware and the consent handlers cannot pick different
|
|
91
|
+
* answers — two limiters keyed differently are two quotas, and the one an
|
|
92
|
+
* attacker would find is whichever is keyed more loosely.
|
|
93
|
+
*/
|
|
94
|
+
export declare function rateLimitAddress(req: Request): string;
|
|
95
|
+
export declare function createFixedWindowRateLimiter(options?: RateLimiterOptions): FixedWindowRateLimiter;
|
|
96
|
+
/** The registration endpoint's name for the same factory. */
|
|
97
|
+
export declare const createRegistrationRateLimiter: typeof createFixedWindowRateLimiter;
|
|
56
98
|
//# sourceMappingURL=rate-limit.d.ts.map
|
package/dist/rate-limit.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,OAAO,EAAE,cAAc,EAAY,MAAM,SAAS,CAAC;AAE/E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,MAAM,WAAW,kBAAkB;IACjC,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,eAAO,MAAM,+BAA+B,KAAK,CAAC;AAClD,eAAO,MAAM,mCAAmC,QAAiB,CAAC;AAElE;;;;;;;;GAQG;AACH,eAAO,MAAM,sCAAsC,KAAK,CAAC;AAQzD,MAAM,WAAW,sBAAsB;IACrC,wCAAwC;IACxC,UAAU,EAAE,cAAc,CAAC;IAC3B;;;;OAIG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,iBAAiB,EAAE,MAAM,CAAA;KAAE,CAAC;IACxE,KAAK,IAAI,IAAI,CAAC;CACf;AAED,4EAA4E;AAC5E,MAAM,MAAM,uBAAuB,GAAG,sBAAsB,CAAC;AAE7D;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAErD;AAED,wBAAgB,4BAA4B,CAC1C,OAAO,GAAE,kBAAuB,GAC/B,sBAAsB,CAmDxB;AAED,6DAA6D;AAC7D,eAAO,MAAM,6BAA6B,qCAA+B,CAAC"}
|
package/dist/rate-limit.js
CHANGED
|
@@ -1,11 +1,32 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DEFAULT_REGISTRATION_RATE_WINDOW_MS = exports.DEFAULT_REGISTRATION_RATE_LIMIT = void 0;
|
|
4
|
-
exports.
|
|
3
|
+
exports.createRegistrationRateLimiter = exports.DEFAULT_METADATA_RESOLUTION_RATE_LIMIT = exports.DEFAULT_REGISTRATION_RATE_WINDOW_MS = exports.DEFAULT_REGISTRATION_RATE_LIMIT = void 0;
|
|
4
|
+
exports.rateLimitAddress = rateLimitAddress;
|
|
5
|
+
exports.createFixedWindowRateLimiter = createFixedWindowRateLimiter;
|
|
5
6
|
exports.DEFAULT_REGISTRATION_RATE_LIMIT = 10;
|
|
6
7
|
exports.DEFAULT_REGISTRATION_RATE_WINDOW_MS = 60 * 60 * 1000;
|
|
8
|
+
/**
|
|
9
|
+
* First-sight metadata-document resolutions allowed per address per window.
|
|
10
|
+
*
|
|
11
|
+
* The same number as registration, and for the same reason: reaching it is not
|
|
12
|
+
* something an honest browser does. A resolution is charged only when the
|
|
13
|
+
* `client_id` is an https URL with **no row yet** — every later visit to the
|
|
14
|
+
* same client is served from the row and costs nothing — so ten distinct,
|
|
15
|
+
* never-before-seen connectors an hour from one address is already generous.
|
|
16
|
+
*/
|
|
17
|
+
exports.DEFAULT_METADATA_RESOLUTION_RATE_LIMIT = 10;
|
|
7
18
|
const DEFAULT_MAX_TRACKED_ADDRESSES = 10000;
|
|
8
|
-
|
|
19
|
+
/**
|
|
20
|
+
* The address a limiter counts against.
|
|
21
|
+
*
|
|
22
|
+
* Exported so the middleware and the consent handlers cannot pick different
|
|
23
|
+
* answers — two limiters keyed differently are two quotas, and the one an
|
|
24
|
+
* attacker would find is whichever is keyed more loosely.
|
|
25
|
+
*/
|
|
26
|
+
function rateLimitAddress(req) {
|
|
27
|
+
return req.ip ?? req.socket?.remoteAddress ?? 'unknown';
|
|
28
|
+
}
|
|
29
|
+
function createFixedWindowRateLimiter(options = {}) {
|
|
9
30
|
const limit = options.limit ?? exports.DEFAULT_REGISTRATION_RATE_LIMIT;
|
|
10
31
|
const windowMs = options.windowMs ?? exports.DEFAULT_REGISTRATION_RATE_WINDOW_MS;
|
|
11
32
|
const now = options.now ?? (() => Date.now());
|
|
@@ -36,7 +57,7 @@ function createRegistrationRateLimiter(options = {}) {
|
|
|
36
57
|
return { allowed: true, retryAfterSeconds: 0 };
|
|
37
58
|
}
|
|
38
59
|
const middleware = (req, res, next) => {
|
|
39
|
-
const address = req
|
|
60
|
+
const address = rateLimitAddress(req);
|
|
40
61
|
const verdict = check(address);
|
|
41
62
|
if (verdict.allowed)
|
|
42
63
|
return next();
|
|
@@ -52,4 +73,6 @@ function createRegistrationRateLimiter(options = {}) {
|
|
|
52
73
|
reset: () => windows.clear(),
|
|
53
74
|
};
|
|
54
75
|
}
|
|
76
|
+
/** The registration endpoint's name for the same factory. */
|
|
77
|
+
exports.createRegistrationRateLimiter = createFixedWindowRateLimiter;
|
|
55
78
|
//# sourceMappingURL=rate-limit.js.map
|
package/dist/rate-limit.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":";;;AAuGA,4CAEC;AAED,oEAqDC;AAnGY,QAAA,+BAA+B,GAAG,EAAE,CAAC;AACrC,QAAA,mCAAmC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAElE;;;;;;;;GAQG;AACU,QAAA,sCAAsC,GAAG,EAAE,CAAC;AACzD,MAAM,6BAA6B,GAAG,KAAM,CAAC;AAsB7C;;;;;;GAMG;AACH,SAAgB,gBAAgB,CAAC,GAAY;IAC3C,OAAO,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,MAAM,EAAE,aAAa,IAAI,SAAS,CAAC;AAC1D,CAAC;AAED,SAAgB,4BAA4B,CAC1C,UAA8B,EAAE;IAEhC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,uCAA+B,CAAC;IAC/D,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,2CAAmC,CAAC;IACzE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAC9C,MAAM,UAAU,GAAG,OAAO,CAAC,mBAAmB,IAAI,6BAA6B,CAAC;IAEhF,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAE1C,SAAS,KAAK,CAAC,OAAe;QAC5B,MAAM,EAAE,GAAG,GAAG,EAAE,CAAC;QACjB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAEtC,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;YACxC,wEAAwE;YACxE,yEAAyE;YACzE,sBAAsB;YACtB,IAAI,OAAO,CAAC,IAAI,IAAI,UAAU,EAAE,CAAC;gBAC/B,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC;gBACrC,IAAI,CAAC,MAAM,CAAC,IAAI;oBAAE,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACjD,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,GAAG,QAAQ,EAAE,CAAC,CAAC;YAC3D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC,EAAE,CAAC;QACjD,CAAC;QAED,QAAQ,CAAC,KAAK,IAAI,CAAC,CAAC;QACpB,IAAI,QAAQ,CAAC,KAAK,GAAG,KAAK,EAAE,CAAC;YAC3B,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,iBAAiB,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,OAAO,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;aAC1E,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC,EAAE,CAAC;IACjD,CAAC;IAED,MAAM,UAAU,GAAmB,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QACrF,MAAM,OAAO,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;QACtC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;QAC/B,IAAI,OAAO,CAAC,OAAO;YAAE,OAAO,IAAI,EAAE,CAAC;QAEnC,GAAG,CAAC,SAAS,CAAC,aAAa,EAAE,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC;QAChE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YACnB,KAAK,EAAE,yBAAyB;YAChC,iBAAiB,EAAE,mEAAmE;SACvF,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,OAAO;QACL,UAAU;QACV,KAAK;QACL,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE;KAC7B,CAAC;AACJ,CAAC;AAED,6DAA6D;AAChD,QAAA,6BAA6B,GAAG,4BAA4B,CAAC"}
|
package/dist/redirect-uri.d.ts
CHANGED
|
@@ -25,16 +25,24 @@
|
|
|
25
25
|
*
|
|
26
26
|
* ## The matching rule
|
|
27
27
|
*
|
|
28
|
-
* **Exact string match**, with exactly one exception: a registered
|
|
29
|
-
* `
|
|
30
|
-
* is RFC 8252 §7.3, and it is what Claude Code's published Client
|
|
31
|
-
* Document requires — it binds an ephemeral loopback port at connect
|
|
32
|
-
* the port it will present is not knowable at registration.
|
|
28
|
+
* **Exact string match**, with exactly one exception: a registered entry whose
|
|
29
|
+
* *host* is `localhost` or `127.0.0.1` matches a presented URI that differs only
|
|
30
|
+
* in port. That is RFC 8252 §7.3, and it is what Claude Code's published Client
|
|
31
|
+
* ID Metadata Document requires — it binds an ephemeral loopback port at connect
|
|
32
|
+
* time, so the port it will present is not knowable at registration.
|
|
33
33
|
*
|
|
34
|
-
* The
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
34
|
+
* The key is the host and not the `loopback-http` shape, so an
|
|
35
|
+
* `https://localhost/cb` registration gets its port relaxed too. §7.3 is about
|
|
36
|
+
* loopback **http**, so an argument exists for narrowing the exception to `http`
|
|
37
|
+
* alone — but a port on the loopback interface is ephemeral whichever scheme
|
|
38
|
+
* terminates it, and narrowing would break an operator running a local client
|
|
39
|
+
* over TLS for no security gain. That is a decision, not an oversight, and the
|
|
40
|
+
* test table pins it.
|
|
41
|
+
*
|
|
42
|
+
* A private-use scheme is refused the exception even so, because it has no TCP
|
|
43
|
+
* port to be ephemeral: the operating system routes it by scheme name, so
|
|
44
|
+
* `myapp://localhost/cb` gets the plain exact rule even though its host happens
|
|
45
|
+
* to read as loopback.
|
|
38
46
|
*
|
|
39
47
|
* The exception lives here, in one function with its own test table, rather
|
|
40
48
|
* than being threaded through the authorization controller as a special case.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redirect-uri.d.ts","sourceRoot":"","sources":["../src/redirect-uri.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"redirect-uri.d.ts","sourceRoot":"","sources":["../src/redirect-uri.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AA8DH,4DAA4D;AAC5D,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,eAAe,GAAG,aAAa,CAAC;AAEzE;;;;;;GAMG;AACH,MAAM,MAAM,yBAAyB,GACjC;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,gBAAgB,CAAC;IAAC,GAAG,EAAE,GAAG,CAAA;CAAE,GACrD;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AA6BxC;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,yBAAyB,CAsC5E;AAiBD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CA+C/E;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAE/F;AAED,0DAA0D;AAC1D,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD"}
|
package/dist/redirect-uri.js
CHANGED
|
@@ -26,16 +26,24 @@
|
|
|
26
26
|
*
|
|
27
27
|
* ## The matching rule
|
|
28
28
|
*
|
|
29
|
-
* **Exact string match**, with exactly one exception: a registered
|
|
30
|
-
* `
|
|
31
|
-
* is RFC 8252 §7.3, and it is what Claude Code's published Client
|
|
32
|
-
* Document requires — it binds an ephemeral loopback port at connect
|
|
33
|
-
* the port it will present is not knowable at registration.
|
|
29
|
+
* **Exact string match**, with exactly one exception: a registered entry whose
|
|
30
|
+
* *host* is `localhost` or `127.0.0.1` matches a presented URI that differs only
|
|
31
|
+
* in port. That is RFC 8252 §7.3, and it is what Claude Code's published Client
|
|
32
|
+
* ID Metadata Document requires — it binds an ephemeral loopback port at connect
|
|
33
|
+
* time, so the port it will present is not knowable at registration.
|
|
34
34
|
*
|
|
35
|
-
* The
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
35
|
+
* The key is the host and not the `loopback-http` shape, so an
|
|
36
|
+
* `https://localhost/cb` registration gets its port relaxed too. §7.3 is about
|
|
37
|
+
* loopback **http**, so an argument exists for narrowing the exception to `http`
|
|
38
|
+
* alone — but a port on the loopback interface is ephemeral whichever scheme
|
|
39
|
+
* terminates it, and narrowing would break an operator running a local client
|
|
40
|
+
* over TLS for no security gain. That is a decision, not an oversight, and the
|
|
41
|
+
* test table pins it.
|
|
42
|
+
*
|
|
43
|
+
* A private-use scheme is refused the exception even so, because it has no TCP
|
|
44
|
+
* port to be ephemeral: the operating system routes it by scheme name, so
|
|
45
|
+
* `myapp://localhost/cb` gets the plain exact rule even though its host happens
|
|
46
|
+
* to read as loopback.
|
|
39
47
|
*
|
|
40
48
|
* The exception lives here, in one function with its own test table, rather
|
|
41
49
|
* than being threaded through the authorization controller as a special case.
|
|
@@ -50,7 +58,15 @@ exports.classifyRedirectUri = classifyRedirectUri;
|
|
|
50
58
|
exports.matchRedirectUri = matchRedirectUri;
|
|
51
59
|
exports.matchesAnyRedirectUri = matchesAnyRedirectUri;
|
|
52
60
|
exports.isLoopbackHost = isLoopbackHost;
|
|
53
|
-
/**
|
|
61
|
+
/**
|
|
62
|
+
* Hosts a native app may bind an ephemeral port on (RFC 8252 §7.3).
|
|
63
|
+
*
|
|
64
|
+
* The IPv6 loopback `[::1]` is deliberately absent. §7.3 contemplates it, so
|
|
65
|
+
* this refuses a real client — but it refuses it at *registration*, where
|
|
66
|
+
* `validateRedirectUris` calls `http://[::1]:52731/cb` http on a non-loopback
|
|
67
|
+
* host, rather than silently at match time. The reference page says so under
|
|
68
|
+
* "Redirect URIs".
|
|
69
|
+
*/
|
|
54
70
|
const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1']);
|
|
55
71
|
/**
|
|
56
72
|
* Schemes a private-use redirect may not claim, on three separate grounds.
|
package/dist/redirect-uri.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redirect-uri.js","sourceRoot":"","sources":["../src/redirect-uri.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"redirect-uri.js","sourceRoot":"","sources":["../src/redirect-uri.ts"],"names":[],"mappings":"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"}
|
|
@@ -33,6 +33,16 @@ export interface RegistrationHandlerDeps {
|
|
|
33
33
|
*
|
|
34
34
|
* `client_secret` is deliberately absent rather than null: this server issues
|
|
35
35
|
* only public clients, and a client that found the field would try to use it.
|
|
36
|
+
*
|
|
37
|
+
* `registration_access_token` and `registration_client_uri` are absent for the
|
|
38
|
+
* same reason one step further out. They are RFC 7592's credential and address
|
|
39
|
+
* for managing a registration afterwards, and this server implements no such
|
|
40
|
+
* surface — `PUT`/`DELETE /oauth/register/:client_id` are unmounted by decision,
|
|
41
|
+
* not by omission. Emitting either field would hand a client a credential and a
|
|
42
|
+
* URL that resolve to a 404, which is worse than saying nothing: a client that
|
|
43
|
+
* reads them plans to re-register on failure rather than to re-register at all.
|
|
44
|
+
* If that decision is ever reversed, these two fields and the routes ship
|
|
45
|
+
* together or neither ships.
|
|
36
46
|
*/
|
|
37
47
|
export declare function toRegistrationResponse(client: OauthClient): Record<string, unknown>;
|
|
38
48
|
export declare function createRegistrationHandlers(deps: RegistrationHandlerDeps): {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registration-controller.d.ts","sourceRoot":"","sources":["../src/registration-controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAQ,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AAE5D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAE1E,OAAO,EAAiC,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACzF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,WAAW,uBAAuB;IACtC,cAAc,EAAE,qBAAqB,CAAC;CACvC;AAED
|
|
1
|
+
{"version":3,"file":"registration-controller.d.ts","sourceRoot":"","sources":["../src/registration-controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAQ,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AAE5D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAE1E,OAAO,EAAiC,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACzF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,WAAW,uBAAuB;IACtC,cAAc,EAAE,qBAAqB,CAAC;CACvC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAkBnF;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,uBAAuB,GAAG;IACzE,QAAQ,EAAE,cAAc,CAAC;CAC1B,CA6BA;AAED,MAAM,WAAW,yBAA0B,SAAQ,uBAAuB;IACxE;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,kBAAkB,GAAG,KAAK,CAAC;IACvC,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,yBAAyB,GAAG,MAAM,CAYnF"}
|
|
@@ -11,6 +11,16 @@ const rate_limit_js_1 = require("./rate-limit.js");
|
|
|
11
11
|
*
|
|
12
12
|
* `client_secret` is deliberately absent rather than null: this server issues
|
|
13
13
|
* only public clients, and a client that found the field would try to use it.
|
|
14
|
+
*
|
|
15
|
+
* `registration_access_token` and `registration_client_uri` are absent for the
|
|
16
|
+
* same reason one step further out. They are RFC 7592's credential and address
|
|
17
|
+
* for managing a registration afterwards, and this server implements no such
|
|
18
|
+
* surface — `PUT`/`DELETE /oauth/register/:client_id` are unmounted by decision,
|
|
19
|
+
* not by omission. Emitting either field would hand a client a credential and a
|
|
20
|
+
* URL that resolve to a 404, which is worse than saying nothing: a client that
|
|
21
|
+
* reads them plans to re-register on failure rather than to re-register at all.
|
|
22
|
+
* If that decision is ever reversed, these two fields and the routes ship
|
|
23
|
+
* together or neither ships.
|
|
14
24
|
*/
|
|
15
25
|
function toRegistrationResponse(client) {
|
|
16
26
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registration-controller.js","sourceRoot":"","sources":["../src/registration-controller.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"registration-controller.js","sourceRoot":"","sources":["../src/registration-controller.ts"],"names":[],"mappings":"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"}
|
package/dist/request-client.d.ts
CHANGED
|
@@ -15,5 +15,11 @@ import type { OauthClient } from './types.js';
|
|
|
15
15
|
* copy of the check is the one that would forget suspension, and then a retired
|
|
16
16
|
* client could still reach one of the two endpoints.
|
|
17
17
|
*/
|
|
18
|
-
|
|
18
|
+
/** Where a refusal to identify the client is reported, and under which name. */
|
|
19
|
+
export interface RequestClientReport {
|
|
20
|
+
/** The endpoint as an operator would search for it: `POST /oauth/token`. */
|
|
21
|
+
endpoint: string;
|
|
22
|
+
warn(message: string): void;
|
|
23
|
+
}
|
|
24
|
+
export declare function resolveRequestClient(clientRegistry: ClientRegistryService, body: unknown, report: RequestClientReport): Promise<OauthClient>;
|
|
19
25
|
//# sourceMappingURL=request-client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-client.d.ts","sourceRoot":"","sources":["../src/request-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAE1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;;;;;;GAcG;
|
|
1
|
+
{"version":3,"file":"request-client.d.ts","sourceRoot":"","sources":["../src/request-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAE1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;;;;;;GAcG;AAEH,gFAAgF;AAChF,MAAM,WAAW,mBAAmB;IAClC,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAkCD,wBAAsB,oBAAoB,CACxC,cAAc,EAAE,qBAAqB,EACrC,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC,WAAW,CAAC,CAetB"}
|