1 | "use strict";
|
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
|
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
|
4 | };
|
5 | Object.defineProperty(exports, "__esModule", { value: true });
|
6 | const uuid_1 = require("uuid");
|
7 | const pg_1 = require("pg");
|
8 | const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
9 | const crypto_1 = require("crypto");
|
10 | const model_1 = require("./model");
|
11 | const errors_1 = require("./errors");
|
12 | const scopes_1 = require("./util/scopes");
|
13 | const loader_1 = require("./loader");
|
14 | const scopes_2 = require("@authx/scopes");
|
15 | const x_1 = __importDefault(require("./x"));
|
16 | async function assertPermissions(realm, executor, grant, values) {
|
17 | if (
|
18 |
|
19 | !(await grant.can(executor, values, scopes_1.createV2AuthXScope(realm, {
|
20 | type: "user",
|
21 | userId: grant.userId
|
22 | }, {
|
23 | basic: "r"
|
24 | }))) ||
|
25 |
|
26 | !(await grant.can(executor, values, scopes_1.createV2AuthXScope(realm, {
|
27 | type: "grant",
|
28 | clientId: grant.clientId,
|
29 | grantId: grant.id,
|
30 | userId: grant.userId
|
31 | }, {
|
32 | basic: "r",
|
33 | scopes: "",
|
34 | secrets: ""
|
35 | }))) ||
|
36 | !(await grant.can(executor, values, scopes_1.createV2AuthXScope(realm, {
|
37 | type: "grant",
|
38 | clientId: grant.clientId,
|
39 | grantId: grant.id,
|
40 | userId: grant.userId
|
41 | }, {
|
42 | basic: "r",
|
43 | scopes: "",
|
44 | secrets: "r"
|
45 | }))) ||
|
46 | !(await grant.can(executor, values, scopes_1.createV2AuthXScope(realm, {
|
47 | type: "grant",
|
48 | clientId: grant.clientId,
|
49 | grantId: grant.id,
|
50 | userId: grant.userId
|
51 | }, {
|
52 | basic: "r",
|
53 | scopes: "r",
|
54 | secrets: ""
|
55 | }))) ||
|
56 | !(await grant.can(executor, values, scopes_1.createV2AuthXScope(realm, {
|
57 | type: "grant",
|
58 | clientId: grant.clientId,
|
59 | grantId: grant.id,
|
60 | userId: grant.userId
|
61 | }, {
|
62 | basic: "w",
|
63 | scopes: "",
|
64 | secrets: "w"
|
65 | }))) ||
|
66 |
|
67 | !(await grant.can(executor, values, scopes_1.createV2AuthXScope(realm, {
|
68 | type: "authorization",
|
69 | authorizationId: "*",
|
70 | clientId: grant.clientId,
|
71 | grantId: grant.id,
|
72 | userId: grant.userId
|
73 | }, {
|
74 | basic: "r",
|
75 | scopes: "",
|
76 | secrets: ""
|
77 | }))) ||
|
78 | !(await grant.can(executor, values, scopes_1.createV2AuthXScope(realm, {
|
79 | type: "authorization",
|
80 | authorizationId: "*",
|
81 | clientId: grant.clientId,
|
82 | grantId: grant.id,
|
83 | userId: grant.userId
|
84 | }, {
|
85 | basic: "r",
|
86 | scopes: "r",
|
87 | secrets: ""
|
88 | }))) ||
|
89 | !(await grant.can(executor, values, scopes_1.createV2AuthXScope(realm, {
|
90 | type: "authorization",
|
91 | authorizationId: "*",
|
92 | clientId: grant.clientId,
|
93 | grantId: grant.id,
|
94 | userId: grant.userId
|
95 | }, {
|
96 | basic: "r",
|
97 | scopes: "",
|
98 | secrets: "r"
|
99 | }))) ||
|
100 | !(await grant.can(executor, values, scopes_1.createV2AuthXScope(realm, {
|
101 | type: "authorization",
|
102 | authorizationId: "*",
|
103 | clientId: grant.clientId,
|
104 | grantId: grant.id,
|
105 | userId: grant.userId
|
106 | }, {
|
107 | basic: "w",
|
108 | scopes: "",
|
109 | secrets: ""
|
110 | }))) ||
|
111 | !(await grant.can(executor, values, scopes_1.createV2AuthXScope(realm, {
|
112 | type: "authorization",
|
113 | authorizationId: "*",
|
114 | clientId: grant.clientId,
|
115 | grantId: grant.id,
|
116 | userId: grant.userId
|
117 | }, {
|
118 | basic: "w",
|
119 | scopes: "w",
|
120 | secrets: ""
|
121 | }))) ||
|
122 | !(await grant.can(executor, values, scopes_1.createV2AuthXScope(realm, {
|
123 | type: "authorization",
|
124 | authorizationId: "*",
|
125 | clientId: grant.clientId,
|
126 | grantId: grant.id,
|
127 | userId: grant.userId
|
128 | }, {
|
129 | basic: "w",
|
130 | scopes: "",
|
131 | secrets: "w"
|
132 | })))) {
|
133 | throw new OAuthError("invalid_grant", "The grant contains insufficient permission for OAuth.", undefined, grant.clientId, 403);
|
134 | }
|
135 | }
|
136 | class OAuthError extends Error {
|
137 | constructor(code, message, uri, clientId, statusCode = 400) {
|
138 | super(typeof message === "undefined" ? code : message);
|
139 | this.code = code;
|
140 | this.uri = uri || null;
|
141 | this.clientId = clientId;
|
142 | this.statusCode = statusCode;
|
143 | }
|
144 | }
|
145 |
|
146 |
|
147 | function getRefreshToken(secrets) {
|
148 | let refreshToken = null;
|
149 | let max = 0;
|
150 | for (const secret of secrets) {
|
151 | const issuedString = Buffer.from(secret, "base64")
|
152 | .toString("utf8")
|
153 | .split(":")[1];
|
154 | const issuedNumber = parseInt(issuedString);
|
155 | if (issuedString && issuedNumber && issuedNumber > max) {
|
156 | refreshToken = secret;
|
157 | max = issuedNumber;
|
158 | }
|
159 | }
|
160 | return refreshToken;
|
161 | }
|
162 | async function oAuth2Middleware(ctx) {
|
163 | var _a, _b, _c;
|
164 | ctx.response.set("Cache-Control", "no-store");
|
165 | ctx.response.set("Pragma", "no-cache");
|
166 | const { executor, realm, jwtValidityDuration, codeValidityDuration, privateKey } = ctx[x_1.default];
|
167 | const strategies = executor.strategies;
|
168 | const pool = executor.connection;
|
169 | if (!(pool instanceof pg_1.Pool)) {
|
170 | throw new Error("INVARIANT: The executor connection is expected to be an instance of Pool.");
|
171 | }
|
172 | try {
|
173 | const tx = await pool.connect();
|
174 | try {
|
175 |
|
176 | const executor = new loader_1.DataLoaderExecutor(tx, strategies);
|
177 |
|
178 | const request = ctx.request.body;
|
179 | if (!request || typeof request !== "object") {
|
180 | throw new OAuthError("invalid_request", "The request body must be a JSON object.");
|
181 | }
|
182 | const grantType = typeof request.grant_type === "string" ? request.grant_type : undefined;
|
183 |
|
184 |
|
185 | if (grantType === "authorization_code") {
|
186 | try {
|
187 | tx.query("BEGIN DEFERRABLE");
|
188 | const now = Math.floor(Date.now() / 1000);
|
189 | const paramsClientId = typeof request.client_id === "string"
|
190 | ? request.client_id
|
191 | : undefined;
|
192 | const paramsClientSecret = typeof request.client_secret === "string"
|
193 | ? request.client_secret
|
194 | : undefined;
|
195 | const paramsCode = typeof request.code === "string" ? request.code : undefined;
|
196 | const paramsNonce = typeof request.nonce === "string" ? request.nonce : undefined;
|
197 | if (!paramsClientId || !paramsClientSecret || !paramsCode) {
|
198 | throw new OAuthError("invalid_request", "The request body must include fields `client_id`, `client_secret`, and `code`.", undefined, paramsClientId);
|
199 | }
|
200 |
|
201 | let client;
|
202 | try {
|
203 | client = await model_1.Client.read(executor, paramsClientId);
|
204 | }
|
205 | catch (error) {
|
206 | if (!(error instanceof errors_1.NotFoundError))
|
207 | throw error;
|
208 | throw new OAuthError("invalid_client", undefined, undefined, paramsClientId);
|
209 | }
|
210 | if (!client.secrets.has(paramsClientSecret)) {
|
211 | throw new OAuthError("invalid_client", undefined, undefined, paramsClientId);
|
212 | }
|
213 | if (!client.enabled) {
|
214 | throw new OAuthError("unauthorized_client", undefined, undefined, paramsClientId);
|
215 | }
|
216 |
|
217 | await client.invoke(executor, {
|
218 | id: uuid_1.v4(),
|
219 | createdAt: new Date()
|
220 | });
|
221 |
|
222 | const [grantId, issuedAt, nonce] = Buffer.from(paramsCode, "base64")
|
223 | .toString("utf8")
|
224 | .split(":");
|
225 | if (!grantId || !issuedAt || !nonce) {
|
226 | throw new OAuthError("invalid_grant", "The authorization code is malformed.", undefined, paramsClientId);
|
227 | }
|
228 | if (parseInt(issuedAt, 10) + codeValidityDuration < now) {
|
229 | throw new OAuthError("invalid_grant", "The authorization code is expired.", undefined, paramsClientId);
|
230 | }
|
231 |
|
232 | let grant;
|
233 | try {
|
234 | grant = await model_1.Grant.read(executor, grantId);
|
235 | }
|
236 | catch (error) {
|
237 | if (!(error instanceof errors_1.NotFoundError))
|
238 | throw error;
|
239 | throw new OAuthError("invalid_grant", "The authorization code is invalid.", undefined, paramsClientId);
|
240 | }
|
241 | if (!grant.enabled) {
|
242 | throw new OAuthError("invalid_grant", "The authorization code is invalid.", undefined, paramsClientId);
|
243 | }
|
244 | if (!grant.codes.has(paramsCode)) {
|
245 | throw new OAuthError("invalid_grant", "The authorization code is invalid.", undefined, paramsClientId);
|
246 | }
|
247 |
|
248 | await grant.invoke(executor, {
|
249 | id: uuid_1.v4(),
|
250 | createdAt: new Date()
|
251 | });
|
252 |
|
253 | const user = await grant.user(executor);
|
254 | if (!user.enabled) {
|
255 | throw new OAuthError("invalid_grant", "The authorization code is invalid.", undefined, paramsClientId);
|
256 | }
|
257 | const requestedScopes = grant.scopes;
|
258 | const values = {
|
259 | currentUserId: grant.userId,
|
260 | currentGrantId: grant.id,
|
261 | currentClientId: grant.clientId,
|
262 | currentAuthorizationId: null
|
263 | };
|
264 |
|
265 | await assertPermissions(realm, executor, grant, values);
|
266 |
|
267 | const authorizations = (await grant.authorizations(executor)).filter(t => t.enabled);
|
268 |
|
269 |
|
270 | const possibleRootAuthorizations = authorizations.filter(t => scopes_2.isEqual("**:**:**", t.scopes));
|
271 | let rootAuthorization;
|
272 | if (possibleRootAuthorizations.length) {
|
273 |
|
274 | ctx[x_1.default].authorization = rootAuthorization =
|
275 | possibleRootAuthorizations[0];
|
276 | }
|
277 | else {
|
278 |
|
279 | const authorizationId = uuid_1.v4();
|
280 | ctx[x_1.default].authorization = rootAuthorization = await model_1.Authorization.write(tx, {
|
281 | id: authorizationId,
|
282 | enabled: true,
|
283 | userId: user.id,
|
284 | grantId: grant.id,
|
285 | secret: crypto_1.randomBytes(16).toString("hex"),
|
286 | scopes: ["**:**:**"]
|
287 | }, {
|
288 | recordId: uuid_1.v4(),
|
289 | createdByAuthorizationId: authorizationId,
|
290 | createdByCredentialId: null,
|
291 | createdAt: new Date()
|
292 | });
|
293 | }
|
294 |
|
295 |
|
296 | const possibleRequestedAuthorizations = authorizations.filter(t => scopes_2.isEqual(requestedScopes, t.scopes));
|
297 | let requestedAuthorization;
|
298 | if (possibleRequestedAuthorizations.length) {
|
299 |
|
300 | requestedAuthorization = possibleRequestedAuthorizations[0];
|
301 | }
|
302 | else {
|
303 |
|
304 | requestedAuthorization = await model_1.Authorization.write(tx, {
|
305 | id: uuid_1.v4(),
|
306 | enabled: true,
|
307 | userId: user.id,
|
308 | grantId: grant.id,
|
309 | secret: crypto_1.randomBytes(16).toString("hex"),
|
310 | scopes: requestedScopes
|
311 | }, {
|
312 | recordId: uuid_1.v4(),
|
313 | createdByAuthorizationId: rootAuthorization.id,
|
314 | createdByCredentialId: null,
|
315 | createdAt: new Date()
|
316 | });
|
317 | }
|
318 |
|
319 |
|
320 | const codes = [...grant.codes].filter(code => {
|
321 | const issued = Buffer.from(code, "base64")
|
322 | .toString("utf8")
|
323 | .split(":")[1];
|
324 | return (code !== paramsCode &&
|
325 | issued &&
|
326 | parseInt(issued) + codeValidityDuration > now);
|
327 | });
|
328 | grant = await model_1.Grant.write(tx, {
|
329 | ...grant,
|
330 | codes
|
331 | }, {
|
332 | recordId: uuid_1.v4(),
|
333 | createdByAuthorizationId: rootAuthorization.id,
|
334 | createdAt: new Date()
|
335 | });
|
336 | const scopes = await requestedAuthorization.access(executor);
|
337 | const tokenId = uuid_1.v4();
|
338 | await requestedAuthorization.invoke(executor, {
|
339 | id: tokenId,
|
340 | format: "bearer",
|
341 | createdAt: new Date()
|
342 | });
|
343 | const body = {
|
344 |
|
345 | token_type: "bearer",
|
346 | access_token: jsonwebtoken_1.default.sign({
|
347 | aid: requestedAuthorization.id,
|
348 | scopes,
|
349 | nonce: paramsNonce
|
350 | }, privateKey, {
|
351 | jwtid: tokenId,
|
352 | algorithm: "RS512",
|
353 | expiresIn: jwtValidityDuration,
|
354 | audience: client.id,
|
355 | subject: user.id,
|
356 | issuer: realm
|
357 | }),
|
358 | refresh_token: getRefreshToken(grant.secrets),
|
359 | expires_in: jwtValidityDuration,
|
360 | scope: scopes.join(" ")
|
361 |
|
362 | };
|
363 | await tx.query("COMMIT");
|
364 | ctx.response.body = body;
|
365 | return;
|
366 | }
|
367 | catch (error) {
|
368 | await tx.query("ROLLBACK");
|
369 | throw error;
|
370 | }
|
371 | }
|
372 |
|
373 |
|
374 | if (grantType === "refresh_token") {
|
375 | try {
|
376 | tx.query("BEGIN DEFERRABLE");
|
377 | const paramsClientId = typeof request.client_id === "string"
|
378 | ? request.client_id
|
379 | : undefined;
|
380 | const paramsClientSecret = typeof request.client_secret === "string"
|
381 | ? request.client_secret
|
382 | : undefined;
|
383 | const paramsRefreshToken = typeof request.refresh_token === "string"
|
384 | ? request.refresh_token
|
385 | : undefined;
|
386 | const paramsScope = typeof request.scope === "string" ? request.scope : undefined;
|
387 | const paramsNonce = typeof request.nonce === "string" ? request.nonce : undefined;
|
388 | if (!paramsClientId || !paramsClientSecret || !paramsRefreshToken) {
|
389 | throw new OAuthError("invalid_request", "The request body must include fields `client_id`, `client_secret`, and `refresh_token`.", undefined, paramsClientId);
|
390 | }
|
391 | const requestedScopeTemplates = paramsScope
|
392 | ? paramsScope.split(" ")
|
393 | : [];
|
394 | if (paramsScope &&
|
395 | !requestedScopeTemplates.every(scopes_2.isValidScopeTemplate)) {
|
396 | throw new OAuthError("invalid_scope", undefined, undefined, paramsClientId);
|
397 | }
|
398 |
|
399 | let client;
|
400 | try {
|
401 | client = await model_1.Client.read(executor, paramsClientId);
|
402 | }
|
403 | catch (error) {
|
404 | if (!(error instanceof errors_1.NotFoundError))
|
405 | throw error;
|
406 | throw new OAuthError("invalid_client", undefined, undefined, paramsClientId);
|
407 | }
|
408 | if (!client.secrets.has(paramsClientSecret)) {
|
409 | throw new OAuthError("invalid_client", undefined, undefined, paramsClientId);
|
410 | }
|
411 | if (!client.enabled) {
|
412 | throw new OAuthError("unauthorized_client", undefined, undefined, paramsClientId);
|
413 | }
|
414 |
|
415 | await client.invoke(tx, {
|
416 | id: uuid_1.v4(),
|
417 | createdAt: new Date()
|
418 | });
|
419 |
|
420 | const [grantId, issuedAt, nonce] = Buffer.from(paramsRefreshToken, "base64")
|
421 | .toString("utf8")
|
422 | .split(":");
|
423 | if (!grantId || !issuedAt || !nonce) {
|
424 | throw new OAuthError("invalid_grant", "Invalid authorization code.", undefined, paramsClientId);
|
425 | }
|
426 |
|
427 | let grant;
|
428 | try {
|
429 | grant = await model_1.Grant.read(executor, grantId);
|
430 | }
|
431 | catch (error) {
|
432 | if (!(error instanceof errors_1.NotFoundError))
|
433 | throw error;
|
434 | throw new OAuthError("invalid_grant", "Invalid authorization code.", undefined, paramsClientId);
|
435 | }
|
436 | if (!grant.enabled) {
|
437 | throw new OAuthError("invalid_grant", "Invalid authorization code.", undefined, paramsClientId);
|
438 | }
|
439 | if (grant.clientId !== client.id) {
|
440 | throw new OAuthError("invalid_grant", "Invalid authorization code.", undefined, paramsClientId);
|
441 | }
|
442 | if (!grant.secrets.has(paramsRefreshToken)) {
|
443 | throw new OAuthError("invalid_grant", "Invalid authorization code.", undefined, paramsClientId);
|
444 | }
|
445 |
|
446 | await grant.invoke(executor, {
|
447 | id: uuid_1.v4(),
|
448 | createdAt: new Date()
|
449 | });
|
450 |
|
451 | const user = await grant.user(executor);
|
452 | if (!user.enabled) {
|
453 | throw new OAuthError("invalid_grant", "Invalid authorization code.", undefined, paramsClientId);
|
454 | }
|
455 |
|
456 | await assertPermissions(realm, executor, grant, {
|
457 | currentUserId: grant.userId,
|
458 | currentGrantId: grant.id,
|
459 | currentClientId: grant.clientId,
|
460 | currentAuthorizationId: null
|
461 | });
|
462 |
|
463 | const authorizations = (await grant.authorizations(executor)).filter(t => t.enabled);
|
464 |
|
465 |
|
466 | const possibleRootAuthorizations = authorizations.filter(t => scopes_2.isEqual("**:**:**", t.scopes));
|
467 | let rootAuthorization;
|
468 | if (possibleRootAuthorizations.length) {
|
469 |
|
470 | ctx[x_1.default].authorization = rootAuthorization =
|
471 | possibleRootAuthorizations[0];
|
472 | }
|
473 | else {
|
474 |
|
475 | const authorizationId = uuid_1.v4();
|
476 | ctx[x_1.default].authorization = rootAuthorization = await model_1.Authorization.write(tx, {
|
477 | id: authorizationId,
|
478 | enabled: true,
|
479 | userId: user.id,
|
480 | grantId: grant.id,
|
481 | secret: crypto_1.randomBytes(16).toString("hex"),
|
482 | scopes: ["**:**:**"]
|
483 | }, {
|
484 | recordId: uuid_1.v4(),
|
485 | createdByAuthorizationId: authorizationId,
|
486 | createdByCredentialId: null,
|
487 | createdAt: new Date()
|
488 | });
|
489 | }
|
490 |
|
491 |
|
492 | const possibleRequestedAuthorizations = authorizations.filter(t => {
|
493 | var _a, _b, _c, _d;
|
494 | return scopes_2.isEqual(scopes_2.inject(requestedScopeTemplates, {
|
495 |
|
496 | current_user_id: (_a = grant.userId) !== null && _a !== void 0 ? _a : null,
|
497 | current_grant_id: (_b = grant.id) !== null && _b !== void 0 ? _b : null,
|
498 | current_client_id: (_c = grant.clientId) !== null && _c !== void 0 ? _c : null,
|
499 | current_authorization_id: (_d = t.id) !== null && _d !== void 0 ? _d : null
|
500 |
|
501 | }), t.scopes);
|
502 | });
|
503 | let requestedAuthorization;
|
504 | if (possibleRequestedAuthorizations.length) {
|
505 |
|
506 | requestedAuthorization = possibleRequestedAuthorizations[0];
|
507 | }
|
508 | else {
|
509 |
|
510 | const authorizationId = uuid_1.v4();
|
511 | requestedAuthorization = await model_1.Authorization.write(tx, {
|
512 | id: authorizationId,
|
513 | enabled: true,
|
514 | userId: user.id,
|
515 | grantId: grant.id,
|
516 | secret: crypto_1.randomBytes(16).toString("hex"),
|
517 | scopes: scopes_2.inject(requestedScopeTemplates, {
|
518 |
|
519 | current_user_id: (_a = grant.userId) !== null && _a !== void 0 ? _a : null,
|
520 | current_grant_id: (_b = grant.id) !== null && _b !== void 0 ? _b : null,
|
521 | current_client_id: (_c = grant.clientId) !== null && _c !== void 0 ? _c : null,
|
522 | current_authorization_id: authorizationId !== null && authorizationId !== void 0 ? authorizationId : null
|
523 |
|
524 | })
|
525 | }, {
|
526 | recordId: uuid_1.v4(),
|
527 | createdByAuthorizationId: rootAuthorization.id,
|
528 | createdByCredentialId: null,
|
529 | createdAt: new Date()
|
530 | });
|
531 | }
|
532 | const scopes = await requestedAuthorization.access(executor);
|
533 | const tokenId = uuid_1.v4();
|
534 | await requestedAuthorization.invoke(executor, {
|
535 | id: tokenId,
|
536 | format: "bearer",
|
537 | createdAt: new Date()
|
538 | });
|
539 | const body = {
|
540 |
|
541 | token_type: "bearer",
|
542 | access_token: jsonwebtoken_1.default.sign({
|
543 | aid: requestedAuthorization.id,
|
544 | scopes,
|
545 | nonce: paramsNonce
|
546 | }, privateKey, {
|
547 | jwtid: tokenId,
|
548 | algorithm: "RS512",
|
549 | expiresIn: jwtValidityDuration,
|
550 | audience: client.id,
|
551 | subject: user.id,
|
552 | issuer: realm
|
553 | }),
|
554 | refresh_token: getRefreshToken(grant.secrets),
|
555 | expires_in: jwtValidityDuration,
|
556 | scope: scopes.join(" ")
|
557 |
|
558 | };
|
559 | await tx.query("COMMIT");
|
560 | ctx.response.body = body;
|
561 | return;
|
562 | }
|
563 | catch (error) {
|
564 | await tx.query("ROLLBACK");
|
565 | throw error;
|
566 | }
|
567 | }
|
568 |
|
569 | throw new OAuthError("unsupported_grant_type");
|
570 | }
|
571 | finally {
|
572 | tx.release();
|
573 | }
|
574 | }
|
575 | catch (error) {
|
576 | if (!(error instanceof OAuthError))
|
577 | throw error;
|
578 | const body = { error: error.code };
|
579 | if (error.message !== error.code) {
|
580 |
|
581 | body.error_message = error.message;
|
582 | }
|
583 | if (error.uri) {
|
584 |
|
585 | body.error_uri = error.uri;
|
586 | }
|
587 | ctx.response.status = error.statusCode;
|
588 | ctx.response.body = body;
|
589 | ctx.app.emit("error", error, ctx);
|
590 | }
|
591 | }
|
592 | exports.default = oAuth2Middleware;
|
593 |
|
\ | No newline at end of file |