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