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.
Files changed (41) hide show
  1. package/.vscode/settings.json +1 -3
  2. package/README.md +20 -0
  3. package/dist/cjs/index.js +294 -162
  4. package/dist/cjs/index.js.map +1 -1
  5. package/dist/cjs/types/helpers/dataSigner.d.ts +38 -0
  6. package/dist/cjs/types/helpers/genRouteHandler.d.ts +10 -10
  7. package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/index.d.ts +15 -13
  8. package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +0 -6
  9. package/dist/cjs/types/index.d.ts +3 -2
  10. package/dist/cjs/types/server/initCrossServerCredentialCollection.d.ts +8 -0
  11. package/dist/cjs/types/server/initServer.d.ts +11 -0
  12. package/dist/cjs/types/types/CrossServerCredential.d.ts +11 -0
  13. package/dist/cjs/types/types/ReactKitErrorCode.d.ts +13 -1
  14. package/dist/esm/index.js +293 -162
  15. package/dist/esm/index.js.map +1 -1
  16. package/dist/esm/types/helpers/dataSigner.d.ts +38 -0
  17. package/dist/esm/types/helpers/genRouteHandler.d.ts +10 -10
  18. package/dist/esm/types/helpers/visitEndpointOnAnotherServer/index.d.ts +15 -13
  19. package/dist/esm/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +0 -6
  20. package/dist/esm/types/index.d.ts +3 -2
  21. package/dist/esm/types/server/initCrossServerCredentialCollection.d.ts +8 -0
  22. package/dist/esm/types/server/initServer.d.ts +11 -0
  23. package/dist/esm/types/types/CrossServerCredential.d.ts +11 -0
  24. package/dist/esm/types/types/ReactKitErrorCode.d.ts +13 -1
  25. package/dist/index.d.ts +62 -35
  26. package/genEncodedSecret.ts +28 -0
  27. package/package.json +5 -2
  28. package/src/components/IntelliTable.tsx +1 -1
  29. package/src/components/ItemPicker/NestableItemList.tsx +0 -1
  30. package/src/helpers/dataSigner.ts +232 -0
  31. package/src/helpers/genRouteHandler.ts +55 -95
  32. package/src/helpers/visitEndpointOnAnotherServer/index.ts +42 -41
  33. package/src/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.ts +3 -5
  34. package/src/index.ts +4 -2
  35. package/src/server/initCrossServerCredentialCollection.ts +16 -0
  36. package/src/server/initServer.ts +18 -0
  37. package/src/types/CrossServerCredential.ts +16 -0
  38. package/src/types/ReactKitErrorCode.tsx +13 -1
  39. package/dist/cjs/types/helpers/getWordCount.d.ts +0 -10
  40. package/dist/esm/types/helpers/getWordCount.d.ts +0 -10
  41. package/src/helpers/getWordCount.ts +0 -26
@@ -0,0 +1,232 @@
1
+ // Import crypto
2
+ import { createHash } from 'crypto';
3
+
4
+ // Import jwt
5
+ import jwt from 'jwt-simple';
6
+
7
+ // Import shared helpers
8
+ import { internalGetCrossServerCredentialCollection } from '../server/initServer';
9
+
10
+ // Import shared types
11
+ import ErrorWithCode from '../errors/ErrorWithCode';
12
+ import ReactKitErrorCode from '../types/ReactKitErrorCode';
13
+ import CrossServerCredential from '../types/CrossServerCredential';
14
+
15
+ // Import shared constants
16
+ import MINUTE_IN_MS from '../constants/MINUTE_IN_MS';
17
+
18
+ /*------------------------------------------------------------------------*/
19
+ /* ------------------------------ Encoding ------------------------------ */
20
+ /*------------------------------------------------------------------------*/
21
+
22
+ /**
23
+ * Validate a JWT token
24
+ * @author Gabe Abrams
25
+ * @param encodedSecret the encoded secret
26
+ * @return the secret
27
+ */
28
+ const decodeCredentialSecret = (encodedSecret: string): string => {
29
+ // Get the credential encoding salt
30
+ const credentialEncodingSalt = process.env.REACTKIT_CRED_ENCODING_SALT;
31
+ if (!credentialEncodingSalt) {
32
+ throw new ErrorWithCode(
33
+ 'Cannot decode a cross-server credential secret because the credential encoding salt was not found in env.',
34
+ ReactKitErrorCode.CrossServerNoCredentialEncodingSalt,
35
+ );
36
+ }
37
+
38
+ // Decode the secret
39
+ return jwt.decode(encodedSecret, credentialEncodingSalt);
40
+ };
41
+
42
+ /*------------------------------------------------------------------------*/
43
+ /* ------------------------------- Signing ------------------------------ */
44
+ /*------------------------------------------------------------------------*/
45
+
46
+ /**
47
+ * Sign using sha 256
48
+ * @author Gabe Abrams
49
+ * @param opts object containing all arguments
50
+ * @param opts.pack the pack to sign
51
+ * @param opts.secret the reactkit secret to sign with
52
+ * @return the signed hash
53
+ */
54
+ const genSignature = (
55
+ opts: {
56
+ pack: string,
57
+ secret: string,
58
+ },
59
+ ): string => {
60
+ // Generate signature
61
+ return (
62
+ createHash('sha256')
63
+ .update(JSON.stringify({ pack: opts.pack, secret: opts.secret }))
64
+ .digest('base64')
65
+ );
66
+ };
67
+
68
+ /**
69
+ * Sign data with a private reactkit key, package it into a signed data pack
70
+ * @author Gabe Abrams
71
+ * @param opts object containing all arguments
72
+ * @param opts.method the method to sign
73
+ * @param opts.path the http request path
74
+ * @param opts.params the data in the body to sign
75
+ * @param opts.key the reactkit key to sign with
76
+ * @param opts.secret the reactkit secret to sign with
77
+ * @return the signed data
78
+ */
79
+ export const createSignedPack = (
80
+ opts: {
81
+ method: string,
82
+ path: string,
83
+ params: { [key: string]: any },
84
+ key: string,
85
+ secret: string,
86
+ },
87
+ ): string => {
88
+ // Create a timestamp
89
+ const timestamp = Date.now();
90
+
91
+ // Create the pack
92
+ const pack = JSON.stringify({
93
+ method: opts.method,
94
+ path: opts.path,
95
+ params: opts.params,
96
+ key: opts.key,
97
+ timestamp,
98
+ });
99
+
100
+ // Generate signature
101
+ const signature = genSignature({
102
+ pack,
103
+ secret: opts.secret,
104
+ });
105
+
106
+ // Create a signed pack
107
+ const signedPack = encodeURIComponent(JSON.stringify({
108
+ pack,
109
+ signature,
110
+ }));
111
+
112
+ // Return the signed pack
113
+ return signedPack;
114
+ };
115
+
116
+ /**
117
+ * Parse signed pack. Throws an error if invalid
118
+ * @author Gabe Abrams
119
+ * @param opts object containing all arguments
120
+ * @param opts.method the method of the data validate
121
+ * @param opts.path the http request path to validate
122
+ * @param opts.scope the name of the scope to validate
123
+ * @param opts.signedPack the signed data pack to validate
124
+ * @returns parsed and validated params
125
+ */
126
+ export const parseSignedPack = async (
127
+ opts: {
128
+ method: string,
129
+ path: string,
130
+ scope: string,
131
+ signedPack: string,
132
+ },
133
+ ): Promise<{ [key: string]: any }> => {
134
+ // Extract signature
135
+ let pack: string;
136
+ let signature: string;
137
+ let method: string;
138
+ let path: string;
139
+ let key: string;
140
+ let timestamp: number;
141
+ let params: { [k: string]: any };
142
+ try {
143
+ ({
144
+ pack,
145
+ signature,
146
+ } = JSON.parse(decodeURIComponent(opts.signedPack)));
147
+
148
+ // Unpack
149
+ ({
150
+ method,
151
+ path,
152
+ params,
153
+ key,
154
+ timestamp,
155
+ } = JSON.parse(pack));
156
+ } catch (err) {
157
+ throw new ErrorWithCode(
158
+ 'Could not validate a cross-server request because the request could not be parsed.',
159
+ ReactKitErrorCode.PackUnparseable,
160
+ );
161
+ }
162
+
163
+ // Make sure the method and path match
164
+ if (method !== opts.method) {
165
+ throw new ErrorWithCode(
166
+ 'Could not validate a cross-server request because the method did not match.',
167
+ ReactKitErrorCode.PackInvalidMethod,
168
+ );
169
+ }
170
+ if (path !== opts.path) {
171
+ throw new ErrorWithCode(
172
+ 'Could not validate a cross-server request because the path did not match.',
173
+ ReactKitErrorCode.PackInvalidPath,
174
+ );
175
+ }
176
+
177
+ // Make sure the timestamp was recent enough
178
+ const elapsedMs = Math.abs(Date.now() - timestamp);
179
+ if (elapsedMs < MINUTE_IN_MS) {
180
+ throw new ErrorWithCode(
181
+ 'Could not validate a cross-server request because the request was too old.',
182
+ ReactKitErrorCode.PackInvalidTimestamp,
183
+ );
184
+ }
185
+
186
+ // Get the cross-server credential collection
187
+ const crossServerCredentialCollection = internalGetCrossServerCredentialCollection();
188
+ if (!crossServerCredentialCollection) {
189
+ throw new ErrorWithCode(
190
+ 'Could not validate a cross-server request because the cross-server credential collection was not ready in time.',
191
+ ReactKitErrorCode.PackInvalidCollection,
192
+ );
193
+ }
194
+
195
+ // Get the cross-server credential
196
+ const crossServerCredential: CrossServerCredential = await crossServerCredentialCollection.find({ key });
197
+ if (!crossServerCredential) {
198
+ throw new ErrorWithCode(
199
+ 'Could not validate a cross-server request because the credential was not found.',
200
+ ReactKitErrorCode.PackInvalidCredential,
201
+ );
202
+ }
203
+
204
+ // Make sure the scope is included
205
+ const allowedScopes = crossServerCredential.scopes;
206
+ if (!allowedScopes.includes(opts.scope)) {
207
+ throw new ErrorWithCode(
208
+ 'Could not validate a cross-server request because the scope was not included.',
209
+ ReactKitErrorCode.PackInvalidScope,
210
+ );
211
+ }
212
+
213
+ // Decode the secret
214
+ const secret = decodeCredentialSecret(crossServerCredential.encodedeSecret);
215
+
216
+ // Generate signature
217
+ const expectedSignature = genSignature({
218
+ pack,
219
+ secret,
220
+ });
221
+
222
+ // Make sure the signature matches
223
+ if (signature !== expectedSignature) {
224
+ throw new ErrorWithCode(
225
+ 'Could not validate a cross-server request because the signature did not match.',
226
+ ReactKitErrorCode.PackInvalidSignature,
227
+ );
228
+ }
229
+
230
+ // Return body
231
+ return params ?? {};
232
+ };
@@ -17,6 +17,7 @@ import genErrorPage from '../html/genErrorPage';
17
17
  import genInfoPage from '../html/genInfoPage';
18
18
  import parseUserAgent from './parseUserAgent';
19
19
  import getTimeInfoInET from './getTimeInfoInET';
20
+ import { parseSignedPack } from './dataSigner';
20
21
 
21
22
  // Import shared types
22
23
  import LogFunction from '../types/LogFunction';
@@ -29,6 +30,7 @@ import LogSourceSpecificInfo from '../types/Log/LogSourceSpecificInfo';
29
30
  import LogBuiltInMetadata from '../types/LogBuiltInMetadata';
30
31
  import LogAction from '../types/LogAction';
31
32
  import LogLevel from '../types/LogLevel';
33
+ import ErrorWithCode from '../errors/ErrorWithCode';
32
34
 
33
35
  /**
34
36
  * Generate an express API route handler
@@ -37,14 +39,15 @@ import LogLevel from '../types/LogLevel';
37
39
  * @param opts.paramTypes map containing the types for each parameter that is
38
40
  * included in the request (map: param name => type)
39
41
  * @param opts.handler function that processes the request
40
- * @param [opts.skipSessionCheck] if true, skip the session check (allow users
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
+ * @param [opts.crossServerScope] the scope associated with this endpoint.
43
+ * If defined, this is a cross-server endpoint, which will never
44
+ * have any launch data, will never check Canvas roles or launch status, and will
45
+ * instead use scopes and reactkit credentials to sign and validate requests.
46
+ * Never start the path with /api/ttm or /api/admin if the endpoint is a cross-server
47
+ * endpoint because those roles will not be validated
48
+ * @param [opts.skipSessionCheck=true if crossServerScope defined] if true, skip
49
+ * the session check (allow users to not be logged in and launched via LTI).
50
+ * If crossServerScope is defined, this is always true
48
51
  * @param [opts.unhandledErrorMessagePrefix] if included, when an error that
49
52
  * is not of type ErrorWithCode is thrown, the client will receive an error
50
53
  * where the error message is prefixed with this string. For example,
@@ -107,106 +110,67 @@ const genRouteHandler = (
107
110
  logServerEvent: LogFunction,
108
111
  },
109
112
  ) => any,
113
+ crossServerScope?: string,
110
114
  skipSessionCheck?: boolean,
111
- allowedHosts?: string[] | string,
112
- bannedHosts?: string[] | string,
113
115
  unhandledErrorMessagePrefix?: string,
114
116
  },
115
117
  ) => {
116
118
  // Return a route handler
117
119
  return async (req: any, res: any, next: () => void) => {
120
+ /*----------------------------------------*/
121
+ /* ------------- Preparation ------------ */
122
+ /*----------------------------------------*/
123
+
118
124
  // Output params
119
125
  const output: { [k in string]: any } = {};
120
126
 
121
- /*----------------------------------------*/
122
- /* ----------- Hostname Check ----------- */
123
- /*----------------------------------------*/
127
+ // Determine cross server scopes
128
+ let crossServerScope: string | null = null;
129
+ if (opts.crossServerScope) {
130
+ crossServerScope = opts.crossServerScope ?? null;
131
+ }
124
132
 
125
- // Get hostnames
126
- const originURL = String(
127
- req.get('origin')
128
- || req.headers.origin
129
- || req.headers.referer,
133
+ // Determine whether we're skipping the session check
134
+ const skipSessionCheck = !!(
135
+ opts.skipSessionCheck
136
+ || crossServerScope
130
137
  );
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
138
 
149
- // Check if server is localhost
150
- if (serverHostname === 'localhost') {
151
- // Allow localhost
152
- allowedArray.push('localhost');
153
- }
139
+ // Get body from everywhere it can come from
140
+ let requestBody: {
141
+ [k: string]: any,
142
+ } = {
143
+ ...req.body,
144
+ ...req.query,
145
+ ...req.params,
146
+ };
154
147
 
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
- }
148
+ /*----------------------------------------*/
149
+ /* ------- Cross-Server Validation ------ */
150
+ /*----------------------------------------*/
161
151
 
162
- // Check full hostname
163
- return originHostname.toLowerCase() === allowedHost.toLowerCase();
164
- });
152
+ if (crossServerScope) {
153
+ // Get the signed pack
154
+ const { signedPack } = requestBody;
165
155
 
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
- },
156
+ // If no pack, throw error
157
+ if (!signedPack || typeof signedPack !== 'string') {
158
+ throw new ErrorWithCode(
159
+ 'Could not process a cross server request because there was no valid signed pack.',
160
+ ReactKitErrorCode.CrossServerNoPack,
175
161
  );
176
162
  }
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
163
 
195
- // Check full hostname
196
- return originHostname.toLowerCase() === bannedHost.toLowerCase();
164
+ // Validate the request
165
+ const crossServerParams = await parseSignedPack({
166
+ method: req.method ?? 'GET',
167
+ path: req.path,
168
+ scope: crossServerScope,
169
+ signedPack,
197
170
  });
198
171
 
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
- }
172
+ // Replace body with params from the pack
173
+ requestBody = crossServerParams ?? {};
210
174
  }
211
175
 
212
176
  /*----------------------------------------*/
@@ -219,11 +183,7 @@ const genRouteHandler = (
219
183
  const [name, type] = paramList[i];
220
184
 
221
185
  // Find the value as a string
222
- const value = (
223
- req.params[name]
224
- || req.query[name]
225
- || req.body[name]
226
- );
186
+ const value = requestBody[name];
227
187
 
228
188
  // Parse
229
189
  if (type === ParamType.Boolean || type === ParamType.BooleanOptional) {
@@ -406,7 +366,7 @@ const genRouteHandler = (
406
366
  // Not launched
407
367
  (!launched || !launchInfo)
408
368
  // Not skipping the session check
409
- && !opts.skipSessionCheck
369
+ && !skipSessionCheck
410
370
  ) {
411
371
  return handleError(
412
372
  res,
@@ -437,7 +397,7 @@ const genRouteHandler = (
437
397
  )
438
398
  )
439
399
  // Not skipping the session check
440
- && !opts.skipSessionCheck
400
+ && !skipSessionCheck
441
401
  ) {
442
402
  return handleError(
443
403
  res,
@@ -1,71 +1,72 @@
1
- // Import custom error
2
- import ErrorWithCode from '../../errors/ErrorWithCode';
1
+ // Import data signer
2
+ import { createSignedPack } from '../dataSigner';
3
3
 
4
4
  // Import shared types
5
+ import ErrorWithCode from '../../errors/ErrorWithCode';
5
6
  import ReactKitErrorCode from '../../types/ReactKitErrorCode';
6
-
7
- // Import other helpers
8
7
  import sendServerToServerRequest from './sendServerToServerRequest';
9
8
 
10
9
  /**
11
- * Send a server-to-server request from this sever to another server that uses
12
- * dce-reactkit [for server only]
10
+ * Visit an endpoint on another server
13
11
  * @author Gabe Abrams
14
12
  * @param opts object containing all arguments
15
- * @param opts.host - the host of the other server
16
- * @param opts.path - the path of the other server's endpoint
17
- * @param [opts.method=GET] - the method of the endpoint
18
- * @param [opts.params] - query/body parameters to include
19
- * @param [opts.headers] - headers to include
20
- * @returns response from server
13
+ * @param opts.method the method of the endpoint
14
+ * @param opts.path the path of the other server's endpoint
15
+ * @param opts.host the host of the other server
16
+ * @param [opts.key=process.env.REACTKIT_CROSS_SERVER_CREDENTIAL_KEY] reactkit cross-server
17
+ * credential key
18
+ * @param [opts.secret=process.env.REACTKIT_CROSS_SERVER_CREDENTIAL_SECRET reactkit cross-server
19
+ * credential secret
20
+ * @param [opts.params={}] query/body parameters to include
21
+ * @param [opts.responseType=JSON] the response type from the other server
21
22
  */
22
23
  const visitEndpointOnAnotherServer = async (
23
24
  opts: {
24
- host: string,
25
+ method: 'GET' | 'POST' | 'DELETE' | 'PUT',
25
26
  path: string,
26
- method?: ('GET' | 'POST' | 'DELETE' | 'PUT'),
27
+ host: string,
28
+ key?: string,
29
+ secret?: string,
27
30
  params?: { [key in string]: any },
28
- headers?: { [k in string]: any },
31
+ responseType?: 'JSON' | 'Text',
29
32
  },
30
33
  ): Promise<any> => {
31
- // Remove properties with undefined values
32
- let params: { [key in string]: any } | undefined;
33
- if (opts.params) {
34
- params = Object.fromEntries(
35
- Object
36
- .entries(opts.params)
37
- .filter(([, value]) => {
38
- return value !== undefined;
39
- }),
40
- );
41
- }
34
+ // Get cross-server credentials
35
+ const key = opts.key ?? process.env.REACTKIT_CROSS_SERVER_KEY;
36
+ const secret = opts.secret ?? process.env.REACTKIT_CROSS_SERVER_SECRET;
42
37
 
43
- // Automatically JSONify arrays and objects
44
- if (params) {
45
- params = Object.fromEntries(
46
- Object
47
- .entries(params)
48
- .map(([key, value]) => {
49
- if (Array.isArray(value) || typeof value === 'object') {
50
- return [key, JSON.stringify(value)];
51
- }
52
- return [key, value];
53
- }),
38
+ // Throw error if no credentials
39
+ if (!key || !secret) {
40
+ throw new ErrorWithCode(
41
+ 'Cannot send cross-server signed request because either or both the key and secret were not included or found in env.',
42
+ ReactKitErrorCode.CrossServerNoCredentialsToSignWith,
54
43
  );
55
44
  }
56
45
 
46
+ // Create signed pack
47
+ const signedPack = createSignedPack({
48
+ method: opts.method,
49
+ path: opts.path,
50
+ params: opts.params ?? {},
51
+ key,
52
+ secret,
53
+ });
54
+
57
55
  // Send the request
58
56
  const response = await sendServerToServerRequest({
59
- host: opts.host,
60
57
  path: opts.path,
61
- method: opts.method ?? 'GET',
62
- params,
58
+ host: opts.host,
59
+ method: opts.method,
60
+ params: {
61
+ signedPack,
62
+ },
63
+ responseType: opts.responseType,
63
64
  });
64
65
 
65
66
  // Check for failure
66
67
  if (!response || !response.body) {
67
68
  throw new ErrorWithCode(
68
- 'We didn\'t get a response from the server. Please check your internet connection.',
69
+ 'We didn\'t get a response from the other server. Please check your internet connection.',
69
70
  ReactKitErrorCode.NoResponse,
70
71
  );
71
72
  }
@@ -15,9 +15,6 @@ import ErrorWithCode from '../../errors/ErrorWithCode';
15
15
  * @param [opts.host] host to send request to
16
16
  * @param [opts.method=GET] http method to use
17
17
  * @param [opts.params] body/data to include in the request
18
- * @param [opts.headers] headers to include in the request
19
- * @param [opts.sendCrossDomainCredentials=true if in development mode] if true,
20
- * send cross-domain credentials even if not in dev mode
21
18
  * @param [opts.responseType=JSON] expected response type
22
19
  * @returns { body, status, headers } on success
23
20
  */
@@ -27,7 +24,6 @@ const sendServerToServerRequest = async (
27
24
  host?: string,
28
25
  method?: ('GET' | 'POST' | 'PUT' | 'DELETE'),
29
26
  params?: { [k in string]: any },
30
- headers?: { [k in string]: any },
31
27
  responseType?: 'Text' | 'JSON',
32
28
  },
33
29
  ): Promise<{
@@ -70,7 +66,9 @@ const sendServerToServerRequest = async (
70
66
  }
71
67
 
72
68
  // Update headers
73
- const headers = opts.headers || {};
69
+ const headers: {
70
+ [k: string]: any,
71
+ } = {};
74
72
  let data: string | null | { [k: string]: any } | undefined = null;
75
73
  if (!headers['Content-Type']) {
76
74
  // Form encoded
package/src/index.ts CHANGED
@@ -70,6 +70,7 @@ import onlyKeepLetters from './helpers/onlyKeepLetters';
70
70
  import parallelLimit from './helpers/parallelLimit';
71
71
  import logClientEvent, { setClientEventMetadataPopulator } from './helpers/logClientEvent';
72
72
  import initLogCollection from './server/initLogCollection';
73
+ import initCrossServerCredentialCollection from './server/initCrossServerCredentialCollection';
73
74
  import getMonthName from './helpers/getMonthName';
74
75
  import genCSV from './helpers/genCSV';
75
76
  import canReviewLogs from './helpers/canReviewLogs';
@@ -95,7 +96,6 @@ import someAsync from './helpers/asyncArrayFunctions/someAsync';
95
96
  import capitalize from './helpers/capitalize';
96
97
  import shuffleArray from './helpers/shuffleArray';
97
98
  import visitEndpointOnAnotherServer from './helpers/visitEndpointOnAnotherServer';
98
- import getWordCount from './helpers/getWordCount';
99
99
 
100
100
  // Import types
101
101
  import ModalButtonType from './types/ModalButtonType';
@@ -114,6 +114,7 @@ import LogMetadataType from './types/LogMetadataType';
114
114
  import LogFunction from './types/LogFunction';
115
115
  import IntelliTableColumn from './types/IntelliTableColumn';
116
116
  import DropdownItemType from './types/DropdownItemType';
117
+ import CrossServerCredential from './types/CrossServerCredential';
117
118
 
118
119
  // Component-specific-types
119
120
  import PickableItem from './components/ItemPicker/types/PickableItem';
@@ -202,7 +203,6 @@ export {
202
203
  someAsync,
203
204
  capitalize,
204
205
  shuffleArray,
205
- getWordCount,
206
206
  // Client helpers
207
207
  initClient,
208
208
  visitServerEndpoint,
@@ -218,6 +218,7 @@ export {
218
218
  handleError,
219
219
  handleSuccess,
220
220
  initLogCollection,
221
+ initCrossServerCredentialCollection,
221
222
  addDBEditorEndpoints,
222
223
  visitEndpointOnAnotherServer,
223
224
  // Types
@@ -236,6 +237,7 @@ export {
236
237
  LogFunction,
237
238
  IntelliTableColumn,
238
239
  DropdownItemType,
240
+ CrossServerCredential,
239
241
  // Component-specific-types
240
242
  PickableItem,
241
243
  DBEntry,
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Initialize a cross-server credential collection given the dce-mango Collection class
3
+ * @author Gabe Abrams
4
+ * @param Collection the Collection class from dce-mango
5
+ * @returns initialized logCollection
6
+ */
7
+ const initCrossServerCredentialCollection = (Collection: any) => {
8
+ return new Collection(
9
+ 'CrossServerCredential',
10
+ {
11
+ uniqueIndexKey: 'key',
12
+ },
13
+ );
14
+ };
15
+
16
+ export default initCrossServerCredentialCollection;