UNPKG

28.4 kBJavaScriptView Raw
1"use strict";
2var __importDefault = (this && this.__importDefault) || function (mod) {
3 return (mod && mod.__esModule) ? mod : { "default": mod };
4};
5Object.defineProperty(exports, "__esModule", { value: true });
6const uuid_1 = require("uuid");
7const pg_1 = require("pg");
8const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
9const crypto_1 = require("crypto");
10const model_1 = require("./model");
11const errors_1 = require("./errors");
12const scopes_1 = require("./util/scopes");
13const loader_1 = require("./loader");
14const scopes_2 = require("@authx/scopes");
15const x_1 = __importDefault(require("./x"));
16async function assertPermissions(realm, executor, grant, values) {
17 if (
18 // Check that we have every relevant user scope:
19 !(await grant.can(executor, values, scopes_1.createV2AuthXScope(realm, {
20 type: "user",
21 userId: grant.userId
22 }, {
23 basic: "r"
24 }))) ||
25 // Check that we have every relevant grant scope:
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 // Check that we have every relevant authorization scope:
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}
136class 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// Loop through an iterable of grant secrets and select the secret that was most
146// recently issued
147function 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}
162async 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 // Make sure this transaction is used for queries made by the executor.
176 const executor = new loader_1.DataLoaderExecutor(tx, strategies);
177 // Make sure the body is an object.
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 // Authorization Code
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 // Authenticate the client with its secret.
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 // Invoke the client.
217 await client.invoke(executor, {
218 id: uuid_1.v4(),
219 createdAt: new Date()
220 });
221 // Decode and validate the authorization code.
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 // Fetch the grant.
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 // Invoke the grant.
248 await grant.invoke(executor, {
249 id: uuid_1.v4(),
250 createdAt: new Date()
251 });
252 // Fetch the user.
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 // Make sure we have the necessary access.
265 await assertPermissions(realm, executor, grant, values);
266 // Get all enabled authorizations of this grant.
267 const authorizations = (await grant.authorizations(executor)).filter(t => t.enabled);
268 // Look for an existing active authorization for this grant with all
269 // grant scopes.
270 const possibleRootAuthorizations = authorizations.filter(t => scopes_2.isEqual("**:**:**", t.scopes));
271 let rootAuthorization;
272 if (possibleRootAuthorizations.length) {
273 // Use an existing authorization.
274 ctx[x_1.default].authorization = rootAuthorization =
275 possibleRootAuthorizations[0];
276 }
277 else {
278 // Create a new authorization.
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 // Look for an existing active authorization for this grant with the
295 // requested scopes.
296 const possibleRequestedAuthorizations = authorizations.filter(t => scopes_2.isEqual(requestedScopes, t.scopes));
297 let requestedAuthorization;
298 if (possibleRequestedAuthorizations.length) {
299 // Use an existing authorization.
300 requestedAuthorization = possibleRequestedAuthorizations[0];
301 }
302 else {
303 // Create a new authorization.
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 // Remove the authorization code we used, and prune any others that
319 // have expired.
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 /* eslint-disable camelcase */
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 /* eslint-enable camelcase */
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 // Refresh Token
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 // Authenticate the client with its secret.
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 // Invoke the client.
415 await client.invoke(tx, {
416 id: uuid_1.v4(),
417 createdAt: new Date()
418 });
419 // Decode and validate the authorization code.
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 // Fetch the grant.
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 // Invoke the grant.
446 await grant.invoke(executor, {
447 id: uuid_1.v4(),
448 createdAt: new Date()
449 });
450 // Fetch the user.
451 const user = await grant.user(executor);
452 if (!user.enabled) {
453 throw new OAuthError("invalid_grant", "Invalid authorization code.", undefined, paramsClientId);
454 }
455 // Make sure we have the necessary access.
456 await assertPermissions(realm, executor, grant, {
457 currentUserId: grant.userId,
458 currentGrantId: grant.id,
459 currentClientId: grant.clientId,
460 currentAuthorizationId: null
461 });
462 // Get all enabled authorizations of this grant.
463 const authorizations = (await grant.authorizations(executor)).filter(t => t.enabled);
464 // Look for an existing active authorization for this grant with all
465 // grant scopes.
466 const possibleRootAuthorizations = authorizations.filter(t => scopes_2.isEqual("**:**:**", t.scopes));
467 let rootAuthorization;
468 if (possibleRootAuthorizations.length) {
469 // Use an existing authorization.
470 ctx[x_1.default].authorization = rootAuthorization =
471 possibleRootAuthorizations[0];
472 }
473 else {
474 // Create a new authorization.
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 // Look for an existing active authorization for this grant with the
491 // requested scopes.
492 const possibleRequestedAuthorizations = authorizations.filter(t => {
493 var _a, _b, _c, _d;
494 return scopes_2.isEqual(scopes_2.inject(requestedScopeTemplates, {
495 /* eslint-disable camelcase */
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 /* eslint-enable camelcase */
501 }), t.scopes);
502 });
503 let requestedAuthorization;
504 if (possibleRequestedAuthorizations.length) {
505 // Use an existing authorization.
506 requestedAuthorization = possibleRequestedAuthorizations[0];
507 }
508 else {
509 // Create a new authorization.
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 /* eslint-disable camelcase */
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 /* eslint-enable camelcase */
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 /* eslint-disable camelcase */
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 /* eslint-enabme camelcase */
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 // Unsupported Grant Type
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 // eslint-disable-next-line camelcase
581 body.error_message = error.message;
582 }
583 if (error.uri) {
584 // eslint-disable-next-line camelcase
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}
592exports.default = oAuth2Middleware;
593//# sourceMappingURL=oauth2.js.map
\No newline at end of file