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