@openstax/ts-utils 1.9.1 → 1.12.0

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 (37) hide show
  1. package/dist/cjs/config/envConfig.d.ts +2 -2
  2. package/dist/cjs/config/envConfig.js +13 -6
  3. package/dist/cjs/config/resolveConfigValue.d.ts +1 -1
  4. package/dist/cjs/errors/index.js +4 -4
  5. package/dist/cjs/services/authProvider/browser.js +30 -9
  6. package/dist/cjs/services/authProvider/decryption.js +6 -1
  7. package/dist/cjs/services/authProvider/utils/decryptAndVerify.d.ts +20 -4
  8. package/dist/cjs/services/authProvider/utils/decryptAndVerify.js +74 -62
  9. package/dist/cjs/services/authProvider/utils/embeddedAuthProvider.d.ts +6 -2
  10. package/dist/cjs/services/authProvider/utils/embeddedAuthProvider.js +10 -3
  11. package/dist/cjs/services/exercisesGateway/index.js +2 -1
  12. package/dist/cjs/services/fileServer/index.d.ts +17 -0
  13. package/dist/cjs/services/fileServer/index.js +19 -0
  14. package/dist/cjs/services/fileServer/localFileServer.d.ts +13 -0
  15. package/dist/cjs/services/fileServer/localFileServer.js +23 -0
  16. package/dist/cjs/services/fileServer/s3FileServer.d.ts +16 -0
  17. package/dist/cjs/services/fileServer/s3FileServer.js +25 -0
  18. package/dist/cjs/tsconfig.without-specs.cjs.tsbuildinfo +1 -1
  19. package/dist/esm/config/envConfig.d.ts +2 -2
  20. package/dist/esm/config/envConfig.js +12 -5
  21. package/dist/esm/config/resolveConfigValue.d.ts +1 -1
  22. package/dist/esm/errors/index.js +2 -2
  23. package/dist/esm/services/authProvider/browser.js +30 -9
  24. package/dist/esm/services/authProvider/decryption.js +6 -1
  25. package/dist/esm/services/authProvider/utils/decryptAndVerify.d.ts +20 -4
  26. package/dist/esm/services/authProvider/utils/decryptAndVerify.js +72 -36
  27. package/dist/esm/services/authProvider/utils/embeddedAuthProvider.d.ts +6 -2
  28. package/dist/esm/services/authProvider/utils/embeddedAuthProvider.js +10 -3
  29. package/dist/esm/services/exercisesGateway/index.js +2 -1
  30. package/dist/esm/services/fileServer/index.d.ts +17 -0
  31. package/dist/esm/services/fileServer/index.js +13 -0
  32. package/dist/esm/services/fileServer/localFileServer.d.ts +13 -0
  33. package/dist/esm/services/fileServer/localFileServer.js +16 -0
  34. package/dist/esm/services/fileServer/s3FileServer.d.ts +16 -0
  35. package/dist/esm/services/fileServer/s3FileServer.js +21 -0
  36. package/dist/esm/tsconfig.without-specs.esm.tsbuildinfo +1 -1
  37. package/package.json +19 -3
@@ -1,4 +1,4 @@
1
- import { ConfigValueProvider } from '.';
1
+ import type { ConfigValueProvider } from '.';
2
2
  /**
3
3
  * A list of environment variables that were requested at build time. Used by webpack to
4
4
  * capture build-time environment variables values.
@@ -21,4 +21,4 @@ export declare const ENV_BUILD_CONFIGS: string[];
21
21
  *
22
22
  * @example const config = { configValue: envConfig('environment_variable_name') };
23
23
  */
24
- export declare const envConfig: (name: string, type?: 'build' | 'runtime', defaultValue?: ConfigValueProvider<string> | undefined) => ConfigValueProvider<string>;
24
+ export declare const envConfig: (name: string, type?: "build" | "runtime" | undefined, defaultValue?: ConfigValueProvider<string> | undefined) => ConfigValueProvider<string>;
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.envConfig = exports.ENV_BUILD_CONFIGS = void 0;
4
- const _1 = require(".");
4
+ const resolveConfigValue_1 = require("./resolveConfigValue");
5
5
  /**
6
6
  * A list of environment variables that were requested at build time. Used by webpack to
7
7
  * capture build-time environment variables values.
@@ -24,7 +24,12 @@ exports.ENV_BUILD_CONFIGS = [];
24
24
  *
25
25
  * @example const config = { configValue: envConfig('environment_variable_name') };
26
26
  */
27
- const envConfig = (name, type = 'build', defaultValue) => {
27
+ const envConfig = (name, type, defaultValue) => {
28
+ // this doesn't use a default parameter value because of a:
29
+ // "Regular parameters should not come after default parameters."
30
+ // error that occurs when the defaultValue optional default of `undefined`
31
+ // gets optimized out, causing a problem in cloudfront functions.
32
+ type !== null && type !== void 0 ? type : (type = 'build');
28
33
  if (type === 'build') {
29
34
  exports.ENV_BUILD_CONFIGS.push(name);
30
35
  }
@@ -33,17 +38,19 @@ const envConfig = (name, type = 'build', defaultValue) => {
33
38
  // @ts-ignore - hack to get around the way webpack/define works
34
39
  // - https://github.com/webpack/webpack/issues/14800
35
40
  // - https://github.com/webpack/webpack/issues/5392
36
- const envs = { ...process.env, ...(typeof __PROCESS_ENV !== 'undefined' ? __PROCESS_ENV : {}) };
37
- if (envs[name] === undefined) {
41
+ // also, spread operator not supported in cloudfront functions
42
+ const envs = Object.assign({}, process.env, typeof __PROCESS_ENV !== 'undefined' ? __PROCESS_ENV : {});
43
+ const value = envs[name];
44
+ if (value === undefined) {
38
45
  if (defaultValue === undefined) {
39
46
  throw new Error(`expected to find environment variable with name: ${name}`);
40
47
  }
41
48
  else {
42
- return (0, _1.resolveConfigValue)(defaultValue);
49
+ return (0, resolveConfigValue_1.resolveConfigValue)(defaultValue);
43
50
  }
44
51
  }
45
52
  else {
46
- return envs[name];
53
+ return value;
47
54
  }
48
55
  };
49
56
  };
@@ -1,4 +1,4 @@
1
- import { ConfigValueProvider } from '.';
1
+ import type { ConfigValueProvider } from '.';
2
2
  /**
3
3
  * resolves a config value into a string, to be used inside of things that are provided configurations
4
4
  */
@@ -1,4 +1,6 @@
1
1
  "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SessionExpiredError = exports.NotFoundError = exports.UnauthorizedError = exports.InvalidRequestError = exports.isAppError = void 0;
2
4
  /*
3
5
  * if code is split into multiple bundles, sometimes each bundle
4
6
  * will get its own definition of this module and then instanceof checks
@@ -11,10 +13,8 @@
11
13
  * error instanceof InvalidRequestError
12
14
  *
13
15
  */
14
- Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.SessionExpiredError = exports.NotFoundError = exports.UnauthorizedError = exports.InvalidRequestError = exports.isAppError = void 0;
16
- const errorIsType = ({ TYPE }) => (e) => e instanceof Error
17
- && e.constructor.TYPE === TYPE;
16
+ const errorIsType = (t) => (e) => e instanceof Error
17
+ && e.constructor.TYPE === t.TYPE;
18
18
  /**
19
19
  * Returns true if the error is defined in this library
20
20
  */
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.browserAuthProvider = void 0;
4
+ const lodash_1 = require("lodash");
4
5
  const __1 = require("../..");
5
6
  const config_1 = require("../../config");
7
+ const errors_1 = require("../../errors");
6
8
  const guards_1 = require("../../guards");
7
9
  const helpers_1 = require("../../routing/helpers");
8
10
  const embeddedAuthProvider_1 = require("./utils/embeddedAuthProvider");
@@ -12,14 +14,14 @@ const browserAuthProvider = ({ window, configSpace }) => (configProvider) => {
12
14
  const accountsBase = (0, __1.once)(() => (0, config_1.resolveConfigValue)(config.accountsBase));
13
15
  const queryString = window.location.search;
14
16
  const queryKey = 'auth';
15
- const authQuery = new URLSearchParams(queryString).get(queryKey);
17
+ const urlSearchParams = new URLSearchParams(queryString);
18
+ const authQuery = urlSearchParams.get(queryKey);
16
19
  const referrer = window.document.referrer ? new URL(window.document.referrer) : undefined;
17
20
  const isEmbedded = window.parent !== window;
18
21
  const trustedParent = isEmbedded && referrer && referrer.hostname.match(/^(openstax\.org|((.*)(\.openstax\.org|local|localhost)))$/) ? referrer : undefined;
19
- const { embeddedQueryValue, getAuthorizedEmbedUrl } = (0, embeddedAuthProvider_1.embeddedAuthProvider)(() => getUserData(), { queryKey, window });
20
- let userData = {
21
- token: [null, embeddedQueryValue].includes(authQuery) ? null : authQuery
22
- };
22
+ const { embeddedQueryKey, embeddedQueryValue, getAuthorizedEmbedUrl } = (0, embeddedAuthProvider_1.embeddedAuthProvider)(() => getUserData(), { authQuery: { key: queryKey, value: authQuery }, window });
23
+ const embeddedQuery = urlSearchParams.get(embeddedQueryKey);
24
+ let userData = { token: authQuery };
23
25
  const getAuthToken = async () => {
24
26
  return (await getUserData()).token;
25
27
  };
@@ -35,6 +37,12 @@ const browserAuthProvider = ({ window, configSpace }) => (configProvider) => {
35
37
  if (authQuery) {
36
38
  url.searchParams.set(queryKey, authQuery);
37
39
  }
40
+ else if (embeddedQuery) {
41
+ url.searchParams.set(queryKey, 'embedded');
42
+ }
43
+ if (embeddedQuery) {
44
+ url.searchParams.set(embeddedQueryKey, embeddedQuery);
45
+ }
38
46
  return url.href;
39
47
  };
40
48
  // *note* that this does not actually prevent cookies from being sent on same-origin
@@ -86,10 +94,23 @@ const browserAuthProvider = ({ window, configSpace }) => (configProvider) => {
86
94
  throw new Error(`Error response from Accounts ${response.status}: ${message}`);
87
95
  };
88
96
  const getUserData = (0, __1.once)(async () => {
89
- userData = authQuery === embeddedQueryValue
90
- ? await getParentWindowUser()
91
- : await getFetchUser();
92
- return userData;
97
+ // For backwards compatibility
98
+ if (authQuery === 'embedded') {
99
+ return getParentWindowUser();
100
+ }
101
+ // getFetchUser() will throw here if authQuery is not set
102
+ if (embeddedQuery !== embeddedQueryValue) {
103
+ return getFetchUser();
104
+ }
105
+ const userDataFromParentWindow = await getParentWindowUser();
106
+ if (!authQuery) {
107
+ return userDataFromParentWindow;
108
+ }
109
+ const userDataFromAuthQuery = await getFetchUser();
110
+ if ((0, lodash_1.isEqual)(userDataFromAuthQuery, userDataFromParentWindow)) {
111
+ return userDataFromAuthQuery;
112
+ }
113
+ throw new errors_1.UnauthorizedError();
93
114
  });
94
115
  const getUser = async () => {
95
116
  return (await getUserData()).user;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.decryptionAuthProvider = void 0;
4
4
  const resolveConfigValue_1 = require("../../config/resolveConfigValue");
5
+ const errors_1 = require("../../errors");
5
6
  const guards_1 = require("../../guards");
6
7
  const helpers_1 = require("../../misc/helpers");
7
8
  const decryptAndVerify_1 = require("./utils/decryptAndVerify");
@@ -25,7 +26,11 @@ const decryptionAuthProvider = (initializer) => (configProvider) => {
25
26
  if (!token) {
26
27
  return undefined;
27
28
  }
28
- return (0, decryptAndVerify_1.decryptAndVerify)(token, await encryptionPrivateKey(), await signaturePublicKey());
29
+ const result = (0, decryptAndVerify_1.decryptAndVerify)(token, await encryptionPrivateKey(), await signaturePublicKey());
30
+ if ('error' in result && result.error == 'expired token') {
31
+ throw new errors_1.SessionExpiredError();
32
+ }
33
+ return 'user' in result ? result.user : undefined;
29
34
  };
30
35
  return {
31
36
  getAuthorizedFetchConfig,
@@ -1,11 +1,27 @@
1
- import { User } from '..';
1
+ /// <reference types="node" />
2
+ import type { User } from '..';
3
+ export declare const decryptJwe: (jwe: string, encryptionPrivateKey: Buffer | string) => string | undefined;
4
+ declare type MaybeAccountsSSOToken = {
5
+ iss?: string;
6
+ sub?: User | string;
7
+ aud?: string;
8
+ exp?: number;
9
+ nbf?: number;
10
+ iat?: number;
11
+ jti?: string;
12
+ };
13
+ export declare const verifyJws: (jws: string, signaturePublicKey: Buffer | string) => MaybeAccountsSSOToken | undefined;
2
14
  /**
3
15
  * Decrypts and verifies a SSO cookie.
4
16
  *
5
17
  * @param token the encrypted token
6
18
  * @param encryptionPrivateKey the private key used to encrypt the token
7
19
  * @param signaturePublicKey the public key used to verify the decrypted token
8
- * @throws SessionExpiredError if the token is expired
9
- * @returns User (success) or undefined (failure)
20
+ * @returns {user: User} (success) or {error: string} (failure)
10
21
  */
11
- export declare const decryptAndVerify: (token: string, encryptionPrivateKey: string, signaturePublicKey: string) => User | undefined;
22
+ export declare const decryptAndVerify: (token: string, encryptionPrivateKey: string, signaturePublicKey: string) => {
23
+ user: User;
24
+ } | {
25
+ error: string;
26
+ };
27
+ export {};
@@ -1,79 +1,91 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
- var __importDefault = (this && this.__importDefault) || function (mod) {
26
- return (mod && mod.__esModule) ? mod : { "default": mod };
27
- };
28
2
  Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.decryptAndVerify = void 0;
30
- const crypto = __importStar(require("crypto"));
31
- const util_1 = require("util");
32
- const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
33
- const errors_1 = require("../../../errors");
3
+ exports.decryptAndVerify = exports.verifyJws = exports.decryptJwe = void 0;
4
+ const crypto_1 = require("crypto");
34
5
  const guards_1 = require("../../../guards");
35
- const decrypt = (input, key) => {
36
- const splitInput = input.split('.');
37
- const aad = new util_1.TextEncoder().encode(splitInput[0]);
38
- const iv = Buffer.from(splitInput[2], 'base64');
39
- const cipherText = Buffer.from(splitInput[3], 'base64');
40
- const tag = Buffer.from(splitInput[4], 'base64');
41
- const decipher = crypto.createDecipheriv('aes-256-gcm', Buffer.from(key), iv, { authTagLength: 16 });
6
+ const decryptJwe = (jwe, encryptionPrivateKey) => {
7
+ const jweParts = jwe.split('.', 6);
8
+ if (jweParts.length !== 5 || jweParts[1]) {
9
+ return undefined;
10
+ } // Invalid/unsupported JWE
11
+ const header = JSON.parse(Buffer.from(jweParts[0], 'base64url').toString());
12
+ if (header.alg !== 'dir' || header.enc !== 'A256GCM') {
13
+ // Unsupported signature/encryption algorithm
14
+ return undefined;
15
+ }
16
+ const aad = Buffer.from(jweParts[0]);
17
+ const iv = Buffer.from(jweParts[2], 'base64url');
18
+ const cipherText = Buffer.from(jweParts[3], 'base64url');
19
+ const authTag = Buffer.from(jweParts[4], 'base64url');
20
+ // Verify token signature and decrypt
21
+ const decipher = (0, crypto_1.createDecipheriv)('aes-256-gcm', encryptionPrivateKey, iv, { authTagLength: 16 });
42
22
  decipher.setAAD(aad, { plaintextLength: cipherText.length });
43
- decipher.setAuthTag(tag);
44
- const result = Buffer.concat([
45
- decipher.update(cipherText),
46
- decipher.final(),
47
- ]);
48
- return result.toString('utf-8');
23
+ try {
24
+ decipher.setAuthTag(authTag);
25
+ return `${decipher.update(cipherText)}${decipher.final()}`;
26
+ }
27
+ catch (error) {
28
+ // Invalid cipherText or authTag
29
+ return undefined;
30
+ }
31
+ };
32
+ exports.decryptJwe = decryptJwe;
33
+ const issuer = 'OpenStax Accounts';
34
+ const audience = 'OpenStax';
35
+ const clockTolerance = 300; // 5 minutes
36
+ const verifyJws = (jws, signaturePublicKey) => {
37
+ const jwsParts = jws.split('.', 4);
38
+ if (jwsParts.length !== 3) {
39
+ return undefined;
40
+ } // Invalid JWS
41
+ const header = JSON.parse(Buffer.from(jwsParts[0], 'base64url').toString());
42
+ if (header.alg !== 'RS256' || header.typ !== 'JWT') {
43
+ return undefined;
44
+ } // Unsupported JWS
45
+ const signedContent = Buffer.from(`${jwsParts[0]}.${jwsParts[1]}`);
46
+ const signature = Buffer.from(jwsParts[2], 'base64url');
47
+ if (!(0, crypto_1.verify)('RSA-SHA256', signedContent, signaturePublicKey, signature)) {
48
+ return undefined;
49
+ }
50
+ const payload = Buffer.from(jwsParts[1], 'base64url').toString();
51
+ try {
52
+ return JSON.parse(payload);
53
+ }
54
+ catch (error) {
55
+ return undefined;
56
+ }
49
57
  };
58
+ exports.verifyJws = verifyJws;
50
59
  /**
51
60
  * Decrypts and verifies a SSO cookie.
52
61
  *
53
62
  * @param token the encrypted token
54
63
  * @param encryptionPrivateKey the private key used to encrypt the token
55
64
  * @param signaturePublicKey the public key used to verify the decrypted token
56
- * @throws SessionExpiredError if the token is expired
57
- * @returns User (success) or undefined (failure)
65
+ * @returns {user: User} (success) or {error: string} (failure)
58
66
  */
59
67
  const decryptAndVerify = (token, encryptionPrivateKey, signaturePublicKey) => {
60
- try {
61
- // Decrypt SSO cookie
62
- const plaintext = decrypt(token, encryptionPrivateKey);
63
- const payload = jsonwebtoken_1.default.verify(plaintext, signaturePublicKey, {
64
- clockTolerance: 300 // 5 minutes
65
- });
66
- if (!(0, guards_1.isPlainObject)(payload) || !(0, guards_1.isPlainObject)(payload.sub) || !payload.sub.uuid) {
67
- return undefined;
68
- }
69
- // TS is confused because the library types the `sub` as a string
70
- return payload.sub;
68
+ const timestamp = Math.floor(Date.now() / 1000);
69
+ const jws = (0, exports.decryptJwe)(token, encryptionPrivateKey);
70
+ if (!jws) {
71
+ return { error: 'invalid token' };
71
72
  }
72
- catch (err) {
73
- if (err instanceof jsonwebtoken_1.default.TokenExpiredError) {
74
- throw new errors_1.SessionExpiredError();
75
- }
76
- return undefined;
73
+ const payload = (0, exports.verifyJws)(jws, signaturePublicKey);
74
+ // Ensure payload contains all the claims we expect
75
+ // Normally "sub" would be a string but Accounts uses an object for it instead
76
+ if (!(0, guards_1.isPlainObject)(payload) ||
77
+ !(0, guards_1.isPlainObject)(payload.sub) || !payload.sub.uuid ||
78
+ payload.iss !== issuer ||
79
+ payload.aud !== audience ||
80
+ !payload.exp ||
81
+ !payload.nbf || payload.nbf > timestamp + clockTolerance ||
82
+ !payload.iat || payload.iat > timestamp + clockTolerance ||
83
+ !payload.jti) {
84
+ return { error: 'invalid token' };
85
+ }
86
+ if (payload.exp < timestamp - clockTolerance) {
87
+ return { error: 'expired token' };
77
88
  }
89
+ return { user: payload.sub };
78
90
  };
79
91
  exports.decryptAndVerify = decryptAndVerify;
@@ -9,10 +9,14 @@ export declare enum PostMessageTypes {
9
9
  ReceiveUser = "receive-user",
10
10
  RequestUser = "request-user"
11
11
  }
12
- export declare const embeddedAuthProvider: (getUserData: UserDataLoader, { queryKey, window }: {
13
- queryKey?: string | undefined;
12
+ export declare const embeddedAuthProvider: (getUserData: UserDataLoader, { authQuery, window }: {
13
+ authQuery?: {
14
+ key: string;
15
+ value: string | null;
16
+ } | undefined;
14
17
  window: Window;
15
18
  }) => {
19
+ embeddedQueryKey: string;
16
20
  embeddedQueryValue: string;
17
21
  getAuthorizedEmbedUrl: (urlString: string, extraParams?: {
18
22
  [key: string]: string;
@@ -10,9 +10,10 @@ var PostMessageTypes;
10
10
  PostMessageTypes["ReceiveUser"] = "receive-user";
11
11
  PostMessageTypes["RequestUser"] = "request-user";
12
12
  })(PostMessageTypes = exports.PostMessageTypes || (exports.PostMessageTypes = {}));
13
- const embeddedAuthProvider = (getUserData, { queryKey = 'auth', window }) => {
13
+ const embeddedAuthProvider = (getUserData, { authQuery, window }) => {
14
14
  const trustedEmbeds = new Set();
15
- const embeddedQueryValue = 'embedded';
15
+ const embeddedQueryKey = 'embedded';
16
+ const embeddedQueryValue = 'true';
16
17
  const messageHandler = event => {
17
18
  if (event.data.type === PostMessageTypes.RequestUser && trustedEmbeds.has(event.origin)) {
18
19
  getUserData().then(data => {
@@ -25,10 +26,16 @@ const embeddedAuthProvider = (getUserData, { queryKey = 'auth', window }) => {
25
26
  const url = new URL(urlString);
26
27
  trustedEmbeds.add(url.origin);
27
28
  const params = query_string_1.default.parse(url.search);
28
- url.search = query_string_1.default.stringify({ ...params, ...extraParams, [queryKey]: embeddedQueryValue });
29
+ url.search = query_string_1.default.stringify({
30
+ ...params,
31
+ ...extraParams,
32
+ ...(authQuery && authQuery.value ? { [authQuery.key]: authQuery.value } : {}),
33
+ [embeddedQueryKey]: embeddedQueryValue
34
+ });
29
35
  return url.href;
30
36
  };
31
37
  return {
38
+ embeddedQueryKey,
32
39
  embeddedQueryValue,
33
40
  getAuthorizedEmbedUrl,
34
41
  unmount: () => {
@@ -51,7 +51,8 @@ const exercisesGateway = (initializer) => (configProvider) => {
51
51
  question.collaborator_solutions = [
52
52
  { solution_type: 'detailed', images: [], content_html: defaultHint }
53
53
  ];
54
- for (const [index, answer] of question.answers.entries()) {
54
+ for (let index = 0; index < question.answers.length; index++) {
55
+ const answer = question.answers[index];
55
56
  answer.correctness = defaultCorrectIndex === index ? '1.0' : '0.0';
56
57
  answer.feedback_html = defaultCorrectIndex === index ? 'This is the good one!' : defaultHint;
57
58
  }
@@ -0,0 +1,17 @@
1
+ /// <reference types="node" />
2
+ export declare type FileValue = {
3
+ dataType: 'file';
4
+ mimeType: string;
5
+ path: string;
6
+ label: string;
7
+ };
8
+ export declare type FolderValue = {
9
+ dataType: 'folder';
10
+ files: FileValue[];
11
+ };
12
+ export declare const isFileValue: (thing: any) => thing is FileValue;
13
+ export declare const isFolderValue: (thing: any) => thing is FolderValue;
14
+ export interface FileServerAdapter {
15
+ getFileContent: (source: FileValue) => Promise<Buffer>;
16
+ }
17
+ export declare const isFileOrFolder: (thing: any) => thing is FileValue | FolderValue;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isFileOrFolder = exports.isFolderValue = exports.isFileValue = void 0;
4
+ const guards_1 = require("../../guards");
5
+ const isFileValue = (thing) => (0, guards_1.isPlainObject)(thing)
6
+ && Object.keys(thing).every(key => ['dataType', 'path', 'label', 'mimeType'].includes(key))
7
+ && thing.dataType === 'file'
8
+ && typeof thing.mimeType === 'string'
9
+ && typeof thing.path === 'string'
10
+ && typeof thing.label === 'string';
11
+ exports.isFileValue = isFileValue;
12
+ const isFolderValue = (thing) => (0, guards_1.isPlainObject)(thing)
13
+ && Object.keys(thing).every(key => ['dataType', 'files'].includes(key))
14
+ && thing.dataType === 'folder'
15
+ && thing.files instanceof Array
16
+ && thing.files.every(exports.isFileValue);
17
+ exports.isFolderValue = isFolderValue;
18
+ const isFileOrFolder = (thing) => (0, exports.isFileValue)(thing) || (0, exports.isFolderValue)(thing);
19
+ exports.isFileOrFolder = isFileOrFolder;
@@ -0,0 +1,13 @@
1
+ import { ConfigProviderForConfig } from '../../config';
2
+ import { FileServerAdapter } from '.';
3
+ export declare type Config = {
4
+ storagePrefix: string;
5
+ };
6
+ interface Initializer<C> {
7
+ dataDir: string;
8
+ configSpace?: C;
9
+ }
10
+ export declare const localFileServer: <C extends string = "local">(initializer: Initializer<C>) => (configProvider: { [key in C]: {
11
+ storagePrefix: import("../../config").ConfigValueProvider<string>;
12
+ }; }) => FileServerAdapter;
13
+ export {};
@@ -0,0 +1,23 @@
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
+ exports.localFileServer = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const config_1 = require("../../config");
10
+ const guards_1 = require("../../guards");
11
+ const localFileServer = (initializer) => (configProvider) => {
12
+ const config = configProvider[(0, guards_1.ifDefined)(initializer.configSpace, 'local')];
13
+ const storagePrefix = (0, config_1.resolveConfigValue)(config.storagePrefix);
14
+ const fileDir = storagePrefix.then((prefix) => path_1.default.join(initializer.dataDir, prefix));
15
+ const getFileContent = async (source) => {
16
+ const filePath = path_1.default.join(await fileDir, source.path);
17
+ return fs_1.default.promises.readFile(filePath);
18
+ };
19
+ return {
20
+ getFileContent,
21
+ };
22
+ };
23
+ exports.localFileServer = localFileServer;
@@ -0,0 +1,16 @@
1
+ import { S3Client } from '@aws-sdk/client-s3';
2
+ import { ConfigProviderForConfig } from '../../config';
3
+ import { FileServerAdapter } from '.';
4
+ export declare type Config = {
5
+ bucketName: string;
6
+ bucketRegion: string;
7
+ };
8
+ interface Initializer<C> {
9
+ configSpace?: C;
10
+ s3Client?: typeof S3Client;
11
+ }
12
+ export declare const s3FileServer: <C extends string = "deployed">(initializer: Initializer<C>) => (configProvider: { [key in C]: {
13
+ bucketName: import("../../config").ConfigValueProvider<string>;
14
+ bucketRegion: import("../../config").ConfigValueProvider<string>;
15
+ }; }) => FileServerAdapter;
16
+ export {};
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.s3FileServer = void 0;
4
+ const client_s3_1 = require("@aws-sdk/client-s3");
5
+ const __1 = require("../..");
6
+ const assertions_1 = require("../../assertions");
7
+ const config_1 = require("../../config");
8
+ const guards_1 = require("../../guards");
9
+ const s3FileServer = (initializer) => (configProvider) => {
10
+ const config = configProvider[(0, guards_1.ifDefined)(initializer.configSpace, 'deployed')];
11
+ const bucketName = (0, __1.once)(() => (0, config_1.resolveConfigValue)(config.bucketName));
12
+ const bucketRegion = (0, __1.once)(() => (0, config_1.resolveConfigValue)(config.bucketRegion));
13
+ const client = (0, guards_1.ifDefined)(initializer.s3Client, client_s3_1.S3Client);
14
+ const s3Service = (0, __1.once)(async () => new client({ apiVersion: '2012-08-10', region: await bucketRegion() }));
15
+ const getFileContent = async (source) => {
16
+ const bucket = await bucketName();
17
+ const command = new client_s3_1.GetObjectCommand({ Bucket: bucket, Key: source.path });
18
+ const response = await (await s3Service()).send(command);
19
+ return Buffer.from(await (0, assertions_1.assertDefined)(response.Body, new Error('Invalid Response from s3')).transformToByteArray());
20
+ };
21
+ return {
22
+ getFileContent,
23
+ };
24
+ };
25
+ exports.s3FileServer = s3FileServer;