dce-reactkit 3.9.1 → 3.9.3

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.
@@ -9,6 +9,12 @@ import LogFunction from '../types/LogFunction';
9
9
  * @param opts.handler function that processes the request
10
10
  * @param [opts.skipSessionCheck] if true, skip the session check (allow users
11
11
  * to not be logged in and launched via LTI)
12
+ * @param [opts.allowedHosts] if included, only allow requests from these hosts
13
+ * (start a hostname with a "*" to only check the end of the hostname)
14
+ * you can include just one string instead of an array
15
+ * @param [opts.bannedHosts] if included, do not allow requests from these hosts
16
+ * (start a hostname with a "*" to only check the end of the hostname)
17
+ * you can include just one string instead of an array
12
18
  * @param [opts.unhandledErrorMessagePrefix] if included, when an error that
13
19
  * is not of type ErrorWithCode is thrown, the client will receive an error
14
20
  * where the error message is prefixed with this string. For example,
@@ -63,6 +69,8 @@ declare const genRouteHandler: (opts: {
63
69
  logServerEvent: LogFunction;
64
70
  }) => any;
65
71
  skipSessionCheck?: boolean | undefined;
72
+ allowedHosts?: string | string[] | undefined;
73
+ bannedHosts?: string | string[] | undefined;
66
74
  unhandledErrorMessagePrefix?: string | undefined;
67
75
  }) => (req: any, res: any, next: () => void) => Promise<undefined>;
68
76
  export default genRouteHandler;
@@ -8,6 +8,8 @@ declare enum ReactKitErrorCode {
8
8
  SessionExpired = "DRK3",
9
9
  MissingParameter = "DRK4",
10
10
  InvalidParameter = "DRK5",
11
+ HostNotAllowed = "DRK17",
12
+ HostBanned = "DRK18",
11
13
  WrongCourse = "DRK6",
12
14
  NoCACCLSendRequestFunction = "DRK7",
13
15
  NoCACCLGetLaunchInfoFunction = "DRK8",
package/dist/index.d.ts CHANGED
@@ -1044,6 +1044,12 @@ type LogFunction = (opts: ({
1044
1044
  * @param opts.handler function that processes the request
1045
1045
  * @param [opts.skipSessionCheck] if true, skip the session check (allow users
1046
1046
  * to not be logged in and launched via LTI)
1047
+ * @param [opts.allowedHosts] if included, only allow requests from these hosts
1048
+ * (start a hostname with a "*" to only check the end of the hostname)
1049
+ * you can include just one string instead of an array
1050
+ * @param [opts.bannedHosts] if included, do not allow requests from these hosts
1051
+ * (start a hostname with a "*" to only check the end of the hostname)
1052
+ * you can include just one string instead of an array
1047
1053
  * @param [opts.unhandledErrorMessagePrefix] if included, when an error that
1048
1054
  * is not of type ErrorWithCode is thrown, the client will receive an error
1049
1055
  * where the error message is prefixed with this string. For example,
@@ -1098,6 +1104,8 @@ declare const genRouteHandler: (opts: {
1098
1104
  logServerEvent: LogFunction;
1099
1105
  }) => any;
1100
1106
  skipSessionCheck?: boolean | undefined;
1107
+ allowedHosts?: string | string[] | undefined;
1108
+ bannedHosts?: string | string[] | undefined;
1101
1109
  unhandledErrorMessagePrefix?: string | undefined;
1102
1110
  }) => (req: any, res: any, next: () => void) => Promise<undefined>;
1103
1111
 
@@ -1637,6 +1645,8 @@ declare enum ReactKitErrorCode {
1637
1645
  SessionExpired = "DRK3",
1638
1646
  MissingParameter = "DRK4",
1639
1647
  InvalidParameter = "DRK5",
1648
+ HostNotAllowed = "DRK17",
1649
+ HostBanned = "DRK18",
1640
1650
  WrongCourse = "DRK6",
1641
1651
  NoCACCLSendRequestFunction = "DRK7",
1642
1652
  NoCACCLGetLaunchInfoFunction = "DRK8",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dce-reactkit",
3
- "version": "3.9.1",
3
+ "version": "3.9.3",
4
4
  "description": "Shared components for Harvard DCE apps",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -39,6 +39,12 @@ import LogLevel from '../types/LogLevel';
39
39
  * @param opts.handler function that processes the request
40
40
  * @param [opts.skipSessionCheck] if true, skip the session check (allow users
41
41
  * to not be logged in and launched via LTI)
42
+ * @param [opts.allowedHosts] if included, only allow requests from these hosts
43
+ * (start a hostname with a "*" to only check the end of the hostname)
44
+ * you can include just one string instead of an array
45
+ * @param [opts.bannedHosts] if included, do not allow requests from these hosts
46
+ * (start a hostname with a "*" to only check the end of the hostname)
47
+ * you can include just one string instead of an array
42
48
  * @param [opts.unhandledErrorMessagePrefix] if included, when an error that
43
49
  * is not of type ErrorWithCode is thrown, the client will receive an error
44
50
  * where the error message is prefixed with this string. For example,
@@ -102,6 +108,8 @@ const genRouteHandler = (
102
108
  },
103
109
  ) => any,
104
110
  skipSessionCheck?: boolean,
111
+ allowedHosts?: string[] | string,
112
+ bannedHosts?: string[] | string,
105
113
  unhandledErrorMessagePrefix?: string,
106
114
  },
107
115
  ) => {
@@ -110,6 +118,97 @@ const genRouteHandler = (
110
118
  // Output params
111
119
  const output: { [k in string]: any } = {};
112
120
 
121
+ /*----------------------------------------*/
122
+ /* ----------- Hostname Check ----------- */
123
+ /*----------------------------------------*/
124
+
125
+ // Get hostnames
126
+ const originURL = String(
127
+ req.get('origin')
128
+ || req.headers.origin
129
+ || req.headers.referer
130
+ );
131
+ const originHostname = (
132
+ originURL
133
+ // Remove protocol
134
+ .replace(/(^\w+:|^)\/\//, '')
135
+ // Remove port
136
+ .replace(/:\d+$/, '')
137
+ );
138
+ const serverHostname = String(req.hostname);
139
+
140
+ // Check allowed
141
+ if (opts.allowedHosts) {
142
+ // Only accept requests from allowed hosts
143
+ const allowedArray = (
144
+ Array.isArray(opts.allowedHosts)
145
+ ? opts.allowedHosts
146
+ : [opts.allowedHosts]
147
+ );
148
+
149
+ // Check if server is localhost
150
+ if (serverHostname === 'localhost') {
151
+ // Allow localhost
152
+ allowedArray.push('localhost');
153
+ }
154
+
155
+ // Check if current host is allowed
156
+ const allowed = allowedArray.some((allowedHost) => {
157
+ if (allowedHost.startsWith('*')) {
158
+ // Check end of hostname
159
+ return originHostname.endsWith(allowedHost.substring(1));
160
+ }
161
+
162
+ // Check full hostname
163
+ return originHostname.toLowerCase() === allowedHost.toLowerCase();
164
+ });
165
+
166
+ // If not allowed, return error
167
+ if (!allowed) {
168
+ return handleError(
169
+ res,
170
+ {
171
+ message: 'You are not allowed to access this endpoint.',
172
+ code: ReactKitErrorCode.HostNotAllowed,
173
+ status: 403,
174
+ },
175
+ );
176
+ }
177
+ }
178
+
179
+ // Check banned
180
+ if (opts.bannedHosts) {
181
+ // Do not allow requests from banned hosts
182
+ const bannedArray = (
183
+ Array.isArray(opts.bannedHosts)
184
+ ? opts.bannedHosts
185
+ : [opts.bannedHosts]
186
+ );
187
+
188
+ // Check if current host is banned
189
+ const banned = bannedArray.some((bannedHost) => {
190
+ if (bannedHost.startsWith('*')) {
191
+ // Check end of hostname
192
+ return originHostname.endsWith(bannedHost.substring(1));
193
+ }
194
+
195
+ // Check full hostname
196
+ return originHostname.toLowerCase() === bannedHost.toLowerCase();
197
+ });
198
+
199
+ // If banned, return error
200
+ if (banned) {
201
+ return handleError(
202
+ res,
203
+ {
204
+ message: 'You are not allowed to access this endpoint.',
205
+ code: ReactKitErrorCode.HostBanned,
206
+ status: 403,
207
+ },
208
+ );
209
+ }
210
+ }
211
+
113
212
  /*----------------------------------------*/
114
213
  /* ------------ Parse Params ------------ */
115
214
  /*----------------------------------------*/
@@ -354,22 +453,22 @@ const genRouteHandler = (
354
453
  output.userId = (
355
454
  launchInfo
356
455
  ? launchInfo.userId
357
- : undefined
456
+ : (output.userId ?? undefined)
358
457
  );
359
458
  output.userFirstName = (
360
459
  launchInfo
361
460
  ? launchInfo.userFirstName
362
- : undefined
461
+ : (output.userFirstName ?? undefined)
363
462
  );
364
463
  output.userLastName = (
365
464
  launchInfo
366
465
  ? launchInfo.userLastName
367
- : undefined
466
+ : (output.userLastName ?? undefined)
368
467
  );
369
468
  output.userEmail = (
370
469
  launchInfo
371
470
  ? launchInfo.userEmail
372
- : undefined
471
+ : (output.userEmail ?? undefined)
373
472
  );
374
473
  output.userAvatarURL = (
375
474
  launchInfo
@@ -377,32 +476,32 @@ const genRouteHandler = (
377
476
  launchInfo.userImage
378
477
  ?? 'http://www.gravatar.com/avatar/?d=identicon'
379
478
  )
380
- : undefined
479
+ : (output.userAvatarURL ?? undefined)
381
480
  );
382
481
  output.isLearner = (
383
482
  launchInfo
384
483
  ? !!launchInfo.isLearner
385
- : undefined
484
+ : (output.isLearner ?? undefined)
386
485
  );
387
486
  output.isTTM = (
388
487
  launchInfo
389
488
  ? !!launchInfo.isTTM
390
- : undefined
489
+ : (output.isTTM ?? undefined)
391
490
  );
392
491
  output.isAdmin = (
393
492
  launchInfo
394
493
  ? !!launchInfo.isAdmin
395
- : undefined
494
+ : (output.isAdmin ?? undefined)
396
495
  );
397
496
  output.courseId = (
398
497
  launchInfo
399
498
  ? (output.courseId ?? launchInfo.courseId)
400
- : undefined
499
+ : (output.courseId ?? undefined)
401
500
  );
402
501
  output.courseName = (
403
502
  launchInfo
404
503
  ? launchInfo.contextLabel
405
- : undefined
504
+ : (output.courseName ?? undefined)
406
505
  );
407
506
 
408
507
  // Add other session variables
@@ -1,4 +1,4 @@
1
- // Highest error code = DRK16
1
+ // Highest error code = DRK18
2
2
 
3
3
  /**
4
4
  * List of error codes built into the react kit
@@ -10,6 +10,8 @@ enum ReactKitErrorCode {
10
10
  SessionExpired = 'DRK3',
11
11
  MissingParameter = 'DRK4',
12
12
  InvalidParameter = 'DRK5',
13
+ HostNotAllowed = 'DRK17',
14
+ HostBanned = 'DRK18',
13
15
  WrongCourse = 'DRK6',
14
16
  NoCACCLSendRequestFunction = 'DRK7',
15
17
  NoCACCLGetLaunchInfoFunction = 'DRK8',