@boopkit/cap-oauth-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/capability.json +120 -0
  2. package/dist/authorization-code-service.d.ts +105 -0
  3. package/dist/authorization-code-service.d.ts.map +1 -0
  4. package/dist/authorization-code-service.js +142 -0
  5. package/dist/authorization-code-service.js.map +1 -0
  6. package/dist/bearer.d.ts +146 -0
  7. package/dist/bearer.d.ts.map +1 -0
  8. package/dist/bearer.js +150 -0
  9. package/dist/bearer.js.map +1 -0
  10. package/dist/body-field.d.ts +24 -0
  11. package/dist/body-field.d.ts.map +1 -0
  12. package/dist/body-field.js +43 -0
  13. package/dist/body-field.js.map +1 -0
  14. package/dist/client-registry-service.d.ts +120 -0
  15. package/dist/client-registry-service.d.ts.map +1 -0
  16. package/dist/client-registry-service.js +450 -0
  17. package/dist/client-registry-service.js.map +1 -0
  18. package/dist/config.d.ts +82 -0
  19. package/dist/config.d.ts.map +1 -0
  20. package/dist/config.js +157 -0
  21. package/dist/config.js.map +1 -0
  22. package/dist/connections-controller.d.ts +106 -0
  23. package/dist/connections-controller.d.ts.map +1 -0
  24. package/dist/connections-controller.js +161 -0
  25. package/dist/connections-controller.js.map +1 -0
  26. package/dist/consent-controller.d.ts +197 -0
  27. package/dist/consent-controller.d.ts.map +1 -0
  28. package/dist/consent-controller.js +532 -0
  29. package/dist/consent-controller.js.map +1 -0
  30. package/dist/discovery-controller.d.ts +16 -0
  31. package/dist/discovery-controller.d.ts.map +1 -0
  32. package/dist/discovery-controller.js +52 -0
  33. package/dist/discovery-controller.js.map +1 -0
  34. package/dist/discovery.d.ts +95 -0
  35. package/dist/discovery.d.ts.map +1 -0
  36. package/dist/discovery.js +101 -0
  37. package/dist/discovery.js.map +1 -0
  38. package/dist/errors.d.ts +26 -0
  39. package/dist/errors.d.ts.map +1 -0
  40. package/dist/errors.js +41 -0
  41. package/dist/errors.js.map +1 -0
  42. package/dist/express.d.ts +66 -0
  43. package/dist/express.d.ts.map +1 -0
  44. package/dist/express.js +3 -0
  45. package/dist/express.js.map +1 -0
  46. package/dist/grant-service.d.ts +102 -0
  47. package/dist/grant-service.d.ts.map +1 -0
  48. package/dist/grant-service.js +137 -0
  49. package/dist/grant-service.js.map +1 -0
  50. package/dist/index.d.ts +67 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +148 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/metadata-document.d.ts +60 -0
  55. package/dist/metadata-document.d.ts.map +1 -0
  56. package/dist/metadata-document.js +148 -0
  57. package/dist/metadata-document.js.map +1 -0
  58. package/dist/oauth-error.d.ts +45 -0
  59. package/dist/oauth-error.d.ts.map +1 -0
  60. package/dist/oauth-error.js +51 -0
  61. package/dist/oauth-error.js.map +1 -0
  62. package/dist/pkce.d.ts +29 -0
  63. package/dist/pkce.d.ts.map +1 -0
  64. package/dist/pkce.js +65 -0
  65. package/dist/pkce.js.map +1 -0
  66. package/dist/property-array.d.ts +60 -0
  67. package/dist/property-array.d.ts.map +1 -0
  68. package/dist/property-array.js +31 -0
  69. package/dist/property-array.js.map +1 -0
  70. package/dist/public-id.d.ts +35 -0
  71. package/dist/public-id.d.ts.map +1 -0
  72. package/dist/public-id.js +76 -0
  73. package/dist/public-id.js.map +1 -0
  74. package/dist/rate-limit.d.ts +56 -0
  75. package/dist/rate-limit.d.ts.map +1 -0
  76. package/dist/rate-limit.js +55 -0
  77. package/dist/rate-limit.js.map +1 -0
  78. package/dist/redirect-uri.d.ts +89 -0
  79. package/dist/redirect-uri.d.ts.map +1 -0
  80. package/dist/redirect-uri.js +249 -0
  81. package/dist/redirect-uri.js.map +1 -0
  82. package/dist/registration-controller.d.ts +62 -0
  83. package/dist/registration-controller.d.ts.map +1 -0
  84. package/dist/registration-controller.js +79 -0
  85. package/dist/registration-controller.js.map +1 -0
  86. package/dist/request-client.d.ts +19 -0
  87. package/dist/request-client.d.ts.map +1 -0
  88. package/dist/request-client.js +32 -0
  89. package/dist/request-client.js.map +1 -0
  90. package/dist/revocation-controller.d.ts +28 -0
  91. package/dist/revocation-controller.d.ts.map +1 -0
  92. package/dist/revocation-controller.js +176 -0
  93. package/dist/revocation-controller.js.map +1 -0
  94. package/dist/revocation-service.d.ts +113 -0
  95. package/dist/revocation-service.d.ts.map +1 -0
  96. package/dist/revocation-service.js +121 -0
  97. package/dist/revocation-service.js.map +1 -0
  98. package/dist/token-controller.d.ts +79 -0
  99. package/dist/token-controller.d.ts.map +1 -0
  100. package/dist/token-controller.js +207 -0
  101. package/dist/token-controller.js.map +1 -0
  102. package/dist/token-service.d.ts +175 -0
  103. package/dist/token-service.d.ts.map +1 -0
  104. package/dist/token-service.js +251 -0
  105. package/dist/token-service.js.map +1 -0
  106. package/dist/types.d.ts +130 -0
  107. package/dist/types.d.ts.map +1 -0
  108. package/dist/types.js +13 -0
  109. package/dist/types.js.map +1 -0
  110. package/dist/user-store.d.ts +110 -0
  111. package/dist/user-store.d.ts.map +1 -0
  112. package/dist/user-store.js +40 -0
  113. package/dist/user-store.js.map +1 -0
  114. package/package.json +37 -0
  115. package/scaffold/prisma/schema/oauth-clients.prisma +80 -0
  116. package/scaffold/reference/oauth-server.ref.md +478 -0
  117. package/scaffold/src/middleware/oauth-server.ts +227 -0
  118. package/scaffold/src/scripts/seed-oauth-clients.ts +72 -0
  119. package/scaffold/src/services/oauth-server.service.ts +101 -0
  120. package/scaffold/styles/oauth-server.css +345 -0
  121. package/scaffold/views/oauth-connected-clients.hbs +98 -0
  122. package/scaffold/views/oauth-consent-error.hbs +22 -0
  123. package/scaffold/views/oauth-consent.hbs +138 -0
  124. package/scaffold/views/oauth-disconnect-confirm.hbs +57 -0
@@ -0,0 +1,249 @@
1
+ "use strict";
2
+ /**
3
+ * Redirect-URI policy: what may be registered, and what a registered entry
4
+ * accepts at the authorization endpoint.
5
+ *
6
+ * Both questions are answered from one classifier, `classifyRedirectUri`,
7
+ * because the two halves only work as a pair. A shape registration stores but
8
+ * the matcher will not parse is worse than a shape neither accepts: the client
9
+ * registers, believes it is connected, and then fails at the authorization
10
+ * endpoint with `redirect_uri is not registered`. Keeping the rule in one
11
+ * function is what makes that disagreement impossible rather than unlikely.
12
+ *
13
+ * ## The three shapes
14
+ *
15
+ * RFC 8252 (OAuth 2.0 for Native Apps) names exactly three ways a client may be
16
+ * called back, and this server accepts those three and nothing else:
17
+ *
18
+ * `https` an absolute https URL — §7.2, the claimed-https redirect
19
+ * a hosted connector such as Claude's web app uses.
20
+ * `loopback-http` `http` on `localhost` or `127.0.0.1` — §7.3, because a
21
+ * native app cannot terminate TLS on the loopback interface.
22
+ * `private-use` a hierarchical URI in an application's own scheme, such as
23
+ * `com.example.app:/oauth2redirect/cb` or
24
+ * `claude://oauth/callback` — §7.1, how a desktop or mobile
25
+ * app is handed the response by its operating system.
26
+ *
27
+ * ## The matching rule
28
+ *
29
+ * **Exact string match**, with exactly one exception: a registered
30
+ * `loopback-http` entry matches a presented URI that differs only in port. That
31
+ * is RFC 8252 §7.3, and it is what Claude Code's published Client ID Metadata
32
+ * Document requires — it binds an ephemeral loopback port at connect time, so
33
+ * the port it will present is not knowable at registration.
34
+ *
35
+ * The exception is scoped to `loopback-http` and not merely to a loopback
36
+ * *host*. A private-use scheme has no TCP port to be ephemeral: the operating
37
+ * system routes it by scheme name, so `myapp://localhost/cb` gets the plain
38
+ * exact rule even though its host happens to read as loopback.
39
+ *
40
+ * The exception lives here, in one function with its own test table, rather
41
+ * than being threaded through the authorization controller as a special case.
42
+ * A redirect-URI check that is spread across a controller is how the umbrella
43
+ * spec's "declared-but-unenforced redirect URI lists" defect happens: the list
44
+ * is stored, the comparison drifts, and nothing says so.
45
+ *
46
+ * Nothing here reads a database or a request. It compares two strings.
47
+ */
48
+ Object.defineProperty(exports, "__esModule", { value: true });
49
+ exports.classifyRedirectUri = classifyRedirectUri;
50
+ exports.matchRedirectUri = matchRedirectUri;
51
+ exports.matchesAnyRedirectUri = matchesAnyRedirectUri;
52
+ exports.isLoopbackHost = isLoopbackHost;
53
+ /** Hosts RFC 8252 §7.3 lets a native app bind an ephemeral port on. */
54
+ const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1']);
55
+ /**
56
+ * Schemes a private-use redirect may not claim, on three separate grounds.
57
+ *
58
+ * **The WHATWG URL standard's "special" schemes** — `http`, `https`, `ws`,
59
+ * `wss`, `ftp`, `file`. These are network and filesystem locations rather than
60
+ * application callbacks, and `file:///etc/…` in particular parses into a
61
+ * perfectly well-formed hierarchical URL, so the hierarchy rule alone would
62
+ * admit it. `http` and `https` never reach this list today, because the branches
63
+ * above answer them first; they are named anyway so that reordering those
64
+ * branches cannot quietly re-offer `http://evil.example/cb` — a shape already
65
+ * refused as a non-loopback redirect — as a private-use URI.
66
+ *
67
+ * **Schemes whose payload is content or code rather than a location** —
68
+ * `javascript`, `vbscript`, `data`, `blob`, `about`, `filesystem`,
69
+ * `view-source`. A redirect URI is somewhere a browser is *sent*. Sending it to
70
+ * one of these runs script or renders attacker-supplied content in the
71
+ * response, which is the open-redirect defect with a cross-site scripting bug
72
+ * on top. The hierarchy rule below already refuses the usual spellings —
73
+ * `javascript:alert(1)` has an opaque path — but `javascript:/alert(1)` does
74
+ * not, and a security boundary should not rest on a URL-parsing detail.
75
+ *
76
+ * **Privileged browser origins** — `chrome`, `chrome-extension`,
77
+ * `moz-extension`, `resource`. Never a legitimate OAuth callback, and a
78
+ * redirect into one is a request to run inside a context this server has no
79
+ * business reaching.
80
+ */
81
+ const RESERVED_SCHEMES = new Set([
82
+ // Special schemes (WHATWG URL).
83
+ 'http',
84
+ 'https',
85
+ 'ws',
86
+ 'wss',
87
+ 'ftp',
88
+ 'file',
89
+ // Content and code.
90
+ 'javascript',
91
+ 'vbscript',
92
+ 'data',
93
+ 'blob',
94
+ 'about',
95
+ 'filesystem',
96
+ 'view-source',
97
+ // Privileged browser origins.
98
+ 'chrome',
99
+ 'chrome-extension',
100
+ 'moz-extension',
101
+ 'resource',
102
+ ]);
103
+ const SHAPE_REASON = 'must be https, http on a loopback host, or a private-use scheme (RFC 8252 §7.1)';
104
+ const HIERARCHY_REASON = 'uses a private-use scheme but is not a hierarchical URI, as in com.example.app:/callback';
105
+ const refuse = (reason) => ({ accepted: false, reason });
106
+ /**
107
+ * Is this URI hierarchical — does anything after the scheme start with `/`?
108
+ *
109
+ * This is the structural line between a redirect *location* and a URI whose
110
+ * body is an opaque payload. `claude://oauth/callback` and
111
+ * `com.example.app:/oauth2redirect/cb` are hierarchical;
112
+ * `urn:ietf:wg:oauth:2.0:oob`, `mailto:a@b.example` and `javascript:alert(1)`
113
+ * are not, and none of those names a place an operating system can deliver an
114
+ * authorization response to.
115
+ *
116
+ * Read off the serialized `href` rather than `pathname`, because a private-use
117
+ * URI may carry its target in the authority (`myapp://callback`, empty path) or
118
+ * in the path (`com.example.app:/callback`, empty host), and only the
119
+ * serialization sees both.
120
+ */
121
+ function isHierarchical(url) {
122
+ return url.href.slice(url.protocol.length).startsWith('/');
123
+ }
124
+ /**
125
+ * Classify one redirect URI, or say why it is refused.
126
+ *
127
+ * The single source of the policy. `validateRedirectUris` in the client
128
+ * registry calls it to decide whether a URI may be **stored**; `matchRedirectUri`
129
+ * below calls it to decide whether a stored or presented URI may be
130
+ * **compared**.
131
+ */
132
+ function classifyRedirectUri(value) {
133
+ if (typeof value !== 'string' || value === '')
134
+ return refuse('is not an absolute URI');
135
+ let url;
136
+ try {
137
+ url = new URL(value);
138
+ }
139
+ catch {
140
+ return refuse('is not an absolute URI');
141
+ }
142
+ // RFC 6749 §3.1.2 forbids a fragment on a redirection endpoint, and the
143
+ // fragment is where an implicit-style authorization response's own parameters
144
+ // would land.
145
+ if (url.hash !== '')
146
+ return refuse('must not carry a fragment');
147
+ // `url.protocol` is `scheme:`, already lowercased and already known to match
148
+ // RFC 3986 scheme syntax — `new URL` would have thrown otherwise.
149
+ const scheme = url.protocol.slice(0, -1);
150
+ if (scheme === 'https') {
151
+ if (!url.hostname)
152
+ return refuse(SHAPE_REASON);
153
+ return { accepted: true, shape: 'https', url };
154
+ }
155
+ if (scheme === 'http') {
156
+ if (!isLoopbackHost(url.hostname))
157
+ return refuse(SHAPE_REASON);
158
+ return { accepted: true, shape: 'loopback-http', url };
159
+ }
160
+ // Everything remaining is a candidate private-use scheme (RFC 8252 §7.1).
161
+ // A one-character scheme is refused because `c:/Users/…` parses as scheme `c`
162
+ // with path `/Users/…`, and a Windows path is not a callback.
163
+ if (scheme.length < 2 || RESERVED_SCHEMES.has(scheme))
164
+ return refuse(SHAPE_REASON);
165
+ if (!isHierarchical(url))
166
+ return refuse(HIERARCHY_REASON);
167
+ // `claude://` parses, is hierarchical, and names nowhere at all.
168
+ if (!url.hostname && !url.pathname)
169
+ return refuse(HIERARCHY_REASON);
170
+ return { accepted: true, shape: 'private-use', url };
171
+ }
172
+ /**
173
+ * Parse, or refuse.
174
+ *
175
+ * A registry row that is not an acceptable redirect URI is a registration bug
176
+ * rather than a match question, and `matchRedirectUri` answers it the same way
177
+ * it answers a hostile input: no. Routing this through the same classifier
178
+ * registration uses means a row that should never have been written cannot be
179
+ * matched even by an exact string comparison, which is the case that would
180
+ * otherwise sail through the fast path below.
181
+ */
182
+ function parse(value) {
183
+ const verdict = classifyRedirectUri(value);
184
+ return verdict.accepted ? { shape: verdict.shape, url: verdict.url } : undefined;
185
+ }
186
+ /**
187
+ * Does `presented` satisfy the registered entry `registered`?
188
+ *
189
+ * @param registered one entry from the client's `redirect_uris`
190
+ * @param presented the `redirect_uri` the authorization request carried
191
+ */
192
+ function matchRedirectUri(registered, presented) {
193
+ const registeredUri = parse(registered);
194
+ const presentedUri = parse(presented);
195
+ // Both must be acceptable redirect URIs before either the fast path or the
196
+ // exception applies. Checking this *before* the string comparison is
197
+ // deliberate: two identical strings that are not acceptable redirect URIs —
198
+ // `javascript:alert(1)` against itself, say — must not match, because a row
199
+ // like that should never have been written and matching it would hide the
200
+ // fact that it was.
201
+ if (!registeredUri || !presentedUri)
202
+ return false;
203
+ // The rule. Everything below is the one exception to it.
204
+ if (registered === presented)
205
+ return true;
206
+ // The exception is about an ephemeral *TCP port*, and a private-use scheme
207
+ // has none: the operating system routes it by scheme name. So
208
+ // `myapp://localhost/cb` is matched exactly, loopback-looking authority or
209
+ // not. Written as a refusal of the new shape rather than as a requirement of
210
+ // `loopback-http`, because keying it on `loopback-http` would also drop the
211
+ // relaxation `https://localhost/cb` gets today — a narrowing this change has
212
+ // no business making on its way past.
213
+ if (registeredUri.shape === 'private-use')
214
+ return false;
215
+ const registeredUrl = registeredUri.url;
216
+ const presentedUrl = presentedUri.url;
217
+ if (!isLoopbackHost(registeredUrl.hostname))
218
+ return false;
219
+ // The intent to use an ephemeral port is declared by the *registration*. A
220
+ // registration for a public host is not relaxed because the presented value
221
+ // happens to be a loopback address, so the hosts must agree — and `localhost`
222
+ // and `127.0.0.1` are two different hosts, since RFC 8252 relaxes the port
223
+ // rather than name resolution.
224
+ if (registeredUrl.hostname !== presentedUrl.hostname)
225
+ return false;
226
+ // Only the port may differ. Everything else is compared through the parsed
227
+ // URL rather than the raw string, because at this point the two strings are
228
+ // already known to differ and the port is what is allowed to account for it.
229
+ return (registeredUrl.protocol === presentedUrl.protocol &&
230
+ registeredUrl.username === presentedUrl.username &&
231
+ registeredUrl.password === presentedUrl.password &&
232
+ registeredUrl.pathname === presentedUrl.pathname &&
233
+ registeredUrl.search === presentedUrl.search &&
234
+ registeredUrl.hash === presentedUrl.hash);
235
+ }
236
+ /**
237
+ * Does any registered entry accept the presented URI?
238
+ *
239
+ * The authorization controller's actual question, kept next to the rule it is
240
+ * built from so a caller never has to write the loop itself.
241
+ */
242
+ function matchesAnyRedirectUri(registered, presented) {
243
+ return registered.some((entry) => matchRedirectUri(entry, presented));
244
+ }
245
+ /** Is this host one the loopback exception applies to? */
246
+ function isLoopbackHost(host) {
247
+ return LOOPBACK_HOSTS.has(host);
248
+ }
249
+ //# sourceMappingURL=redirect-uri.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redirect-uri.js","sourceRoot":"","sources":["../src/redirect-uri.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ import { Router, type RequestHandler } from 'express';
2
+ import type { ClientRegistryService } from './client-registry-service.js';
3
+ import { type RateLimiterOptions } from './rate-limit.js';
4
+ import type { OauthClient } from './types.js';
5
+ /**
6
+ * `POST /oauth/register` — RFC 7591 open dynamic client registration.
7
+ *
8
+ * ## The one shape that lifts from the donor, and everything that does not
9
+ *
10
+ * Paywalls' `/oauth/register` contributes its response vocabulary: `client_id`,
11
+ * `client_id_issued_at`, the echoed metadata, `grant_types`,
12
+ * `token_endpoint_auth_method`, `response_types` and `scope`. That is about
13
+ * twenty-five lines' worth of shape and it is reproduced below.
14
+ *
15
+ * Its authentication model inverts and is discarded whole. Paywalls reads
16
+ * `req.user`, refuses anyone who is not a `service-user`, looks up that user's
17
+ * account, and creates a row owned by it. Open registration has no caller and
18
+ * no account, so what replaces that gate had to be written fresh:
19
+ * `redirect_uris` required and validated (paywalls never accepts the field at
20
+ * all), a per-IP rate limit here, and a row ceiling in the registry.
21
+ *
22
+ * ## Thin dispatch
23
+ *
24
+ * Parse, call, format. Every rule about what a client may register lives in
25
+ * `ClientRegistryService`, so the seed script and this endpoint cannot disagree
26
+ * about what a valid client is.
27
+ */
28
+ export interface RegistrationHandlerDeps {
29
+ clientRegistry: ClientRegistryService;
30
+ }
31
+ /**
32
+ * The RFC 7591 registration response.
33
+ *
34
+ * `client_secret` is deliberately absent rather than null: this server issues
35
+ * only public clients, and a client that found the field would try to use it.
36
+ */
37
+ export declare function toRegistrationResponse(client: OauthClient): Record<string, unknown>;
38
+ export declare function createRegistrationHandlers(deps: RegistrationHandlerDeps): {
39
+ register: RequestHandler;
40
+ };
41
+ export interface RegistrationRouterOptions extends RegistrationHandlerDeps {
42
+ /**
43
+ * Rate-limiter settings, or `false` to mount none.
44
+ *
45
+ * `false` exists for an app that puts its own limiter in front — a shared
46
+ * one across instances, say. It is not a convenience: an unlimited
47
+ * unauthenticated write endpoint is exactly what the abuse controls are for,
48
+ * so turning this off means taking on the job elsewhere.
49
+ */
50
+ rateLimit?: RateLimiterOptions | false;
51
+ /** Maximum accepted body size. A registration document is small. */
52
+ bodyLimit?: string;
53
+ }
54
+ /**
55
+ * `POST /oauth/register`, with its abuse controls attached.
56
+ *
57
+ * The router parses its own JSON body rather than relying on an app-level
58
+ * `express.json()`, so the endpoint works whatever order the host app mounts
59
+ * its middleware in — and so the body limit here is the one that applies.
60
+ */
61
+ export declare function createRegistrationRouter(options: RegistrationRouterOptions): Router;
62
+ //# sourceMappingURL=registration-controller.d.ts.map
@@ -0,0 +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;;;;;GAKG;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"}
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toRegistrationResponse = toRegistrationResponse;
4
+ exports.createRegistrationHandlers = createRegistrationHandlers;
5
+ exports.createRegistrationRouter = createRegistrationRouter;
6
+ const express_1 = require("express");
7
+ const errors_js_1 = require("./errors.js");
8
+ const rate_limit_js_1 = require("./rate-limit.js");
9
+ /**
10
+ * The RFC 7591 registration response.
11
+ *
12
+ * `client_secret` is deliberately absent rather than null: this server issues
13
+ * only public clients, and a client that found the field would try to use it.
14
+ */
15
+ function toRegistrationResponse(client) {
16
+ return {
17
+ client_id: client.clientId,
18
+ client_id_issued_at: Math.floor(client.createdAt.getTime() / 1000),
19
+ client_name: client.name ?? undefined,
20
+ client_uri: client.clientUri ?? undefined,
21
+ logo_uri: client.logoUri ?? undefined,
22
+ contacts: client.contacts.length > 0 ? client.contacts : undefined,
23
+ policy_uri: client.policyUri ?? undefined,
24
+ tos_uri: client.tosUri ?? undefined,
25
+ software_id: client.softwareId ?? undefined,
26
+ software_version: client.softwareVersion ?? undefined,
27
+ redirect_uris: client.redirectUris,
28
+ grant_types: client.grantTypes,
29
+ response_types: client.responseTypes,
30
+ token_endpoint_auth_method: client.tokenEndpointAuthMethod,
31
+ scope: client.scopes.join(' '),
32
+ };
33
+ }
34
+ function createRegistrationHandlers(deps) {
35
+ const { clientRegistry } = deps;
36
+ const register = async (req, res, next) => {
37
+ const body = req.body;
38
+ if (typeof body !== 'object' || body === null || Array.isArray(body)) {
39
+ res.status(400).json({
40
+ error: 'invalid_client_metadata',
41
+ error_description: 'the request body must be a JSON object',
42
+ });
43
+ return;
44
+ }
45
+ try {
46
+ const client = await clientRegistry.registerDynamic(body);
47
+ res.status(201).json(toRegistrationResponse(client));
48
+ }
49
+ catch (err) {
50
+ // RFC 7591 §3.2.2 error bodies. Translated here rather than left to the
51
+ // global handler, because the registry's refusals are part of this
52
+ // endpoint's contract and a 500 would tell a client to retry.
53
+ if ((0, errors_js_1.isClientRegistryError)(err)) {
54
+ res.status(err.status).json({ error: err.code, error_description: err.message });
55
+ return;
56
+ }
57
+ next(err);
58
+ }
59
+ };
60
+ return { register };
61
+ }
62
+ /**
63
+ * `POST /oauth/register`, with its abuse controls attached.
64
+ *
65
+ * The router parses its own JSON body rather than relying on an app-level
66
+ * `express.json()`, so the endpoint works whatever order the host app mounts
67
+ * its middleware in — and so the body limit here is the one that applies.
68
+ */
69
+ function createRegistrationRouter(options) {
70
+ const handlers = createRegistrationHandlers(options);
71
+ const router = (0, express_1.Router)();
72
+ const middleware = [(0, express_1.json)({ limit: options.bodyLimit ?? '16kb' })];
73
+ if (options.rateLimit !== false) {
74
+ middleware.push((0, rate_limit_js_1.createRegistrationRateLimiter)(options.rateLimit ?? {}).middleware);
75
+ }
76
+ router.post('/oauth/register', ...middleware, handlers.register);
77
+ return router;
78
+ }
79
+ //# sourceMappingURL=registration-controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registration-controller.js","sourceRoot":"","sources":["../src/registration-controller.ts"],"names":[],"mappings":";;AAyCA,wDAkBC;AAED,gEA+BC;AAuBD,4DAYC;AA/HD,qCAA4D;AAG5D,2CAAoD;AACpD,mDAAyF;AA+BzF;;;;;GAKG;AACH,SAAgB,sBAAsB,CAAC,MAAmB;IACxD,OAAO;QACL,SAAS,EAAE,MAAM,CAAC,QAAQ;QAC1B,mBAAmB,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC;QAClE,WAAW,EAAE,MAAM,CAAC,IAAI,IAAI,SAAS;QACrC,UAAU,EAAE,MAAM,CAAC,SAAS,IAAI,SAAS;QACzC,QAAQ,EAAE,MAAM,CAAC,OAAO,IAAI,SAAS;QACrC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;QAClE,UAAU,EAAE,MAAM,CAAC,SAAS,IAAI,SAAS;QACzC,OAAO,EAAE,MAAM,CAAC,MAAM,IAAI,SAAS;QACnC,WAAW,EAAE,MAAM,CAAC,UAAU,IAAI,SAAS;QAC3C,gBAAgB,EAAE,MAAM,CAAC,eAAe,IAAI,SAAS;QACrD,aAAa,EAAE,MAAM,CAAC,YAAY;QAClC,WAAW,EAAE,MAAM,CAAC,UAAU;QAC9B,cAAc,EAAE,MAAM,CAAC,aAAa;QACpC,0BAA0B,EAAE,MAAM,CAAC,uBAAuB;QAC1D,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;KAC/B,CAAC;AACJ,CAAC;AAED,SAAgB,0BAA0B,CAAC,IAA6B;IAGtE,MAAM,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC;IAEhC,MAAM,QAAQ,GAAmB,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACxD,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;QACtB,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACrE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACnB,KAAK,EAAE,yBAAyB;gBAChC,iBAAiB,EAAE,wCAAwC;aAC5D,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,eAAe,CAAC,IAA+B,CAAC,CAAC;YACrF,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAC,CAAC;QACvD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,wEAAwE;YACxE,mEAAmE;YACnE,8DAA8D;YAC9D,IAAI,IAAA,iCAAqB,EAAC,GAAG,CAAC,EAAE,CAAC;gBAC/B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,IAAI,EAAE,iBAAiB,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;gBACjF,OAAO;YACT,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,CAAC;QACZ,CAAC;IACH,CAAC,CAAC;IAEF,OAAO,EAAE,QAAQ,EAAE,CAAC;AACtB,CAAC;AAgBD;;;;;;GAMG;AACH,SAAgB,wBAAwB,CAAC,OAAkC;IACzE,MAAM,QAAQ,GAAG,0BAA0B,CAAC,OAAO,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,IAAA,gBAAM,GAAE,CAAC;IAExB,MAAM,UAAU,GAAqB,CAAC,IAAA,cAAI,EAAC,EAAE,KAAK,EAAE,OAAO,CAAC,SAAS,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC;IACpF,IAAI,OAAO,CAAC,SAAS,KAAK,KAAK,EAAE,CAAC;QAChC,UAAU,CAAC,IAAI,CAAC,IAAA,6CAA6B,EAAC,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC;IACrF,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,iBAAiB,EAAE,GAAG,UAAU,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAEjE,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type { ClientRegistryService } from './client-registry-service.js';
2
+ import type { OauthClient } from './types.js';
3
+ /**
4
+ * Authenticating the client behind a back-channel POST.
5
+ *
6
+ * Every client this server issues to is public, so `token_endpoint_auth_method`
7
+ * is `none` and "authentication" here is nothing more than a `client_id` that
8
+ * names a registered, enabled client. That is a small rule with two edges worth
9
+ * keeping in one place: an unknown id and a **suspended** one both answer `401
10
+ * invalid_client`, and the suspension check is the whole reason the `status`
11
+ * column exists — a client whose row is still pointed at by live tokens is
12
+ * stopped here rather than deleted.
13
+ *
14
+ * `POST /oauth/token` and `POST /oauth/revoke` both begin this way. A second
15
+ * copy of the check is the one that would forget suspension, and then a retired
16
+ * client could still reach one of the two endpoints.
17
+ */
18
+ export declare function resolveRequestClient(clientRegistry: ClientRegistryService, body: unknown): Promise<OauthClient>;
19
+ //# sourceMappingURL=request-client.d.ts.map
@@ -0,0 +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;AACH,wBAAsB,oBAAoB,CACxC,cAAc,EAAE,qBAAqB,EACrC,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC,WAAW,CAAC,CAUtB"}
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveRequestClient = resolveRequestClient;
4
+ const body_field_js_1 = require("./body-field.js");
5
+ const oauth_error_js_1 = require("./oauth-error.js");
6
+ /**
7
+ * Authenticating the client behind a back-channel POST.
8
+ *
9
+ * Every client this server issues to is public, so `token_endpoint_auth_method`
10
+ * is `none` and "authentication" here is nothing more than a `client_id` that
11
+ * names a registered, enabled client. That is a small rule with two edges worth
12
+ * keeping in one place: an unknown id and a **suspended** one both answer `401
13
+ * invalid_client`, and the suspension check is the whole reason the `status`
14
+ * column exists — a client whose row is still pointed at by live tokens is
15
+ * stopped here rather than deleted.
16
+ *
17
+ * `POST /oauth/token` and `POST /oauth/revoke` both begin this way. A second
18
+ * copy of the check is the one that would forget suspension, and then a retired
19
+ * client could still reach one of the two endpoints.
20
+ */
21
+ async function resolveRequestClient(clientRegistry, body) {
22
+ const clientId = (0, body_field_js_1.required)(body, 'client_id');
23
+ const client = await clientRegistry.findByClientId(clientId);
24
+ if (!client) {
25
+ throw new oauth_error_js_1.OAuthError('invalid_client', 'unknown client_id');
26
+ }
27
+ if (client.status !== 'enabled') {
28
+ throw new oauth_error_js_1.OAuthError('invalid_client', 'this client is suspended');
29
+ }
30
+ return client;
31
+ }
32
+ //# sourceMappingURL=request-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-client.js","sourceRoot":"","sources":["../src/request-client.ts"],"names":[],"mappings":";;AAoBA,oDAaC;AAjCD,mDAA2C;AAE3C,qDAA8C;AAG9C;;;;;;;;;;;;;;GAcG;AACI,KAAK,UAAU,oBAAoB,CACxC,cAAqC,EACrC,IAAa;IAEb,MAAM,QAAQ,GAAG,IAAA,wBAAQ,EAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;IAC7D,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,2BAAU,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,CAAC;IAC9D,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAChC,MAAM,IAAI,2BAAU,CAAC,gBAAgB,EAAE,0BAA0B,CAAC,CAAC;IACrE,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,28 @@
1
+ import { Router, type RequestHandler } from 'express';
2
+ import type { ClientRegistryService } from './client-registry-service.js';
3
+ import { type RevocationService } from './revocation-service.js';
4
+ import { type TokenService } from './token-service.js';
5
+ export interface RevocationHandlerDeps {
6
+ clientRegistry: ClientRegistryService;
7
+ tokenService: TokenService;
8
+ revocationService: RevocationService;
9
+ }
10
+ export interface RevocationHandlers {
11
+ revoke: RequestHandler;
12
+ }
13
+ export declare function createRevocationHandlers(deps: RevocationHandlerDeps): RevocationHandlers;
14
+ export interface RevocationRouterOptions extends RevocationHandlerDeps {
15
+ /** Maximum accepted body size. A revocation request is three fields. */
16
+ bodyLimit?: string;
17
+ }
18
+ /**
19
+ * `POST /oauth/revoke`, with both body parsers attached.
20
+ *
21
+ * Form and JSON, for the reason `createTokenRouter` mounts both: the RFC says
22
+ * form and the ecosystem sends JSON, and a client that gets this wrong on the
23
+ * revocation endpoint has already got it right on the token endpoint. The
24
+ * router parses its own body so the endpoint behaves the same whatever order the
25
+ * host application mounts its middleware in.
26
+ */
27
+ export declare function createRevocationRouter(options: RevocationRouterOptions): Router;
28
+ //# sourceMappingURL=revocation-controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revocation-controller.d.ts","sourceRoot":"","sources":["../src/revocation-controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAkC,KAAK,cAAc,EAAiB,MAAM,SAAS,CAAC;AAGrG,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAG1E,OAAO,EAAqB,KAAK,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACpF,OAAO,EAIL,KAAK,YAAY,EAClB,MAAM,oBAAoB,CAAC;AAwF5B,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,qBAAqB,CAAC;IACtC,YAAY,EAAE,YAAY,CAAC;IAC3B,iBAAiB,EAAE,iBAAiB,CAAC;CACtC;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,qBAAqB,GAAG,kBAAkB,CAiFxF;AAED,MAAM,WAAW,uBAAwB,SAAQ,qBAAqB;IACpE,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,uBAAuB,GAAG,MAAM,CAa/E"}