dce-reactkit 3.11.4 → 3.12.1-beta-cross-server.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/.vscode/settings.json +1 -3
- package/README.md +20 -0
- package/dist/cjs/index.js +294 -162
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/helpers/dataSigner.d.ts +38 -0
- package/dist/cjs/types/helpers/genRouteHandler.d.ts +10 -10
- package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/index.d.ts +15 -13
- package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +0 -6
- package/dist/cjs/types/index.d.ts +3 -2
- package/dist/cjs/types/server/initCrossServerCredentialCollection.d.ts +8 -0
- package/dist/cjs/types/server/initServer.d.ts +11 -0
- package/dist/cjs/types/types/CrossServerCredential.d.ts +11 -0
- package/dist/cjs/types/types/ReactKitErrorCode.d.ts +13 -1
- package/dist/esm/index.js +293 -162
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/helpers/dataSigner.d.ts +38 -0
- package/dist/esm/types/helpers/genRouteHandler.d.ts +10 -10
- package/dist/esm/types/helpers/visitEndpointOnAnotherServer/index.d.ts +15 -13
- package/dist/esm/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +0 -6
- package/dist/esm/types/index.d.ts +3 -2
- package/dist/esm/types/server/initCrossServerCredentialCollection.d.ts +8 -0
- package/dist/esm/types/server/initServer.d.ts +11 -0
- package/dist/esm/types/types/CrossServerCredential.d.ts +11 -0
- package/dist/esm/types/types/ReactKitErrorCode.d.ts +13 -1
- package/dist/index.d.ts +62 -35
- package/genEncodedSecret.ts +28 -0
- package/package.json +5 -2
- package/src/components/IntelliTable.tsx +1 -1
- package/src/components/ItemPicker/NestableItemList.tsx +0 -1
- package/src/helpers/dataSigner.ts +232 -0
- package/src/helpers/genRouteHandler.ts +55 -95
- package/src/helpers/visitEndpointOnAnotherServer/index.ts +42 -41
- package/src/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.ts +3 -5
- package/src/index.ts +4 -2
- package/src/server/initCrossServerCredentialCollection.ts +16 -0
- package/src/server/initServer.ts +18 -0
- package/src/types/CrossServerCredential.ts +16 -0
- package/src/types/ReactKitErrorCode.tsx +13 -1
- package/dist/cjs/types/helpers/getWordCount.d.ts +0 -10
- package/dist/esm/types/helpers/getWordCount.d.ts +0 -10
- package/src/helpers/getWordCount.ts +0 -26
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 =
|
|
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
|
|
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.
|
|
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.
|
|
14649
|
-
*
|
|
14650
|
-
*
|
|
14651
|
-
*
|
|
14652
|
-
*
|
|
14653
|
-
*
|
|
14654
|
-
*
|
|
14655
|
-
*
|
|
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
|
-
|
|
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
|
-
/*
|
|
14879
|
+
/* ------- Cross-Server Validation ------ */
|
|
14688
14880
|
/*----------------------------------------*/
|
|
14689
|
-
|
|
14690
|
-
|
|
14691
|
-
|
|
14692
|
-
|
|
14693
|
-
|
|
14694
|
-
|
|
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');
|
|
14709
|
-
}
|
|
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();
|
|
14718
|
-
});
|
|
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
|
-
});
|
|
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);
|
|
14726
14887
|
}
|
|
14727
|
-
|
|
14728
|
-
|
|
14729
|
-
|
|
14730
|
-
|
|
14731
|
-
|
|
14732
|
-
|
|
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();
|
|
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,
|
|
14742
14894
|
});
|
|
14743
|
-
//
|
|
14744
|
-
|
|
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((
|
|
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 =
|
|
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
|
-
&& !
|
|
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
|
-
&& !
|
|
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
|
-
: ((
|
|
15094
|
+
: ((_d = output.userId) !== null && _d !== void 0 ? _d : undefined));
|
|
14951
15095
|
output.userFirstName = (launchInfo
|
|
14952
15096
|
? launchInfo.userFirstName
|
|
14953
|
-
: ((
|
|
15097
|
+
: ((_e = output.userFirstName) !== null && _e !== void 0 ? _e : undefined));
|
|
14954
15098
|
output.userLastName = (launchInfo
|
|
14955
15099
|
? launchInfo.userLastName
|
|
14956
|
-
: ((
|
|
15100
|
+
: ((_f = output.userLastName) !== null && _f !== void 0 ? _f : undefined));
|
|
14957
15101
|
output.userEmail = (launchInfo
|
|
14958
15102
|
? launchInfo.userEmail
|
|
14959
|
-
: ((
|
|
15103
|
+
: ((_g = output.userEmail) !== null && _g !== void 0 ? _g : undefined));
|
|
14960
15104
|
output.userAvatarURL = (launchInfo
|
|
14961
|
-
? ((
|
|
14962
|
-
: ((
|
|
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
|
-
: ((
|
|
15109
|
+
: ((_k = output.isLearner) !== null && _k !== void 0 ? _k : undefined));
|
|
14966
15110
|
output.isTTM = (launchInfo
|
|
14967
15111
|
? !!launchInfo.isTTM
|
|
14968
|
-
: ((
|
|
15112
|
+
: ((_l = output.isTTM) !== null && _l !== void 0 ? _l : undefined));
|
|
14969
15113
|
output.isAdmin = (launchInfo
|
|
14970
15114
|
? !!launchInfo.isAdmin
|
|
14971
|
-
: ((
|
|
15115
|
+
: ((_m = output.isAdmin) !== null && _m !== void 0 ? _m : undefined));
|
|
14972
15116
|
output.courseId = (launchInfo
|
|
14973
|
-
? ((
|
|
14974
|
-
: ((
|
|
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
|
-
: ((
|
|
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
|
|
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
|
-
: ((
|
|
15084
|
-
subcontext: ((
|
|
15085
|
-
tags: ((
|
|
15086
|
-
level: ((
|
|
15087
|
-
metadata: ((
|
|
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: (
|
|
15094
|
-
errorCode: (
|
|
15095
|
-
errorStack: (
|
|
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: ((
|
|
15100
|
-
action: ((
|
|
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:', ((
|
|
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',
|
|
@@ -15485,6 +15629,18 @@ const initLogCollection = (Collection) => {
|
|
|
15485
15629
|
});
|
|
15486
15630
|
};
|
|
15487
15631
|
|
|
15632
|
+
/**
|
|
15633
|
+
* Initialize a cross-server credential collection given the dce-mango Collection class
|
|
15634
|
+
* @author Gabe Abrams
|
|
15635
|
+
* @param Collection the Collection class from dce-mango
|
|
15636
|
+
* @returns initialized logCollection
|
|
15637
|
+
*/
|
|
15638
|
+
const initCrossServerCredentialCollection = (Collection) => {
|
|
15639
|
+
return new Collection('CrossServerCredential', {
|
|
15640
|
+
uniqueIndexKey: 'key',
|
|
15641
|
+
});
|
|
15642
|
+
};
|
|
15643
|
+
|
|
15488
15644
|
/*------------------------------------------------------------------------*/
|
|
15489
15645
|
/* -------------------------------- Cache ------------------------------- */
|
|
15490
15646
|
/*------------------------------------------------------------------------*/
|
|
@@ -16234,9 +16390,6 @@ const shuffleArray = (arr) => {
|
|
|
16234
16390
|
* @param [opts.host] host to send request to
|
|
16235
16391
|
* @param [opts.method=GET] http method to use
|
|
16236
16392
|
* @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
16393
|
* @param [opts.responseType=JSON] expected response type
|
|
16241
16394
|
* @returns { body, status, headers } on success
|
|
16242
16395
|
*/
|
|
@@ -16273,7 +16426,7 @@ const sendServerToServerRequest = (opts) => __awaiter(void 0, void 0, void 0, fu
|
|
|
16273
16426
|
url = `https://${opts.host}${opts.path}${query}`;
|
|
16274
16427
|
}
|
|
16275
16428
|
// Update headers
|
|
16276
|
-
const headers =
|
|
16429
|
+
const headers = {};
|
|
16277
16430
|
let data = null;
|
|
16278
16431
|
if (!headers['Content-Type']) {
|
|
16279
16432
|
// Form encoded
|
|
@@ -16346,49 +16499,49 @@ const sendServerToServerRequest = (opts) => __awaiter(void 0, void 0, void 0, fu
|
|
|
16346
16499
|
});
|
|
16347
16500
|
|
|
16348
16501
|
/**
|
|
16349
|
-
*
|
|
16350
|
-
* dce-reactkit [for server only]
|
|
16502
|
+
* Visit an endpoint on another server
|
|
16351
16503
|
* @author Gabe Abrams
|
|
16352
16504
|
* @param opts object containing all arguments
|
|
16353
|
-
* @param opts.
|
|
16354
|
-
* @param opts.path
|
|
16355
|
-
* @param
|
|
16356
|
-
* @param [opts.
|
|
16357
|
-
*
|
|
16358
|
-
* @
|
|
16505
|
+
* @param opts.method the method of the endpoint
|
|
16506
|
+
* @param opts.path the path of the other server's endpoint
|
|
16507
|
+
* @param opts.host the host of the other server
|
|
16508
|
+
* @param [opts.key=process.env.REACTKIT_CROSS_SERVER_CREDENTIAL_KEY] reactkit cross-server
|
|
16509
|
+
* credential key
|
|
16510
|
+
* @param [opts.secret=process.env.REACTKIT_CROSS_SERVER_CREDENTIAL_SECRET reactkit cross-server
|
|
16511
|
+
* credential secret
|
|
16512
|
+
* @param [opts.params={}] query/body parameters to include
|
|
16513
|
+
* @param [opts.responseType=JSON] the response type from the other server
|
|
16359
16514
|
*/
|
|
16360
16515
|
const visitEndpointOnAnotherServer = (opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
16361
|
-
var _a;
|
|
16362
|
-
//
|
|
16363
|
-
|
|
16364
|
-
|
|
16365
|
-
|
|
16366
|
-
|
|
16367
|
-
|
|
16368
|
-
|
|
16369
|
-
|
|
16370
|
-
|
|
16371
|
-
|
|
16372
|
-
|
|
16373
|
-
params =
|
|
16374
|
-
|
|
16375
|
-
|
|
16376
|
-
|
|
16377
|
-
return [key, JSON.stringify(value)];
|
|
16378
|
-
}
|
|
16379
|
-
return [key, value];
|
|
16380
|
-
}));
|
|
16381
|
-
}
|
|
16516
|
+
var _a, _b, _c;
|
|
16517
|
+
// Get cross-server credentials
|
|
16518
|
+
const key = (_a = opts.key) !== null && _a !== void 0 ? _a : process.env.REACTKIT_CROSS_SERVER_KEY;
|
|
16519
|
+
const secret = (_b = opts.secret) !== null && _b !== void 0 ? _b : process.env.REACTKIT_CROSS_SERVER_SECRET;
|
|
16520
|
+
// Throw error if no credentials
|
|
16521
|
+
if (!key || !secret) {
|
|
16522
|
+
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);
|
|
16523
|
+
}
|
|
16524
|
+
// Create signed pack
|
|
16525
|
+
const signedPack = createSignedPack({
|
|
16526
|
+
method: opts.method,
|
|
16527
|
+
path: opts.path,
|
|
16528
|
+
params: (_c = opts.params) !== null && _c !== void 0 ? _c : {},
|
|
16529
|
+
key,
|
|
16530
|
+
secret,
|
|
16531
|
+
});
|
|
16382
16532
|
// Send the request
|
|
16383
16533
|
const response = yield sendServerToServerRequest({
|
|
16384
|
-
host: opts.host,
|
|
16385
16534
|
path: opts.path,
|
|
16386
|
-
|
|
16387
|
-
|
|
16535
|
+
host: opts.host,
|
|
16536
|
+
method: opts.method,
|
|
16537
|
+
params: {
|
|
16538
|
+
signedPack,
|
|
16539
|
+
},
|
|
16540
|
+
responseType: opts.responseType,
|
|
16388
16541
|
});
|
|
16389
16542
|
// Check for failure
|
|
16390
16543
|
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);
|
|
16544
|
+
throw new ErrorWithCode('We didn\'t get a response from the other server. Please check your internet connection.', ReactKitErrorCode$1.NoResponse);
|
|
16392
16545
|
}
|
|
16393
16546
|
if (!response.body.success) {
|
|
16394
16547
|
// Other errors
|
|
@@ -16402,27 +16555,6 @@ const visitEndpointOnAnotherServer = (opts) => __awaiter(void 0, void 0, void 0,
|
|
|
16402
16555
|
return body;
|
|
16403
16556
|
});
|
|
16404
16557
|
|
|
16405
|
-
const punctuationRegex = /[!@#$%^&*(),.?\/;:'"\-\[\]]/g;
|
|
16406
|
-
/**
|
|
16407
|
-
* Get number of words in string
|
|
16408
|
-
* @author Gardenia Liu
|
|
16409
|
-
* @author Allison Zhang
|
|
16410
|
-
* @author Gabe Abrams
|
|
16411
|
-
* @param text the string to check
|
|
16412
|
-
* @returns number of words in the string
|
|
16413
|
-
*/
|
|
16414
|
-
const getWordCount = (text) => {
|
|
16415
|
-
const trimmedTextWithoutPunctuation = (text
|
|
16416
|
-
// Remove leading and trailing whitespace
|
|
16417
|
-
.trim()
|
|
16418
|
-
// Remove punctuation
|
|
16419
|
-
.replace(punctuationRegex, ''));
|
|
16420
|
-
if (trimmedTextWithoutPunctuation.length === 0) {
|
|
16421
|
-
return 0;
|
|
16422
|
-
}
|
|
16423
|
-
return trimmedTextWithoutPunctuation.split(/\s+/g).length;
|
|
16424
|
-
};
|
|
16425
|
-
|
|
16426
16558
|
/**
|
|
16427
16559
|
* Days of the week
|
|
16428
16560
|
* @author Gabe Abrams
|
|
@@ -16507,11 +16639,11 @@ exports.getMonthName = getMonthName;
|
|
|
16507
16639
|
exports.getOrdinal = getOrdinal;
|
|
16508
16640
|
exports.getPartOfDay = getPartOfDay;
|
|
16509
16641
|
exports.getTimeInfoInET = getTimeInfoInET;
|
|
16510
|
-
exports.getWordCount = getWordCount;
|
|
16511
16642
|
exports.handleError = handleError;
|
|
16512
16643
|
exports.handleSuccess = handleSuccess;
|
|
16513
16644
|
exports.idify = idify;
|
|
16514
16645
|
exports.initClient = initClient;
|
|
16646
|
+
exports.initCrossServerCredentialCollection = initCrossServerCredentialCollection;
|
|
16515
16647
|
exports.initLogCollection = initLogCollection;
|
|
16516
16648
|
exports.initServer = initServer;
|
|
16517
16649
|
exports.isMobileOrTablet = isMobileOrTablet;
|