@openstax/ts-utils 1.1.33 → 1.1.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import type { ConfigProviderForConfig } from '../../config';
2
- import { CookieAuthProvider } from '.';
2
+ import { CookieAuthProvider, User } from '.';
3
3
  declare type Config = {
4
4
  cookieName: string;
5
5
  encryptionPrivateKey: string;
@@ -8,6 +8,9 @@ declare type Config = {
8
8
  interface Initializer<C> {
9
9
  configSpace?: C;
10
10
  }
11
+ export declare type Jwt = {
12
+ sub?: User;
13
+ };
11
14
  export declare const decryptionAuthProvider: <C extends string = "decryption">(initializer: Initializer<C>) => (configProvider: { [key in C]: {
12
15
  cookieName: import("../../config").ConfigValueProvider<string>;
13
16
  encryptionPrivateKey: import("../../config").ConfigValueProvider<string>;
@@ -1,26 +1,13 @@
1
- import { compactDecrypt, compactVerify, importSPKI } from 'jose';
2
1
  import { resolveConfigValue } from '../../config/resolveConfigValue';
3
2
  import { ifDefined } from '../../guards';
4
3
  import { once } from '../../misc/helpers';
4
+ import { decryptAndVerify } from './utils/decryptAndVerify';
5
5
  import { getAuthTokenOrCookie } from '.';
6
6
  export const decryptionAuthProvider = (initializer) => (configProvider) => {
7
7
  const config = configProvider[ifDefined(initializer.configSpace, 'decryption')];
8
8
  const cookieName = once(() => resolveConfigValue(config.cookieName));
9
9
  const encryptionPrivateKey = once(() => resolveConfigValue(config.encryptionPrivateKey));
10
10
  const signaturePublicKey = once(() => resolveConfigValue(config.signaturePublicKey));
11
- const decryptAndVerify = async (jwt) => {
12
- try {
13
- // Decrypt SSO cookie
14
- const { plaintext } = await compactDecrypt(jwt, Buffer.from(await encryptionPrivateKey()), // Note: Buffer.from() is node-js only
15
- { contentEncryptionAlgorithms: ['A256GCM'], keyManagementAlgorithms: ['dir'] });
16
- // Verify SSO cookie signature
17
- const { payload } = await compactVerify(plaintext, await importSPKI(await signaturePublicKey(), 'RS256'), { algorithms: ['RS256'] });
18
- return payload;
19
- }
20
- catch {
21
- return undefined;
22
- }
23
- };
24
11
  return ({ request, profile }) => {
25
12
  let user;
26
13
  const getAuthorizedFetchConfig = profile.track('getAuthorizedFetchConfig', () => async () => {
@@ -35,18 +22,7 @@ export const decryptionAuthProvider = (initializer) => (configProvider) => {
35
22
  if (!token) {
36
23
  return undefined;
37
24
  }
38
- const payload = await decryptAndVerify(token);
39
- if (!payload) {
40
- return undefined;
41
- }
42
- // Note: Uint8Array.toString() returns text in node-js only
43
- // The browser version is new TextDecoder().decode(payload)
44
- const jwt = JSON.parse(payload.toString());
45
- // Allow clock skew up to 5 minutes
46
- if (!jwt.sub || !jwt.sub.uuid || (jwt.exp && jwt.exp < Math.floor(Date.now() / 1000) - 300)) {
47
- return undefined;
48
- }
49
- return jwt.sub;
25
+ return decryptAndVerify(token, await encryptionPrivateKey(), await signaturePublicKey());
50
26
  });
51
27
  return {
52
28
  getAuthorizedFetchConfig,
@@ -0,0 +1,2 @@
1
+ import { User } from '..';
2
+ export declare const decryptAndVerify: (token: string, encryptionPrivateKey: string, signaturePublicKey: string) => User | undefined;
@@ -0,0 +1,36 @@
1
+ import * as crypto from 'crypto';
2
+ import { TextEncoder } from 'util';
3
+ import jwt from 'jsonwebtoken';
4
+ import { isPlainObject } from '../../../guards';
5
+ const decrypt = (input, key) => {
6
+ const splitInput = input.split('.');
7
+ const aad = new TextEncoder().encode(splitInput[0]);
8
+ const iv = Buffer.from(splitInput[2], 'base64');
9
+ const ciphertext = Buffer.from(splitInput[3], 'base64');
10
+ const tag = Buffer.from(splitInput[4], 'base64');
11
+ const decipher = crypto.createDecipheriv('aes-256-gcm', Buffer.from(key), iv, { authTagLength: 16 });
12
+ decipher.setAAD(aad, { plaintextLength: ciphertext.length });
13
+ decipher.setAuthTag(tag);
14
+ const result = Buffer.concat([
15
+ decipher.update(ciphertext),
16
+ decipher.final(),
17
+ ]);
18
+ return result.toString('utf-8');
19
+ };
20
+ export const decryptAndVerify = (token, encryptionPrivateKey, signaturePublicKey) => {
21
+ try {
22
+ // Decrypt SSO cookie
23
+ const plaintext = decrypt(token, encryptionPrivateKey);
24
+ const payload = jwt.verify(plaintext, signaturePublicKey, {
25
+ clockTolerance: 300 // 5 minutes
26
+ });
27
+ if (!isPlainObject(payload) || !isPlainObject(payload.sub) || !payload.sub.uuid) {
28
+ return undefined;
29
+ }
30
+ // TS is confused because the library types the `sub` as a string
31
+ return payload.sub;
32
+ }
33
+ catch {
34
+ return undefined;
35
+ }
36
+ };