dce-expresskit 4.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +93 -0
- package/LICENSE +21 -0
- package/README.md +17 -0
- package/genEncodedSecret.ts +84 -0
- package/lib/constants/LOG_REVIEW_ROUTE_PATH_PREFIX.d.ts +6 -0
- package/lib/constants/LOG_REVIEW_ROUTE_PATH_PREFIX.js +13 -0
- package/lib/constants/LOG_REVIEW_ROUTE_PATH_PREFIX.js.map +1 -0
- package/lib/constants/LOG_REVIEW_STATUS_ROUTE.d.ts +7 -0
- package/lib/constants/LOG_REVIEW_STATUS_ROUTE.js +14 -0
- package/lib/constants/LOG_REVIEW_STATUS_ROUTE.js.map +1 -0
- package/lib/constants/LOG_ROUTE_PATH.d.ts +6 -0
- package/lib/constants/LOG_ROUTE_PATH.js +13 -0
- package/lib/constants/LOG_ROUTE_PATH.js.map +1 -0
- package/lib/constants/ROUTE_PATH_PREFIX.d.ts +6 -0
- package/lib/constants/ROUTE_PATH_PREFIX.js +9 -0
- package/lib/constants/ROUTE_PATH_PREFIX.js.map +1 -0
- package/lib/errors/ErrorWithCode.d.ts +9 -0
- package/lib/errors/ErrorWithCode.js +33 -0
- package/lib/errors/ErrorWithCode.js.map +1 -0
- package/lib/helpers/addDBEditorEndpoints/generateEndpointPath.d.ts +9 -0
- package/lib/helpers/addDBEditorEndpoints/generateEndpointPath.js +17 -0
- package/lib/helpers/addDBEditorEndpoints/generateEndpointPath.js.map +1 -0
- package/lib/helpers/addDBEditorEndpoints/index.d.ts +41 -0
- package/lib/helpers/addDBEditorEndpoints/index.js +134 -0
- package/lib/helpers/addDBEditorEndpoints/index.js.map +1 -0
- package/lib/helpers/dataSigner.d.ts +40 -0
- package/lib/helpers/dataSigner.js +231 -0
- package/lib/helpers/dataSigner.js.map +1 -0
- package/lib/helpers/genRouteHandler.d.ts +75 -0
- package/lib/helpers/genRouteHandler.js +661 -0
- package/lib/helpers/genRouteHandler.js.map +1 -0
- package/lib/helpers/handleError.d.ts +18 -0
- package/lib/helpers/handleError.js +51 -0
- package/lib/helpers/handleError.js.map +1 -0
- package/lib/helpers/handleSuccess.d.ts +8 -0
- package/lib/helpers/handleSuccess.js +20 -0
- package/lib/helpers/handleSuccess.js.map +1 -0
- package/lib/helpers/initCrossServerCredentialCollection.d.ts +11 -0
- package/lib/helpers/initCrossServerCredentialCollection.js +15 -0
- package/lib/helpers/initCrossServerCredentialCollection.js.map +1 -0
- package/lib/helpers/initLogCollection.d.ts +11 -0
- package/lib/helpers/initLogCollection.js +26 -0
- package/lib/helpers/initLogCollection.js.map +1 -0
- package/lib/helpers/initServer.d.ts +43 -0
- package/lib/helpers/initServer.js +297 -0
- package/lib/helpers/initServer.js.map +1 -0
- package/lib/helpers/parseUserAgent.d.ts +17 -0
- package/lib/helpers/parseUserAgent.js +108 -0
- package/lib/helpers/parseUserAgent.js.map +1 -0
- package/lib/helpers/visitEndpointOnAnotherServer/index.d.ts +18 -0
- package/lib/helpers/visitEndpointOnAnotherServer/index.js +156 -0
- package/lib/helpers/visitEndpointOnAnotherServer/index.js.map +1 -0
- package/lib/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +23 -0
- package/lib/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.js +168 -0
- package/lib/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.js.map +1 -0
- package/lib/html/genErrorPage.d.ts +19 -0
- package/lib/html/genErrorPage.js +27 -0
- package/lib/html/genErrorPage.js.map +1 -0
- package/lib/html/genInfoPage.d.ts +13 -0
- package/lib/html/genInfoPage.js +16 -0
- package/lib/html/genInfoPage.js.map +1 -0
- package/lib/index.d.ts +11 -0
- package/lib/index.js +68 -0
- package/lib/index.js.map +1 -0
- package/lib/types/CrossServerCredential.d.ts +11 -0
- package/lib/types/CrossServerCredential.js +3 -0
- package/lib/types/CrossServerCredential.js.map +1 -0
- package/lib/types/ExpressKitErrorCode.d.ts +31 -0
- package/lib/types/ExpressKitErrorCode.js +38 -0
- package/lib/types/ExpressKitErrorCode.js.map +1 -0
- package/package.json +52 -0
- package/src/constants/LOG_REVIEW_ROUTE_PATH_PREFIX.ts +9 -0
- package/src/constants/LOG_REVIEW_STATUS_ROUTE.ts +10 -0
- package/src/constants/LOG_ROUTE_PATH.ts +9 -0
- package/src/constants/ROUTE_PATH_PREFIX.ts +7 -0
- package/src/errors/ErrorWithCode.tsx +15 -0
- package/src/helpers/addDBEditorEndpoints/generateEndpointPath.ts +16 -0
- package/src/helpers/addDBEditorEndpoints/index.ts +130 -0
- package/src/helpers/dataSigner.ts +296 -0
- package/src/helpers/genRouteHandler.ts +914 -0
- package/src/helpers/handleError.ts +66 -0
- package/src/helpers/handleSuccess.ts +18 -0
- package/src/helpers/initCrossServerCredentialCollection.ts +19 -0
- package/src/helpers/initLogCollection.ts +31 -0
- package/src/helpers/initServer.ts +284 -0
- package/src/helpers/parseUserAgent.ts +108 -0
- package/src/helpers/visitEndpointOnAnotherServer/index.ts +157 -0
- package/src/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.ts +164 -0
- package/src/html/genErrorPage.ts +144 -0
- package/src/html/genInfoPage.ts +101 -0
- package/src/index.ts +125 -0
- package/src/types/CrossServerCredential.ts +16 -0
- package/src/types/ExpressKitErrorCode.ts +37 -0
- 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
|