dce-reactkit 3.11.3 → 3.12.1-beta-cross-server.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 (37) hide show
  1. package/README.md +20 -0
  2. package/dist/cjs/index.js +281 -140
  3. package/dist/cjs/index.js.map +1 -1
  4. package/dist/cjs/types/helpers/dataSigner.d.ts +38 -0
  5. package/dist/cjs/types/helpers/genRouteHandler.d.ts +10 -10
  6. package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/index.d.ts +15 -13
  7. package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +0 -6
  8. package/dist/cjs/types/index.d.ts +2 -1
  9. package/dist/cjs/types/server/initCrossServerCredentialCollection.d.ts +8 -0
  10. package/dist/cjs/types/server/initServer.d.ts +11 -0
  11. package/dist/cjs/types/types/CrossServerCredential.d.ts +11 -0
  12. package/dist/cjs/types/types/ReactKitErrorCode.d.ts +13 -1
  13. package/dist/esm/index.js +280 -140
  14. package/dist/esm/index.js.map +1 -1
  15. package/dist/esm/types/helpers/dataSigner.d.ts +38 -0
  16. package/dist/esm/types/helpers/genRouteHandler.d.ts +10 -10
  17. package/dist/esm/types/helpers/visitEndpointOnAnotherServer/index.d.ts +15 -13
  18. package/dist/esm/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +0 -6
  19. package/dist/esm/types/index.d.ts +2 -1
  20. package/dist/esm/types/server/initCrossServerCredentialCollection.d.ts +8 -0
  21. package/dist/esm/types/server/initServer.d.ts +11 -0
  22. package/dist/esm/types/types/CrossServerCredential.d.ts +11 -0
  23. package/dist/esm/types/types/ReactKitErrorCode.d.ts +13 -1
  24. package/dist/index.d.ts +54 -25
  25. package/genEncodedSecret.ts +28 -0
  26. package/package.json +5 -2
  27. package/src/components/IntelliTable.tsx +1 -1
  28. package/src/components/ItemPicker/NestableItemList.tsx +0 -1
  29. package/src/helpers/dataSigner.ts +232 -0
  30. package/src/helpers/genRouteHandler.ts +55 -95
  31. package/src/helpers/visitEndpointOnAnotherServer/index.ts +42 -41
  32. package/src/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.ts +3 -5
  33. package/src/index.ts +2 -0
  34. package/src/server/initCrossServerCredentialCollection.ts +16 -0
  35. package/src/server/initServer.ts +18 -0
  36. package/src/types/CrossServerCredential.ts +16 -0
  37. package/src/types/ReactKitErrorCode.tsx +13 -1
package/README.md CHANGED
@@ -14,3 +14,23 @@ npm install dce-reactkit --save
14
14
  ```js
15
15
  import { LoadingSpinner } from 'dce-reactkit';
16
16
  ```
17
+
18
+ ## Cross-server Requests
19
+
20
+ If your server will be _accepting_ cross-server requests, include a `REACTKIT_CRED_ENCODING_SALT` environment variable with secure, secret text.
21
+
22
+ To generate new encoded secrets, use `npm run gen-reactkit-cross-server-secret`. You'll need to put credentials into a collection. Create that collection in your `mongo.ts` file:
23
+
24
+ ```ts
25
+ // Import dce-reactkit
26
+ import { initCrossServerCredentialCollection, CrossServerCredential } from 'dce-reactkit';
27
+
28
+ ...
29
+
30
+ // Cross-server Credentials
31
+ export const crossServerCredentialCollection = initCrossServerCredentialCollection(Collection) as Collection<CrossServerCredential>;
32
+ ```
33
+
34
+ In that collection, manually insert into the database individual credentials, one for each service. You'll need to add a description, list of allowed scopes, and you'll need to come up with a public key.
35
+
36
+ If your server will be _sending_ cross-server requests, after creating a secret and key using the process above, in the server that will be _sending_ the cross-server requests, add the credential to the environment as `REACTKIT_CROSS_SERVER_KEY` and `REACTKIT_CROSS_SERVER_SECRET`.
package/dist/cjs/index.js CHANGED
@@ -7,6 +7,8 @@ var freeSolidSvgIcons = require('@fortawesome/free-solid-svg-icons');
7
7
  var reactFontawesome = require('@fortawesome/react-fontawesome');
8
8
  var ReactDOM = require('react-dom');
9
9
  var freeRegularSvgIcons = require('@fortawesome/free-regular-svg-icons');
10
+ var crypto = require('crypto');
11
+ var jwt = require('jwt-simple');
10
12
  var qs = require('qs');
11
13
 
12
14
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
@@ -32,6 +34,7 @@ function _interopNamespace(e) {
32
34
  var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
33
35
  var React__namespace = /*#__PURE__*/_interopNamespace(React);
34
36
  var ReactDOM__default = /*#__PURE__*/_interopDefaultLegacy(ReactDOM);
37
+ var jwt__default = /*#__PURE__*/_interopDefaultLegacy(jwt);
35
38
  var qs__default = /*#__PURE__*/_interopDefaultLegacy(qs);
36
39
 
37
40
  /******************************************************************************
@@ -59,7 +62,7 @@ function __awaiter(thisArg, _arguments, P, generator) {
59
62
  });
60
63
  }
61
64
 
62
- // Highest error code = DRK18
65
+ // Highest error code = DRK30
63
66
  /**
64
67
  * List of error codes built into the react kit
65
68
  * @author Gabe Abrams
@@ -85,6 +88,18 @@ var ReactKitErrorCode;
85
88
  ReactKitErrorCode["NotConnected"] = "DRK14";
86
89
  ReactKitErrorCode["SelfSigned"] = "DRK15";
87
90
  ReactKitErrorCode["ResponseParseError"] = "DRK16";
91
+ ReactKitErrorCode["PackUnparseable"] = "DRK28";
92
+ ReactKitErrorCode["PackInvalidMethod"] = "DRK19";
93
+ ReactKitErrorCode["PackInvalidPath"] = "DRK20";
94
+ ReactKitErrorCode["PackInvalidCollection"] = "DRK21";
95
+ ReactKitErrorCode["PackInvalidCredential"] = "DRK23";
96
+ ReactKitErrorCode["PackInvalidScope"] = "DRK22";
97
+ ReactKitErrorCode["PackInvalidTimestamp"] = "DRK24";
98
+ ReactKitErrorCode["PackInvalidSignature"] = "DRK25";
99
+ ReactKitErrorCode["PackInvalidBody"] = "DRK26";
100
+ ReactKitErrorCode["CrossServerNoCredentialsToSignWith"] = "DRK27";
101
+ ReactKitErrorCode["CrossServerNoPack"] = "DRK29";
102
+ ReactKitErrorCode["CrossServerNoCredentialEncodingSalt"] = "DRK30";
88
103
  })(ReactKitErrorCode || (ReactKitErrorCode = {}));
89
104
  var ReactKitErrorCode$1 = ReactKitErrorCode;
90
105
 
@@ -2672,7 +2687,7 @@ const NestableItemList = (props) => {
2672
2687
  React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: childExpanded[item.id] ? freeSolidSvgIcons.faChevronDown : freeSolidSvgIcons.faChevronRight })))),
2673
2688
  React__default["default"].createElement(CheckboxButton, { className: `NestableItemList-CheckboxButton-${item.id}`, text: item.name, checked: item.isGroup ? allChecked(item.children) : item.checked, dashed: item.isGroup ? !noneChecked(item.children) : false, onChanged: (checked) => {
2674
2689
  onChanged(changeChecked(item.id, checked, items));
2675
- }, ariaLabel: `Select ${item.name}`, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }),
2690
+ }, ariaLabel: `Select ${item.name}`, checkedVariant: Variant$1.Light }),
2676
2691
  (item.isGroup && childExpanded[item.id]) && (React__default["default"].createElement("div", { className: "NestableItemList-children-container", style: {
2677
2692
  paddingLeft: '2.2rem',
2678
2693
  } },
@@ -3077,7 +3092,7 @@ const IntelliTable = (props) => {
3077
3092
  param: column.param,
3078
3093
  visible: checked,
3079
3094
  });
3080
- }, checked: columnVisibilityMap[column.param], ariaLabel: `show "${column.title}" column in the ${title} table`, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }));
3095
+ }, checked: columnVisibilityMap[column.param], ariaLabel: `show "${column.title}" column in the ${title} table`, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Secondary }));
3081
3096
  }),
3082
3097
  React__default["default"].createElement("div", { className: "mt-3" }, "Or you can:"),
3083
3098
  React__default["default"].createElement("button", { type: "button", id: `IntelliTable-${id}-select-all-columns`, className: "btn btn-secondary me-2", "aria-label": `show all columns in the ${title} table`, onClick: () => {
@@ -14049,6 +14064,8 @@ const LOG_REVIEW_STATUS_ROUTE = `${ROUTE_PATH_PREFIX}/logs/access_allowed`;
14049
14064
  let _cacclGetLaunchInfo;
14050
14065
  // Stored copy of dce-mango log collection
14051
14066
  let _logCollection;
14067
+ // Stored copy of dce-mango cross-server credential collection
14068
+ let _crossServerCredentialCollection;
14052
14069
  /*------------------------------------------------------------------------*/
14053
14070
  /* Helpers */
14054
14071
  /*------------------------------------------------------------------------*/
@@ -14073,6 +14090,15 @@ const cacclGetLaunchInfo = (req) => {
14073
14090
  const internalGetLogCollection = () => {
14074
14091
  return _logCollection !== null && _logCollection !== void 0 ? _logCollection : null;
14075
14092
  };
14093
+ /**
14094
+ * Get cross-server credential collection
14095
+ * @author Gabe Abrams
14096
+ * @return cross-server credential collection if one was included during launch or null
14097
+ * if we don't have a cross-server credential collection (yet)
14098
+ */
14099
+ const internalGetCrossServerCredentialCollection = () => {
14100
+ return _crossServerCredentialCollection !== null && _crossServerCredentialCollection !== void 0 ? _crossServerCredentialCollection : null;
14101
+ };
14076
14102
  /*------------------------------------------------------------------------*/
14077
14103
  /* Main */
14078
14104
  /*------------------------------------------------------------------------*/
@@ -14092,10 +14118,14 @@ const internalGetLogCollection = () => {
14092
14118
  * userIds are allowed to review logs. If a dce-mango collection, only
14093
14119
  * Canvas admins with entries in that collection ({ userId, ...}) are allowed
14094
14120
  * to review logs
14121
+ * @param [opts.crossServerCredentialCollection] mongo collection from dce-mango to use for
14122
+ * storing cross-server credentials. If none is included, cross-server credentials
14123
+ * are not supported
14095
14124
  */
14096
14125
  const initServer = (opts) => {
14097
14126
  _cacclGetLaunchInfo = opts.getLaunchInfo;
14098
14127
  _logCollection = opts.logCollection;
14128
+ _crossServerCredentialCollection = opts.crossServerCredentialCollection;
14099
14129
  /*----------------------------------------*/
14100
14130
  /* Logging */
14101
14131
  /*----------------------------------------*/
@@ -14638,6 +14668,154 @@ const parseUserAgent = (userAgent) => {
14638
14668
  };
14639
14669
  };
14640
14670
 
14671
+ /*------------------------------------------------------------------------*/
14672
+ /* ------------------------------ Encoding ------------------------------ */
14673
+ /*------------------------------------------------------------------------*/
14674
+ /**
14675
+ * Validate a JWT token
14676
+ * @author Gabe Abrams
14677
+ * @param encodedSecret the encoded secret
14678
+ * @return the secret
14679
+ */
14680
+ const decodeCredentialSecret = (encodedSecret) => {
14681
+ // Get the credential encoding salt
14682
+ const credentialEncodingSalt = process.env.REACTKIT_CRED_ENCODING_SALT;
14683
+ if (!credentialEncodingSalt) {
14684
+ throw new ErrorWithCode('Cannot decode a cross-server credential secret because the credential encoding salt was not found in env.', ReactKitErrorCode$1.CrossServerNoCredentialEncodingSalt);
14685
+ }
14686
+ // Decode the secret
14687
+ return jwt__default["default"].decode(encodedSecret, credentialEncodingSalt);
14688
+ };
14689
+ /*------------------------------------------------------------------------*/
14690
+ /* ------------------------------- Signing ------------------------------ */
14691
+ /*------------------------------------------------------------------------*/
14692
+ /**
14693
+ * Sign using sha 256
14694
+ * @author Gabe Abrams
14695
+ * @param opts object containing all arguments
14696
+ * @param opts.pack the pack to sign
14697
+ * @param opts.secret the reactkit secret to sign with
14698
+ * @return the signed hash
14699
+ */
14700
+ const genSignature = (opts) => {
14701
+ // Generate signature
14702
+ return (crypto.createHash('sha256')
14703
+ .update(JSON.stringify({ pack: opts.pack, secret: opts.secret }))
14704
+ .digest('base64'));
14705
+ };
14706
+ /**
14707
+ * Sign data with a private reactkit key, package it into a signed data pack
14708
+ * @author Gabe Abrams
14709
+ * @param opts object containing all arguments
14710
+ * @param opts.method the method to sign
14711
+ * @param opts.path the http request path
14712
+ * @param opts.params the data in the body to sign
14713
+ * @param opts.key the reactkit key to sign with
14714
+ * @param opts.secret the reactkit secret to sign with
14715
+ * @return the signed data
14716
+ */
14717
+ const createSignedPack = (opts) => {
14718
+ // Create a timestamp
14719
+ const timestamp = Date.now();
14720
+ // Create the pack
14721
+ const pack = JSON.stringify({
14722
+ method: opts.method,
14723
+ path: opts.path,
14724
+ params: opts.params,
14725
+ key: opts.key,
14726
+ timestamp,
14727
+ });
14728
+ // Generate signature
14729
+ const signature = genSignature({
14730
+ pack,
14731
+ secret: opts.secret,
14732
+ });
14733
+ // Create a signed pack
14734
+ const signedPack = encodeURIComponent(JSON.stringify({
14735
+ pack,
14736
+ signature,
14737
+ }));
14738
+ // Return the signed pack
14739
+ return signedPack;
14740
+ };
14741
+ /**
14742
+ * Parse signed pack. Throws an error if invalid
14743
+ * @author Gabe Abrams
14744
+ * @param opts object containing all arguments
14745
+ * @param opts.method the method of the data validate
14746
+ * @param opts.path the http request path to validate
14747
+ * @param opts.scope the name of the scope to validate
14748
+ * @param opts.signedPack the signed data pack to validate
14749
+ * @returns parsed and validated params
14750
+ */
14751
+ const parseSignedPack = (opts) => __awaiter(void 0, void 0, void 0, function* () {
14752
+ // Extract signature
14753
+ let pack;
14754
+ let signature;
14755
+ let method;
14756
+ let path;
14757
+ let key;
14758
+ let timestamp;
14759
+ let params;
14760
+ try {
14761
+ ({
14762
+ pack,
14763
+ signature,
14764
+ } = JSON.parse(decodeURIComponent(opts.signedPack)));
14765
+ // Unpack
14766
+ ({
14767
+ method,
14768
+ path,
14769
+ params,
14770
+ key,
14771
+ timestamp,
14772
+ } = JSON.parse(pack));
14773
+ }
14774
+ catch (err) {
14775
+ throw new ErrorWithCode('Could not validate a cross-server request because the request could not be parsed.', ReactKitErrorCode$1.PackUnparseable);
14776
+ }
14777
+ // Make sure the method and path match
14778
+ if (method !== opts.method) {
14779
+ throw new ErrorWithCode('Could not validate a cross-server request because the method did not match.', ReactKitErrorCode$1.PackInvalidMethod);
14780
+ }
14781
+ if (path !== opts.path) {
14782
+ throw new ErrorWithCode('Could not validate a cross-server request because the path did not match.', ReactKitErrorCode$1.PackInvalidPath);
14783
+ }
14784
+ // Make sure the timestamp was recent enough
14785
+ const elapsedMs = Math.abs(Date.now() - timestamp);
14786
+ if (elapsedMs < MINUTE_IN_MS) {
14787
+ throw new ErrorWithCode('Could not validate a cross-server request because the request was too old.', ReactKitErrorCode$1.PackInvalidTimestamp);
14788
+ }
14789
+ // Get the cross-server credential collection
14790
+ const crossServerCredentialCollection = internalGetCrossServerCredentialCollection();
14791
+ if (!crossServerCredentialCollection) {
14792
+ throw new ErrorWithCode('Could not validate a cross-server request because the cross-server credential collection was not ready in time.', ReactKitErrorCode$1.PackInvalidCollection);
14793
+ }
14794
+ // Get the cross-server credential
14795
+ const crossServerCredential = yield crossServerCredentialCollection.find({ key });
14796
+ if (!crossServerCredential) {
14797
+ throw new ErrorWithCode('Could not validate a cross-server request because the credential was not found.', ReactKitErrorCode$1.PackInvalidCredential);
14798
+ }
14799
+ // Make sure the scope is included
14800
+ const allowedScopes = crossServerCredential.scopes;
14801
+ if (!allowedScopes.includes(opts.scope)) {
14802
+ throw new ErrorWithCode('Could not validate a cross-server request because the scope was not included.', ReactKitErrorCode$1.PackInvalidScope);
14803
+ }
14804
+ // Decode the secret
14805
+ const secret = decodeCredentialSecret(crossServerCredential.encodedeSecret);
14806
+ // Generate signature
14807
+ const expectedSignature = genSignature({
14808
+ pack,
14809
+ secret,
14810
+ });
14811
+ // Make sure the signature matches
14812
+ if (signature !== expectedSignature) {
14813
+ throw new ErrorWithCode('Could not validate a cross-server request because the signature did not match.', ReactKitErrorCode$1.PackInvalidSignature);
14814
+ }
14815
+ // Return body
14816
+ return params !== null && params !== void 0 ? params : {};
14817
+ });
14818
+
14641
14819
  /**
14642
14820
  * Generate an express API route handler
14643
14821
  * @author Gabe Abrams
@@ -14645,14 +14823,15 @@ const parseUserAgent = (userAgent) => {
14645
14823
  * @param opts.paramTypes map containing the types for each parameter that is
14646
14824
  * included in the request (map: param name => type)
14647
14825
  * @param opts.handler function that processes the request
14648
- * @param [opts.skipSessionCheck] if true, skip the session check (allow users
14649
- * to not be logged in and launched via LTI)
14650
- * @param [opts.allowedHosts] if included, only allow requests from these hosts
14651
- * (start a hostname with a "*" to only check the end of the hostname)
14652
- * you can include just one string instead of an array
14653
- * @param [opts.bannedHosts] if included, do not allow requests from these hosts
14654
- * (start a hostname with a "*" to only check the end of the hostname)
14655
- * you can include just one string instead of an array
14826
+ * @param [opts.crossServerScope] the scope associated with this endpoint.
14827
+ * If defined, this is a cross-server endpoint, which will never
14828
+ * have any launch data, will never check Canvas roles or launch status, and will
14829
+ * instead use scopes and reactkit credentials to sign and validate requests.
14830
+ * Never start the path with /api/ttm or /api/admin if the endpoint is a cross-server
14831
+ * endpoint because those roles will not be validated
14832
+ * @param [opts.skipSessionCheck=true if crossServerScope defined] if true, skip
14833
+ * the session check (allow users to not be logged in and launched via LTI).
14834
+ * If crossServerScope is defined, this is always true
14656
14835
  * @param [opts.unhandledErrorMessagePrefix] if included, when an error that
14657
14836
  * is not of type ErrorWithCode is thrown, the client will receive an error
14658
14837
  * where the error message is prefixed with this string. For example,
@@ -14680,86 +14859,51 @@ const parseUserAgent = (userAgent) => {
14680
14859
  const genRouteHandler = (opts) => {
14681
14860
  // Return a route handler
14682
14861
  return (req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
14683
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
14862
+ /*----------------------------------------*/
14863
+ /* ------------- Preparation ------------ */
14864
+ /*----------------------------------------*/
14865
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
14684
14866
  // Output params
14685
14867
  const output = {};
14868
+ // Determine cross server scopes
14869
+ let crossServerScope = null;
14870
+ if (opts.crossServerScope) {
14871
+ crossServerScope = (_a = opts.crossServerScope) !== null && _a !== void 0 ? _a : null;
14872
+ }
14873
+ // Determine whether we're skipping the session check
14874
+ const skipSessionCheck = !!(opts.skipSessionCheck
14875
+ || crossServerScope);
14876
+ // Get body from everywhere it can come from
14877
+ let requestBody = Object.assign(Object.assign(Object.assign({}, req.body), req.query), req.params);
14686
14878
  /*----------------------------------------*/
14687
- /* ----------- Hostname Check ----------- */
14879
+ /* ------- Cross-Server Validation ------ */
14688
14880
  /*----------------------------------------*/
14689
- // Get hostnames
14690
- const originURL = String(req.get('origin')
14691
- || req.headers.origin
14692
- || req.headers.referer);
14693
- const originHostname = (originURL
14694
- // Remove protocol
14695
- .replace(/(^\w+:|^)\/\//, '')
14696
- // Remove port
14697
- .replace(/:\d+$/, ''));
14698
- const serverHostname = String(req.hostname);
14699
- // Check allowed
14700
- if (opts.allowedHosts) {
14701
- // Only accept requests from allowed hosts
14702
- const allowedArray = (Array.isArray(opts.allowedHosts)
14703
- ? opts.allowedHosts
14704
- : [opts.allowedHosts]);
14705
- // Check if server is localhost
14706
- if (serverHostname === 'localhost') {
14707
- // Allow localhost
14708
- allowedArray.push('localhost');
14881
+ if (crossServerScope) {
14882
+ // Get the signed pack
14883
+ const { signedPack } = requestBody;
14884
+ // If no pack, throw error
14885
+ if (!signedPack || typeof signedPack !== 'string') {
14886
+ throw new ErrorWithCode('Could not process a cross server request because there was no valid signed pack.', ReactKitErrorCode$1.CrossServerNoPack);
14709
14887
  }
14710
- // Check if current host is allowed
14711
- const allowed = allowedArray.some((allowedHost) => {
14712
- if (allowedHost.startsWith('*')) {
14713
- // Check end of hostname
14714
- return originHostname.endsWith(allowedHost.substring(1));
14715
- }
14716
- // Check full hostname
14717
- return originHostname.toLowerCase() === allowedHost.toLowerCase();
14888
+ // Validate the request
14889
+ const crossServerParams = yield parseSignedPack({
14890
+ method: (_b = req.method) !== null && _b !== void 0 ? _b : 'GET',
14891
+ path: req.path,
14892
+ scope: crossServerScope,
14893
+ signedPack,
14718
14894
  });
14719
- // If not allowed, return error
14720
- if (!allowed) {
14721
- return handleError(res, {
14722
- message: 'You are not allowed to access this endpoint.',
14723
- code: ReactKitErrorCode$1.HostNotAllowed,
14724
- status: 403,
14725
- });
14726
- }
14727
- }
14728
- // Check banned
14729
- if (opts.bannedHosts) {
14730
- // Do not allow requests from banned hosts
14731
- const bannedArray = (Array.isArray(opts.bannedHosts)
14732
- ? opts.bannedHosts
14733
- : [opts.bannedHosts]);
14734
- // Check if current host is banned
14735
- const banned = bannedArray.some((bannedHost) => {
14736
- if (bannedHost.startsWith('*')) {
14737
- // Check end of hostname
14738
- return originHostname.endsWith(bannedHost.substring(1));
14739
- }
14740
- // Check full hostname
14741
- return originHostname.toLowerCase() === bannedHost.toLowerCase();
14742
- });
14743
- // If banned, return error
14744
- if (banned) {
14745
- return handleError(res, {
14746
- message: 'You are not allowed to access this endpoint.',
14747
- code: ReactKitErrorCode$1.HostBanned,
14748
- status: 403,
14749
- });
14750
- }
14895
+ // Replace body with params from the pack
14896
+ requestBody = crossServerParams !== null && crossServerParams !== void 0 ? crossServerParams : {};
14751
14897
  }
14752
14898
  /*----------------------------------------*/
14753
14899
  /* ------------ Parse Params ------------ */
14754
14900
  /*----------------------------------------*/
14755
14901
  // Process items one by one
14756
- const paramList = Object.entries((_a = opts.paramTypes) !== null && _a !== void 0 ? _a : {});
14902
+ const paramList = Object.entries((_c = opts.paramTypes) !== null && _c !== void 0 ? _c : {});
14757
14903
  for (let i = 0; i < paramList.length; i++) {
14758
14904
  const [name, type] = paramList[i];
14759
14905
  // Find the value as a string
14760
- const value = (req.params[name]
14761
- || req.query[name]
14762
- || req.body[name]);
14906
+ const value = requestBody[name];
14763
14907
  // Parse
14764
14908
  if (type === ParamType$1.Boolean || type === ParamType$1.BooleanOptional) {
14765
14909
  // Boolean
@@ -14917,7 +15061,7 @@ const genRouteHandler = (opts) => {
14917
15061
  // Not launched
14918
15062
  (!launched || !launchInfo)
14919
15063
  // Not skipping the session check
14920
- && !opts.skipSessionCheck) {
15064
+ && !skipSessionCheck) {
14921
15065
  return handleError(res, {
14922
15066
  message: 'Your session has expired. Please refresh the page and try again.',
14923
15067
  code: ReactKitErrorCode$1.SessionExpired,
@@ -14937,7 +15081,7 @@ const genRouteHandler = (opts) => {
14937
15081
  && !launchInfo.isLearner
14938
15082
  && !launchInfo.isAdmin))
14939
15083
  // Not skipping the session check
14940
- && !opts.skipSessionCheck) {
15084
+ && !skipSessionCheck) {
14941
15085
  return handleError(res, {
14942
15086
  message: 'Your session was invalid. Please refresh the page and try again.',
14943
15087
  code: ReactKitErrorCode$1.SessionExpired,
@@ -14947,34 +15091,34 @@ const genRouteHandler = (opts) => {
14947
15091
  // Add launch info to output
14948
15092
  output.userId = (launchInfo
14949
15093
  ? launchInfo.userId
14950
- : ((_b = output.userId) !== null && _b !== void 0 ? _b : undefined));
15094
+ : ((_d = output.userId) !== null && _d !== void 0 ? _d : undefined));
14951
15095
  output.userFirstName = (launchInfo
14952
15096
  ? launchInfo.userFirstName
14953
- : ((_c = output.userFirstName) !== null && _c !== void 0 ? _c : undefined));
15097
+ : ((_e = output.userFirstName) !== null && _e !== void 0 ? _e : undefined));
14954
15098
  output.userLastName = (launchInfo
14955
15099
  ? launchInfo.userLastName
14956
- : ((_d = output.userLastName) !== null && _d !== void 0 ? _d : undefined));
15100
+ : ((_f = output.userLastName) !== null && _f !== void 0 ? _f : undefined));
14957
15101
  output.userEmail = (launchInfo
14958
15102
  ? launchInfo.userEmail
14959
- : ((_e = output.userEmail) !== null && _e !== void 0 ? _e : undefined));
15103
+ : ((_g = output.userEmail) !== null && _g !== void 0 ? _g : undefined));
14960
15104
  output.userAvatarURL = (launchInfo
14961
- ? ((_f = launchInfo.userImage) !== null && _f !== void 0 ? _f : 'http://www.gravatar.com/avatar/?d=identicon')
14962
- : ((_g = output.userAvatarURL) !== null && _g !== void 0 ? _g : undefined));
15105
+ ? ((_h = launchInfo.userImage) !== null && _h !== void 0 ? _h : 'http://www.gravatar.com/avatar/?d=identicon')
15106
+ : ((_j = output.userAvatarURL) !== null && _j !== void 0 ? _j : undefined));
14963
15107
  output.isLearner = (launchInfo
14964
15108
  ? !!launchInfo.isLearner
14965
- : ((_h = output.isLearner) !== null && _h !== void 0 ? _h : undefined));
15109
+ : ((_k = output.isLearner) !== null && _k !== void 0 ? _k : undefined));
14966
15110
  output.isTTM = (launchInfo
14967
15111
  ? !!launchInfo.isTTM
14968
- : ((_j = output.isTTM) !== null && _j !== void 0 ? _j : undefined));
15112
+ : ((_l = output.isTTM) !== null && _l !== void 0 ? _l : undefined));
14969
15113
  output.isAdmin = (launchInfo
14970
15114
  ? !!launchInfo.isAdmin
14971
- : ((_k = output.isAdmin) !== null && _k !== void 0 ? _k : undefined));
15115
+ : ((_m = output.isAdmin) !== null && _m !== void 0 ? _m : undefined));
14972
15116
  output.courseId = (launchInfo
14973
- ? ((_l = output.courseId) !== null && _l !== void 0 ? _l : launchInfo.courseId)
14974
- : ((_m = output.courseId) !== null && _m !== void 0 ? _m : undefined));
15117
+ ? ((_o = output.courseId) !== null && _o !== void 0 ? _o : launchInfo.courseId)
15118
+ : ((_p = output.courseId) !== null && _p !== void 0 ? _p : undefined));
14975
15119
  output.courseName = (launchInfo
14976
15120
  ? launchInfo.contextLabel
14977
- : ((_o = output.courseName) !== null && _o !== void 0 ? _o : undefined));
15121
+ : ((_q = output.courseName) !== null && _q !== void 0 ? _q : undefined));
14978
15122
  // Add other session variables
14979
15123
  Object.keys(req.session).forEach((propName) => {
14980
15124
  // Skip if prop already in output
@@ -15048,7 +15192,7 @@ const genRouteHandler = (opts) => {
15048
15192
  * @author Gabe Abrams
15049
15193
  */
15050
15194
  const logServerEvent = (logOpts) => __awaiter(void 0, void 0, void 0, function* () {
15051
- var _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1;
15195
+ var _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3;
15052
15196
  // NOTE: internally, we slip through an opts.overrideAsClientEvent boolean
15053
15197
  // that indicates that this is actually a client event, but we don't
15054
15198
  // include that in the LogFunction type because this is internal and
@@ -15080,24 +15224,24 @@ const genRouteHandler = (opts) => {
15080
15224
  timestamp,
15081
15225
  context: (typeof logOpts.context === 'string'
15082
15226
  ? logOpts.context
15083
- : ((_q = ((_p = logOpts.context) !== null && _p !== void 0 ? _p : {})._) !== null && _q !== void 0 ? _q : LogBuiltInMetadata.Context.Uncategorized)),
15084
- subcontext: ((_r = logOpts.subcontext) !== null && _r !== void 0 ? _r : LogBuiltInMetadata.Context.Uncategorized),
15085
- tags: ((_s = logOpts.tags) !== null && _s !== void 0 ? _s : []),
15086
- level: ((_t = logOpts.level) !== null && _t !== void 0 ? _t : LogLevel$1.Info),
15087
- metadata: ((_u = logOpts.metadata) !== null && _u !== void 0 ? _u : {}),
15227
+ : ((_s = ((_r = logOpts.context) !== null && _r !== void 0 ? _r : {})._) !== null && _s !== void 0 ? _s : LogBuiltInMetadata.Context.Uncategorized)),
15228
+ subcontext: ((_t = logOpts.subcontext) !== null && _t !== void 0 ? _t : LogBuiltInMetadata.Context.Uncategorized),
15229
+ tags: ((_u = logOpts.tags) !== null && _u !== void 0 ? _u : []),
15230
+ level: ((_v = logOpts.level) !== null && _v !== void 0 ? _v : LogLevel$1.Info),
15231
+ metadata: ((_w = logOpts.metadata) !== null && _w !== void 0 ? _w : {}),
15088
15232
  };
15089
15233
  // Type-specific info
15090
15234
  const typeSpecificInfo = (('error' in opts && opts.error)
15091
15235
  ? {
15092
15236
  type: LogType$1.Error,
15093
- errorMessage: (_v = logOpts.error.message) !== null && _v !== void 0 ? _v : 'Unknown message',
15094
- errorCode: (_w = logOpts.error.code) !== null && _w !== void 0 ? _w : ReactKitErrorCode$1.NoCode,
15095
- errorStack: (_x = logOpts.error.stack) !== null && _x !== void 0 ? _x : 'No stack',
15237
+ errorMessage: (_x = logOpts.error.message) !== null && _x !== void 0 ? _x : 'Unknown message',
15238
+ errorCode: (_y = logOpts.error.code) !== null && _y !== void 0 ? _y : ReactKitErrorCode$1.NoCode,
15239
+ errorStack: (_z = logOpts.error.stack) !== null && _z !== void 0 ? _z : 'No stack',
15096
15240
  }
15097
15241
  : {
15098
15242
  type: LogType$1.Action,
15099
- target: ((_y = logOpts.target) !== null && _y !== void 0 ? _y : LogBuiltInMetadata.Target.NoTarget),
15100
- action: ((_z = logOpts.action) !== null && _z !== void 0 ? _z : LogAction$1.Unknown),
15243
+ target: ((_0 = logOpts.target) !== null && _0 !== void 0 ? _0 : LogBuiltInMetadata.Target.NoTarget),
15244
+ action: ((_1 = logOpts.action) !== null && _1 !== void 0 ? _1 : LogAction$1.Unknown),
15101
15245
  });
15102
15246
  // Source-specific info
15103
15247
  const sourceSpecificInfo = (logOpts.overrideAsClientEvent
@@ -15132,7 +15276,7 @@ const genRouteHandler = (opts) => {
15132
15276
  catch (err) {
15133
15277
  // Print because we cannot store the error
15134
15278
  // eslint-disable-next-line no-console
15135
- console.error('Could not log the following:', logOpts, 'due to this error:', ((_0 = err) !== null && _0 !== void 0 ? _0 : {}).message, ((_1 = err) !== null && _1 !== void 0 ? _1 : {}).stack);
15279
+ console.error('Could not log the following:', logOpts, 'due to this error:', ((_2 = err) !== null && _2 !== void 0 ? _2 : {}).message, ((_3 = err) !== null && _3 !== void 0 ? _3 : {}).stack);
15136
15280
  // Create a dummy log to return
15137
15281
  const dummyMainInfo = {
15138
15282
  id: '-1',
@@ -16234,9 +16378,6 @@ const shuffleArray = (arr) => {
16234
16378
  * @param [opts.host] host to send request to
16235
16379
  * @param [opts.method=GET] http method to use
16236
16380
  * @param [opts.params] body/data to include in the request
16237
- * @param [opts.headers] headers to include in the request
16238
- * @param [opts.sendCrossDomainCredentials=true if in development mode] if true,
16239
- * send cross-domain credentials even if not in dev mode
16240
16381
  * @param [opts.responseType=JSON] expected response type
16241
16382
  * @returns { body, status, headers } on success
16242
16383
  */
@@ -16273,7 +16414,7 @@ const sendServerToServerRequest = (opts) => __awaiter(void 0, void 0, void 0, fu
16273
16414
  url = `https://${opts.host}${opts.path}${query}`;
16274
16415
  }
16275
16416
  // Update headers
16276
- const headers = opts.headers || {};
16417
+ const headers = {};
16277
16418
  let data = null;
16278
16419
  if (!headers['Content-Type']) {
16279
16420
  // Form encoded
@@ -16346,49 +16487,49 @@ const sendServerToServerRequest = (opts) => __awaiter(void 0, void 0, void 0, fu
16346
16487
  });
16347
16488
 
16348
16489
  /**
16349
- * Send a server-to-server request from this sever to another server that uses
16350
- * dce-reactkit [for server only]
16490
+ * Visit an endpoint on another server
16351
16491
  * @author Gabe Abrams
16352
16492
  * @param opts object containing all arguments
16353
- * @param opts.host - the host of the other server
16354
- * @param opts.path - the path of the other server's endpoint
16355
- * @param [opts.method=GET] - the method of the endpoint
16356
- * @param [opts.params] - query/body parameters to include
16357
- * @param [opts.headers] - headers to include
16358
- * @returns response from server
16493
+ * @param opts.method the method of the endpoint
16494
+ * @param opts.path the path of the other server's endpoint
16495
+ * @param opts.host the host of the other server
16496
+ * @param [opts.key=process.env.REACTKIT_CROSS_SERVER_CREDENTIAL_KEY] reactkit cross-server
16497
+ * credential key
16498
+ * @param [opts.secret=process.env.REACTKIT_CROSS_SERVER_CREDENTIAL_SECRET reactkit cross-server
16499
+ * credential secret
16500
+ * @param [opts.params={}] query/body parameters to include
16501
+ * @param [opts.responseType=JSON] the response type from the other server
16359
16502
  */
16360
16503
  const visitEndpointOnAnotherServer = (opts) => __awaiter(void 0, void 0, void 0, function* () {
16361
- var _a;
16362
- // Remove properties with undefined values
16363
- let params;
16364
- if (opts.params) {
16365
- params = Object.fromEntries(Object
16366
- .entries(opts.params)
16367
- .filter(([, value]) => {
16368
- return value !== undefined;
16369
- }));
16370
- }
16371
- // Automatically JSONify arrays and objects
16372
- if (params) {
16373
- params = Object.fromEntries(Object
16374
- .entries(params)
16375
- .map(([key, value]) => {
16376
- if (Array.isArray(value) || typeof value === 'object') {
16377
- return [key, JSON.stringify(value)];
16378
- }
16379
- return [key, value];
16380
- }));
16381
- }
16504
+ var _a, _b, _c;
16505
+ // Get cross-server credentials
16506
+ const key = (_a = opts.key) !== null && _a !== void 0 ? _a : process.env.REACTKIT_CROSS_SERVER_KEY;
16507
+ const secret = (_b = opts.secret) !== null && _b !== void 0 ? _b : process.env.REACTKIT_CROSS_SERVER_SECRET;
16508
+ // Throw error if no credentials
16509
+ if (!key || !secret) {
16510
+ throw new ErrorWithCode('Cannot send cross-server signed request because either or both the key and secret were not included or found in env.', ReactKitErrorCode$1.CrossServerNoCredentialsToSignWith);
16511
+ }
16512
+ // Create signed pack
16513
+ const signedPack = createSignedPack({
16514
+ method: opts.method,
16515
+ path: opts.path,
16516
+ params: (_c = opts.params) !== null && _c !== void 0 ? _c : {},
16517
+ key,
16518
+ secret,
16519
+ });
16382
16520
  // Send the request
16383
16521
  const response = yield sendServerToServerRequest({
16384
- host: opts.host,
16385
16522
  path: opts.path,
16386
- method: (_a = opts.method) !== null && _a !== void 0 ? _a : 'GET',
16387
- params,
16523
+ host: opts.host,
16524
+ method: opts.method,
16525
+ params: {
16526
+ signedPack,
16527
+ },
16528
+ responseType: opts.responseType,
16388
16529
  });
16389
16530
  // Check for failure
16390
16531
  if (!response || !response.body) {
16391
- throw new ErrorWithCode('We didn\'t get a response from the server. Please check your internet connection.', ReactKitErrorCode$1.NoResponse);
16532
+ throw new ErrorWithCode('We didn\'t get a response from the other server. Please check your internet connection.', ReactKitErrorCode$1.NoResponse);
16392
16533
  }
16393
16534
  if (!response.body.success) {
16394
16535
  // Other errors