@openstax/ts-utils 1.15.5 → 1.16.2

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.
Files changed (27) hide show
  1. package/dist/cjs/middleware/apiSlowResponseMiddleware.js +1 -2
  2. package/dist/cjs/services/authProvider/browser.js +1 -1
  3. package/dist/cjs/services/authProvider/decryption.d.ts +5 -2
  4. package/dist/cjs/services/authProvider/decryption.js +15 -4
  5. package/dist/cjs/services/authProvider/index.d.ts +2 -2
  6. package/dist/cjs/services/authProvider/utils/decryptAndVerify.d.ts +3 -1
  7. package/dist/cjs/services/authProvider/utils/decryptAndVerify.js +3 -3
  8. package/dist/cjs/services/documentStore/unversioned/file-system.js +1 -1
  9. package/dist/cjs/services/launchParams/signer.d.ts +1 -1
  10. package/dist/cjs/services/launchParams/signer.js +11 -2
  11. package/dist/cjs/services/launchParams/verifier.d.ts +2 -1
  12. package/dist/cjs/services/launchParams/verifier.js +14 -2
  13. package/dist/cjs/tsconfig.without-specs.cjs.tsbuildinfo +1 -1
  14. package/dist/esm/middleware/apiSlowResponseMiddleware.js +1 -2
  15. package/dist/esm/services/authProvider/browser.js +1 -1
  16. package/dist/esm/services/authProvider/decryption.d.ts +5 -2
  17. package/dist/esm/services/authProvider/decryption.js +15 -4
  18. package/dist/esm/services/authProvider/index.d.ts +2 -2
  19. package/dist/esm/services/authProvider/utils/decryptAndVerify.d.ts +3 -1
  20. package/dist/esm/services/authProvider/utils/decryptAndVerify.js +3 -3
  21. package/dist/esm/services/documentStore/unversioned/file-system.js +1 -1
  22. package/dist/esm/services/launchParams/signer.d.ts +1 -1
  23. package/dist/esm/services/launchParams/signer.js +11 -2
  24. package/dist/esm/services/launchParams/verifier.d.ts +2 -1
  25. package/dist/esm/services/launchParams/verifier.js +14 -2
  26. package/dist/esm/tsconfig.without-specs.esm.tsbuildinfo +1 -1
  27. package/package.json +3 -1
@@ -32,7 +32,7 @@ export const createSlowResponseMiddleware = (config) => {
32
32
  });
33
33
  resolve(apiTextResponse(504, '504 Gateway Timeout'));
34
34
  }, timeoutAfter));
35
- const requestPromise = response.then(response => {
35
+ const requestPromise = response.finally(() => {
36
36
  const time = Date.now() - start;
37
37
  if (timeout !== undefined) {
38
38
  clearTimeout(timeout);
@@ -43,7 +43,6 @@ export const createSlowResponseMiddleware = (config) => {
43
43
  time,
44
44
  });
45
45
  }
46
- return response;
47
46
  });
48
47
  return timeoutPromise ? Promise.race([timeoutPromise, requestPromise]) : requestPromise;
49
48
  });
@@ -130,6 +130,6 @@ export const browserAuthProvider = ({ window, configSpace }) => (configProvider)
130
130
  /**
131
131
  * loads current user identity. does not reflect changes in identity after being called the first time.
132
132
  */
133
- getUser
133
+ getUser,
134
134
  };
135
135
  };
@@ -1,5 +1,5 @@
1
1
  import type { ConfigProviderForConfig } from '../../config';
2
- import { CookieAuthProvider } from '.';
2
+ import { AuthProvider, CookieAuthProvider } from '.';
3
3
  declare type Config = {
4
4
  cookieName: string;
5
5
  encryptionPrivateKey: string;
@@ -8,9 +8,12 @@ declare type Config = {
8
8
  interface Initializer<C> {
9
9
  configSpace?: C;
10
10
  }
11
+ export declare type DecryptionAuthProvider = AuthProvider & {
12
+ getTokenExpiration: (tokenString?: string) => Promise<number | null | undefined>;
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>;
14
17
  signaturePublicKey: import("../../config").ConfigValueProvider<string>;
15
- }; }) => CookieAuthProvider;
18
+ }; }) => CookieAuthProvider<DecryptionAuthProvider>;
16
19
  export {};
@@ -18,12 +18,18 @@ export const decryptionAuthProvider = (initializer) => (configProvider) => {
18
18
  }
19
19
  return { headers };
20
20
  };
21
- const loadUser = async () => {
22
- const [token] = getAuthTokenOrCookie(request, await cookieName());
21
+ const getPayload = async (tokenString) => {
22
+ const token = tokenString !== null && tokenString !== void 0 ? tokenString : getAuthTokenOrCookie(request, await cookieName())[0];
23
23
  if (!token) {
24
24
  return undefined;
25
25
  }
26
- const result = decryptAndVerify(token, await encryptionPrivateKey(), await signaturePublicKey());
26
+ return decryptAndVerify(token, await encryptionPrivateKey(), await signaturePublicKey());
27
+ };
28
+ const loadUser = async () => {
29
+ const result = await getPayload();
30
+ if (!result) {
31
+ return undefined;
32
+ }
27
33
  if ('error' in result && result.error == 'expired token') {
28
34
  throw new SessionExpiredError();
29
35
  }
@@ -31,12 +37,17 @@ export const decryptionAuthProvider = (initializer) => (configProvider) => {
31
37
  };
32
38
  return {
33
39
  getAuthorizedFetchConfig,
40
+ getTokenExpiration: async (tokenString) => {
41
+ var _a;
42
+ const payload = await getPayload(tokenString);
43
+ return payload ? ((_a = payload.exp) !== null && _a !== void 0 ? _a : null) : undefined;
44
+ },
34
45
  getUser: async () => {
35
46
  if (!user) {
36
47
  user = await loadUser();
37
48
  }
38
49
  return user;
39
- }
50
+ },
40
51
  };
41
52
  };
42
53
  };
@@ -45,9 +45,9 @@ export declare type CookieAuthProviderRequest = {
45
45
  headers: HttpHeaders;
46
46
  queryStringParameters?: QueryParams;
47
47
  };
48
- export declare type CookieAuthProvider = (inputs: {
48
+ export declare type CookieAuthProvider<T extends AuthProvider = AuthProvider> = (inputs: {
49
49
  request: CookieAuthProviderRequest;
50
- }) => AuthProvider;
50
+ }) => T;
51
51
  export declare type StubAuthProvider = (user: User | undefined) => AuthProvider;
52
52
  export declare const stubAuthProvider: (user?: User | undefined) => AuthProvider;
53
53
  export declare const getAuthTokenOrCookie: (request: CookieAuthProviderRequest, cookieName: string, queryKey?: string) => [string, {
@@ -17,11 +17,13 @@ export declare const verifyJws: (jws: string, signaturePublicKey: Buffer | strin
17
17
  * @param token the encrypted token
18
18
  * @param encryptionPrivateKey the private key used to encrypt the token
19
19
  * @param signaturePublicKey the public key used to verify the decrypted token
20
- * @returns {user: User} (success) or {error: string} (failure)
20
+ * @returns {user: User; exp: number} (success) or {error: string} (failure)
21
21
  */
22
22
  export declare const decryptAndVerify: (token: string, encryptionPrivateKey: string, signaturePublicKey: string) => {
23
23
  user: User;
24
+ exp: number;
24
25
  } | {
25
26
  error: string;
27
+ exp?: number;
26
28
  };
27
29
  export {};
@@ -57,7 +57,7 @@ export const verifyJws = (jws, signaturePublicKey) => {
57
57
  * @param token the encrypted token
58
58
  * @param encryptionPrivateKey the private key used to encrypt the token
59
59
  * @param signaturePublicKey the public key used to verify the decrypted token
60
- * @returns {user: User} (success) or {error: string} (failure)
60
+ * @returns {user: User; exp: number} (success) or {error: string} (failure)
61
61
  */
62
62
  export const decryptAndVerify = (token, encryptionPrivateKey, signaturePublicKey) => {
63
63
  const timestamp = Math.floor(Date.now() / 1000);
@@ -79,7 +79,7 @@ export const decryptAndVerify = (token, encryptionPrivateKey, signaturePublicKey
79
79
  return { error: 'invalid token' };
80
80
  }
81
81
  if (payload.exp < timestamp - clockTolerance) {
82
- return { error: 'expired token' };
82
+ return { error: 'expired token', exp: payload.exp };
83
83
  }
84
- return { user: payload.sub };
84
+ return { user: payload.sub, exp: payload.exp };
85
85
  };
@@ -51,7 +51,7 @@ export const fileSystemUnversionedDocumentStore = (initializer) => () => (config
51
51
  await mkTableDir;
52
52
  const data = await load(filename);
53
53
  if (!data) {
54
- throw new Error(`Item with ${hashKey.toString()} "${id}" does not exist`);
54
+ throw new NotFoundError(`Item with ${hashKey.toString()} "${id}" does not exist`);
55
55
  }
56
56
  const newValue = typeof data[attribute] === 'number' ? data[attribute] + 1 : 1;
57
57
  const newItem = { ...data, [hashKey]: id, [attribute]: newValue };
@@ -21,7 +21,7 @@ export declare const createLaunchSigner: <C extends string = "launch">({ configS
21
21
  jwks: () => Promise<{
22
22
  keys: JWK.RawKey[];
23
23
  }>;
24
- sign: (subject: string) => Promise<string>;
24
+ sign: (subject: string, maxExpiresIn?: string | number | null | undefined) => Promise<string>;
25
25
  };
26
26
  export declare type LaunchSigner = ReturnType<ReturnType<typeof createLaunchSigner>>;
27
27
  export {};
@@ -1,4 +1,5 @@
1
1
  import jwt from 'jsonwebtoken';
2
+ import ms from 'ms';
2
3
  import { JWK } from 'node-jose';
3
4
  import { once } from '../..';
4
5
  import { resolveConfigValue } from '../../config';
@@ -27,9 +28,17 @@ export const createLaunchSigner = ({ configSpace }) => (configProvider) => {
27
28
  return keystore;
28
29
  });
29
30
  const jwks = async () => (await getKeyStore()).toJSON(false);
30
- const sign = async (subject) => {
31
- const alg = await getAlg();
31
+ const getExpiresInWithMax = async (maxExpiresIn) => {
32
32
  const expiresIn = await getExpiresIn();
33
+ const maxExpiresInSeconds = typeof maxExpiresIn === 'number' ? maxExpiresIn :
34
+ typeof maxExpiresIn === 'string' ? Math.floor(ms(maxExpiresIn) / 1000) :
35
+ undefined;
36
+ return maxExpiresInSeconds ? Math.min(Math.floor(ms(expiresIn) / 1000), maxExpiresInSeconds) : expiresIn;
37
+ };
38
+ const sign = async (subject, maxExpiresIn) => {
39
+ const alg = await getAlg();
40
+ // expiresIn can be a number of seconds or a string like '1h' or '1d'
41
+ const expiresIn = await getExpiresInWithMax(maxExpiresIn);
33
42
  const iss = await getIss();
34
43
  const header = { alg, iss };
35
44
  return jwt.sign({}, await getPrivateKey(), { algorithm: alg, expiresIn, header, issuer: iss, subject });
@@ -1,3 +1,4 @@
1
+ import jwt from 'jsonwebtoken';
1
2
  import type { JWK } from 'node-jose';
2
3
  import { ConfigProviderForConfig } from '../../config';
3
4
  declare type Config = {
@@ -15,7 +16,7 @@ interface Initializer<C> {
15
16
  export declare const createLaunchVerifier: <C extends string = "launch">({ configSpace, fetcher }: Initializer<C>) => (configProvider: { [key in C]: {
16
17
  trustedDomain: import("../../config").ConfigValueProvider<string>;
17
18
  }; }) => {
18
- verify: (token: string) => Promise<string>;
19
+ verify: <T = undefined>(...[token, validator]: T extends undefined ? [string] : [string, (input: any) => T]) => Promise<T extends undefined ? jwt.JwtPayload : T>;
19
20
  };
20
21
  export declare type LaunchVerifier = ReturnType<ReturnType<typeof createLaunchVerifier>>;
21
22
  export {};
@@ -34,7 +34,7 @@ export const createLaunchVerifier = ({ configSpace, fetcher }) => (configProvide
34
34
  callback(error);
35
35
  }
36
36
  };
37
- const verify = (token) => new Promise((resolve, reject) => jwt.verify(token, getKey, {}, (err, payload) => {
37
+ const verify = (...[token, validator]) => new Promise((resolve, reject) => jwt.verify(token, getKey, {}, (err, payload) => {
38
38
  if (err && err instanceof TokenExpiredError) {
39
39
  reject(new SessionExpiredError());
40
40
  }
@@ -48,7 +48,19 @@ export const createLaunchVerifier = ({ configSpace, fetcher }) => (configProvide
48
48
  reject(new Error('JWT payload missing sub claim'));
49
49
  }
50
50
  else {
51
- resolve(payload.sub);
51
+ // we are migrating away from json encoding all the parameters into the `sub` claim
52
+ // and into using separate claims for each parameter. in transition, we check if the sub
53
+ // is json and return it if it is. this is still a breaking change when using this
54
+ // utility because applications no longer need to independently json parse the result
55
+ // starting now.
56
+ const parsed = payload;
57
+ try {
58
+ const jsonSubContents = JSON.parse(payload.sub);
59
+ Object.assign(parsed, jsonSubContents);
60
+ }
61
+ catch (e) { } // eslint-disable-line no-empty
62
+ // conditional return types are annoying
63
+ resolve((validator ? validator(parsed) : parsed));
52
64
  }
53
65
  }));
54
66
  return { verify };