UNPKG

27.9 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 jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
8const crypto_1 = require("crypto");
9const model_1 = require("./model");
10const errors_1 = require("./errors");
11const scopes_1 = require("./util/scopes");
12const scopes_2 = require("@authx/scopes");
13const x_1 = __importDefault(require("./x"));
14async function assertPermissions(realm, tx, grant, values) {
15 if (
16 // Check that we have every relevant user scope:
17 !(await grant.can(tx, values, scopes_1.createV2AuthXScope(realm, {
18 type: "user",
19 userId: grant.userId
20 }, {
21 basic: "r"
22 }))) ||
23 // Check that we have every relevant grant scope:
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 // Check that we have every relevant authorization scope:
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}
134class 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// Loop through an iterable of grant secrets and select the secret that was most
144// recently issued
145function 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}
160async 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 // Make sure the body is an object.
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 // Authorization Code
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 // Authenticate the client with its secret.
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 // Invoke the client.
208 await client.invoke(tx, {
209 id: uuid_1.v4(),
210 createdAt: new Date()
211 });
212 // Decode and validate the authorization code.
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 // Fetch the grant.
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 // Invoke the grant.
239 await grant.invoke(tx, {
240 id: uuid_1.v4(),
241 createdAt: new Date()
242 });
243 // Fetch the user.
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 // Make sure we have the necessary access.
256 await assertPermissions(realm, tx, grant, values);
257 // Get all enabled authorizations of this grant.
258 const authorizations = (await grant.authorizations(tx)).filter(t => t.enabled);
259 // Look for an existing active authorization for this grant with all
260 // grant scopes.
261 const possibleRootAuthorizations = authorizations.filter(t => scopes_2.isEqual("**:**:**", t.scopes));
262 let rootAuthorization;
263 if (possibleRootAuthorizations.length) {
264 // Use an existing authorization.
265 ctx[x_1.default].authorization = rootAuthorization =
266 possibleRootAuthorizations[0];
267 }
268 else {
269 // Create a new authorization.
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 // Look for an existing active authorization for this grant with the
286 // requested scopes.
287 const possibleRequestedAuthorizations = authorizations.filter(t => scopes_2.isEqual(requestedScopes, t.scopes));
288 let requestedAuthorization;
289 if (possibleRequestedAuthorizations.length) {
290 // Use an existing authorization.
291 requestedAuthorization = possibleRequestedAuthorizations[0];
292 }
293 else {
294 // Create a new authorization.
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 // Remove the authorization code we used, and prune any others that
310 // have expired.
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 /* eslint-disable @typescript-eslint/camelcase */
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 /* eslint-enable @typescript-eslint/camelcase */
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 // Refresh Token
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 // Authenticate the client with its secret.
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 // Invoke the client.
406 await client.invoke(tx, {
407 id: uuid_1.v4(),
408 createdAt: new Date()
409 });
410 // Decode and validate the authorization code.
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 // Fetch the grant.
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 // Invoke the grant.
437 await grant.invoke(tx, {
438 id: uuid_1.v4(),
439 createdAt: new Date()
440 });
441 // Fetch the user.
442 const user = await grant.user(tx);
443 if (!user.enabled) {
444 throw new OAuthError("invalid_grant", "Invalid authorization code.", undefined, paramsClientId);
445 }
446 // Make sure we have the necessary access.
447 await assertPermissions(realm, tx, grant, {
448 currentUserId: grant.userId,
449 currentGrantId: grant.id,
450 currentClientId: grant.clientId,
451 currentAuthorizationId: null
452 });
453 // Get all enabled authorizations of this grant.
454 const authorizations = (await grant.authorizations(tx)).filter(t => t.enabled);
455 // Look for an existing active authorization for this grant with all
456 // grant scopes.
457 const possibleRootAuthorizations = authorizations.filter(t => scopes_2.isEqual("**:**:**", t.scopes));
458 let rootAuthorization;
459 if (possibleRootAuthorizations.length) {
460 // Use an existing authorization.
461 ctx[x_1.default].authorization = rootAuthorization =
462 possibleRootAuthorizations[0];
463 }
464 else {
465 // Create a new authorization.
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 // Look for an existing active authorization for this grant with the
482 // requested scopes.
483 const possibleRequestedAuthorizations = authorizations.filter(t => {
484 var _a, _b, _c, _d;
485 return scopes_2.isEqual(scopes_2.inject(requestedScopeTemplates, {
486 /* eslint-disable @typescript-eslint/camelcase */
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 /* eslint-enable @typescript-eslint/camelcase */
492 }), t.scopes);
493 });
494 let requestedAuthorization;
495 if (possibleRequestedAuthorizations.length) {
496 // Use an existing authorization.
497 requestedAuthorization = possibleRequestedAuthorizations[0];
498 }
499 else {
500 // Create a new authorization.
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 /* eslint-disable @typescript-eslint/camelcase */
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 /* eslint-enable @typescript-eslint/camelcase */
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 /* eslint-disable @typescript-eslint/camelcase */
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 /* eslint-enabme @typescript-eslint/camelcase */
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 // Unsupported Grant Type
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 // eslint-disable-next-line @typescript-eslint/camelcase
572 body.error_message = error.message;
573 }
574 if (error.uri) {
575 // eslint-disable-next-line @typescript-eslint/camelcase
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}
583exports.default = oAuth2Middleware;
584//# sourceMappingURL=oauth2.js.map
\No newline at end of file