dce-reactkit 3.12.1-beta-cross-server.2 → 3.12.1-beta-cross-server.4
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/dist/cjs/index.js +8003 -190
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/helpers/dataSigner.d.ts +13 -11
- package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/index.d.ts +0 -6
- package/dist/cjs/types/types/ReactKitErrorCode.d.ts +13 -11
- package/dist/esm/index.js +8001 -189
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/helpers/dataSigner.d.ts +13 -11
- package/dist/esm/types/helpers/visitEndpointOnAnotherServer/index.d.ts +0 -6
- package/dist/esm/types/types/ReactKitErrorCode.d.ts +13 -11
- package/dist/index.d.ts +13 -17
- package/genEncodedSecret.ts +42 -11
- package/package.json +4 -2
- package/rollup.config.js +2 -0
- package/src/helpers/dataSigner.ts +182 -114
- package/src/helpers/genRouteHandler.ts +27 -24
- package/src/helpers/visitEndpointOnAnotherServer/index.ts +84 -26
- package/src/types/ReactKitErrorCode.tsx +13 -11
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
// Import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
// Import jwt
|
|
5
|
-
import jwt from 'jwt-simple';
|
|
1
|
+
// Import cryptography lib
|
|
2
|
+
import Cryptr from 'cryptr';
|
|
6
3
|
|
|
7
4
|
// Import shared helpers
|
|
5
|
+
// eslint-disable-next-line import/no-cycle
|
|
8
6
|
import { internalGetCrossServerCredentialCollection } from '../server/initServer';
|
|
9
7
|
|
|
10
8
|
// Import shared types
|
|
@@ -16,57 +14,120 @@ import CrossServerCredential from '../types/CrossServerCredential';
|
|
|
16
14
|
import MINUTE_IN_MS from '../constants/MINUTE_IN_MS';
|
|
17
15
|
|
|
18
16
|
/*------------------------------------------------------------------------*/
|
|
19
|
-
/*
|
|
17
|
+
/* --------------------------- Dynamic Imports -------------------------- */
|
|
20
18
|
/*------------------------------------------------------------------------*/
|
|
21
19
|
|
|
22
20
|
/**
|
|
23
|
-
*
|
|
21
|
+
* Get a copy of the oauth lib
|
|
24
22
|
* @author Gabe Abrams
|
|
25
|
-
* @
|
|
26
|
-
* @return the secret
|
|
23
|
+
* @return the oauth lib
|
|
27
24
|
*/
|
|
28
|
-
const
|
|
29
|
-
// Get the
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
const getOauthLib = async () => {
|
|
26
|
+
// Get the oauth signing library (included with caccl)
|
|
27
|
+
// Ignore because this is assumed to be included with caccl
|
|
28
|
+
try {
|
|
29
|
+
// @ts-ignore
|
|
30
|
+
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
31
|
+
const oauth = await import('oauth-signature');
|
|
32
|
+
return oauth;
|
|
33
|
+
} catch (err) {
|
|
32
34
|
throw new ErrorWithCode(
|
|
33
|
-
'
|
|
34
|
-
ReactKitErrorCode.
|
|
35
|
+
'Could not sign a cross-server request because we could not load the oauth library. Please make sure this app has caccl as one of its dependencies.',
|
|
36
|
+
ReactKitErrorCode.NoOauthLib,
|
|
35
37
|
);
|
|
36
38
|
}
|
|
37
|
-
|
|
38
|
-
// Decode the secret
|
|
39
|
-
return jwt.decode(encodedSecret, credentialEncodingSalt);
|
|
40
39
|
};
|
|
41
40
|
|
|
42
41
|
/*------------------------------------------------------------------------*/
|
|
43
|
-
/* -------------------------------
|
|
42
|
+
/* ------------------------------- Helpers ------------------------------ */
|
|
44
43
|
/*------------------------------------------------------------------------*/
|
|
45
44
|
|
|
46
45
|
/**
|
|
47
|
-
*
|
|
46
|
+
* Generate an oauth signature
|
|
48
47
|
* @author Gabe Abrams
|
|
49
48
|
* @param opts object containing all arguments
|
|
50
|
-
* @param opts.
|
|
51
|
-
* @param opts.
|
|
52
|
-
* @
|
|
49
|
+
* @param opts.method the http method
|
|
50
|
+
* @param opts.path the http request path
|
|
51
|
+
* @param opts.params the data in the body to sign
|
|
52
|
+
* @param opts.secret the secret to sign with
|
|
53
|
+
* @return the signature
|
|
53
54
|
*/
|
|
54
|
-
const genSignature = (
|
|
55
|
+
const genSignature = async (
|
|
55
56
|
opts: {
|
|
56
|
-
|
|
57
|
+
method?: string,
|
|
58
|
+
path?: string,
|
|
59
|
+
params?: { [key: string]: any },
|
|
57
60
|
secret: string,
|
|
58
61
|
},
|
|
59
|
-
): string => {
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
): Promise<string> => {
|
|
63
|
+
// Destructure opts
|
|
64
|
+
const {
|
|
65
|
+
method,
|
|
66
|
+
path,
|
|
67
|
+
params,
|
|
68
|
+
secret,
|
|
69
|
+
} = opts;
|
|
70
|
+
|
|
71
|
+
// Get the oauth library
|
|
72
|
+
const oauth = await getOauthLib();
|
|
73
|
+
|
|
74
|
+
// Order the params alphabetically by key
|
|
75
|
+
const keys = Object.keys(params ?? {});
|
|
76
|
+
keys.sort();
|
|
77
|
+
const orderedParams: {
|
|
78
|
+
[key: string]: any,
|
|
79
|
+
} = {};
|
|
80
|
+
keys.forEach((key) => {
|
|
81
|
+
// Skip oauth_signature
|
|
82
|
+
if (key === 'oauth_signature') {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Add the param
|
|
87
|
+
orderedParams[key] = (params ?? {})[key];
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Generate the signature
|
|
91
|
+
return decodeURIComponent(oauth.generate(
|
|
92
|
+
method ?? 'GET',
|
|
93
|
+
path ?? 'no-path',
|
|
94
|
+
orderedParams,
|
|
95
|
+
secret,
|
|
96
|
+
));
|
|
66
97
|
};
|
|
67
98
|
|
|
68
99
|
/**
|
|
69
|
-
*
|
|
100
|
+
* Decrypt an encrypted string using a secret
|
|
101
|
+
* @author Gabe Abrams
|
|
102
|
+
* @param str the encrypted string
|
|
103
|
+
* @return the decrypted string
|
|
104
|
+
*/
|
|
105
|
+
const decrypt = async (
|
|
106
|
+
encryptedPack: string,
|
|
107
|
+
): Promise<string> => {
|
|
108
|
+
// Get the encryption secret
|
|
109
|
+
const { REACTKIT_CRED_ENCODING_SALT } = process.env;
|
|
110
|
+
if (!REACTKIT_CRED_ENCODING_SALT) {
|
|
111
|
+
throw new ErrorWithCode(
|
|
112
|
+
'Could not decrypt a string because the encryption salt was not set.',
|
|
113
|
+
ReactKitErrorCode.CrossServerNoCredentialEncodingSalt,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Decrypt the string
|
|
118
|
+
const cryptr = new Cryptr(REACTKIT_CRED_ENCODING_SALT);
|
|
119
|
+
const str = cryptr.decrypt(encryptedPack);
|
|
120
|
+
|
|
121
|
+
// Return the decrypted string
|
|
122
|
+
return str;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/*------------------------------------------------------------------------*/
|
|
126
|
+
/* ------------------------------- Signing ------------------------------ */
|
|
127
|
+
/*------------------------------------------------------------------------*/
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Sign a request and get the new request params
|
|
70
131
|
* @author Gabe Abrams
|
|
71
132
|
* @param opts object containing all arguments
|
|
72
133
|
* @param opts.method the method to sign
|
|
@@ -74,9 +135,9 @@ const genSignature = (
|
|
|
74
135
|
* @param opts.params the data in the body to sign
|
|
75
136
|
* @param opts.key the reactkit key to sign with
|
|
76
137
|
* @param opts.secret the reactkit secret to sign with
|
|
77
|
-
* @return the
|
|
138
|
+
* @return augmented params for the request, including a signature, timestamp, and key
|
|
78
139
|
*/
|
|
79
|
-
export const
|
|
140
|
+
export const signRequest = async (
|
|
80
141
|
opts: {
|
|
81
142
|
method: string,
|
|
82
143
|
path: string,
|
|
@@ -84,111 +145,108 @@ export const createSignedPack = (
|
|
|
84
145
|
key: string,
|
|
85
146
|
secret: string,
|
|
86
147
|
},
|
|
87
|
-
): string => {
|
|
88
|
-
//
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
148
|
+
): Promise<{ [key: string]: any }> => {
|
|
149
|
+
// Destructure opts
|
|
150
|
+
const method = opts.method.toUpperCase();
|
|
151
|
+
const {
|
|
152
|
+
path,
|
|
153
|
+
params,
|
|
154
|
+
key,
|
|
155
|
+
secret,
|
|
156
|
+
} = opts;
|
|
157
|
+
|
|
158
|
+
// Augment the params
|
|
159
|
+
const augmentedParams: {
|
|
160
|
+
[key: string]: any,
|
|
161
|
+
} = {
|
|
162
|
+
...params,
|
|
163
|
+
oauth_consumer_key: key,
|
|
164
|
+
oauth_nonce: Math.random().toString(36),
|
|
165
|
+
oauth_timestamp: Date.now(),
|
|
166
|
+
};
|
|
99
167
|
|
|
100
|
-
// Generate signature
|
|
101
|
-
const signature = genSignature({
|
|
102
|
-
|
|
103
|
-
|
|
168
|
+
// Generate a signature
|
|
169
|
+
const signature = await genSignature({
|
|
170
|
+
method,
|
|
171
|
+
path,
|
|
172
|
+
params,
|
|
173
|
+
secret,
|
|
104
174
|
});
|
|
105
175
|
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
pack,
|
|
109
|
-
signature,
|
|
110
|
-
}));
|
|
176
|
+
// Add signature to the augmented params
|
|
177
|
+
augmentedParams.oauth_signature = signature;
|
|
111
178
|
|
|
112
|
-
// Return the
|
|
113
|
-
return
|
|
179
|
+
// Return the augmented params
|
|
180
|
+
return augmentedParams;
|
|
114
181
|
};
|
|
115
182
|
|
|
116
183
|
/**
|
|
117
|
-
*
|
|
184
|
+
* Validate a signed request. Throws an error if invalid
|
|
118
185
|
* @author Gabe Abrams
|
|
119
186
|
* @param opts object containing all arguments
|
|
120
187
|
* @param opts.method the method of the data validate
|
|
121
188
|
* @param opts.path the http request path to validate
|
|
122
189
|
* @param opts.scope the name of the scope to validate
|
|
123
|
-
* @param opts.
|
|
190
|
+
* @param opts.params the request data to validate
|
|
124
191
|
* @returns parsed and validated params
|
|
125
192
|
*/
|
|
126
|
-
export const
|
|
193
|
+
export const validateSignedRequest = async (
|
|
127
194
|
opts: {
|
|
128
195
|
method: string,
|
|
129
196
|
path: string,
|
|
130
197
|
scope: string,
|
|
131
|
-
|
|
198
|
+
params: { [key: string]: any },
|
|
132
199
|
},
|
|
133
|
-
)
|
|
134
|
-
|
|
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
|
-
}
|
|
200
|
+
) => {
|
|
201
|
+
/* ---------- Collect Info ---------- */
|
|
162
202
|
|
|
163
|
-
//
|
|
164
|
-
if (
|
|
203
|
+
// Get the signature
|
|
204
|
+
if (!opts.params.oauth_signature) {
|
|
165
205
|
throw new ErrorWithCode(
|
|
166
|
-
'Could not validate a cross-server request
|
|
167
|
-
ReactKitErrorCode.
|
|
206
|
+
'Could not validate a cross-server request there was no oauth signature.',
|
|
207
|
+
ReactKitErrorCode.CrossServerMissingSignedRequestInfo,
|
|
168
208
|
);
|
|
169
209
|
}
|
|
170
|
-
|
|
210
|
+
const signature = opts.params.oauth_signature;
|
|
211
|
+
|
|
212
|
+
// Get the timestamp
|
|
213
|
+
if (
|
|
214
|
+
// No timestamp
|
|
215
|
+
!opts.params.oauth_timestamp
|
|
216
|
+
// Invalid timestamp
|
|
217
|
+
|| Number.isNaN(Number.parseInt(opts.params.oauth_timestamp, 10))
|
|
218
|
+
) {
|
|
171
219
|
throw new ErrorWithCode(
|
|
172
|
-
'Could not validate a cross-server request
|
|
173
|
-
ReactKitErrorCode.
|
|
220
|
+
'Could not validate a cross-server request there was no valid oauth timestamp.',
|
|
221
|
+
ReactKitErrorCode.CrossServerMissingSignedRequestInfo,
|
|
174
222
|
);
|
|
175
223
|
}
|
|
224
|
+
const timestamp = Number.parseInt(opts.params.oauth_timestamp, 10);
|
|
176
225
|
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
if (elapsedMs < MINUTE_IN_MS) {
|
|
226
|
+
// Get the key
|
|
227
|
+
if (!opts.params.oauth_consumer_key) {
|
|
180
228
|
throw new ErrorWithCode(
|
|
181
|
-
'Could not validate a cross-server request
|
|
182
|
-
ReactKitErrorCode.
|
|
229
|
+
'Could not validate a cross-server request there was no oauth consumer key.',
|
|
230
|
+
ReactKitErrorCode.CrossServerMissingSignedRequestInfo,
|
|
183
231
|
);
|
|
184
232
|
}
|
|
233
|
+
const key = opts.params.oauth_consumer_key;
|
|
234
|
+
|
|
235
|
+
// Get the rest of the info
|
|
236
|
+
const {
|
|
237
|
+
method,
|
|
238
|
+
path,
|
|
239
|
+
params,
|
|
240
|
+
} = opts;
|
|
241
|
+
|
|
242
|
+
/* ------- Look Up Credential ------- */
|
|
185
243
|
|
|
186
244
|
// Get the cross-server credential collection
|
|
187
245
|
const crossServerCredentialCollection = internalGetCrossServerCredentialCollection();
|
|
188
246
|
if (!crossServerCredentialCollection) {
|
|
189
247
|
throw new ErrorWithCode(
|
|
190
248
|
'Could not validate a cross-server request because the cross-server credential collection was not ready in time.',
|
|
191
|
-
ReactKitErrorCode.
|
|
249
|
+
ReactKitErrorCode.SignedRequestInvalidCollection,
|
|
192
250
|
);
|
|
193
251
|
}
|
|
194
252
|
|
|
@@ -197,7 +255,7 @@ export const parseSignedPack = async (
|
|
|
197
255
|
if (!crossServerCredential) {
|
|
198
256
|
throw new ErrorWithCode(
|
|
199
257
|
'Could not validate a cross-server request because the credential was not found.',
|
|
200
|
-
ReactKitErrorCode.
|
|
258
|
+
ReactKitErrorCode.SignedRequestInvalidCredential,
|
|
201
259
|
);
|
|
202
260
|
}
|
|
203
261
|
|
|
@@ -206,27 +264,37 @@ export const parseSignedPack = async (
|
|
|
206
264
|
if (!allowedScopes.includes(opts.scope)) {
|
|
207
265
|
throw new ErrorWithCode(
|
|
208
266
|
'Could not validate a cross-server request because the scope was not included.',
|
|
209
|
-
ReactKitErrorCode.
|
|
267
|
+
ReactKitErrorCode.SignedRequestInvalidScope,
|
|
210
268
|
);
|
|
211
269
|
}
|
|
212
270
|
|
|
213
271
|
// Decode the secret
|
|
214
|
-
const secret =
|
|
272
|
+
const secret = await decrypt(crossServerCredential.encodedeSecret);
|
|
273
|
+
|
|
274
|
+
/* -------- Verify Signature -------- */
|
|
215
275
|
|
|
216
|
-
// Generate signature
|
|
217
|
-
const expectedSignature = genSignature({
|
|
218
|
-
|
|
276
|
+
// Generate a new signature to compare
|
|
277
|
+
const expectedSignature = await genSignature({
|
|
278
|
+
method,
|
|
279
|
+
path,
|
|
280
|
+
params,
|
|
219
281
|
secret,
|
|
220
282
|
});
|
|
221
283
|
|
|
222
|
-
// Make sure the
|
|
284
|
+
// Make sure the signatures match
|
|
223
285
|
if (signature !== expectedSignature) {
|
|
224
286
|
throw new ErrorWithCode(
|
|
225
287
|
'Could not validate a cross-server request because the signature did not match.',
|
|
226
|
-
ReactKitErrorCode.
|
|
288
|
+
ReactKitErrorCode.SignedRequestInvalidSignature,
|
|
227
289
|
);
|
|
228
290
|
}
|
|
229
291
|
|
|
230
|
-
//
|
|
231
|
-
|
|
292
|
+
// Make sure the timestamp was recent enough
|
|
293
|
+
const elapsedMs = Math.abs(Date.now() - timestamp);
|
|
294
|
+
if (elapsedMs < MINUTE_IN_MS) {
|
|
295
|
+
throw new ErrorWithCode(
|
|
296
|
+
'Could not validate a cross-server request because the request was too old.',
|
|
297
|
+
ReactKitErrorCode.SignedRequestInvalidTimestamp,
|
|
298
|
+
);
|
|
299
|
+
}
|
|
232
300
|
};
|
|
@@ -17,7 +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 {
|
|
20
|
+
import { validateSignedRequest } from './dataSigner';
|
|
21
21
|
|
|
22
22
|
// Import shared types
|
|
23
23
|
import LogFunction from '../types/LogFunction';
|
|
@@ -30,7 +30,6 @@ import LogSourceSpecificInfo from '../types/Log/LogSourceSpecificInfo';
|
|
|
30
30
|
import LogBuiltInMetadata from '../types/LogBuiltInMetadata';
|
|
31
31
|
import LogAction from '../types/LogAction';
|
|
32
32
|
import LogLevel from '../types/LogLevel';
|
|
33
|
-
import ErrorWithCode from '../errors/ErrorWithCode';
|
|
34
33
|
|
|
35
34
|
/**
|
|
36
35
|
* Generate an express API route handler
|
|
@@ -137,7 +136,7 @@ const genRouteHandler = (
|
|
|
137
136
|
);
|
|
138
137
|
|
|
139
138
|
// Get body from everywhere it can come from
|
|
140
|
-
|
|
139
|
+
const requestBody: {
|
|
141
140
|
[k: string]: any,
|
|
142
141
|
} = {
|
|
143
142
|
...req.body,
|
|
@@ -150,27 +149,31 @@ const genRouteHandler = (
|
|
|
150
149
|
/*----------------------------------------*/
|
|
151
150
|
|
|
152
151
|
if (crossServerScope) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
152
|
+
try {
|
|
153
|
+
// Validate the request body
|
|
154
|
+
await validateSignedRequest({
|
|
155
|
+
method: req.method ?? 'GET',
|
|
156
|
+
path: req.path,
|
|
157
|
+
scope: crossServerScope,
|
|
158
|
+
params: requestBody,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Valid! Remove oauth values
|
|
162
|
+
Object.keys(requestBody).forEach((key) => {
|
|
163
|
+
if (key.startsWith('oauth_')) {
|
|
164
|
+
delete requestBody[key];
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
} catch (err) {
|
|
168
|
+
return handleError(
|
|
169
|
+
res,
|
|
170
|
+
{
|
|
171
|
+
message: `The authenticity of a cross-server request could not be validated because an error occurred: ${(err as any).message ?? 'unknown error'}`,
|
|
172
|
+
code: ((err as any).code ?? ReactKitErrorCode.UnknownCrossServerError),
|
|
173
|
+
status: 401,
|
|
174
|
+
},
|
|
161
175
|
);
|
|
162
176
|
}
|
|
163
|
-
|
|
164
|
-
// Validate the request
|
|
165
|
-
const crossServerParams = await parseSignedPack({
|
|
166
|
-
method: req.method ?? 'GET',
|
|
167
|
-
path: req.path,
|
|
168
|
-
scope: crossServerScope,
|
|
169
|
-
signedPack,
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
// Replace body with params from the pack
|
|
173
|
-
requestBody = crossServerParams ?? {};
|
|
174
177
|
}
|
|
175
178
|
|
|
176
179
|
/*----------------------------------------*/
|
|
@@ -586,14 +589,14 @@ const genRouteHandler = (
|
|
|
586
589
|
// Main log info
|
|
587
590
|
const mainLogInfo: LogMainInfo = {
|
|
588
591
|
id: `${launchInfo ? launchInfo.userId : 'unknown'}-${Date.now()}-${Math.floor(Math.random() * 100000)}-${Math.floor(Math.random() * 100000)}`,
|
|
589
|
-
userFirstName: (launchInfo ? launchInfo.userFirstName: 'unknown'),
|
|
592
|
+
userFirstName: (launchInfo ? launchInfo.userFirstName : 'unknown'),
|
|
590
593
|
userLastName: (launchInfo ? launchInfo.userLastName : 'unknown'),
|
|
591
594
|
userEmail: (launchInfo ? launchInfo.userEmail : 'unknown'),
|
|
592
595
|
userId: (launchInfo ? launchInfo.userId : 'unknown'),
|
|
593
596
|
isLearner: (launchInfo && !!launchInfo.isLearner),
|
|
594
597
|
isAdmin: (launchInfo && !!launchInfo.isAdmin),
|
|
595
598
|
isTTM: (launchInfo && !!launchInfo.isTTM),
|
|
596
|
-
courseId: (launchInfo ? launchInfo.courseId : 'unknown'
|
|
599
|
+
courseId: (launchInfo ? launchInfo.courseId : 'unknown'),
|
|
597
600
|
courseName: (launchInfo ? launchInfo.contextLabel : 'unknown'),
|
|
598
601
|
browser,
|
|
599
602
|
device,
|
|
@@ -1,11 +1,86 @@
|
|
|
1
1
|
// Import data signer
|
|
2
|
-
import {
|
|
2
|
+
import { signRequest } from '../dataSigner';
|
|
3
3
|
|
|
4
4
|
// Import shared types
|
|
5
5
|
import ErrorWithCode from '../../errors/ErrorWithCode';
|
|
6
6
|
import ReactKitErrorCode from '../../types/ReactKitErrorCode';
|
|
7
7
|
import sendServerToServerRequest from './sendServerToServerRequest';
|
|
8
8
|
|
|
9
|
+
/*------------------------------------------------------------------------*/
|
|
10
|
+
/* ----------------------------- Credentials ---------------------------- */
|
|
11
|
+
/*------------------------------------------------------------------------*/
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
REACTKIT_CROSS_SERVER_CREDENTIALS format:
|
|
15
|
+
|host:key:secret||host:key:secret|...
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const credentials: {
|
|
19
|
+
host: string,
|
|
20
|
+
key: string,
|
|
21
|
+
secret: string,
|
|
22
|
+
}[] = (
|
|
23
|
+
(process.env.REACTKIT_CROSS_SERVER_CREDENTIALS ?? '')
|
|
24
|
+
// Replace multiple | with a single one
|
|
25
|
+
.replace(/\|+/g, '|')
|
|
26
|
+
// Split by |
|
|
27
|
+
.split('|')
|
|
28
|
+
// Remove empty strings
|
|
29
|
+
.filter((str) => {
|
|
30
|
+
return str.trim().length > 0;
|
|
31
|
+
})
|
|
32
|
+
// Process each credential
|
|
33
|
+
.map((str) => {
|
|
34
|
+
// Split by :
|
|
35
|
+
const parts = str.split(':');
|
|
36
|
+
|
|
37
|
+
// Check for errors
|
|
38
|
+
if (parts.length !== 3) {
|
|
39
|
+
throw new ErrorWithCode(
|
|
40
|
+
'Invalid REACTKIT_CROSS_SERVER_CREDENTIALS format. Each credential must be in the format |host:key:secret|',
|
|
41
|
+
ReactKitErrorCode.InvalidCrossServerCredentialsFormat,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Return the credential
|
|
46
|
+
return {
|
|
47
|
+
host: parts[0].trim(),
|
|
48
|
+
key: parts[1].trim(),
|
|
49
|
+
secret: parts[2].trim(),
|
|
50
|
+
};
|
|
51
|
+
})
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
/*------------------------------------------------------------------------*/
|
|
55
|
+
/* ------------------------------- Helpers ------------------------------ */
|
|
56
|
+
/*------------------------------------------------------------------------*/
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get the credential to use for the request to another server
|
|
60
|
+
* @author Gabe Abrams
|
|
61
|
+
* @param host the host of the other server
|
|
62
|
+
* @return the credential to use
|
|
63
|
+
*/
|
|
64
|
+
const getCrossServerCredential = (host: string) => {
|
|
65
|
+
// Find the credential
|
|
66
|
+
const credential = credentials.find((cred) => {
|
|
67
|
+
return cred.host.toLowerCase() === host.toLowerCase();
|
|
68
|
+
});
|
|
69
|
+
if (!credential) {
|
|
70
|
+
throw new ErrorWithCode(
|
|
71
|
+
'Cannot send cross-server signed request there was no credential that matched the host that the request is being sent to.',
|
|
72
|
+
ReactKitErrorCode.CrossServerNoCredentialsToSignWith,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Return credential
|
|
77
|
+
return credential;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/*------------------------------------------------------------------------*/
|
|
81
|
+
/* -------------------------------- Main -------------------------------- */
|
|
82
|
+
/*------------------------------------------------------------------------*/
|
|
83
|
+
|
|
9
84
|
/**
|
|
10
85
|
* Visit an endpoint on another server
|
|
11
86
|
* @author Gabe Abrams
|
|
@@ -13,10 +88,6 @@ import sendServerToServerRequest from './sendServerToServerRequest';
|
|
|
13
88
|
* @param opts.method the method of the endpoint
|
|
14
89
|
* @param opts.path the path of the other server's endpoint
|
|
15
90
|
* @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
91
|
* @param [opts.params={}] query/body parameters to include
|
|
21
92
|
* @param [opts.responseType=JSON] the response type from the other server
|
|
22
93
|
*/
|
|
@@ -25,31 +96,20 @@ const visitEndpointOnAnotherServer = async (
|
|
|
25
96
|
method: 'GET' | 'POST' | 'DELETE' | 'PUT',
|
|
26
97
|
path: string,
|
|
27
98
|
host: string,
|
|
28
|
-
key?: string,
|
|
29
|
-
secret?: string,
|
|
30
99
|
params?: { [key in string]: any },
|
|
31
100
|
responseType?: 'JSON' | 'Text',
|
|
32
101
|
},
|
|
33
102
|
): Promise<any> => {
|
|
34
|
-
// Get cross-server
|
|
35
|
-
const
|
|
36
|
-
const secret = opts.secret ?? process.env.REACTKIT_CROSS_SERVER_SECRET;
|
|
37
|
-
|
|
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,
|
|
43
|
-
);
|
|
44
|
-
}
|
|
103
|
+
// Get cross-server credential
|
|
104
|
+
const credential = getCrossServerCredential(opts.host);
|
|
45
105
|
|
|
46
|
-
//
|
|
47
|
-
const
|
|
106
|
+
// Sign the request, get new params
|
|
107
|
+
const augmentedParams = await signRequest({
|
|
48
108
|
method: opts.method,
|
|
49
109
|
path: opts.path,
|
|
50
110
|
params: opts.params ?? {},
|
|
51
|
-
key,
|
|
52
|
-
secret,
|
|
111
|
+
key: credential.key,
|
|
112
|
+
secret: credential.secret,
|
|
53
113
|
});
|
|
54
114
|
|
|
55
115
|
// Send the request
|
|
@@ -57,16 +117,14 @@ const visitEndpointOnAnotherServer = async (
|
|
|
57
117
|
path: opts.path,
|
|
58
118
|
host: opts.host,
|
|
59
119
|
method: opts.method,
|
|
60
|
-
params:
|
|
61
|
-
signedPack,
|
|
62
|
-
},
|
|
120
|
+
params: augmentedParams,
|
|
63
121
|
responseType: opts.responseType,
|
|
64
122
|
});
|
|
65
123
|
|
|
66
124
|
// Check for failure
|
|
67
125
|
if (!response || !response.body) {
|
|
68
126
|
throw new ErrorWithCode(
|
|
69
|
-
'We didn\'t get a response from the other server. Please check
|
|
127
|
+
'We didn\'t get a response from the other server. Please check the network between the two connection.',
|
|
70
128
|
ReactKitErrorCode.NoResponse,
|
|
71
129
|
);
|
|
72
130
|
}
|