dce-expresskit 4.0.0-beta.10

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 (95) hide show
  1. package/.eslintrc.js +93 -0
  2. package/LICENSE +21 -0
  3. package/README.md +17 -0
  4. package/genEncodedSecret.ts +107 -0
  5. package/genSalt.ts +15 -0
  6. package/lib/constants/LOG_REVIEW_ROUTE_PATH_PREFIX.d.ts +6 -0
  7. package/lib/constants/LOG_REVIEW_ROUTE_PATH_PREFIX.js +13 -0
  8. package/lib/constants/LOG_REVIEW_ROUTE_PATH_PREFIX.js.map +1 -0
  9. package/lib/constants/LOG_REVIEW_STATUS_ROUTE.d.ts +7 -0
  10. package/lib/constants/LOG_REVIEW_STATUS_ROUTE.js +14 -0
  11. package/lib/constants/LOG_REVIEW_STATUS_ROUTE.js.map +1 -0
  12. package/lib/constants/LOG_ROUTE_PATH.d.ts +6 -0
  13. package/lib/constants/LOG_ROUTE_PATH.js +13 -0
  14. package/lib/constants/LOG_ROUTE_PATH.js.map +1 -0
  15. package/lib/constants/ROUTE_PATH_PREFIX.d.ts +6 -0
  16. package/lib/constants/ROUTE_PATH_PREFIX.js +9 -0
  17. package/lib/constants/ROUTE_PATH_PREFIX.js.map +1 -0
  18. package/lib/errors/ErrorWithCode.d.ts +9 -0
  19. package/lib/errors/ErrorWithCode.js +33 -0
  20. package/lib/errors/ErrorWithCode.js.map +1 -0
  21. package/lib/helpers/addDBEditorEndpoints/generateEndpointPath.d.ts +9 -0
  22. package/lib/helpers/addDBEditorEndpoints/generateEndpointPath.js +17 -0
  23. package/lib/helpers/addDBEditorEndpoints/generateEndpointPath.js.map +1 -0
  24. package/lib/helpers/addDBEditorEndpoints/index.d.ts +41 -0
  25. package/lib/helpers/addDBEditorEndpoints/index.js +134 -0
  26. package/lib/helpers/addDBEditorEndpoints/index.js.map +1 -0
  27. package/lib/helpers/dataSigner.d.ts +40 -0
  28. package/lib/helpers/dataSigner.js +236 -0
  29. package/lib/helpers/dataSigner.js.map +1 -0
  30. package/lib/helpers/genRouteHandler.d.ts +75 -0
  31. package/lib/helpers/genRouteHandler.js +661 -0
  32. package/lib/helpers/genRouteHandler.js.map +1 -0
  33. package/lib/helpers/handleError.d.ts +18 -0
  34. package/lib/helpers/handleError.js +51 -0
  35. package/lib/helpers/handleError.js.map +1 -0
  36. package/lib/helpers/handleSuccess.d.ts +8 -0
  37. package/lib/helpers/handleSuccess.js +20 -0
  38. package/lib/helpers/handleSuccess.js.map +1 -0
  39. package/lib/helpers/initCrossServerCredentialCollection.d.ts +11 -0
  40. package/lib/helpers/initCrossServerCredentialCollection.js +15 -0
  41. package/lib/helpers/initCrossServerCredentialCollection.js.map +1 -0
  42. package/lib/helpers/initLogCollection.d.ts +11 -0
  43. package/lib/helpers/initLogCollection.js +26 -0
  44. package/lib/helpers/initLogCollection.js.map +1 -0
  45. package/lib/helpers/initServer.d.ts +45 -0
  46. package/lib/helpers/initServer.js +293 -0
  47. package/lib/helpers/initServer.js.map +1 -0
  48. package/lib/helpers/parseUserAgent.d.ts +17 -0
  49. package/lib/helpers/parseUserAgent.js +108 -0
  50. package/lib/helpers/parseUserAgent.js.map +1 -0
  51. package/lib/helpers/visitEndpointOnAnotherServer/index.d.ts +18 -0
  52. package/lib/helpers/visitEndpointOnAnotherServer/index.js +156 -0
  53. package/lib/helpers/visitEndpointOnAnotherServer/index.js.map +1 -0
  54. package/lib/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +23 -0
  55. package/lib/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.js +168 -0
  56. package/lib/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.js.map +1 -0
  57. package/lib/html/genErrorPage.d.ts +19 -0
  58. package/lib/html/genErrorPage.js +27 -0
  59. package/lib/html/genErrorPage.js.map +1 -0
  60. package/lib/html/genInfoPage.d.ts +13 -0
  61. package/lib/html/genInfoPage.js +16 -0
  62. package/lib/html/genInfoPage.js.map +1 -0
  63. package/lib/index.d.ts +11 -0
  64. package/lib/index.js +68 -0
  65. package/lib/index.js.map +1 -0
  66. package/lib/types/CrossServerCredential.d.ts +11 -0
  67. package/lib/types/CrossServerCredential.js +3 -0
  68. package/lib/types/CrossServerCredential.js.map +1 -0
  69. package/lib/types/ExpressKitErrorCode.d.ts +31 -0
  70. package/lib/types/ExpressKitErrorCode.js +38 -0
  71. package/lib/types/ExpressKitErrorCode.js.map +1 -0
  72. package/package.json +53 -0
  73. package/src/constants/LOG_REVIEW_ROUTE_PATH_PREFIX.ts +9 -0
  74. package/src/constants/LOG_REVIEW_STATUS_ROUTE.ts +10 -0
  75. package/src/constants/LOG_ROUTE_PATH.ts +9 -0
  76. package/src/constants/ROUTE_PATH_PREFIX.ts +7 -0
  77. package/src/errors/ErrorWithCode.tsx +15 -0
  78. package/src/helpers/addDBEditorEndpoints/generateEndpointPath.ts +16 -0
  79. package/src/helpers/addDBEditorEndpoints/index.ts +130 -0
  80. package/src/helpers/dataSigner.ts +306 -0
  81. package/src/helpers/genRouteHandler.ts +914 -0
  82. package/src/helpers/handleError.ts +66 -0
  83. package/src/helpers/handleSuccess.ts +18 -0
  84. package/src/helpers/initCrossServerCredentialCollection.ts +19 -0
  85. package/src/helpers/initLogCollection.ts +31 -0
  86. package/src/helpers/initServer.ts +284 -0
  87. package/src/helpers/parseUserAgent.ts +108 -0
  88. package/src/helpers/visitEndpointOnAnotherServer/index.ts +157 -0
  89. package/src/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.ts +164 -0
  90. package/src/html/genErrorPage.ts +144 -0
  91. package/src/html/genInfoPage.ts +101 -0
  92. package/src/index.ts +125 -0
  93. package/src/types/CrossServerCredential.ts +16 -0
  94. package/src/types/ExpressKitErrorCode.ts +37 -0
  95. package/tsconfig.json +19 -0
@@ -0,0 +1,661 @@
1
+ "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
13
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
14
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
15
+ return new (P || (P = Promise))(function (resolve, reject) {
16
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
17
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
18
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
19
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
20
+ });
21
+ };
22
+ var __generator = (this && this.__generator) || function (thisArg, body) {
23
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
24
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
25
+ function verb(n) { return function (v) { return step([n, v]); }; }
26
+ function step(op) {
27
+ if (f) throw new TypeError("Generator is already executing.");
28
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
29
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
30
+ if (y = 0, t) op = [op[0] & 2, t.value];
31
+ switch (op[0]) {
32
+ case 0: case 1: t = op; break;
33
+ case 4: _.label++; return { value: op[1], done: false };
34
+ case 5: _.label++; y = op[1]; op = [0]; continue;
35
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
36
+ default:
37
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
38
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
39
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
40
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
41
+ if (t[2]) _.ops.pop();
42
+ _.trys.pop(); continue;
43
+ }
44
+ op = body.call(thisArg, _);
45
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
46
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
47
+ }
48
+ };
49
+ var __importDefault = (this && this.__importDefault) || function (mod) {
50
+ return (mod && mod.__esModule) ? mod : { "default": mod };
51
+ };
52
+ Object.defineProperty(exports, "__esModule", { value: true });
53
+ // Import dce-reactkit
54
+ var dce_reactkit_1 = require("dce-reactkit");
55
+ // Import caccl
56
+ var server_1 = require("caccl/server");
57
+ // Import caccl functions
58
+ var initServer_1 = require("./initServer");
59
+ // Import shared types
60
+ var ExpressKitErrorCode_1 = __importDefault(require("../types/ExpressKitErrorCode"));
61
+ // Import helpers
62
+ var handleError_1 = __importDefault(require("./handleError"));
63
+ var handleSuccess_1 = __importDefault(require("./handleSuccess"));
64
+ var genErrorPage_1 = __importDefault(require("../html/genErrorPage"));
65
+ var genInfoPage_1 = __importDefault(require("../html/genInfoPage"));
66
+ var parseUserAgent_1 = __importDefault(require("./parseUserAgent"));
67
+ var dataSigner_1 = require("./dataSigner");
68
+ /**
69
+ * Generate an express API route handler
70
+ * @author Gabe Abrams
71
+ * @param opts object containing all arguments
72
+ * @param opts.paramTypes map containing the types for each parameter that is
73
+ * included in the request (map: param name => type)
74
+ * @param opts.handler function that processes the request
75
+ * @param [opts.crossServerScope] the scope associated with this endpoint.
76
+ * If defined, this is a cross-server endpoint, which will never
77
+ * have any launch data, will never check Canvas roles or launch status, and will
78
+ * instead use scopes and reactkit credentials to sign and validate requests.
79
+ * Never start the path with /api/ttm or /api/admin if the endpoint is a cross-server
80
+ * endpoint because those roles will not be validated
81
+ * @param [opts.skipSessionCheck=true if crossServerScope defined] if true, skip
82
+ * the session check (allow users to not be logged in and launched via LTI).
83
+ * If crossServerScope is defined, this is always true
84
+ * @param [opts.unhandledErrorMessagePrefix] if included, when an error that
85
+ * is not of type ErrorWithCode is thrown, the client will receive an error
86
+ * where the error message is prefixed with this string. For example,
87
+ * if unhandledErrorMessagePrefix is
88
+ * 'While saving progress, we encountered an error:'
89
+ * and the error is 'progressInfo is not an object',
90
+ * the client will receive an error with the message
91
+ * 'While saving progress, we encountered an error: progressInfo is not an object'
92
+ * @returns express route handler that takes the following arguments:
93
+ * params (map: param name => value),
94
+ * req (express request object),
95
+ * next (express next function),
96
+ * send (a function that sends a string to the client),
97
+ * redirect (takes a url and redirects the user to that url),
98
+ * renderErrorPage (shows a static error page to the user),
99
+ * renderInfoPage (shows a static info page to the user),
100
+ * renderCustomHTML (renders custom html and sends it to the user),
101
+ * and returns the value to send to the client as a JSON API response, or
102
+ * calls next() or redirect(...) or send(...) or renderErrorPage(...).
103
+ * Note: params also has userId, userFirstName,
104
+ * userLastName, userEmail, userAvatarURL, isLearner, isTTM, isAdmin,
105
+ * and any other variables that
106
+ * are directly added to the session, if the user does have a session.
107
+ */
108
+ var genRouteHandler = function (opts) {
109
+ // Return a route handler
110
+ return function (req, res, next) { return __awaiter(void 0, void 0, void 0, function () {
111
+ var output, crossServerScope, skipSessionCheck, requestBody, err_1, paramList, i, _a, name_1, type, value, simpleVal, _b, launched, launchInfo, logServerEvent, responseSent, redirect, send, renderErrorPage, renderInfoPage, renderCustomHTML, results, err_2;
112
+ var _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u;
113
+ return __generator(this, function (_v) {
114
+ switch (_v.label) {
115
+ case 0:
116
+ output = {};
117
+ crossServerScope = null;
118
+ if (opts.crossServerScope) {
119
+ crossServerScope = (_c = opts.crossServerScope) !== null && _c !== void 0 ? _c : null;
120
+ }
121
+ skipSessionCheck = !!(opts.skipSessionCheck
122
+ || crossServerScope);
123
+ requestBody = __assign(__assign(__assign({}, req.body), req.query), req.params);
124
+ if (!crossServerScope) return [3 /*break*/, 4];
125
+ _v.label = 1;
126
+ case 1:
127
+ _v.trys.push([1, 3, , 4]);
128
+ // Validate the request body
129
+ return [4 /*yield*/, (0, dataSigner_1.validateSignedRequest)({
130
+ method: (_d = req.method) !== null && _d !== void 0 ? _d : 'GET',
131
+ path: req.path,
132
+ scope: crossServerScope,
133
+ params: requestBody,
134
+ })];
135
+ case 2:
136
+ // Validate the request body
137
+ _v.sent();
138
+ // Valid! Remove oauth values
139
+ Object.keys(requestBody).forEach(function (key) {
140
+ if (key.startsWith('oauth_')) {
141
+ delete requestBody[key];
142
+ }
143
+ });
144
+ return [3 /*break*/, 4];
145
+ case 3:
146
+ err_1 = _v.sent();
147
+ return [2 /*return*/, (0, handleError_1.default)(res, {
148
+ message: "The authenticity of a cross-server request could not be validated because an error occurred: ".concat((_e = err_1.message) !== null && _e !== void 0 ? _e : 'unknown error'),
149
+ code: ((_f = err_1.code) !== null && _f !== void 0 ? _f : ExpressKitErrorCode_1.default.UnknownCrossServerError),
150
+ status: 401,
151
+ })];
152
+ case 4:
153
+ paramList = Object.entries((_g = opts.paramTypes) !== null && _g !== void 0 ? _g : {});
154
+ for (i = 0; i < paramList.length; i++) {
155
+ _a = paramList[i], name_1 = _a[0], type = _a[1];
156
+ value = requestBody[name_1];
157
+ // Parse
158
+ if (type === dce_reactkit_1.ParamType.Boolean || type === dce_reactkit_1.ParamType.BooleanOptional) {
159
+ // Boolean
160
+ // Handle case where value doesn't exist
161
+ if (value === undefined) {
162
+ if (type === dce_reactkit_1.ParamType.BooleanOptional) {
163
+ output[name_1] = undefined;
164
+ }
165
+ else {
166
+ return [2 /*return*/, (0, handleError_1.default)(res, {
167
+ message: "Parameter ".concat(name_1, " is required, but it was not included."),
168
+ code: ExpressKitErrorCode_1.default.MissingParameter,
169
+ status: 422,
170
+ })];
171
+ }
172
+ }
173
+ else {
174
+ simpleVal = (String(value)
175
+ .trim()
176
+ .toLowerCase());
177
+ // Parse
178
+ output[name_1] = ([
179
+ 'true',
180
+ 'yes',
181
+ 'y',
182
+ '1',
183
+ 't',
184
+ ].indexOf(simpleVal) >= 0);
185
+ }
186
+ }
187
+ else if (type === dce_reactkit_1.ParamType.Float || type === dce_reactkit_1.ParamType.FloatOptional) {
188
+ // Float
189
+ // Handle case where value doesn't exist
190
+ if (value === undefined) {
191
+ if (type === dce_reactkit_1.ParamType.FloatOptional) {
192
+ output[name_1] = undefined;
193
+ }
194
+ else {
195
+ return [2 /*return*/, (0, handleError_1.default)(res, {
196
+ message: "Parameter ".concat(name_1, " is required, but it was not included."),
197
+ code: ExpressKitErrorCode_1.default.MissingParameter,
198
+ status: 422,
199
+ })];
200
+ }
201
+ }
202
+ else if (!Number.isNaN(Number.parseFloat(String(value)))) {
203
+ // Value is a number
204
+ output[name_1] = Number.parseFloat(String(value));
205
+ }
206
+ else {
207
+ // Issue!
208
+ return [2 /*return*/, (0, handleError_1.default)(res, {
209
+ message: "Request data was malformed: ".concat(name_1, " was not a valid float."),
210
+ code: ExpressKitErrorCode_1.default.InvalidParameter,
211
+ status: 422,
212
+ })];
213
+ }
214
+ }
215
+ else if (type === dce_reactkit_1.ParamType.Int || type === dce_reactkit_1.ParamType.IntOptional) {
216
+ // Int
217
+ // Handle case where value doesn't exist
218
+ if (value === undefined) {
219
+ if (type === dce_reactkit_1.ParamType.IntOptional) {
220
+ output[name_1] = undefined;
221
+ }
222
+ else {
223
+ return [2 /*return*/, (0, handleError_1.default)(res, {
224
+ message: "Parameter ".concat(name_1, " is required, but it was not included."),
225
+ code: ExpressKitErrorCode_1.default.MissingParameter,
226
+ status: 422,
227
+ })];
228
+ }
229
+ }
230
+ else if (!Number.isNaN(Number.parseInt(String(value), 10))) {
231
+ // Value is a number
232
+ output[name_1] = Number.parseInt(String(value), 10);
233
+ }
234
+ else {
235
+ // Issue!
236
+ return [2 /*return*/, (0, handleError_1.default)(res, {
237
+ message: "Request data was malformed: ".concat(name_1, " was not a valid int."),
238
+ code: ExpressKitErrorCode_1.default.InvalidParameter,
239
+ status: 422,
240
+ })];
241
+ }
242
+ }
243
+ else if (type === dce_reactkit_1.ParamType.JSON || type === dce_reactkit_1.ParamType.JSONOptional) {
244
+ // Stringified JSON
245
+ // Handle case where value doesn't exist
246
+ if (value === undefined) {
247
+ if (type === dce_reactkit_1.ParamType.JSONOptional) {
248
+ output[name_1] = undefined;
249
+ }
250
+ else {
251
+ return [2 /*return*/, (0, handleError_1.default)(res, {
252
+ message: "Parameter ".concat(name_1, " is required, but it was not included."),
253
+ code: ExpressKitErrorCode_1.default.MissingParameter,
254
+ status: 422,
255
+ })];
256
+ }
257
+ }
258
+ else {
259
+ // Value exists
260
+ // Parse
261
+ try {
262
+ output[name_1] = JSON.parse(String(value));
263
+ }
264
+ catch (err) {
265
+ return [2 /*return*/, (0, handleError_1.default)(res, {
266
+ message: "Request data was malformed: ".concat(name_1, " was not a valid JSON payload."),
267
+ code: ExpressKitErrorCode_1.default.InvalidParameter,
268
+ status: 422,
269
+ })];
270
+ }
271
+ }
272
+ }
273
+ else if (type === dce_reactkit_1.ParamType.String || type === dce_reactkit_1.ParamType.StringOptional) {
274
+ // String
275
+ // Handle case where value doesn't exist
276
+ if (value === undefined) {
277
+ if (type === dce_reactkit_1.ParamType.StringOptional) {
278
+ output[name_1] = undefined;
279
+ }
280
+ else {
281
+ return [2 /*return*/, (0, handleError_1.default)(res, {
282
+ message: "Parameter ".concat(name_1, " is required, but it was not included."),
283
+ code: ExpressKitErrorCode_1.default.MissingParameter,
284
+ status: 422,
285
+ })];
286
+ }
287
+ }
288
+ else {
289
+ // Value exists
290
+ // Leave as is
291
+ output[name_1] = value;
292
+ }
293
+ }
294
+ else {
295
+ // No valid data type
296
+ return [2 /*return*/, (0, handleError_1.default)(res, {
297
+ message: "An internal error occurred: we could not determine the type of ".concat(name_1, "."),
298
+ code: ExpressKitErrorCode_1.default.InvalidParameter,
299
+ status: 422,
300
+ })];
301
+ }
302
+ }
303
+ _b = (0, server_1.getLaunchInfo)(req), launched = _b.launched, launchInfo = _b.launchInfo;
304
+ if (
305
+ // Not launched
306
+ (!launched || !launchInfo)
307
+ // Not skipping the session check
308
+ && !skipSessionCheck) {
309
+ return [2 /*return*/, (0, handleError_1.default)(res, {
310
+ message: 'Your session has expired. Please refresh the page and try again.',
311
+ code: dce_reactkit_1.ReactKitErrorCode.SessionExpired,
312
+ status: 401,
313
+ })];
314
+ }
315
+ // Error if user info cannot be found
316
+ if (
317
+ // User information is incomplete
318
+ (!launchInfo
319
+ || !launchInfo.userId
320
+ || !launchInfo.userFirstName
321
+ || !launchInfo.userLastName
322
+ || (launchInfo.notInCourse
323
+ && !launchInfo.isAdmin)
324
+ || (!launchInfo.isTTM
325
+ && !launchInfo.isLearner
326
+ && !launchInfo.isAdmin))
327
+ // Not skipping the session check
328
+ && !skipSessionCheck) {
329
+ return [2 /*return*/, (0, handleError_1.default)(res, {
330
+ message: 'Your session was invalid. Please refresh the page and try again.',
331
+ code: dce_reactkit_1.ReactKitErrorCode.SessionExpired,
332
+ status: 401,
333
+ })];
334
+ }
335
+ // Add launch info to output
336
+ output.userId = (launchInfo
337
+ ? launchInfo.userId
338
+ : ((_h = output.userId) !== null && _h !== void 0 ? _h : undefined));
339
+ output.userFirstName = (launchInfo
340
+ ? launchInfo.userFirstName
341
+ : ((_j = output.userFirstName) !== null && _j !== void 0 ? _j : undefined));
342
+ output.userLastName = (launchInfo
343
+ ? launchInfo.userLastName
344
+ : ((_k = output.userLastName) !== null && _k !== void 0 ? _k : undefined));
345
+ output.userEmail = (launchInfo
346
+ ? launchInfo.userEmail
347
+ : ((_l = output.userEmail) !== null && _l !== void 0 ? _l : undefined));
348
+ output.userAvatarURL = (launchInfo
349
+ ? ((_m = launchInfo.userImage) !== null && _m !== void 0 ? _m : 'http://www.gravatar.com/avatar/?d=identicon')
350
+ : ((_o = output.userAvatarURL) !== null && _o !== void 0 ? _o : undefined));
351
+ output.isLearner = (launchInfo
352
+ ? !!launchInfo.isLearner
353
+ : ((_p = output.isLearner) !== null && _p !== void 0 ? _p : undefined));
354
+ output.isTTM = (launchInfo
355
+ ? !!launchInfo.isTTM
356
+ : ((_q = output.isTTM) !== null && _q !== void 0 ? _q : undefined));
357
+ output.isAdmin = (launchInfo
358
+ ? !!launchInfo.isAdmin
359
+ : ((_r = output.isAdmin) !== null && _r !== void 0 ? _r : undefined));
360
+ output.courseId = (launchInfo
361
+ ? ((_s = output.courseId) !== null && _s !== void 0 ? _s : launchInfo.courseId)
362
+ : ((_t = output.courseId) !== null && _t !== void 0 ? _t : undefined));
363
+ output.courseName = (launchInfo
364
+ ? launchInfo.contextLabel
365
+ : ((_u = output.courseName) !== null && _u !== void 0 ? _u : undefined));
366
+ // Add other session variables
367
+ Object.keys(req.session).forEach(function (propName) {
368
+ // Skip if prop already in output
369
+ if (output[propName] !== undefined) {
370
+ return;
371
+ }
372
+ // Add to output
373
+ var value = req.session[propName];
374
+ if (typeof value === 'string'
375
+ || typeof value === 'boolean'
376
+ || typeof value === 'number') {
377
+ output[propName] = value;
378
+ }
379
+ });
380
+ /*----------------------------------------*/
381
+ /* ----- Require Course Consistency ----- */
382
+ /*----------------------------------------*/
383
+ // Make sure the user actually launched from the appropriate course
384
+ if (output.courseId
385
+ && launchInfo
386
+ && launchInfo.courseId
387
+ && output.courseId !== launchInfo.courseId
388
+ && !output.isTTM
389
+ && !output.isAdmin) {
390
+ // Course of interest is not the launch course
391
+ return [2 /*return*/, (0, handleError_1.default)(res, {
392
+ message: 'You switched sessions by opening this app in another tab. Please refresh the page and try again.',
393
+ code: ExpressKitErrorCode_1.default.WrongCourse,
394
+ status: 401,
395
+ })];
396
+ }
397
+ /*----------------------------------------*/
398
+ /* Require Proper Permissions */
399
+ /*----------------------------------------*/
400
+ // Add TTM endpoint security
401
+ if (
402
+ // This is a TTM endpoint
403
+ req.path.startsWith('/api/ttm')
404
+ // User is not a TTM
405
+ && (
406
+ // User is not a TTM
407
+ !output.isTTM
408
+ // User is not an admin
409
+ && !output.isAdmin)) {
410
+ // User does not have access
411
+ return [2 /*return*/, (0, handleError_1.default)(res, {
412
+ message: 'This action is only allowed if you are a teaching team member for the course. Please go back to Canvas, log in as a teaching team member, and try again.',
413
+ code: ExpressKitErrorCode_1.default.NotTTM,
414
+ status: 401,
415
+ })];
416
+ }
417
+ // Add Admin endpoint security
418
+ if (
419
+ // This is an admin endpoint
420
+ req.path.startsWith('/api/admin')
421
+ // User is not an admin
422
+ && !output.isAdmin) {
423
+ // User does not have access
424
+ return [2 /*return*/, (0, handleError_1.default)(res, {
425
+ message: 'This action is only allowed if you are a Canvas admin. Please go back to Canvas, log in as an admin, and try again.',
426
+ code: ExpressKitErrorCode_1.default.NotAdmin,
427
+ status: 401,
428
+ })];
429
+ }
430
+ logServerEvent = function (logOpts) { return __awaiter(void 0, void 0, void 0, function () {
431
+ var _a, browser, device, _b, timestamp, year, month, day, hour, minute, mainLogInfo, typeSpecificInfo, sourceSpecificInfo, log, logCollection, err_3, dummyMainInfo, dummyTypeSpecificInfo, dummySourceSpecificInfo, log;
432
+ var _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
433
+ return __generator(this, function (_r) {
434
+ switch (_r.label) {
435
+ case 0:
436
+ _r.trys.push([0, 4, , 5]);
437
+ _a = (0, parseUserAgent_1.default)(req.headers['user-agent']), browser = _a.browser, device = _a.device;
438
+ _b = (0, dce_reactkit_1.getTimeInfoInET)(), timestamp = _b.timestamp, year = _b.year, month = _b.month, day = _b.day, hour = _b.hour, minute = _b.minute;
439
+ mainLogInfo = {
440
+ id: "".concat(launchInfo ? launchInfo.userId : 'unknown', "-").concat(Date.now(), "-").concat(Math.floor(Math.random() * 100000), "-").concat(Math.floor(Math.random() * 100000)),
441
+ userFirstName: (launchInfo ? launchInfo.userFirstName : 'unknown'),
442
+ userLastName: (launchInfo ? launchInfo.userLastName : 'unknown'),
443
+ userEmail: (launchInfo ? launchInfo.userEmail : 'unknown'),
444
+ userId: (launchInfo ? launchInfo.userId : -1),
445
+ isLearner: (launchInfo && !!launchInfo.isLearner),
446
+ isAdmin: (launchInfo && !!launchInfo.isAdmin),
447
+ isTTM: (launchInfo && !!launchInfo.isTTM),
448
+ courseId: (launchInfo ? launchInfo.courseId : -1),
449
+ courseName: (launchInfo ? launchInfo.contextLabel : 'unknown'),
450
+ browser: browser,
451
+ device: device,
452
+ year: year,
453
+ month: month,
454
+ day: day,
455
+ hour: hour,
456
+ minute: minute,
457
+ timestamp: timestamp,
458
+ context: (typeof logOpts.context === 'string'
459
+ ? logOpts.context
460
+ : ((_d = ((_c = logOpts.context) !== null && _c !== void 0 ? _c : {})._) !== null && _d !== void 0 ? _d : dce_reactkit_1.LogBuiltInMetadata.Context.Uncategorized)),
461
+ subcontext: ((_e = logOpts.subcontext) !== null && _e !== void 0 ? _e : dce_reactkit_1.LogBuiltInMetadata.Context.Uncategorized),
462
+ tags: ((_f = logOpts.tags) !== null && _f !== void 0 ? _f : []),
463
+ level: ((_g = logOpts.level) !== null && _g !== void 0 ? _g : dce_reactkit_1.LogLevel.Info),
464
+ metadata: ((_h = logOpts.metadata) !== null && _h !== void 0 ? _h : {}),
465
+ };
466
+ typeSpecificInfo = (('error' in opts && opts.error)
467
+ ? {
468
+ type: dce_reactkit_1.LogType.Error,
469
+ errorMessage: (_j = logOpts.error.message) !== null && _j !== void 0 ? _j : 'Unknown message',
470
+ errorCode: (_k = logOpts.error.code) !== null && _k !== void 0 ? _k : dce_reactkit_1.ReactKitErrorCode.NoCode,
471
+ errorStack: (_l = logOpts.error.stack) !== null && _l !== void 0 ? _l : 'No stack',
472
+ }
473
+ : {
474
+ type: dce_reactkit_1.LogType.Action,
475
+ target: ((_m = logOpts.target) !== null && _m !== void 0 ? _m : dce_reactkit_1.LogBuiltInMetadata.Target.NoTarget),
476
+ action: ((_o = logOpts.action) !== null && _o !== void 0 ? _o : dce_reactkit_1.LogAction.Unknown),
477
+ });
478
+ sourceSpecificInfo = (logOpts.overrideAsClientEvent
479
+ ? {
480
+ source: dce_reactkit_1.LogSource.Client,
481
+ }
482
+ : {
483
+ source: dce_reactkit_1.LogSource.Server,
484
+ routePath: req.path,
485
+ routeTemplate: req.route.path,
486
+ });
487
+ log = __assign(__assign(__assign({}, mainLogInfo), typeSpecificInfo), sourceSpecificInfo);
488
+ logCollection = (0, initServer_1.internalGetLogCollection)();
489
+ if (!logCollection) return [3 /*break*/, 2];
490
+ // Store to the log collection
491
+ return [4 /*yield*/, logCollection.insert(log)];
492
+ case 1:
493
+ // Store to the log collection
494
+ _r.sent();
495
+ return [3 /*break*/, 3];
496
+ case 2:
497
+ if (log.type === dce_reactkit_1.LogType.Error) {
498
+ // Print to console
499
+ // eslint-disable-next-line no-console
500
+ console.error('dce-reactkit error log:', log);
501
+ }
502
+ else {
503
+ // eslint-disable-next-line no-console
504
+ console.log('dce-reactkit action log:', log);
505
+ }
506
+ _r.label = 3;
507
+ case 3:
508
+ // Return log entry
509
+ return [2 /*return*/, log];
510
+ case 4:
511
+ err_3 = _r.sent();
512
+ // Print because we cannot store the error
513
+ // eslint-disable-next-line no-console
514
+ console.error('Could not log the following:', logOpts, 'due to this error:', ((_p = err_3) !== null && _p !== void 0 ? _p : {}).message, ((_q = err_3) !== null && _q !== void 0 ? _q : {}).stack);
515
+ dummyMainInfo = {
516
+ id: '-1',
517
+ userFirstName: 'Unknown',
518
+ userLastName: 'Unknown',
519
+ userEmail: 'unknown@harvard.edu',
520
+ userId: 1,
521
+ isLearner: false,
522
+ isAdmin: false,
523
+ isTTM: false,
524
+ courseId: 1,
525
+ courseName: 'Unknown',
526
+ browser: {
527
+ name: 'Unknown',
528
+ version: 'Unknown',
529
+ },
530
+ device: {
531
+ isMobile: false,
532
+ os: 'Unknown',
533
+ },
534
+ year: 1,
535
+ month: 1,
536
+ day: 1,
537
+ hour: 1,
538
+ minute: 1,
539
+ timestamp: Date.now(),
540
+ tags: [],
541
+ level: dce_reactkit_1.LogLevel.Warn,
542
+ metadata: {},
543
+ context: dce_reactkit_1.LogBuiltInMetadata.Context.Uncategorized,
544
+ subcontext: dce_reactkit_1.LogBuiltInMetadata.Context.Uncategorized,
545
+ };
546
+ dummyTypeSpecificInfo = {
547
+ type: dce_reactkit_1.LogType.Error,
548
+ errorMessage: 'Unknown',
549
+ errorCode: 'Unknown',
550
+ errorStack: 'No Stack',
551
+ };
552
+ dummySourceSpecificInfo = {
553
+ source: dce_reactkit_1.LogSource.Server,
554
+ routePath: req.path,
555
+ routeTemplate: req.route.path,
556
+ };
557
+ log = __assign(__assign(__assign({}, dummyMainInfo), dummyTypeSpecificInfo), dummySourceSpecificInfo);
558
+ return [2 /*return*/, log];
559
+ case 5: return [2 /*return*/];
560
+ }
561
+ });
562
+ }); };
563
+ responseSent = false;
564
+ redirect = function (pathOrURL) {
565
+ responseSent = true;
566
+ res.redirect(pathOrURL);
567
+ };
568
+ send = function (text, status) {
569
+ if (status === void 0) { status = 200; }
570
+ responseSent = true;
571
+ res.status(status).send(text);
572
+ };
573
+ renderErrorPage = function (renderOpts) {
574
+ var _a, _b, _c;
575
+ if (renderOpts === void 0) { renderOpts = {}; }
576
+ var html = (0, genErrorPage_1.default)(renderOpts);
577
+ send(html, (_a = renderOpts.status) !== null && _a !== void 0 ? _a : 500);
578
+ // Log server-side error if not a session expired error or 404
579
+ if (renderOpts.status && renderOpts.status === 404) {
580
+ return;
581
+ }
582
+ if ((_b = renderOpts.title) === null || _b === void 0 ? void 0 : _b.toLowerCase().includes('session expired')) {
583
+ return;
584
+ }
585
+ logServerEvent({
586
+ context: dce_reactkit_1.LogBuiltInMetadata.Context.ServerRenderedErrorPage,
587
+ error: {
588
+ message: "".concat(renderOpts.title, ": ").concat(renderOpts.description),
589
+ code: renderOpts.code,
590
+ },
591
+ metadata: {
592
+ title: renderOpts.title,
593
+ description: renderOpts.description,
594
+ code: renderOpts.code,
595
+ pageTitle: renderOpts.pageTitle,
596
+ status: (_c = renderOpts.status) !== null && _c !== void 0 ? _c : 500,
597
+ },
598
+ });
599
+ };
600
+ renderInfoPage = function (renderOpts) {
601
+ var html = (0, genInfoPage_1.default)(renderOpts);
602
+ send(html, 200);
603
+ };
604
+ renderCustomHTML = function (htmlOpts) {
605
+ var _a;
606
+ send(htmlOpts.html, (_a = htmlOpts.status) !== null && _a !== void 0 ? _a : 200);
607
+ };
608
+ _v.label = 5;
609
+ case 5:
610
+ _v.trys.push([5, 7, , 8]);
611
+ return [4 /*yield*/, opts.handler({
612
+ params: output,
613
+ req: req,
614
+ send: send,
615
+ next: function () {
616
+ responseSent = true;
617
+ next();
618
+ },
619
+ redirect: redirect,
620
+ renderErrorPage: renderErrorPage,
621
+ renderInfoPage: renderInfoPage,
622
+ renderCustomHTML: renderCustomHTML,
623
+ logServerEvent: logServerEvent,
624
+ })];
625
+ case 6:
626
+ results = _v.sent();
627
+ // Send results to client (only if next wasn't called)
628
+ if (!responseSent) {
629
+ return [2 /*return*/, (0, handleSuccess_1.default)(res, results !== null && results !== void 0 ? results : undefined)];
630
+ }
631
+ return [3 /*break*/, 8];
632
+ case 7:
633
+ err_2 = _v.sent();
634
+ // Prefix error message if needed
635
+ if (opts.unhandledErrorMessagePrefix
636
+ && err_2 instanceof Error
637
+ && err_2.message
638
+ && err_2.name !== 'ErrorWithCode') {
639
+ err_2.message = "".concat(opts.unhandledErrorMessagePrefix.trim(), " ").concat(err_2.message.trim());
640
+ }
641
+ // Send error to client (only if next wasn't called)
642
+ if (!responseSent) {
643
+ (0, handleError_1.default)(res, err_2);
644
+ // Log server-side error
645
+ logServerEvent({
646
+ context: dce_reactkit_1.LogBuiltInMetadata.Context.ServerEndpointError,
647
+ error: err_2,
648
+ });
649
+ return [2 /*return*/];
650
+ }
651
+ // Log error that was not responded with
652
+ // eslint-disable-next-line no-console
653
+ console.log('Error occurred but could not be sent to client because a response was already sent:', err_2);
654
+ return [3 /*break*/, 8];
655
+ case 8: return [2 /*return*/];
656
+ }
657
+ });
658
+ }); };
659
+ };
660
+ exports.default = genRouteHandler;
661
+ //# sourceMappingURL=genRouteHandler.js.map