@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.
- package/capability.json +120 -0
- package/dist/authorization-code-service.d.ts +105 -0
- package/dist/authorization-code-service.d.ts.map +1 -0
- package/dist/authorization-code-service.js +142 -0
- package/dist/authorization-code-service.js.map +1 -0
- package/dist/bearer.d.ts +146 -0
- package/dist/bearer.d.ts.map +1 -0
- package/dist/bearer.js +150 -0
- package/dist/bearer.js.map +1 -0
- package/dist/body-field.d.ts +24 -0
- package/dist/body-field.d.ts.map +1 -0
- package/dist/body-field.js +43 -0
- package/dist/body-field.js.map +1 -0
- package/dist/client-registry-service.d.ts +120 -0
- package/dist/client-registry-service.d.ts.map +1 -0
- package/dist/client-registry-service.js +450 -0
- package/dist/client-registry-service.js.map +1 -0
- package/dist/config.d.ts +82 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +157 -0
- package/dist/config.js.map +1 -0
- package/dist/connections-controller.d.ts +106 -0
- package/dist/connections-controller.d.ts.map +1 -0
- package/dist/connections-controller.js +161 -0
- package/dist/connections-controller.js.map +1 -0
- package/dist/consent-controller.d.ts +197 -0
- package/dist/consent-controller.d.ts.map +1 -0
- package/dist/consent-controller.js +532 -0
- package/dist/consent-controller.js.map +1 -0
- package/dist/discovery-controller.d.ts +16 -0
- package/dist/discovery-controller.d.ts.map +1 -0
- package/dist/discovery-controller.js +52 -0
- package/dist/discovery-controller.js.map +1 -0
- package/dist/discovery.d.ts +95 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +101 -0
- package/dist/discovery.js.map +1 -0
- package/dist/errors.d.ts +26 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +41 -0
- package/dist/errors.js.map +1 -0
- package/dist/express.d.ts +66 -0
- package/dist/express.d.ts.map +1 -0
- package/dist/express.js +3 -0
- package/dist/express.js.map +1 -0
- package/dist/grant-service.d.ts +102 -0
- package/dist/grant-service.d.ts.map +1 -0
- package/dist/grant-service.js +137 -0
- package/dist/grant-service.js.map +1 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +148 -0
- package/dist/index.js.map +1 -0
- package/dist/metadata-document.d.ts +60 -0
- package/dist/metadata-document.d.ts.map +1 -0
- package/dist/metadata-document.js +148 -0
- package/dist/metadata-document.js.map +1 -0
- package/dist/oauth-error.d.ts +45 -0
- package/dist/oauth-error.d.ts.map +1 -0
- package/dist/oauth-error.js +51 -0
- package/dist/oauth-error.js.map +1 -0
- package/dist/pkce.d.ts +29 -0
- package/dist/pkce.d.ts.map +1 -0
- package/dist/pkce.js +65 -0
- package/dist/pkce.js.map +1 -0
- package/dist/property-array.d.ts +60 -0
- package/dist/property-array.d.ts.map +1 -0
- package/dist/property-array.js +31 -0
- package/dist/property-array.js.map +1 -0
- package/dist/public-id.d.ts +35 -0
- package/dist/public-id.d.ts.map +1 -0
- package/dist/public-id.js +76 -0
- package/dist/public-id.js.map +1 -0
- package/dist/rate-limit.d.ts +56 -0
- package/dist/rate-limit.d.ts.map +1 -0
- package/dist/rate-limit.js +55 -0
- package/dist/rate-limit.js.map +1 -0
- package/dist/redirect-uri.d.ts +89 -0
- package/dist/redirect-uri.d.ts.map +1 -0
- package/dist/redirect-uri.js +249 -0
- package/dist/redirect-uri.js.map +1 -0
- package/dist/registration-controller.d.ts +62 -0
- package/dist/registration-controller.d.ts.map +1 -0
- package/dist/registration-controller.js +79 -0
- package/dist/registration-controller.js.map +1 -0
- package/dist/request-client.d.ts +19 -0
- package/dist/request-client.d.ts.map +1 -0
- package/dist/request-client.js +32 -0
- package/dist/request-client.js.map +1 -0
- package/dist/revocation-controller.d.ts +28 -0
- package/dist/revocation-controller.d.ts.map +1 -0
- package/dist/revocation-controller.js +176 -0
- package/dist/revocation-controller.js.map +1 -0
- package/dist/revocation-service.d.ts +113 -0
- package/dist/revocation-service.d.ts.map +1 -0
- package/dist/revocation-service.js +121 -0
- package/dist/revocation-service.js.map +1 -0
- package/dist/token-controller.d.ts +79 -0
- package/dist/token-controller.d.ts.map +1 -0
- package/dist/token-controller.js +207 -0
- package/dist/token-controller.js.map +1 -0
- package/dist/token-service.d.ts +175 -0
- package/dist/token-service.d.ts.map +1 -0
- package/dist/token-service.js +251 -0
- package/dist/token-service.js.map +1 -0
- package/dist/types.d.ts +130 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/dist/user-store.d.ts +110 -0
- package/dist/user-store.d.ts.map +1 -0
- package/dist/user-store.js +40 -0
- package/dist/user-store.js.map +1 -0
- package/package.json +37 -0
- package/scaffold/prisma/schema/oauth-clients.prisma +80 -0
- package/scaffold/reference/oauth-server.ref.md +478 -0
- package/scaffold/src/middleware/oauth-server.ts +227 -0
- package/scaffold/src/scripts/seed-oauth-clients.ts +72 -0
- package/scaffold/src/services/oauth-server.service.ts +101 -0
- package/scaffold/styles/oauth-server.css +345 -0
- package/scaffold/views/oauth-connected-clients.hbs +98 -0
- package/scaffold/views/oauth-consent-error.hbs +22 -0
- package/scaffold/views/oauth-consent.hbs +138 -0
- package/scaffold/views/oauth-disconnect-confirm.hbs +57 -0
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SCOPE_DESCRIPTIONS_LOCAL = void 0;
|
|
4
|
+
exports.toScopeViews = toScopeViews;
|
|
5
|
+
exports.scopeDescriptionsFrom = scopeDescriptionsFrom;
|
|
6
|
+
exports.warnUndescribedScopes = warnUndescribedScopes;
|
|
7
|
+
exports.provenanceFor = provenanceFor;
|
|
8
|
+
exports.safeLogoUri = safeLogoUri;
|
|
9
|
+
exports.monogramFor = monogramFor;
|
|
10
|
+
exports.createConsentHandlers = createConsentHandlers;
|
|
11
|
+
exports.createConsentRouter = createConsentRouter;
|
|
12
|
+
const express_1 = require("express");
|
|
13
|
+
const pkce_js_1 = require("./pkce.js");
|
|
14
|
+
const redirect_uri_js_1 = require("./redirect-uri.js");
|
|
15
|
+
require("./express.js");
|
|
16
|
+
/**
|
|
17
|
+
* `GET` and `POST /oauth/authorize` — the consent screen.
|
|
18
|
+
*
|
|
19
|
+
* This is the product of the capability. Everything else is plumbing that makes
|
|
20
|
+
* this page's answer enforceable, and the page belongs to the application it is
|
|
21
|
+
* rendered in: it is one of the app's own pages, met at the moment somebody is
|
|
22
|
+
* deciding whether to trust a third party. It is not a protocol console.
|
|
23
|
+
*
|
|
24
|
+
* ## One page, three variants
|
|
25
|
+
*
|
|
26
|
+
* Authenticated, inline login, inline signup — following both donors, because
|
|
27
|
+
* an authorization request is a long query string and round-tripping it through
|
|
28
|
+
* cap-users' `?next=` would mean URL-encoding a URL into a redirect target,
|
|
29
|
+
* which is fragile and is the classic open-redirect shape. The request stays on
|
|
30
|
+
* the page in hidden fields and posts back here.
|
|
31
|
+
*
|
|
32
|
+
* **Signing in is the application's decision, not ours.** Both faces resolve
|
|
33
|
+
* credentials through injected `signIn` and `register` ports rather than
|
|
34
|
+
* through cap-users' `authService`, so an application whose own login admits
|
|
35
|
+
* somebody this page would refuse cannot end up with two doors that disagree
|
|
36
|
+
* about the same password. See `ConsentSignIn` for the failure that produced
|
|
37
|
+
* the ports.
|
|
38
|
+
*
|
|
39
|
+
* **Inline signup does not approve.** Both donors continue straight from a
|
|
40
|
+
* successful inline registration into the approval path, so one button press
|
|
41
|
+
* creates an account *and* grants a third party access to it. Those are two
|
|
42
|
+
* decisions with different consequences, and the second one is the entire point
|
|
43
|
+
* of the page. After signup the consent card is re-rendered in its
|
|
44
|
+
* authenticated variant and a second, explicit Approve is required. The cost is
|
|
45
|
+
* one extra click on the rarest path.
|
|
46
|
+
*
|
|
47
|
+
* ## Two hostile inputs the page is built against
|
|
48
|
+
*
|
|
49
|
+
* **`logo_uri` belongs to a stranger.** Under open dynamic registration the
|
|
50
|
+
* name, description and icon are supplied by an unauthenticated registrant, and
|
|
51
|
+
* they are the entire evidence base for the only decision on the page. So the
|
|
52
|
+
* icon renders inside a **fixed box** whose size is set in CSS and never by the
|
|
53
|
+
* image, an unusable or absent `logo_uri` falls back to a monogram, and the
|
|
54
|
+
* provenance line says in words where the client's claims came from — an
|
|
55
|
+
* operator-configured client can say so, a metadata-document client names the
|
|
56
|
+
* origin its document was fetched from, and a self-registered one says that
|
|
57
|
+
* nothing about it has been verified.
|
|
58
|
+
*
|
|
59
|
+
* **Raw scope strings are not consent.** What `workflows:advance` means is the
|
|
60
|
+
* application's domain knowledge, which the umbrella spec makes explicit — so
|
|
61
|
+
* this capability has no map to ship and no way to invent one. Descriptions
|
|
62
|
+
* come from `app.locals.oauthScopeDescriptions`, published the way
|
|
63
|
+
* cap-oauth-client publishes its provider buttons, and any advertised scope the
|
|
64
|
+
* application did not describe produces a **boot warning naming it**. Asking
|
|
65
|
+
* somebody to approve a string they cannot evaluate is asking a question they
|
|
66
|
+
* are not prepared to answer.
|
|
67
|
+
*
|
|
68
|
+
* ## And the coverage statement
|
|
69
|
+
*
|
|
70
|
+
* The settled data model keys the grant on the *user*, so a client reaches
|
|
71
|
+
* every account that user reaches, present and future. No donor page says
|
|
72
|
+
* anything of the kind, because neither donor had a multi-account model. It
|
|
73
|
+
* goes **before** the Approve button: a person cannot discover afterwards that
|
|
74
|
+
* they approved a connector for accounts they did not have in mind.
|
|
75
|
+
*/
|
|
76
|
+
/** The session cookie cap-users sets. Matched so inline login logs in here. */
|
|
77
|
+
const SESSION_COOKIE = 'auth_id';
|
|
78
|
+
const ONE_YEAR_MS = 365 * 24 * 60 * 60 * 1000;
|
|
79
|
+
exports.SCOPE_DESCRIPTIONS_LOCAL = 'oauthScopeDescriptions';
|
|
80
|
+
/**
|
|
81
|
+
* Turn a scope list into what a page shows, given the application's vocabulary.
|
|
82
|
+
*
|
|
83
|
+
* Exported because the consent screen and the connected-clients screen must
|
|
84
|
+
* describe the same scope with the same words. A user who approved "See your
|
|
85
|
+
* name and email address" and later reads "profile" on the management screen
|
|
86
|
+
* cannot tell they are looking at the same thing.
|
|
87
|
+
*/
|
|
88
|
+
function toScopeViews(scopes, descriptions) {
|
|
89
|
+
return scopes.map((value) => {
|
|
90
|
+
const description = descriptions?.[value];
|
|
91
|
+
const described = typeof description === 'string' && description.trim() !== '';
|
|
92
|
+
return { value, description: described ? description.trim() : value, described };
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The application's scope vocabulary, wherever it was published.
|
|
97
|
+
*
|
|
98
|
+
* `app.locals` wins over anything passed at construction, because it is written
|
|
99
|
+
* later and by the application itself. Merging rather than choosing means the
|
|
100
|
+
* two cannot silently disagree: a factory argument that the page ignored used to
|
|
101
|
+
* be enough to silence the boot warning while the page still showed raw strings.
|
|
102
|
+
*/
|
|
103
|
+
function scopeDescriptionsFrom(req, defaults) {
|
|
104
|
+
const published = req.app?.locals?.[exports.SCOPE_DESCRIPTIONS_LOCAL];
|
|
105
|
+
if (!published)
|
|
106
|
+
return defaults;
|
|
107
|
+
if (!defaults)
|
|
108
|
+
return published;
|
|
109
|
+
return { ...defaults, ...published };
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Warn, once at boot, about every advertised scope the application has not
|
|
113
|
+
* described.
|
|
114
|
+
*
|
|
115
|
+
* Called from the router factory rather than left to the developer to remember,
|
|
116
|
+
* because the failure it prevents is silent: the page renders, the raw string
|
|
117
|
+
* appears where a sentence should be, and nothing anywhere says that a consent
|
|
118
|
+
* screen is asking an unanswerable question.
|
|
119
|
+
*/
|
|
120
|
+
function warnUndescribedScopes(scopesSupported, descriptions, warn = (message) => console.warn(`[cap-oauth-server] ${message}`)) {
|
|
121
|
+
const undescribed = scopesSupported.filter((scope) => typeof descriptions?.[scope] !== 'string' || descriptions[scope].trim() === '');
|
|
122
|
+
if (undescribed.length > 0) {
|
|
123
|
+
warn(`no consent-screen description for ${undescribed.join(', ')} — the consent page will show ` +
|
|
124
|
+
`the raw scope string, which asks the user to approve something they cannot evaluate. ` +
|
|
125
|
+
`Set app.locals.${exports.SCOPE_DESCRIPTIONS_LOCAL}.`);
|
|
126
|
+
}
|
|
127
|
+
return undescribed;
|
|
128
|
+
}
|
|
129
|
+
/** What the page says about where this client's claims came from. */
|
|
130
|
+
function provenanceFor(client) {
|
|
131
|
+
const mode = client.mode;
|
|
132
|
+
if (mode === 'static') {
|
|
133
|
+
return 'This application was configured by the people who run this site.';
|
|
134
|
+
}
|
|
135
|
+
if (mode === 'metadata_document') {
|
|
136
|
+
let origin = client.metadataUri ?? client.clientId;
|
|
137
|
+
try {
|
|
138
|
+
origin = new URL(origin).origin;
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// Left as the raw string; it is escaped by the template either way.
|
|
142
|
+
}
|
|
143
|
+
return `Its details were published at ${origin}.`;
|
|
144
|
+
}
|
|
145
|
+
return 'This application registered itself. Nothing about it has been verified.';
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* An icon URL we are willing to put in an `<img src>`, or nothing.
|
|
149
|
+
*
|
|
150
|
+
* `https` only. `http` would break the page's padlock, and `data:` and
|
|
151
|
+
* `javascript:` are a stranger's payload rendered inside our own origin's page.
|
|
152
|
+
* The box's size is fixed in CSS regardless, so this decides only whether there
|
|
153
|
+
* is an image at all — never how much room it gets.
|
|
154
|
+
*/
|
|
155
|
+
function safeLogoUri(value) {
|
|
156
|
+
if (typeof value !== 'string' || value.trim() === '')
|
|
157
|
+
return undefined;
|
|
158
|
+
try {
|
|
159
|
+
const url = new URL(value);
|
|
160
|
+
return url.protocol === 'https:' ? url.toString() : undefined;
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/** The letter in the icon box when there is no usable image. */
|
|
167
|
+
function monogramFor(client) {
|
|
168
|
+
// The name first, then the `client_id`, then a question mark. Falling through
|
|
169
|
+
// matters because a registrant supplies the name: `" "` and `"!!!"` are
|
|
170
|
+
// both names a stranger can register, and an empty icon box reads as a broken
|
|
171
|
+
// page rather than as a client with a strange name.
|
|
172
|
+
for (const source of [client.name, client.clientId]) {
|
|
173
|
+
if (typeof source !== 'string')
|
|
174
|
+
continue;
|
|
175
|
+
const first = [...source.trim()].find((ch) => /[\p{L}\p{N}]/u.test(ch));
|
|
176
|
+
if (first)
|
|
177
|
+
return first.toUpperCase();
|
|
178
|
+
}
|
|
179
|
+
return '?';
|
|
180
|
+
}
|
|
181
|
+
/** Errors that must be shown on our own page, because we cannot redirect yet. */
|
|
182
|
+
class ConsentRequestError extends Error {
|
|
183
|
+
constructor(message, status = 400) {
|
|
184
|
+
super(message);
|
|
185
|
+
this.name = 'ConsentRequestError';
|
|
186
|
+
this.status = status;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/** Errors that RFC 6749 §4.1.2.1 says go back to the client as a redirect. */
|
|
190
|
+
class RedirectableError extends Error {
|
|
191
|
+
constructor(code, message) {
|
|
192
|
+
super(message);
|
|
193
|
+
this.name = 'RedirectableError';
|
|
194
|
+
this.code = code;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function first(value) {
|
|
198
|
+
if (typeof value === 'string') {
|
|
199
|
+
const trimmed = value.trim();
|
|
200
|
+
return trimmed === '' ? undefined : trimmed;
|
|
201
|
+
}
|
|
202
|
+
// Express hands back an array for a repeated parameter. A repeated
|
|
203
|
+
// `redirect_uri` is not a request we should try to interpret.
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
function parseScopeList(raw) {
|
|
207
|
+
if (!raw)
|
|
208
|
+
return [];
|
|
209
|
+
return [...new Set(raw.split(/\s+/).filter(Boolean))];
|
|
210
|
+
}
|
|
211
|
+
function createConsentHandlers(deps) {
|
|
212
|
+
const { clientRegistry, authorizationCodeService, signIn, register, scopesSupported } = deps;
|
|
213
|
+
const viewName = deps.viewName ?? 'oauth-consent';
|
|
214
|
+
const layout = deps.layout ?? 'main';
|
|
215
|
+
const actionPath = deps.actionPath ?? '/oauth/authorize';
|
|
216
|
+
/**
|
|
217
|
+
* Resolve the client and the redirect URI *before* anything else is judged.
|
|
218
|
+
*
|
|
219
|
+
* The order is RFC 6749 §4.1.2.1's and it is not a style choice: until both
|
|
220
|
+
* are known good there is nowhere safe to send an error, so every other
|
|
221
|
+
* failure has to be reported on our own page. Once they are, every remaining
|
|
222
|
+
* failure goes back to the client as a redirect, because a client left with
|
|
223
|
+
* no answer hangs until its own timeout while the user believes they are done.
|
|
224
|
+
*/
|
|
225
|
+
async function resolveTarget(source) {
|
|
226
|
+
const clientId = first(source.client_id);
|
|
227
|
+
if (!clientId)
|
|
228
|
+
throw new ConsentRequestError('client_id is required');
|
|
229
|
+
let client = await clientRegistry.findByClientId(clientId);
|
|
230
|
+
if (!client && /^https:\/\//i.test(clientId)) {
|
|
231
|
+
// A Client ID Metadata Document client is allowed to arrive unseen: the
|
|
232
|
+
// document at its own URL is its registration. A fetch failure here is a
|
|
233
|
+
// refusal rather than a 500, because the URL was supplied by the caller.
|
|
234
|
+
try {
|
|
235
|
+
client = await clientRegistry.resolveMetadataDocument(clientId);
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
client = null;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (!client)
|
|
242
|
+
throw new ConsentRequestError('unknown client_id');
|
|
243
|
+
if (client.status !== 'enabled')
|
|
244
|
+
throw new ConsentRequestError('this client is suspended', 403);
|
|
245
|
+
const redirectUri = first(source.redirect_uri);
|
|
246
|
+
if (!redirectUri)
|
|
247
|
+
throw new ConsentRequestError('redirect_uri is required');
|
|
248
|
+
if (!(0, redirect_uri_js_1.matchesAnyRedirectUri)(client.redirectUris, redirectUri)) {
|
|
249
|
+
// Never redirect to an unregistered URI to report that it is
|
|
250
|
+
// unregistered. That is the open redirect.
|
|
251
|
+
throw new ConsentRequestError('redirect_uri is not registered for this client');
|
|
252
|
+
}
|
|
253
|
+
return { client, redirectUri };
|
|
254
|
+
}
|
|
255
|
+
/** Everything that can be answered with a redirect, once we may redirect. */
|
|
256
|
+
function parseRequest(source, client) {
|
|
257
|
+
const responseType = first(source.response_type);
|
|
258
|
+
if (responseType !== 'code') {
|
|
259
|
+
throw new RedirectableError('unsupported_response_type', 'this server issues authorization codes; response_type must be "code"');
|
|
260
|
+
}
|
|
261
|
+
const codeChallengeMethod = first(source.code_challenge_method) ?? pkce_js_1.CODE_CHALLENGE_METHOD;
|
|
262
|
+
if (codeChallengeMethod !== pkce_js_1.CODE_CHALLENGE_METHOD) {
|
|
263
|
+
throw new RedirectableError('invalid_request', `code_challenge_method must be ${pkce_js_1.CODE_CHALLENGE_METHOD}`);
|
|
264
|
+
}
|
|
265
|
+
const codeChallenge = first(source.code_challenge);
|
|
266
|
+
if (!(0, pkce_js_1.isValidCodeChallenge)(codeChallenge)) {
|
|
267
|
+
// Required, not optional. OAuth 2.1 requires PKCE for public clients and
|
|
268
|
+
// every client this server will ever see is public.
|
|
269
|
+
throw new RedirectableError('invalid_request', 'a valid S256 code_challenge is required');
|
|
270
|
+
}
|
|
271
|
+
const scopes = parseScopeList(first(source.scope));
|
|
272
|
+
const unknown = scopes.filter((scope) => !scopesSupported.includes(scope));
|
|
273
|
+
if (unknown.length > 0) {
|
|
274
|
+
throw new RedirectableError('invalid_scope', `not supported here: ${unknown.join(' ')}`);
|
|
275
|
+
}
|
|
276
|
+
const clientScopes = client.scopes;
|
|
277
|
+
const disallowed = clientScopes.length > 0 ? scopes.filter((scope) => !clientScopes.includes(scope)) : [];
|
|
278
|
+
if (disallowed.length > 0) {
|
|
279
|
+
throw new RedirectableError('invalid_scope', `this client is not registered for: ${disallowed.join(' ')}`);
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
responseType,
|
|
283
|
+
clientId: client.clientId,
|
|
284
|
+
redirectUri: first(source.redirect_uri),
|
|
285
|
+
scopes: scopes.length > 0 ? scopes : [...clientScopes],
|
|
286
|
+
state: first(source.state),
|
|
287
|
+
codeChallenge,
|
|
288
|
+
codeChallengeMethod,
|
|
289
|
+
resource: first(source.resource),
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
/** Build the redirect back to the client, error or success. */
|
|
293
|
+
function redirectTo(res, redirectUri, params) {
|
|
294
|
+
const url = new URL(redirectUri);
|
|
295
|
+
for (const [key, value] of Object.entries(params)) {
|
|
296
|
+
if (value !== undefined)
|
|
297
|
+
url.searchParams.set(key, value);
|
|
298
|
+
}
|
|
299
|
+
res.redirect(302, url.toString());
|
|
300
|
+
}
|
|
301
|
+
function scopeViews(scopes, req) {
|
|
302
|
+
return toScopeViews(scopes, scopeDescriptionsFrom(req, deps.scopeDescriptions));
|
|
303
|
+
}
|
|
304
|
+
/** The hidden fields that carry the request across a form post. */
|
|
305
|
+
function hiddenFields(request) {
|
|
306
|
+
const fields = [
|
|
307
|
+
['response_type', request.responseType],
|
|
308
|
+
['client_id', request.clientId],
|
|
309
|
+
['redirect_uri', request.redirectUri],
|
|
310
|
+
['scope', request.scopes.join(' ')],
|
|
311
|
+
['state', request.state],
|
|
312
|
+
['code_challenge', request.codeChallenge],
|
|
313
|
+
['code_challenge_method', request.codeChallengeMethod],
|
|
314
|
+
['resource', request.resource],
|
|
315
|
+
];
|
|
316
|
+
return fields
|
|
317
|
+
.filter((entry) => entry[1] !== undefined && entry[1] !== '')
|
|
318
|
+
.map(([name, value]) => ({ name, value }));
|
|
319
|
+
}
|
|
320
|
+
function render(req, res, client, request, variant, extra = {}) {
|
|
321
|
+
res.render(viewName, {
|
|
322
|
+
layout,
|
|
323
|
+
action: actionPath,
|
|
324
|
+
variant,
|
|
325
|
+
isAuthenticated: variant === 'authenticated',
|
|
326
|
+
isLogin: variant === 'login',
|
|
327
|
+
isSignup: variant === 'signup',
|
|
328
|
+
client: {
|
|
329
|
+
name: client.name ?? client.clientId,
|
|
330
|
+
description: client.description ?? undefined,
|
|
331
|
+
clientUri: client.clientUri ?? undefined,
|
|
332
|
+
policyUri: client.policyUri ?? undefined,
|
|
333
|
+
tosUri: client.tosUri ?? undefined,
|
|
334
|
+
logoUri: safeLogoUri(client.logoUri),
|
|
335
|
+
monogram: monogramFor(client),
|
|
336
|
+
provenance: provenanceFor(client),
|
|
337
|
+
mode: client.mode,
|
|
338
|
+
},
|
|
339
|
+
scopes: scopeViews(request.scopes, req),
|
|
340
|
+
hiddenFields: hiddenFields(request),
|
|
341
|
+
...extra,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
/** Which face to show. `res.locals.auth` is cap-users' `addAuthContext`. */
|
|
345
|
+
function variantFor(req, res) {
|
|
346
|
+
const tier = res.locals?.auth?.trustTier;
|
|
347
|
+
return req.user && (tier === 'authenticated' || tier === 'verified')
|
|
348
|
+
? 'authenticated'
|
|
349
|
+
: 'login';
|
|
350
|
+
}
|
|
351
|
+
function reportConsentError(res, err) {
|
|
352
|
+
// Our own page, because there is nowhere safe to send the caller. Rendered
|
|
353
|
+
// rather than JSON: whoever is looking at this is a person in a browser.
|
|
354
|
+
res.status(err.status).render('oauth-consent-error', {
|
|
355
|
+
layout,
|
|
356
|
+
errorMessage: err.message,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
const showConsent = async (req, res, next) => {
|
|
360
|
+
let target;
|
|
361
|
+
try {
|
|
362
|
+
target = await resolveTarget(req.query);
|
|
363
|
+
}
|
|
364
|
+
catch (err) {
|
|
365
|
+
if (err instanceof ConsentRequestError)
|
|
366
|
+
return reportConsentError(res, err);
|
|
367
|
+
return next(err);
|
|
368
|
+
}
|
|
369
|
+
try {
|
|
370
|
+
const request = parseRequest(req.query, target.client);
|
|
371
|
+
render(req, res, target.client, request, variantFor(req, res));
|
|
372
|
+
}
|
|
373
|
+
catch (err) {
|
|
374
|
+
if (err instanceof RedirectableError) {
|
|
375
|
+
return redirectTo(res, target.redirectUri, {
|
|
376
|
+
error: err.code,
|
|
377
|
+
error_description: err.message,
|
|
378
|
+
state: first(req.query.state),
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
next(err);
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
const submitConsent = async (req, res, next) => {
|
|
385
|
+
const body = (req.body ?? {});
|
|
386
|
+
let target;
|
|
387
|
+
try {
|
|
388
|
+
target = await resolveTarget(body);
|
|
389
|
+
}
|
|
390
|
+
catch (err) {
|
|
391
|
+
if (err instanceof ConsentRequestError)
|
|
392
|
+
return reportConsentError(res, err);
|
|
393
|
+
return next(err);
|
|
394
|
+
}
|
|
395
|
+
let request;
|
|
396
|
+
try {
|
|
397
|
+
request = parseRequest(body, target.client);
|
|
398
|
+
}
|
|
399
|
+
catch (err) {
|
|
400
|
+
if (err instanceof RedirectableError) {
|
|
401
|
+
return redirectTo(res, target.redirectUri, {
|
|
402
|
+
error: err.code,
|
|
403
|
+
error_description: err.message,
|
|
404
|
+
state: first(body.state),
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
return next(err);
|
|
408
|
+
}
|
|
409
|
+
const action = first(body.action) ?? 'approve';
|
|
410
|
+
try {
|
|
411
|
+
if (action === 'deny') {
|
|
412
|
+
// A protocol response, not a navigation. If Cancel merely went home,
|
|
413
|
+
// the waiting client would hang until its own timeout while the user
|
|
414
|
+
// believed they had declined. The target is derived from the registry
|
|
415
|
+
// above and never taken from the form, or it is an open redirect.
|
|
416
|
+
return redirectTo(res, request.redirectUri, {
|
|
417
|
+
error: 'access_denied',
|
|
418
|
+
error_description: 'the user declined this request',
|
|
419
|
+
state: request.state,
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
if (action === 'login' || action === 'signup') {
|
|
423
|
+
let user;
|
|
424
|
+
let sessionToken;
|
|
425
|
+
try {
|
|
426
|
+
const result = action === 'login'
|
|
427
|
+
? await signIn({
|
|
428
|
+
email: String(body.email ?? ''),
|
|
429
|
+
password: String(body.password ?? ''),
|
|
430
|
+
sessionToken: req.cookies?.[SESSION_COOKIE],
|
|
431
|
+
})
|
|
432
|
+
: await register({
|
|
433
|
+
email: String(body.email ?? ''),
|
|
434
|
+
password: String(body.password ?? ''),
|
|
435
|
+
passwordConfirmation: String(body.passwordConfirmation ?? ''),
|
|
436
|
+
});
|
|
437
|
+
user = result.user;
|
|
438
|
+
sessionToken = result.sessionToken;
|
|
439
|
+
}
|
|
440
|
+
catch (err) {
|
|
441
|
+
return render(req, res, target.client, request, action, {
|
|
442
|
+
email: typeof body.email === 'string' ? body.email : undefined,
|
|
443
|
+
errorMessage: action === 'login'
|
|
444
|
+
? 'Invalid email or password'
|
|
445
|
+
: err instanceof Error && err.message
|
|
446
|
+
? err.message
|
|
447
|
+
: 'We could not create that account',
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
res.cookie(SESSION_COOKIE, sessionToken, { httpOnly: true, maxAge: ONE_YEAR_MS });
|
|
451
|
+
req.user = user;
|
|
452
|
+
// Signing in is not consenting. Re-render the card and require a
|
|
453
|
+
// second, explicit Approve — see the header.
|
|
454
|
+
return render(req, res, target.client, request, 'authenticated', {
|
|
455
|
+
justSignedIn: true,
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
if (action === 'showSignup' || action === 'showLogin') {
|
|
459
|
+
return render(req, res, target.client, request, action === 'showSignup' ? 'signup' : 'login');
|
|
460
|
+
}
|
|
461
|
+
// Approve.
|
|
462
|
+
const user = req.user;
|
|
463
|
+
if (!user) {
|
|
464
|
+
// Not signed in, so there is nobody to approve on behalf of. Show the
|
|
465
|
+
// login variant rather than redirecting: the request must stay on the
|
|
466
|
+
// page.
|
|
467
|
+
return render(req, res, target.client, request, 'login', {
|
|
468
|
+
errorMessage: 'Please sign in to continue.',
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
const { code } = await authorizationCodeService.issue({
|
|
472
|
+
userId: user.id,
|
|
473
|
+
clientId: target.client.clientId,
|
|
474
|
+
clientPublicId: target.client.publicId,
|
|
475
|
+
redirectUri: request.redirectUri,
|
|
476
|
+
codeChallenge: request.codeChallenge,
|
|
477
|
+
codeChallengeMethod: request.codeChallengeMethod,
|
|
478
|
+
scopes: request.scopes,
|
|
479
|
+
resource: request.resource ?? null,
|
|
480
|
+
});
|
|
481
|
+
// `state` is echoed exactly as it arrived and is never interpreted. It is
|
|
482
|
+
// the client's CSRF token, not ours.
|
|
483
|
+
return redirectTo(res, request.redirectUri, { code, state: request.state });
|
|
484
|
+
}
|
|
485
|
+
catch (err) {
|
|
486
|
+
next(err);
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
return { showConsent, submitConsent };
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* `GET` and `POST /oauth/authorize`.
|
|
493
|
+
*
|
|
494
|
+
* ## Why the undescribed-scope warning fires on the first request
|
|
495
|
+
*
|
|
496
|
+
* It used to fire here, at construction, on the argument that a warning a
|
|
497
|
+
* developer only sees when somebody reaches the consent screen is a warning they
|
|
498
|
+
* see in production. That reasoning is right and the placement was still wrong,
|
|
499
|
+
* because at construction there is nothing true to judge: the descriptions live
|
|
500
|
+
* on `app.locals`, an application publishes them after this router is built —
|
|
501
|
+
* boop injects every capability's mount above an application's own
|
|
502
|
+
* `app.locals` assignments — and the factory argument is one the shipped
|
|
503
|
+
* scaffold does not pass. So the check ran against `undefined` and named every
|
|
504
|
+
* advertised scope, on every boot, however carefully the application had
|
|
505
|
+
* described them. The wishfleet integration met exactly that: three scopes
|
|
506
|
+
* described, three scopes reported missing (boop-2vdy.8.5).
|
|
507
|
+
*
|
|
508
|
+
* A warning that always fires is worse than none, because it trains a developer
|
|
509
|
+
* to scroll past the line that would have told them their consent screen was
|
|
510
|
+
* asking an unanswerable question.
|
|
511
|
+
*
|
|
512
|
+
* So it fires on the first request this router serves, which is the earliest
|
|
513
|
+
* moment the value it must judge exists — and, since the router sits on the
|
|
514
|
+
* application's own mount, that is the first request to the site rather than the
|
|
515
|
+
* first one to reach the consent screen. Once per router, not once per request.
|
|
516
|
+
*/
|
|
517
|
+
function createConsentRouter(options) {
|
|
518
|
+
const handlers = createConsentHandlers(options);
|
|
519
|
+
const router = (0, express_1.Router)();
|
|
520
|
+
let warned = false;
|
|
521
|
+
router.use((req, _res, next) => {
|
|
522
|
+
if (!warned) {
|
|
523
|
+
warned = true;
|
|
524
|
+
warnUndescribedScopes(options.scopesSupported, scopeDescriptionsFrom(req, options.scopeDescriptions), options.warn);
|
|
525
|
+
}
|
|
526
|
+
next();
|
|
527
|
+
});
|
|
528
|
+
router.get('/oauth/authorize', handlers.showConsent);
|
|
529
|
+
router.post('/oauth/authorize', (0, express_1.urlencoded)({ extended: false, limit: options.bodyLimit ?? '16kb' }), handlers.submitConsent);
|
|
530
|
+
return router;
|
|
531
|
+
}
|
|
532
|
+
//# sourceMappingURL=consent-controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consent-controller.js","sourceRoot":"","sources":["../src/consent-controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Router, type RequestHandler } from 'express';
|
|
2
|
+
import { type DiscoveryConfig } from './discovery.js';
|
|
3
|
+
export interface DiscoveryHandlers {
|
|
4
|
+
authorizationServerMetadata: RequestHandler;
|
|
5
|
+
protectedResourceMetadata: RequestHandler;
|
|
6
|
+
}
|
|
7
|
+
export declare function createDiscoveryHandlers(config: DiscoveryConfig): DiscoveryHandlers;
|
|
8
|
+
/**
|
|
9
|
+
* A router carrying both documents at their specified paths.
|
|
10
|
+
*
|
|
11
|
+
* Mounted at the application root — `app.use(oauthDiscoveryRoutes)` — because
|
|
12
|
+
* RFC 8414 and RFC 9728 fix these paths at the origin, and a client will not
|
|
13
|
+
* look for them anywhere else.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createDiscoveryRouter(config: DiscoveryConfig): Router;
|
|
16
|
+
//# sourceMappingURL=discovery-controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery-controller.d.ts","sourceRoot":"","sources":["../src/discovery-controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,cAAc,EAAiB,MAAM,SAAS,CAAC;AAErE,OAAO,EAIL,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AAwBxB,MAAM,WAAW,iBAAiB;IAChC,2BAA2B,EAAE,cAAc,CAAC;IAC5C,yBAAyB,EAAE,cAAc,CAAC;CAC3C;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,eAAe,GAAG,iBAAiB,CAWlF;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CAQrE"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createDiscoveryHandlers = createDiscoveryHandlers;
|
|
4
|
+
exports.createDiscoveryRouter = createDiscoveryRouter;
|
|
5
|
+
const express_1 = require("express");
|
|
6
|
+
const discovery_js_1 = require("./discovery.js");
|
|
7
|
+
/**
|
|
8
|
+
* The `.well-known` routes.
|
|
9
|
+
*
|
|
10
|
+
* Both mount **outside `/api`** and are reachable with no authentication —
|
|
11
|
+
* they are what a client reads *before* it has any credential at all, and a 401
|
|
12
|
+
* here is a connect failure the client cannot recover from.
|
|
13
|
+
*
|
|
14
|
+
* There is no `:resource` parameter and no filesystem read. Two fixed paths,
|
|
15
|
+
* two computed bodies. See the header of `discovery.ts` for why.
|
|
16
|
+
*/
|
|
17
|
+
/** One place the two handlers agree on headers, so they cannot drift. */
|
|
18
|
+
function sendDocument(res, body) {
|
|
19
|
+
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
20
|
+
// Public, because the documents carry no secret and every client fetches
|
|
21
|
+
// them on every connect. An hour is the donor's number and the ecosystem's
|
|
22
|
+
// habit; it is short enough that a scope or endpoint change propagates the
|
|
23
|
+
// same working day.
|
|
24
|
+
res.setHeader('Cache-Control', `public, max-age=${discovery_js_1.DISCOVERY_MAX_AGE_SECONDS}`);
|
|
25
|
+
res.status(200).json(body);
|
|
26
|
+
}
|
|
27
|
+
function createDiscoveryHandlers(config) {
|
|
28
|
+
// Built once at construction rather than per request: the configuration is
|
|
29
|
+
// read at boot, so a running process has one answer and cannot serve two
|
|
30
|
+
// different documents to two clients.
|
|
31
|
+
const authorizationServer = (0, discovery_js_1.buildAuthorizationServerMetadata)(config);
|
|
32
|
+
const protectedResource = (0, discovery_js_1.buildProtectedResourceMetadata)(config);
|
|
33
|
+
return {
|
|
34
|
+
authorizationServerMetadata: (_req, res) => sendDocument(res, authorizationServer),
|
|
35
|
+
protectedResourceMetadata: (_req, res) => sendDocument(res, protectedResource),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A router carrying both documents at their specified paths.
|
|
40
|
+
*
|
|
41
|
+
* Mounted at the application root — `app.use(oauthDiscoveryRoutes)` — because
|
|
42
|
+
* RFC 8414 and RFC 9728 fix these paths at the origin, and a client will not
|
|
43
|
+
* look for them anywhere else.
|
|
44
|
+
*/
|
|
45
|
+
function createDiscoveryRouter(config) {
|
|
46
|
+
const handlers = createDiscoveryHandlers(config);
|
|
47
|
+
const router = (0, express_1.Router)();
|
|
48
|
+
router.get('/.well-known/oauth-authorization-server', handlers.authorizationServerMetadata);
|
|
49
|
+
router.get('/.well-known/oauth-protected-resource', handlers.protectedResourceMetadata);
|
|
50
|
+
return router;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=discovery-controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery-controller.js","sourceRoot":"","sources":["../src/discovery-controller.ts"],"names":[],"mappings":";;AAoCA,0DAWC;AASD,sDAQC;AAhED,qCAAqE;AAErE,iDAKwB;AAExB;;;;;;;;;GASG;AAEH,yEAAyE;AACzE,SAAS,YAAY,CAAC,GAAa,EAAE,IAAa;IAChD,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,iCAAiC,CAAC,CAAC;IACjE,yEAAyE;IACzE,2EAA2E;IAC3E,2EAA2E;IAC3E,oBAAoB;IACpB,GAAG,CAAC,SAAS,CAAC,eAAe,EAAE,mBAAmB,wCAAyB,EAAE,CAAC,CAAC;IAC/E,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC;AAOD,SAAgB,uBAAuB,CAAC,MAAuB;IAC7D,2EAA2E;IAC3E,yEAAyE;IACzE,sCAAsC;IACtC,MAAM,mBAAmB,GAAG,IAAA,+CAAgC,EAAC,MAAM,CAAC,CAAC;IACrE,MAAM,iBAAiB,GAAG,IAAA,6CAA8B,EAAC,MAAM,CAAC,CAAC;IAEjE,OAAO;QACL,2BAA2B,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,EAAE,mBAAmB,CAAC;QAClF,yBAAyB,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,EAAE,iBAAiB,CAAC;KAC/E,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,qBAAqB,CAAC,MAAuB;IAC3D,MAAM,QAAQ,GAAG,uBAAuB,CAAC,MAAM,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,IAAA,gBAAM,GAAE,CAAC;IAExB,MAAM,CAAC,GAAG,CAAC,yCAAyC,EAAE,QAAQ,CAAC,2BAA2B,CAAC,CAAC;IAC5F,MAAM,CAAC,GAAG,CAAC,uCAAuC,EAAE,QAAQ,CAAC,yBAAyB,CAAC,CAAC;IAExF,OAAO,MAAM,CAAC;AAChB,CAAC"}
|