1 | import { v4 } from "uuid";
|
2 | import { Pool } from "pg";
|
3 | import jwt from "jsonwebtoken";
|
4 | import { randomBytes } from "crypto";
|
5 | import { Context } from "./Context";
|
6 | import { Client, Grant, Authorization } from "./model";
|
7 | import { NotFoundError } from "./errors";
|
8 | import { createV2AuthXScope } from "./util/scopes";
|
9 | import { DataLoaderExecutor } from "./loader";
|
10 | import { inject, isEqual, isValidScopeTemplate } from "@authx/scopes";
|
11 | import { Context as KoaContext } from "koa";
|
12 | import x from "./x";
|
13 |
|
14 | async function assertPermissions(
|
15 | realm: string,
|
16 | executor: DataLoaderExecutor,
|
17 | grant: Grant,
|
18 | values: {
|
19 | currentUserId: string | null;
|
20 | currentGrantId: string | null;
|
21 | currentClientId: string | null;
|
22 | currentAuthorizationId: string | null;
|
23 | }
|
24 | ): Promise<void> {
|
25 | if (
|
26 |
|
27 | !(await grant.can(
|
28 | executor,
|
29 | values,
|
30 | createV2AuthXScope(
|
31 | realm,
|
32 | {
|
33 | type: "user",
|
34 | userId: grant.userId
|
35 | },
|
36 | {
|
37 | basic: "r"
|
38 | }
|
39 | )
|
40 | )) ||
|
41 |
|
42 | !(await grant.can(
|
43 | executor,
|
44 | values,
|
45 | createV2AuthXScope(
|
46 | realm,
|
47 | {
|
48 | type: "grant",
|
49 | clientId: grant.clientId,
|
50 | grantId: grant.id,
|
51 | userId: grant.userId
|
52 | },
|
53 | {
|
54 | basic: "r",
|
55 | scopes: "",
|
56 | secrets: ""
|
57 | }
|
58 | )
|
59 | )) ||
|
60 | !(await grant.can(
|
61 | executor,
|
62 | values,
|
63 | createV2AuthXScope(
|
64 | realm,
|
65 | {
|
66 | type: "grant",
|
67 | clientId: grant.clientId,
|
68 | grantId: grant.id,
|
69 | userId: grant.userId
|
70 | },
|
71 | {
|
72 | basic: "r",
|
73 | scopes: "",
|
74 | secrets: "r"
|
75 | }
|
76 | )
|
77 | )) ||
|
78 | !(await grant.can(
|
79 | executor,
|
80 | values,
|
81 | createV2AuthXScope(
|
82 | realm,
|
83 | {
|
84 | type: "grant",
|
85 | clientId: grant.clientId,
|
86 | grantId: grant.id,
|
87 | userId: grant.userId
|
88 | },
|
89 | {
|
90 | basic: "r",
|
91 | scopes: "r",
|
92 | secrets: ""
|
93 | }
|
94 | )
|
95 | )) ||
|
96 | !(await grant.can(
|
97 | executor,
|
98 | values,
|
99 | createV2AuthXScope(
|
100 | realm,
|
101 | {
|
102 | type: "grant",
|
103 | clientId: grant.clientId,
|
104 | grantId: grant.id,
|
105 | userId: grant.userId
|
106 | },
|
107 | {
|
108 | basic: "w",
|
109 | scopes: "",
|
110 | secrets: "w"
|
111 | }
|
112 | )
|
113 | )) ||
|
114 |
|
115 | !(await grant.can(
|
116 | executor,
|
117 | values,
|
118 | createV2AuthXScope(
|
119 | realm,
|
120 | {
|
121 | type: "authorization",
|
122 | authorizationId: "*",
|
123 | clientId: grant.clientId,
|
124 | grantId: grant.id,
|
125 | userId: grant.userId
|
126 | },
|
127 | {
|
128 | basic: "r",
|
129 | scopes: "",
|
130 | secrets: ""
|
131 | }
|
132 | )
|
133 | )) ||
|
134 | !(await grant.can(
|
135 | executor,
|
136 | values,
|
137 | createV2AuthXScope(
|
138 | realm,
|
139 | {
|
140 | type: "authorization",
|
141 | authorizationId: "*",
|
142 | clientId: grant.clientId,
|
143 | grantId: grant.id,
|
144 | userId: grant.userId
|
145 | },
|
146 | {
|
147 | basic: "r",
|
148 | scopes: "r",
|
149 | secrets: ""
|
150 | }
|
151 | )
|
152 | )) ||
|
153 | !(await grant.can(
|
154 | executor,
|
155 | values,
|
156 | createV2AuthXScope(
|
157 | realm,
|
158 | {
|
159 | type: "authorization",
|
160 | authorizationId: "*",
|
161 | clientId: grant.clientId,
|
162 | grantId: grant.id,
|
163 | userId: grant.userId
|
164 | },
|
165 | {
|
166 | basic: "r",
|
167 | scopes: "",
|
168 | secrets: "r"
|
169 | }
|
170 | )
|
171 | )) ||
|
172 | !(await grant.can(
|
173 | executor,
|
174 | values,
|
175 | createV2AuthXScope(
|
176 | realm,
|
177 | {
|
178 | type: "authorization",
|
179 | authorizationId: "*",
|
180 | clientId: grant.clientId,
|
181 | grantId: grant.id,
|
182 | userId: grant.userId
|
183 | },
|
184 | {
|
185 | basic: "w",
|
186 | scopes: "",
|
187 | secrets: ""
|
188 | }
|
189 | )
|
190 | )) ||
|
191 | !(await grant.can(
|
192 | executor,
|
193 | values,
|
194 | createV2AuthXScope(
|
195 | realm,
|
196 | {
|
197 | type: "authorization",
|
198 | authorizationId: "*",
|
199 | clientId: grant.clientId,
|
200 | grantId: grant.id,
|
201 | userId: grant.userId
|
202 | },
|
203 | {
|
204 | basic: "w",
|
205 | scopes: "w",
|
206 | secrets: ""
|
207 | }
|
208 | )
|
209 | )) ||
|
210 | !(await grant.can(
|
211 | executor,
|
212 | values,
|
213 | createV2AuthXScope(
|
214 | realm,
|
215 | {
|
216 | type: "authorization",
|
217 | authorizationId: "*",
|
218 | clientId: grant.clientId,
|
219 | grantId: grant.id,
|
220 | userId: grant.userId
|
221 | },
|
222 | {
|
223 | basic: "w",
|
224 | scopes: "",
|
225 | secrets: "w"
|
226 | }
|
227 | )
|
228 | ))
|
229 | ) {
|
230 | throw new OAuthError(
|
231 | "invalid_grant",
|
232 | "The grant contains insufficient permission for OAuth.",
|
233 | undefined,
|
234 | grant.clientId,
|
235 | 403
|
236 | );
|
237 | }
|
238 | }
|
239 |
|
240 | class OAuthError extends Error {
|
241 | public code: string;
|
242 | public uri: null | string;
|
243 | public clientId?: string;
|
244 | public statusCode: number;
|
245 |
|
246 | public constructor(
|
247 | code: string,
|
248 | message?: string,
|
249 | uri?: string,
|
250 | clientId?: string,
|
251 | statusCode: number = 400
|
252 | ) {
|
253 | super(typeof message === "undefined" ? code : message);
|
254 | this.code = code;
|
255 | this.uri = uri || null;
|
256 | this.clientId = clientId;
|
257 | this.statusCode = statusCode;
|
258 | }
|
259 | }
|
260 |
|
261 |
|
262 |
|
263 | function getRefreshToken(secrets: Iterable<string>): null | string {
|
264 | let refreshToken = null;
|
265 | let max = 0;
|
266 | for (const secret of secrets) {
|
267 | const issuedString = Buffer.from(secret, "base64")
|
268 | .toString("utf8")
|
269 | .split(":")[1];
|
270 |
|
271 | const issuedNumber = parseInt(issuedString);
|
272 | if (issuedString && issuedNumber && issuedNumber > max) {
|
273 | refreshToken = secret;
|
274 | max = issuedNumber;
|
275 | }
|
276 | }
|
277 |
|
278 | return refreshToken;
|
279 | }
|
280 |
|
281 | async function oAuth2Middleware(
|
282 | ctx: KoaContext & { [x]: Context }
|
283 | ): Promise<void> {
|
284 | ctx.response.set("Cache-Control", "no-store");
|
285 | ctx.response.set("Pragma", "no-cache");
|
286 |
|
287 | const {
|
288 | executor,
|
289 | realm,
|
290 | jwtValidityDuration,
|
291 | codeValidityDuration,
|
292 | privateKey
|
293 | } = ctx[x];
|
294 |
|
295 | const strategies = executor.strategies;
|
296 | const pool = executor.connection;
|
297 | if (!(pool instanceof Pool)) {
|
298 | throw new Error(
|
299 | "INVARIANT: The executor connection is expected to be an instance of Pool."
|
300 | );
|
301 | }
|
302 |
|
303 | try {
|
304 | const tx = await pool.connect();
|
305 | try {
|
306 |
|
307 | const executor = new DataLoaderExecutor(tx, strategies);
|
308 |
|
309 |
|
310 | const request: {
|
311 | grant_type?: unknown;
|
312 | client_id?: unknown;
|
313 | client_secret?: unknown;
|
314 | code?: unknown;
|
315 | nonce?: unknown;
|
316 | refresh_token?: unknown;
|
317 | scope?: unknown;
|
318 | } = (ctx.request as any).body;
|
319 | if (!request || typeof request !== "object") {
|
320 | throw new OAuthError(
|
321 | "invalid_request",
|
322 | "The request body must be a JSON object."
|
323 | );
|
324 | }
|
325 |
|
326 | const grantType: undefined | string =
|
327 | typeof request.grant_type === "string" ? request.grant_type : undefined;
|
328 |
|
329 |
|
330 |
|
331 | if (grantType === "authorization_code") {
|
332 | try {
|
333 | tx.query("BEGIN DEFERRABLE");
|
334 | const now = Math.floor(Date.now() / 1000);
|
335 | const paramsClientId: undefined | string =
|
336 | typeof request.client_id === "string"
|
337 | ? request.client_id
|
338 | : undefined;
|
339 | const paramsClientSecret: undefined | string =
|
340 | typeof request.client_secret === "string"
|
341 | ? request.client_secret
|
342 | : undefined;
|
343 | const paramsCode: undefined | string =
|
344 | typeof request.code === "string" ? request.code : undefined;
|
345 | const paramsNonce: undefined | string =
|
346 | typeof request.nonce === "string" ? request.nonce : undefined;
|
347 |
|
348 | if (!paramsClientId || !paramsClientSecret || !paramsCode) {
|
349 | throw new OAuthError(
|
350 | "invalid_request",
|
351 | "The request body must include fields `client_id`, `client_secret`, and `code`.",
|
352 | undefined,
|
353 | paramsClientId
|
354 | );
|
355 | }
|
356 |
|
357 |
|
358 | let client;
|
359 | try {
|
360 | client = await Client.read(executor, paramsClientId);
|
361 | } catch (error) {
|
362 | if (!(error instanceof NotFoundError)) throw error;
|
363 | throw new OAuthError(
|
364 | "invalid_client",
|
365 | undefined,
|
366 | undefined,
|
367 | paramsClientId
|
368 | );
|
369 | }
|
370 |
|
371 | if (!client.secrets.has(paramsClientSecret)) {
|
372 | throw new OAuthError(
|
373 | "invalid_client",
|
374 | undefined,
|
375 | undefined,
|
376 | paramsClientId
|
377 | );
|
378 | }
|
379 |
|
380 | if (!client.enabled) {
|
381 | throw new OAuthError(
|
382 | "unauthorized_client",
|
383 | undefined,
|
384 | undefined,
|
385 | paramsClientId
|
386 | );
|
387 | }
|
388 |
|
389 |
|
390 | await client.invoke(executor, {
|
391 | id: v4(),
|
392 | createdAt: new Date()
|
393 | });
|
394 |
|
395 |
|
396 | const [grantId, issuedAt, nonce] = Buffer.from(paramsCode, "base64")
|
397 | .toString("utf8")
|
398 | .split(":");
|
399 |
|
400 | if (!grantId || !issuedAt || !nonce) {
|
401 | throw new OAuthError(
|
402 | "invalid_grant",
|
403 | "The authorization code is malformed.",
|
404 | undefined,
|
405 | paramsClientId
|
406 | );
|
407 | }
|
408 |
|
409 | if (parseInt(issuedAt, 10) + codeValidityDuration < now) {
|
410 | throw new OAuthError(
|
411 | "invalid_grant",
|
412 | "The authorization code is expired.",
|
413 | undefined,
|
414 | paramsClientId
|
415 | );
|
416 | }
|
417 |
|
418 |
|
419 | let grant;
|
420 | try {
|
421 | grant = await Grant.read(executor, grantId);
|
422 | } catch (error) {
|
423 | if (!(error instanceof NotFoundError)) throw error;
|
424 | throw new OAuthError(
|
425 | "invalid_grant",
|
426 | "The authorization code is invalid.",
|
427 | undefined,
|
428 | paramsClientId
|
429 | );
|
430 | }
|
431 |
|
432 | if (!grant.enabled) {
|
433 | throw new OAuthError(
|
434 | "invalid_grant",
|
435 | "The authorization code is invalid.",
|
436 | undefined,
|
437 | paramsClientId
|
438 | );
|
439 | }
|
440 |
|
441 | if (!grant.codes.has(paramsCode)) {
|
442 | throw new OAuthError(
|
443 | "invalid_grant",
|
444 | "The authorization code is invalid.",
|
445 | undefined,
|
446 | paramsClientId
|
447 | );
|
448 | }
|
449 |
|
450 |
|
451 | await grant.invoke(executor, {
|
452 | id: v4(),
|
453 | createdAt: new Date()
|
454 | });
|
455 |
|
456 |
|
457 | const user = await grant.user(executor);
|
458 | if (!user.enabled) {
|
459 | throw new OAuthError(
|
460 | "invalid_grant",
|
461 | "The authorization code is invalid.",
|
462 | undefined,
|
463 | paramsClientId
|
464 | );
|
465 | }
|
466 |
|
467 | const requestedScopes = grant.scopes;
|
468 |
|
469 | const values = {
|
470 | currentUserId: grant.userId,
|
471 | currentGrantId: grant.id,
|
472 | currentClientId: grant.clientId,
|
473 | currentAuthorizationId: null
|
474 | };
|
475 |
|
476 |
|
477 | await assertPermissions(realm, executor, grant, values);
|
478 |
|
479 |
|
480 | const authorizations = (await grant.authorizations(executor)).filter(
|
481 | t => t.enabled
|
482 | );
|
483 |
|
484 |
|
485 |
|
486 | const possibleRootAuthorizations = authorizations.filter(t =>
|
487 | isEqual("**:**:**", t.scopes)
|
488 | );
|
489 |
|
490 | let rootAuthorization: Authorization;
|
491 | if (possibleRootAuthorizations.length) {
|
492 |
|
493 | ctx[x].authorization = rootAuthorization =
|
494 | possibleRootAuthorizations[0];
|
495 | } else {
|
496 |
|
497 | const authorizationId = v4();
|
498 | ctx[
|
499 | x
|
500 | ].authorization = rootAuthorization = await Authorization.write(
|
501 | tx,
|
502 | {
|
503 | id: authorizationId,
|
504 | enabled: true,
|
505 | userId: user.id,
|
506 | grantId: grant.id,
|
507 | secret: randomBytes(16).toString("hex"),
|
508 | scopes: ["**:**:**"]
|
509 | },
|
510 | {
|
511 | recordId: v4(),
|
512 | createdByAuthorizationId: authorizationId,
|
513 | createdByCredentialId: null,
|
514 | createdAt: new Date()
|
515 | }
|
516 | );
|
517 | }
|
518 |
|
519 |
|
520 |
|
521 | const possibleRequestedAuthorizations = authorizations.filter(t =>
|
522 | isEqual(requestedScopes, t.scopes)
|
523 | );
|
524 |
|
525 | let requestedAuthorization: Authorization;
|
526 | if (possibleRequestedAuthorizations.length) {
|
527 |
|
528 | requestedAuthorization = possibleRequestedAuthorizations[0];
|
529 | } else {
|
530 |
|
531 | requestedAuthorization = await Authorization.write(
|
532 | tx,
|
533 | {
|
534 | id: v4(),
|
535 | enabled: true,
|
536 | userId: user.id,
|
537 | grantId: grant.id,
|
538 | secret: randomBytes(16).toString("hex"),
|
539 | scopes: requestedScopes
|
540 | },
|
541 | {
|
542 | recordId: v4(),
|
543 | createdByAuthorizationId: rootAuthorization.id,
|
544 | createdByCredentialId: null,
|
545 | createdAt: new Date()
|
546 | }
|
547 | );
|
548 | }
|
549 |
|
550 |
|
551 |
|
552 | const codes = [...grant.codes].filter(code => {
|
553 | const issued = Buffer.from(code, "base64")
|
554 | .toString("utf8")
|
555 | .split(":")[1];
|
556 | return (
|
557 | code !== paramsCode &&
|
558 | issued &&
|
559 | parseInt(issued) + codeValidityDuration > now
|
560 | );
|
561 | });
|
562 |
|
563 | grant = await Grant.write(
|
564 | tx,
|
565 | {
|
566 | ...grant,
|
567 | codes
|
568 | },
|
569 | {
|
570 | recordId: v4(),
|
571 | createdByAuthorizationId: rootAuthorization.id,
|
572 | createdAt: new Date()
|
573 | }
|
574 | );
|
575 |
|
576 | const scopes = await requestedAuthorization.access(executor);
|
577 | const tokenId = v4();
|
578 | await requestedAuthorization.invoke(executor, {
|
579 | id: tokenId,
|
580 | format: "bearer",
|
581 | createdAt: new Date()
|
582 | });
|
583 |
|
584 | const body = {
|
585 |
|
586 | token_type: "bearer",
|
587 | access_token: jwt.sign(
|
588 | {
|
589 | aid: requestedAuthorization.id,
|
590 | scopes,
|
591 | nonce: paramsNonce
|
592 | },
|
593 | privateKey,
|
594 | {
|
595 | jwtid: tokenId,
|
596 | algorithm: "RS512",
|
597 | expiresIn: jwtValidityDuration,
|
598 | audience: client.id,
|
599 | subject: user.id,
|
600 | issuer: realm
|
601 | }
|
602 | ),
|
603 | refresh_token: getRefreshToken(grant.secrets),
|
604 | expires_in: jwtValidityDuration,
|
605 | scope: scopes.join(" ")
|
606 |
|
607 | };
|
608 |
|
609 | await tx.query("COMMIT");
|
610 |
|
611 | ctx.response.body = body;
|
612 | return;
|
613 | } catch (error) {
|
614 | await tx.query("ROLLBACK");
|
615 | throw error;
|
616 | }
|
617 | }
|
618 |
|
619 |
|
620 |
|
621 | if (grantType === "refresh_token") {
|
622 | try {
|
623 | tx.query("BEGIN DEFERRABLE");
|
624 | const paramsClientId: undefined | string =
|
625 | typeof request.client_id === "string"
|
626 | ? request.client_id
|
627 | : undefined;
|
628 | const paramsClientSecret: undefined | string =
|
629 | typeof request.client_secret === "string"
|
630 | ? request.client_secret
|
631 | : undefined;
|
632 | const paramsRefreshToken: undefined | string =
|
633 | typeof request.refresh_token === "string"
|
634 | ? request.refresh_token
|
635 | : undefined;
|
636 | const paramsScope: undefined | string =
|
637 | typeof request.scope === "string" ? request.scope : undefined;
|
638 | const paramsNonce: undefined | string =
|
639 | typeof request.nonce === "string" ? request.nonce : undefined;
|
640 | if (!paramsClientId || !paramsClientSecret || !paramsRefreshToken) {
|
641 | throw new OAuthError(
|
642 | "invalid_request",
|
643 | "The request body must include fields `client_id`, `client_secret`, and `refresh_token`.",
|
644 | undefined,
|
645 | paramsClientId
|
646 | );
|
647 | }
|
648 |
|
649 | const requestedScopeTemplates = paramsScope
|
650 | ? paramsScope.split(" ")
|
651 | : [];
|
652 | if (
|
653 | paramsScope &&
|
654 | !requestedScopeTemplates.every(isValidScopeTemplate)
|
655 | ) {
|
656 | throw new OAuthError(
|
657 | "invalid_scope",
|
658 | undefined,
|
659 | undefined,
|
660 | paramsClientId
|
661 | );
|
662 | }
|
663 |
|
664 |
|
665 | let client;
|
666 | try {
|
667 | client = await Client.read(executor, paramsClientId);
|
668 | } catch (error) {
|
669 | if (!(error instanceof NotFoundError)) throw error;
|
670 | throw new OAuthError(
|
671 | "invalid_client",
|
672 | undefined,
|
673 | undefined,
|
674 | paramsClientId
|
675 | );
|
676 | }
|
677 |
|
678 | if (!client.secrets.has(paramsClientSecret)) {
|
679 | throw new OAuthError(
|
680 | "invalid_client",
|
681 | undefined,
|
682 | undefined,
|
683 | paramsClientId
|
684 | );
|
685 | }
|
686 |
|
687 | if (!client.enabled) {
|
688 | throw new OAuthError(
|
689 | "unauthorized_client",
|
690 | undefined,
|
691 | undefined,
|
692 | paramsClientId
|
693 | );
|
694 | }
|
695 |
|
696 |
|
697 | await client.invoke(tx, {
|
698 | id: v4(),
|
699 | createdAt: new Date()
|
700 | });
|
701 |
|
702 |
|
703 | const [grantId, issuedAt, nonce] = Buffer.from(
|
704 | paramsRefreshToken,
|
705 | "base64"
|
706 | )
|
707 | .toString("utf8")
|
708 | .split(":");
|
709 |
|
710 | if (!grantId || !issuedAt || !nonce) {
|
711 | throw new OAuthError(
|
712 | "invalid_grant",
|
713 | "Invalid authorization code.",
|
714 | undefined,
|
715 | paramsClientId
|
716 | );
|
717 | }
|
718 |
|
719 |
|
720 | let grant: Grant;
|
721 | try {
|
722 | grant = await Grant.read(executor, grantId);
|
723 | } catch (error) {
|
724 | if (!(error instanceof NotFoundError)) throw error;
|
725 | throw new OAuthError(
|
726 | "invalid_grant",
|
727 | "Invalid authorization code.",
|
728 | undefined,
|
729 | paramsClientId
|
730 | );
|
731 | }
|
732 |
|
733 | if (!grant.enabled) {
|
734 | throw new OAuthError(
|
735 | "invalid_grant",
|
736 | "Invalid authorization code.",
|
737 | undefined,
|
738 | paramsClientId
|
739 | );
|
740 | }
|
741 |
|
742 | if (grant.clientId !== client.id) {
|
743 | throw new OAuthError(
|
744 | "invalid_grant",
|
745 | "Invalid authorization code.",
|
746 | undefined,
|
747 | paramsClientId
|
748 | );
|
749 | }
|
750 |
|
751 | if (!grant.secrets.has(paramsRefreshToken)) {
|
752 | throw new OAuthError(
|
753 | "invalid_grant",
|
754 | "Invalid authorization code.",
|
755 | undefined,
|
756 | paramsClientId
|
757 | );
|
758 | }
|
759 |
|
760 |
|
761 | await grant.invoke(executor, {
|
762 | id: v4(),
|
763 | createdAt: new Date()
|
764 | });
|
765 |
|
766 |
|
767 | const user = await grant.user(executor);
|
768 | if (!user.enabled) {
|
769 | throw new OAuthError(
|
770 | "invalid_grant",
|
771 | "Invalid authorization code.",
|
772 | undefined,
|
773 | paramsClientId
|
774 | );
|
775 | }
|
776 |
|
777 |
|
778 | await assertPermissions(realm, executor, grant, {
|
779 | currentUserId: grant.userId,
|
780 | currentGrantId: grant.id,
|
781 | currentClientId: grant.clientId,
|
782 | currentAuthorizationId: null
|
783 | });
|
784 |
|
785 |
|
786 | const authorizations = (await grant.authorizations(executor)).filter(
|
787 | t => t.enabled
|
788 | );
|
789 |
|
790 |
|
791 |
|
792 | const possibleRootAuthorizations = authorizations.filter(t =>
|
793 | isEqual("**:**:**", t.scopes)
|
794 | );
|
795 |
|
796 | let rootAuthorization: Authorization;
|
797 | if (possibleRootAuthorizations.length) {
|
798 |
|
799 | ctx[x].authorization = rootAuthorization =
|
800 | possibleRootAuthorizations[0];
|
801 | } else {
|
802 |
|
803 | const authorizationId = v4();
|
804 | ctx[
|
805 | x
|
806 | ].authorization = rootAuthorization = await Authorization.write(
|
807 | tx,
|
808 | {
|
809 | id: authorizationId,
|
810 | enabled: true,
|
811 | userId: user.id,
|
812 | grantId: grant.id,
|
813 | secret: randomBytes(16).toString("hex"),
|
814 | scopes: ["**:**:**"]
|
815 | },
|
816 | {
|
817 | recordId: v4(),
|
818 | createdByAuthorizationId: authorizationId,
|
819 | createdByCredentialId: null,
|
820 | createdAt: new Date()
|
821 | }
|
822 | );
|
823 | }
|
824 |
|
825 |
|
826 |
|
827 | const possibleRequestedAuthorizations = authorizations.filter(t =>
|
828 | isEqual(
|
829 | inject(requestedScopeTemplates, {
|
830 |
|
831 | current_user_id: grant.userId ?? null,
|
832 | current_grant_id: grant.id ?? null,
|
833 | current_client_id: grant.clientId ?? null,
|
834 | current_authorization_id: t.id ?? null
|
835 |
|
836 | }),
|
837 | t.scopes
|
838 | )
|
839 | );
|
840 |
|
841 | let requestedAuthorization: Authorization;
|
842 |
|
843 | if (possibleRequestedAuthorizations.length) {
|
844 |
|
845 | requestedAuthorization = possibleRequestedAuthorizations[0];
|
846 | } else {
|
847 |
|
848 | const authorizationId = v4();
|
849 | requestedAuthorization = await Authorization.write(
|
850 | tx,
|
851 | {
|
852 | id: authorizationId,
|
853 | enabled: true,
|
854 | userId: user.id,
|
855 | grantId: grant.id,
|
856 | secret: randomBytes(16).toString("hex"),
|
857 | scopes: inject(requestedScopeTemplates, {
|
858 |
|
859 | current_user_id: grant.userId ?? null,
|
860 | current_grant_id: grant.id ?? null,
|
861 | current_client_id: grant.clientId ?? null,
|
862 | current_authorization_id: authorizationId ?? null
|
863 |
|
864 | })
|
865 | },
|
866 | {
|
867 | recordId: v4(),
|
868 | createdByAuthorizationId: rootAuthorization.id,
|
869 | createdByCredentialId: null,
|
870 | createdAt: new Date()
|
871 | }
|
872 | );
|
873 | }
|
874 |
|
875 | const scopes = await requestedAuthorization.access(executor);
|
876 |
|
877 | const tokenId = v4();
|
878 | await requestedAuthorization.invoke(executor, {
|
879 | id: tokenId,
|
880 | format: "bearer",
|
881 | createdAt: new Date()
|
882 | });
|
883 |
|
884 | const body = {
|
885 |
|
886 | token_type: "bearer",
|
887 | access_token: jwt.sign(
|
888 | {
|
889 | aid: requestedAuthorization.id,
|
890 | scopes,
|
891 | nonce: paramsNonce
|
892 | },
|
893 | privateKey,
|
894 | {
|
895 | jwtid: tokenId,
|
896 | algorithm: "RS512",
|
897 | expiresIn: jwtValidityDuration,
|
898 | audience: client.id,
|
899 | subject: user.id,
|
900 | issuer: realm
|
901 | }
|
902 | ),
|
903 | refresh_token: getRefreshToken(grant.secrets),
|
904 | expires_in: jwtValidityDuration,
|
905 | scope: scopes.join(" ")
|
906 |
|
907 | };
|
908 |
|
909 | await tx.query("COMMIT");
|
910 | ctx.response.body = body;
|
911 | return;
|
912 | } catch (error) {
|
913 | await tx.query("ROLLBACK");
|
914 | throw error;
|
915 | }
|
916 | }
|
917 |
|
918 |
|
919 | throw new OAuthError("unsupported_grant_type");
|
920 | } finally {
|
921 | tx.release();
|
922 | }
|
923 | } catch (error) {
|
924 | if (!(error instanceof OAuthError)) throw error;
|
925 | const body: {
|
926 | error: string;
|
927 | error_message?: string;
|
928 | error_uri?: string;
|
929 | } = { error: error.code };
|
930 |
|
931 | if (error.message !== error.code) {
|
932 |
|
933 | body.error_message = error.message;
|
934 | }
|
935 |
|
936 | if (error.uri) {
|
937 |
|
938 | body.error_uri = error.uri;
|
939 | }
|
940 |
|
941 | ctx.response.status = error.statusCode;
|
942 | ctx.response.body = body;
|
943 | ctx.app.emit("error", error, ctx);
|
944 | }
|
945 | }
|
946 |
|
947 | export default oAuth2Middleware;
|