dce-expresskit 4.0.0-beta-logreviewer.1

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