@casual-simulation/aux-records 3.2.3 → 3.2.4-alpha.5940763320
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/AIChatInterface.d.ts +8 -0
- package/AIChatInterface.js.map +1 -1
- package/AIController.d.ts +10 -4
- package/AIController.js +112 -3
- package/AIController.js.map +1 -1
- package/AuthController.d.ts +4 -8
- package/AuthController.js +39 -45
- package/AuthController.js.map +1 -1
- package/AuthStore.d.ts +236 -4
- package/ConfigurationStore.d.ts +12 -0
- package/ConfigurationStore.js +2 -0
- package/ConfigurationStore.js.map +1 -0
- package/DataRecordsController.d.ts +18 -6
- package/DataRecordsController.js +31 -5
- package/DataRecordsController.js.map +1 -1
- package/Errors.d.ts +4 -0
- package/EventRecordsController.d.ts +12 -3
- package/EventRecordsController.js +27 -5
- package/EventRecordsController.js.map +1 -1
- package/FileRecordsController.d.ts +13 -3
- package/FileRecordsController.js +46 -9
- package/FileRecordsController.js.map +1 -1
- package/MemoryFileRecordsLookup.d.ts +11 -0
- package/MemoryFileRecordsLookup.js +128 -0
- package/MemoryFileRecordsLookup.js.map +1 -0
- package/MemoryStore.d.ts +191 -0
- package/MemoryStore.js +1381 -0
- package/MemoryStore.js.map +1 -0
- package/MetricsStore.d.ts +186 -0
- package/MetricsStore.js +2 -0
- package/MetricsStore.js.map +1 -0
- package/OpenAIChatInterface.js +1 -0
- package/OpenAIChatInterface.js.map +1 -1
- package/PolicyController.d.ts +7 -4
- package/PolicyController.js +33 -8
- package/PolicyController.js.map +1 -1
- package/RecordsController.d.ts +185 -7
- package/RecordsController.js +588 -70
- package/RecordsController.js.map +1 -1
- package/RecordsHttpServer.d.ts +8 -0
- package/RecordsHttpServer.js +450 -8
- package/RecordsHttpServer.js.map +1 -1
- package/RecordsStore.d.ts +263 -5
- package/StripeInterface.d.ts +331 -0
- package/StripeInterface.js +37 -1
- package/StripeInterface.js.map +1 -1
- package/SubscriptionConfiguration.d.ts +2523 -33
- package/SubscriptionConfiguration.js +130 -1
- package/SubscriptionConfiguration.js.map +1 -1
- package/SubscriptionController.d.ts +23 -6
- package/SubscriptionController.js +344 -69
- package/SubscriptionController.js.map +1 -1
- package/TestUtils.d.ts +7 -6
- package/TestUtils.js +28 -14
- package/TestUtils.js.map +1 -1
- package/Utils.js +9 -0
- package/Utils.js.map +1 -1
- package/index.d.ts +5 -4
- package/index.js +5 -4
- package/index.js.map +1 -1
- package/package.json +2 -2
- package/MemoryAuthStore.d.ts +0 -33
- package/MemoryAuthStore.js +0 -186
- package/MemoryAuthStore.js.map +0 -1
- package/MemoryDataRecordsStore.d.ts +0 -10
- package/MemoryDataRecordsStore.js +0 -98
- package/MemoryDataRecordsStore.js.map +0 -1
- package/MemoryEventRecordsStore.d.ts +0 -10
- package/MemoryEventRecordsStore.js +0 -89
- package/MemoryEventRecordsStore.js.map +0 -1
- package/MemoryFileRecordsStore.d.ts +0 -25
- package/MemoryFileRecordsStore.js +0 -310
- package/MemoryFileRecordsStore.js.map +0 -1
- package/MemoryPolicyStore.d.ts +0 -43
- package/MemoryPolicyStore.js +0 -255
- package/MemoryPolicyStore.js.map +0 -1
- package/MemoryRecordsStore.d.ts +0 -13
- package/MemoryRecordsStore.js +0 -67
- package/MemoryRecordsStore.js.map +0 -1
package/RecordsController.js
CHANGED
|
@@ -11,13 +11,162 @@ import { toBase64String, fromBase64String } from './Utils';
|
|
|
11
11
|
import { hashHighEntropyPasswordWithSalt, hashPasswordWithSalt, } from '@casual-simulation/crypto';
|
|
12
12
|
import { randomBytes } from 'tweetnacl';
|
|
13
13
|
import { fromByteArray } from 'base64-js';
|
|
14
|
+
import { v4 as uuid } from 'uuid';
|
|
15
|
+
import { getSubscriptionFeatures } from './SubscriptionConfiguration';
|
|
14
16
|
/**
|
|
15
17
|
* Defines a class that manages records and their keys.
|
|
16
18
|
*/
|
|
17
19
|
export class RecordsController {
|
|
18
|
-
constructor(
|
|
19
|
-
this._store = store;
|
|
20
|
-
this._auth = auth;
|
|
20
|
+
constructor(config) {
|
|
21
|
+
this._store = config.store;
|
|
22
|
+
this._auth = config.auth;
|
|
23
|
+
this._metrics = config.metrics;
|
|
24
|
+
this._config = config.config;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Creates a new record.
|
|
28
|
+
* @param request The request that should be used to create the record.
|
|
29
|
+
*/
|
|
30
|
+
createRecord(request) {
|
|
31
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
32
|
+
try {
|
|
33
|
+
if (!request.userId) {
|
|
34
|
+
return {
|
|
35
|
+
success: false,
|
|
36
|
+
errorCode: 'not_logged_in',
|
|
37
|
+
errorMessage: 'The user must be logged in in order to create a record.',
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const record = yield this._store.getRecordByName(request.recordName);
|
|
41
|
+
if (record) {
|
|
42
|
+
if (record.name === request.userId &&
|
|
43
|
+
record.ownerId !== request.userId &&
|
|
44
|
+
request.ownerId === request.userId) {
|
|
45
|
+
const allowed = yield this._doesSubscriptionAllowToCreateRecord({
|
|
46
|
+
ownerId: request.userId,
|
|
47
|
+
});
|
|
48
|
+
if (!allowed.success) {
|
|
49
|
+
return allowed;
|
|
50
|
+
}
|
|
51
|
+
console.log(`[RecordsController] [action: record.create recordName: ${record.name}, userId: ${request.userId}] Fixing record owner to match actual owner.`);
|
|
52
|
+
record.ownerId = request.userId;
|
|
53
|
+
record.studioId = null;
|
|
54
|
+
// Clear the hashes and re-create the salt so that access to the record is revoked for any record key that was created before.
|
|
55
|
+
record.secretHashes = [];
|
|
56
|
+
record.secretSalt = this._createSalt();
|
|
57
|
+
yield this._store.updateRecord(Object.assign({}, record));
|
|
58
|
+
return {
|
|
59
|
+
success: true,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
let existingStudioMembers = yield this._store.listStudioAssignments(record.name);
|
|
63
|
+
if (existingStudioMembers.length > 0 &&
|
|
64
|
+
record.studioId !== record.name &&
|
|
65
|
+
request.studioId === record.name) {
|
|
66
|
+
const allowed = yield this._doesSubscriptionAllowToCreateRecord({
|
|
67
|
+
studioId: request.studioId,
|
|
68
|
+
});
|
|
69
|
+
if (!allowed.success) {
|
|
70
|
+
return allowed;
|
|
71
|
+
}
|
|
72
|
+
console.log(`[RecordsController] [action: record.create recordName: ${record.name}, userId: ${request.userId}, studioId: ${request.studioId}] Fixing record owner to match actual owner.`);
|
|
73
|
+
record.ownerId = null;
|
|
74
|
+
record.studioId = request.studioId;
|
|
75
|
+
// Clear the hashes and re-create the salt so that access to the record is revoked for any record key that was created before.
|
|
76
|
+
record.secretHashes = [];
|
|
77
|
+
record.secretSalt = this._createSalt();
|
|
78
|
+
yield this._store.updateRecord(Object.assign({}, record));
|
|
79
|
+
return {
|
|
80
|
+
success: true,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
success: false,
|
|
85
|
+
errorCode: 'record_already_exists',
|
|
86
|
+
errorMessage: 'A record with that name already exists.',
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (request.recordName !== request.ownerId &&
|
|
90
|
+
request.recordName !== request.studioId) {
|
|
91
|
+
const existingUser = yield this._auth.findUser(request.recordName);
|
|
92
|
+
if (existingUser) {
|
|
93
|
+
return {
|
|
94
|
+
success: false,
|
|
95
|
+
errorCode: 'record_already_exists',
|
|
96
|
+
errorMessage: 'A record with that name already exists.',
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (!request.ownerId && !request.studioId) {
|
|
101
|
+
return {
|
|
102
|
+
success: false,
|
|
103
|
+
errorCode: 'unacceptable_request',
|
|
104
|
+
errorMessage: 'You must provide an owner ID or a studio ID.',
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
if (request.ownerId) {
|
|
108
|
+
if (request.ownerId !== request.userId) {
|
|
109
|
+
return {
|
|
110
|
+
success: false,
|
|
111
|
+
errorCode: 'not_authorized',
|
|
112
|
+
errorMessage: 'You are not authorized to create a record for another user.',
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
const allowed = yield this._doesSubscriptionAllowToCreateRecord({
|
|
116
|
+
ownerId: request.userId,
|
|
117
|
+
});
|
|
118
|
+
if (!allowed.success) {
|
|
119
|
+
return allowed;
|
|
120
|
+
}
|
|
121
|
+
console.log(`[RecordsController] [action: record.create recordName: ${request.recordName}, userId: ${request.userId}, ownerId: ${request.ownerId}] Creating record.`);
|
|
122
|
+
yield this._store.addRecord({
|
|
123
|
+
name: request.recordName,
|
|
124
|
+
ownerId: request.ownerId,
|
|
125
|
+
secretHashes: [],
|
|
126
|
+
secretSalt: this._createSalt(),
|
|
127
|
+
studioId: null,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
const assignments = yield this._store.listStudioAssignments(request.studioId, {
|
|
132
|
+
userId: request.userId,
|
|
133
|
+
role: 'admin',
|
|
134
|
+
});
|
|
135
|
+
if (assignments.length <= 0) {
|
|
136
|
+
return {
|
|
137
|
+
success: false,
|
|
138
|
+
errorCode: 'not_authorized',
|
|
139
|
+
errorMessage: 'You are not authorized to create a record for this studio.',
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
const allowed = yield this._doesSubscriptionAllowToCreateRecord({
|
|
143
|
+
studioId: request.studioId,
|
|
144
|
+
});
|
|
145
|
+
if (!allowed.success) {
|
|
146
|
+
return allowed;
|
|
147
|
+
}
|
|
148
|
+
console.log(`[RecordsController] [action: record.create recordName: ${request.recordName}, userId: ${request.userId}, studioId: ${request.studioId}] Creating record.`);
|
|
149
|
+
yield this._store.addRecord({
|
|
150
|
+
name: request.recordName,
|
|
151
|
+
ownerId: null,
|
|
152
|
+
secretHashes: [],
|
|
153
|
+
secretSalt: this._createSalt(),
|
|
154
|
+
studioId: request.studioId,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
success: true,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
console.error('[RecordsController] [createRecord] An error occurred while creating a record:', err);
|
|
163
|
+
return {
|
|
164
|
+
success: false,
|
|
165
|
+
errorCode: 'server_error',
|
|
166
|
+
errorMessage: 'A server error occurred.',
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
});
|
|
21
170
|
}
|
|
22
171
|
/**
|
|
23
172
|
* Creates a new public record key for the given bucket name.
|
|
@@ -27,6 +176,7 @@ export class RecordsController {
|
|
|
27
176
|
* @returns
|
|
28
177
|
*/
|
|
29
178
|
createPublicRecordKey(name, policy, userId) {
|
|
179
|
+
var _a;
|
|
30
180
|
return __awaiter(this, void 0, void 0, function* () {
|
|
31
181
|
try {
|
|
32
182
|
if (!userId) {
|
|
@@ -37,7 +187,6 @@ export class RecordsController {
|
|
|
37
187
|
errorReason: 'not_logged_in',
|
|
38
188
|
};
|
|
39
189
|
}
|
|
40
|
-
const record = yield this._store.getRecordByName(name);
|
|
41
190
|
if (!!policy &&
|
|
42
191
|
policy !== 'subjectfull' &&
|
|
43
192
|
policy !== 'subjectless') {
|
|
@@ -48,8 +197,32 @@ export class RecordsController {
|
|
|
48
197
|
errorReason: 'invalid_policy',
|
|
49
198
|
};
|
|
50
199
|
}
|
|
51
|
-
|
|
52
|
-
|
|
200
|
+
let existingStudioMembers = yield this._store.listStudioAssignments(name);
|
|
201
|
+
let createResult;
|
|
202
|
+
// recordName matches studioId
|
|
203
|
+
if (existingStudioMembers.length > 0) {
|
|
204
|
+
createResult = yield this.createRecord({
|
|
205
|
+
recordName: name,
|
|
206
|
+
userId: userId,
|
|
207
|
+
studioId: name,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
createResult = yield this.createRecord({
|
|
212
|
+
recordName: name,
|
|
213
|
+
userId: userId,
|
|
214
|
+
ownerId: userId,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
if (createResult.success === false) {
|
|
218
|
+
if (createResult.errorCode !== 'record_already_exists') {
|
|
219
|
+
return Object.assign(Object.assign({}, createResult), { errorReason: 'not_authorized' });
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const record = yield this._store.getRecordByName(name);
|
|
223
|
+
if (!record) {
|
|
224
|
+
if (createResult.success === false &&
|
|
225
|
+
createResult.errorCode === 'record_already_exists') {
|
|
53
226
|
return {
|
|
54
227
|
success: false,
|
|
55
228
|
errorCode: 'unauthorized_to_create_record_key',
|
|
@@ -57,76 +230,70 @@ export class RecordsController {
|
|
|
57
230
|
errorReason: 'record_owned_by_different_user',
|
|
58
231
|
};
|
|
59
232
|
}
|
|
60
|
-
console.
|
|
61
|
-
if (name === userId) {
|
|
62
|
-
// The user is not currently the owner of their own record.
|
|
63
|
-
// This is an issue that needs to be fixed because users should always own the record that has the same name as their ID.
|
|
64
|
-
console.log(`[RecordsController] [action: recordKey.create recordName: ${name}, userId: ${userId}] Fixing record owner to match actual owner.`);
|
|
65
|
-
record.ownerId = userId;
|
|
66
|
-
// Clear the hashes and re-create the salt so that access to the record is revoked for any record key that was created before.
|
|
67
|
-
record.secretHashes = [];
|
|
68
|
-
record.secretSalt = this._createSalt();
|
|
69
|
-
yield this._store.updateRecord(Object.assign({}, record));
|
|
70
|
-
}
|
|
71
|
-
const passwordBytes = randomBytes(16);
|
|
72
|
-
const password = fromByteArray(passwordBytes); // convert to human-readable string
|
|
73
|
-
const salt = record.secretSalt;
|
|
74
|
-
const passwordHash = hashHighEntropyPasswordWithSalt(password, salt);
|
|
75
|
-
yield this._store.addRecordKey({
|
|
76
|
-
recordName: name,
|
|
77
|
-
secretHash: passwordHash,
|
|
78
|
-
policy: policy !== null && policy !== void 0 ? policy : DEFAULT_RECORD_KEY_POLICY,
|
|
79
|
-
creatorId: record.ownerId,
|
|
80
|
-
});
|
|
233
|
+
console.error(`[RecordsController] [action: recordKey.create recordName: ${name}, userId: ${userId}] Unable to find record that was just created!`);
|
|
81
234
|
return {
|
|
82
|
-
success:
|
|
83
|
-
|
|
84
|
-
|
|
235
|
+
success: false,
|
|
236
|
+
errorCode: 'server_error',
|
|
237
|
+
errorMessage: 'A server error occurred.',
|
|
238
|
+
errorReason: 'server_error',
|
|
85
239
|
};
|
|
86
240
|
}
|
|
87
|
-
|
|
88
|
-
if (
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
errorReason: 'record_owned_by_different_user',
|
|
97
|
-
};
|
|
98
|
-
}
|
|
241
|
+
if (record.ownerId) {
|
|
242
|
+
if (existingStudioMembers.length > 0) {
|
|
243
|
+
console.error(`[RecordsController] [action: recordKey.create recordName: ${name}, userId: ${userId}] Studio members exist for the record, but the record is owned by a user!`);
|
|
244
|
+
return {
|
|
245
|
+
success: false,
|
|
246
|
+
errorCode: 'server_error',
|
|
247
|
+
errorMessage: 'A server error occurred.',
|
|
248
|
+
errorReason: 'server_error',
|
|
249
|
+
};
|
|
99
250
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
secretHash: passwordHash,
|
|
114
|
-
policy: policy !== null && policy !== void 0 ? policy : DEFAULT_RECORD_KEY_POLICY,
|
|
115
|
-
creatorId: userId,
|
|
251
|
+
if (record.ownerId !== userId) {
|
|
252
|
+
return {
|
|
253
|
+
success: false,
|
|
254
|
+
errorCode: 'unauthorized_to_create_record_key',
|
|
255
|
+
errorMessage: 'Another user has already created this record.',
|
|
256
|
+
errorReason: 'record_owned_by_different_user',
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
else if (record.studioId) {
|
|
261
|
+
let existingStudioMembers = yield this._store.listStudioAssignments(record.studioId, {
|
|
262
|
+
userId: userId,
|
|
263
|
+
role: 'admin',
|
|
116
264
|
});
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
265
|
+
if (existingStudioMembers.length <= 0) {
|
|
266
|
+
return {
|
|
267
|
+
success: false,
|
|
268
|
+
errorCode: 'unauthorized_to_create_record_key',
|
|
269
|
+
errorMessage: 'You are not authorized to create a record key for this record.',
|
|
270
|
+
errorReason: 'record_owned_by_different_user',
|
|
271
|
+
};
|
|
272
|
+
}
|
|
122
273
|
}
|
|
274
|
+
console.log(`[RecordsController] [action: recordKey.create recordName: ${name}, userId: ${userId}] Creating record key.`);
|
|
275
|
+
const passwordBytes = randomBytes(16);
|
|
276
|
+
const password = fromByteArray(passwordBytes); // convert to human-readable string
|
|
277
|
+
const salt = record.secretSalt;
|
|
278
|
+
const passwordHash = hashHighEntropyPasswordWithSalt(password, salt);
|
|
279
|
+
yield this._store.addRecordKey({
|
|
280
|
+
recordName: name,
|
|
281
|
+
secretHash: passwordHash,
|
|
282
|
+
policy: policy !== null && policy !== void 0 ? policy : DEFAULT_RECORD_KEY_POLICY,
|
|
283
|
+
creatorId: (_a = record.ownerId) !== null && _a !== void 0 ? _a : userId,
|
|
284
|
+
});
|
|
285
|
+
return {
|
|
286
|
+
success: true,
|
|
287
|
+
recordKey: formatV2RecordKey(name, password, policy),
|
|
288
|
+
recordName: name,
|
|
289
|
+
};
|
|
123
290
|
}
|
|
124
291
|
catch (err) {
|
|
125
|
-
console.error(err);
|
|
292
|
+
console.error('[RecordsController] [createPublicRecordKey] An error occurred while creating a public record key:', err);
|
|
126
293
|
return {
|
|
127
294
|
success: false,
|
|
128
295
|
errorCode: 'server_error',
|
|
129
|
-
errorMessage:
|
|
296
|
+
errorMessage: 'A server error occurred.',
|
|
130
297
|
errorReason: 'server_error',
|
|
131
298
|
};
|
|
132
299
|
}
|
|
@@ -234,11 +401,11 @@ export class RecordsController {
|
|
|
234
401
|
}
|
|
235
402
|
}
|
|
236
403
|
catch (err) {
|
|
237
|
-
console.error(err);
|
|
404
|
+
console.error('[RecordsController] [validatePublicRecordKey] An error occurred while creating a public record key:', err);
|
|
238
405
|
return {
|
|
239
406
|
success: false,
|
|
240
407
|
errorCode: 'server_error',
|
|
241
|
-
errorMessage:
|
|
408
|
+
errorMessage: 'A server error occurred.',
|
|
242
409
|
};
|
|
243
410
|
}
|
|
244
411
|
});
|
|
@@ -254,10 +421,17 @@ export class RecordsController {
|
|
|
254
421
|
const record = yield this._store.getRecordByName(name);
|
|
255
422
|
if (!record) {
|
|
256
423
|
if (userId && name === userId) {
|
|
424
|
+
const allowed = yield this._doesSubscriptionAllowToCreateRecord({
|
|
425
|
+
ownerId: userId,
|
|
426
|
+
});
|
|
427
|
+
if (allowed.success === false) {
|
|
428
|
+
return allowed;
|
|
429
|
+
}
|
|
257
430
|
console.log(`[RecordsController] [validateRecordName recordName: ${name}, userId: ${userId}] Creating record for user.`);
|
|
258
431
|
yield this._store.addRecord({
|
|
259
432
|
name,
|
|
260
433
|
ownerId: userId,
|
|
434
|
+
studioId: null,
|
|
261
435
|
secretHashes: [],
|
|
262
436
|
secretSalt: this._createSalt(),
|
|
263
437
|
});
|
|
@@ -265,6 +439,31 @@ export class RecordsController {
|
|
|
265
439
|
success: true,
|
|
266
440
|
recordName: name,
|
|
267
441
|
ownerId: userId,
|
|
442
|
+
studioId: null,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
let studioMembers = yield this._store.listStudioAssignments(name);
|
|
446
|
+
if (studioMembers.length > 0) {
|
|
447
|
+
const allowed = yield this._doesSubscriptionAllowToCreateRecord({
|
|
448
|
+
studioId: name,
|
|
449
|
+
});
|
|
450
|
+
if (allowed.success === false) {
|
|
451
|
+
return allowed;
|
|
452
|
+
}
|
|
453
|
+
console.log(`[RecordsController] [validateRecordName recordName: ${name}, userId: ${userId}, studioId: ${name}] Creating record for studio.`);
|
|
454
|
+
yield this._store.addRecord({
|
|
455
|
+
name,
|
|
456
|
+
ownerId: null,
|
|
457
|
+
studioId: name,
|
|
458
|
+
secretHashes: [],
|
|
459
|
+
secretSalt: this._createSalt(),
|
|
460
|
+
});
|
|
461
|
+
return {
|
|
462
|
+
success: true,
|
|
463
|
+
recordName: name,
|
|
464
|
+
ownerId: null,
|
|
465
|
+
studioId: name,
|
|
466
|
+
studioMembers,
|
|
268
467
|
};
|
|
269
468
|
}
|
|
270
469
|
return {
|
|
@@ -276,6 +475,12 @@ export class RecordsController {
|
|
|
276
475
|
else if (userId &&
|
|
277
476
|
record.name === userId &&
|
|
278
477
|
record.ownerId !== userId) {
|
|
478
|
+
const allowed = yield this._doesSubscriptionAllowToCreateRecord({
|
|
479
|
+
ownerId: userId,
|
|
480
|
+
});
|
|
481
|
+
if (allowed.success === false) {
|
|
482
|
+
return allowed;
|
|
483
|
+
}
|
|
279
484
|
// The user is not currently the owner of their own record.
|
|
280
485
|
// This is an issue that needs to be fixed because users should always own the record that has the same name as their ID.
|
|
281
486
|
console.log(`[RecordsController] [validateRecordName recordName: ${name}, userId: ${userId}] Fixing record owner to match actual owner.`);
|
|
@@ -285,24 +490,85 @@ export class RecordsController {
|
|
|
285
490
|
record.secretSalt = this._createSalt();
|
|
286
491
|
yield this._store.updateRecord(Object.assign({}, record));
|
|
287
492
|
}
|
|
493
|
+
let existingStudioMembers = yield this._store.listStudioAssignments(name);
|
|
494
|
+
if (existingStudioMembers.length > 0 &&
|
|
495
|
+
record.studioId !== name &&
|
|
496
|
+
record.ownerId !== null) {
|
|
497
|
+
const allowed = yield this._doesSubscriptionAllowToCreateRecord({
|
|
498
|
+
studioId: name,
|
|
499
|
+
});
|
|
500
|
+
if (allowed.success === false) {
|
|
501
|
+
return allowed;
|
|
502
|
+
}
|
|
503
|
+
console.log(`[RecordsController] [validateRecordName recordName: ${name}, userId: ${userId}, studioId: ${name}] Fixing record studio to match actual studio.`);
|
|
504
|
+
record.ownerId = null;
|
|
505
|
+
record.studioId = name;
|
|
506
|
+
record.secretHashes = [];
|
|
507
|
+
record.secretSalt = this._createSalt();
|
|
508
|
+
yield this._store.updateRecord(Object.assign({}, record));
|
|
509
|
+
}
|
|
510
|
+
let studioMembers = undefined;
|
|
511
|
+
if (existingStudioMembers.length > 0) {
|
|
512
|
+
studioMembers = existingStudioMembers;
|
|
513
|
+
}
|
|
514
|
+
else if (record.studioId) {
|
|
515
|
+
studioMembers = yield this._store.listStudioAssignments(record.studioId);
|
|
516
|
+
}
|
|
288
517
|
return {
|
|
289
518
|
success: true,
|
|
290
519
|
recordName: name,
|
|
291
520
|
ownerId: record.ownerId,
|
|
521
|
+
studioId: record.studioId,
|
|
522
|
+
studioMembers,
|
|
292
523
|
};
|
|
293
524
|
}
|
|
294
525
|
catch (err) {
|
|
295
|
-
console.error(err);
|
|
526
|
+
console.error('[RecordsController] [validateRecordName] An error occurred while creating a public record key:', err);
|
|
296
527
|
return {
|
|
297
528
|
success: false,
|
|
298
529
|
errorCode: 'server_error',
|
|
299
|
-
errorMessage:
|
|
530
|
+
errorMessage: 'A server error occurred.',
|
|
300
531
|
};
|
|
301
532
|
}
|
|
302
533
|
});
|
|
303
534
|
}
|
|
535
|
+
_doesSubscriptionAllowToCreateRecord(filter) {
|
|
536
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
537
|
+
const { features, metrics } = yield this._getSubscriptionFeatures(filter);
|
|
538
|
+
if (!features.records.allowed) {
|
|
539
|
+
return {
|
|
540
|
+
success: false,
|
|
541
|
+
errorCode: 'not_authorized',
|
|
542
|
+
errorMessage: 'Records are not allowed for this subscription.',
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
else if (features.records.maxRecords >= 0) {
|
|
546
|
+
if (features.records.maxRecords <= metrics.totalRecords + 1) {
|
|
547
|
+
return {
|
|
548
|
+
success: false,
|
|
549
|
+
errorCode: 'subscription_limit_reached',
|
|
550
|
+
errorMessage: 'This subscription has hit its record limit.',
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return {
|
|
555
|
+
success: true,
|
|
556
|
+
};
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
_getSubscriptionFeatures(filter) {
|
|
560
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
561
|
+
const metrics = yield this._metrics.getSubscriptionRecordMetrics(filter);
|
|
562
|
+
const config = yield this._config.getSubscriptionConfiguration();
|
|
563
|
+
return {
|
|
564
|
+
metrics,
|
|
565
|
+
config,
|
|
566
|
+
features: getSubscriptionFeatures(config, metrics.subscriptionStatus, metrics.subscriptionId, metrics.ownerId ? 'user' : 'studio'),
|
|
567
|
+
};
|
|
568
|
+
});
|
|
569
|
+
}
|
|
304
570
|
/**
|
|
305
|
-
* Gets the list of records that the user with the given ID
|
|
571
|
+
* Gets the list of records that the user with the given ID has access to.
|
|
306
572
|
* @param userId The ID of the user.
|
|
307
573
|
*/
|
|
308
574
|
listRecords(userId) {
|
|
@@ -331,6 +597,258 @@ export class RecordsController {
|
|
|
331
597
|
}
|
|
332
598
|
});
|
|
333
599
|
}
|
|
600
|
+
/**
|
|
601
|
+
* Gets the list of records in the given studio that the user with the given ID has access to.
|
|
602
|
+
* @param studioId The ID of the studio.
|
|
603
|
+
* @param userId The ID of the user that is currently logged in.
|
|
604
|
+
*/
|
|
605
|
+
listStudioRecords(studioId, userId) {
|
|
606
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
607
|
+
try {
|
|
608
|
+
if (!this._store.listRecordsByStudioIdAndUserId) {
|
|
609
|
+
return {
|
|
610
|
+
success: false,
|
|
611
|
+
errorCode: 'not_supported',
|
|
612
|
+
errorMessage: 'This operation is not supported.',
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
const records = yield this._store.listRecordsByStudioIdAndUserId(studioId, userId);
|
|
616
|
+
return {
|
|
617
|
+
success: true,
|
|
618
|
+
records: records,
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
catch (err) {
|
|
622
|
+
console.log('[RecordsController] [listStudioRecords] Error listing records: ', err);
|
|
623
|
+
return {
|
|
624
|
+
success: false,
|
|
625
|
+
errorCode: 'server_error',
|
|
626
|
+
errorMessage: 'A server error occurred.',
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Attempts to create a new studio. That is, an entity that can be used to group records.
|
|
633
|
+
* @param studioName The name of the studio.
|
|
634
|
+
* @param userId The ID of the user that is creating the studio.
|
|
635
|
+
*/
|
|
636
|
+
createStudio(studioName, userId) {
|
|
637
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
638
|
+
try {
|
|
639
|
+
const studioId = uuid();
|
|
640
|
+
yield this._store.createStudioForUser({
|
|
641
|
+
id: studioId,
|
|
642
|
+
displayName: studioName,
|
|
643
|
+
}, userId);
|
|
644
|
+
return {
|
|
645
|
+
success: true,
|
|
646
|
+
studioId: studioId,
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
catch (err) {
|
|
650
|
+
console.error('[RecordsController] [createStudio] An error occurred while creating a studio:', err);
|
|
651
|
+
return {
|
|
652
|
+
success: false,
|
|
653
|
+
errorCode: 'server_error',
|
|
654
|
+
errorMessage: 'A server error occurred.',
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Gets the list of studios that the user with the given ID has access to.
|
|
661
|
+
* @param userId The ID of the user.
|
|
662
|
+
*/
|
|
663
|
+
listStudios(userId) {
|
|
664
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
665
|
+
try {
|
|
666
|
+
const studios = yield this._store.listStudiosForUser(userId);
|
|
667
|
+
return {
|
|
668
|
+
success: true,
|
|
669
|
+
studios: studios.map((s) => ({
|
|
670
|
+
studioId: s.studioId,
|
|
671
|
+
displayName: s.displayName,
|
|
672
|
+
role: s.role,
|
|
673
|
+
isPrimaryContact: s.isPrimaryContact,
|
|
674
|
+
})),
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
catch (err) {
|
|
678
|
+
console.error('[RecordsController] [listStudios] An error occurred while listing studios:', err);
|
|
679
|
+
return {
|
|
680
|
+
success: false,
|
|
681
|
+
errorCode: 'server_error',
|
|
682
|
+
errorMessage: 'A server error occurred.',
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Gets the list of members in a studio.
|
|
689
|
+
* @param studioId The ID of the studio.
|
|
690
|
+
* @param userId The ID of the user that is currently logged in.
|
|
691
|
+
*/
|
|
692
|
+
listStudioMembers(studioId, userId) {
|
|
693
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
694
|
+
try {
|
|
695
|
+
const members = yield this._store.listStudioAssignments(studioId);
|
|
696
|
+
const userAssignment = members.find((m) => m.userId === userId);
|
|
697
|
+
if (!userAssignment) {
|
|
698
|
+
return {
|
|
699
|
+
success: false,
|
|
700
|
+
errorCode: 'not_authorized',
|
|
701
|
+
errorMessage: 'You are not authorized to access this studio.',
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
if (userAssignment.role === 'admin') {
|
|
705
|
+
return {
|
|
706
|
+
success: true,
|
|
707
|
+
members: members.map((m) => ({
|
|
708
|
+
studioId: m.studioId,
|
|
709
|
+
userId: m.userId,
|
|
710
|
+
isPrimaryContact: m.isPrimaryContact,
|
|
711
|
+
role: m.role,
|
|
712
|
+
user: {
|
|
713
|
+
id: m.user.id,
|
|
714
|
+
name: m.user.name,
|
|
715
|
+
email: m.user.email,
|
|
716
|
+
phoneNumber: m.user.phoneNumber,
|
|
717
|
+
},
|
|
718
|
+
})),
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
return {
|
|
722
|
+
success: true,
|
|
723
|
+
members: members.map((m) => ({
|
|
724
|
+
studioId: m.studioId,
|
|
725
|
+
userId: m.userId,
|
|
726
|
+
isPrimaryContact: m.isPrimaryContact,
|
|
727
|
+
role: m.role,
|
|
728
|
+
user: {
|
|
729
|
+
id: m.user.id,
|
|
730
|
+
name: m.user.name,
|
|
731
|
+
},
|
|
732
|
+
})),
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
catch (err) {
|
|
736
|
+
console.error('[RecordsController] [listStudioMembers] An error occurred while listing studio members:', err);
|
|
737
|
+
return {
|
|
738
|
+
success: false,
|
|
739
|
+
errorCode: 'server_error',
|
|
740
|
+
errorMessage: 'A server error occurred.',
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
addStudioMember(request) {
|
|
746
|
+
var _a;
|
|
747
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
748
|
+
try {
|
|
749
|
+
if (!request.userId) {
|
|
750
|
+
return {
|
|
751
|
+
success: false,
|
|
752
|
+
errorCode: 'not_logged_in',
|
|
753
|
+
errorMessage: 'You must be logged in to add a studio member.',
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
const list = yield this._store.listStudioAssignments(request.studioId, {
|
|
757
|
+
userId: request.userId,
|
|
758
|
+
role: 'admin',
|
|
759
|
+
});
|
|
760
|
+
if (list.length <= 0) {
|
|
761
|
+
return {
|
|
762
|
+
success: false,
|
|
763
|
+
errorCode: 'not_authorized',
|
|
764
|
+
errorMessage: 'You are not authorized to perform this operation.',
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
let addedUserId = null;
|
|
768
|
+
if (request.addedUserId) {
|
|
769
|
+
addedUserId = request.addedUserId;
|
|
770
|
+
}
|
|
771
|
+
else if (request.addedEmail || request.addedPhoneNumber) {
|
|
772
|
+
const addedUser = yield this._auth.findUserByAddress((_a = request.addedEmail) !== null && _a !== void 0 ? _a : request.addedPhoneNumber, request.addedEmail ? 'email' : 'phone');
|
|
773
|
+
if (!addedUser) {
|
|
774
|
+
return {
|
|
775
|
+
success: false,
|
|
776
|
+
errorCode: 'user_not_found',
|
|
777
|
+
errorMessage: 'The user was not able to be found.',
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
addedUserId = addedUser.id;
|
|
781
|
+
}
|
|
782
|
+
else {
|
|
783
|
+
return {
|
|
784
|
+
success: false,
|
|
785
|
+
errorCode: 'unacceptable_request',
|
|
786
|
+
errorMessage: 'You must provide an email, phone number, or user ID to add a studio member.',
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
yield this._store.addStudioAssignment({
|
|
790
|
+
studioId: request.studioId,
|
|
791
|
+
userId: addedUserId,
|
|
792
|
+
isPrimaryContact: false,
|
|
793
|
+
role: request.role,
|
|
794
|
+
});
|
|
795
|
+
return {
|
|
796
|
+
success: true,
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
catch (err) {
|
|
800
|
+
console.error('[RecordsController] [addStudioMember] An error occurred while adding a studio member:', err);
|
|
801
|
+
return {
|
|
802
|
+
success: false,
|
|
803
|
+
errorCode: 'server_error',
|
|
804
|
+
errorMessage: 'A server error occurred.',
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
removeStudioMember(request) {
|
|
810
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
811
|
+
try {
|
|
812
|
+
if (!request.userId) {
|
|
813
|
+
return {
|
|
814
|
+
success: false,
|
|
815
|
+
errorCode: 'not_logged_in',
|
|
816
|
+
errorMessage: 'You must be logged in to remove a studio member.',
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
if (request.userId === request.removedUserId) {
|
|
820
|
+
return {
|
|
821
|
+
success: false,
|
|
822
|
+
errorCode: 'not_authorized',
|
|
823
|
+
errorMessage: 'You are not authorized to perform this operation.',
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
const list = yield this._store.listStudioAssignments(request.studioId, {
|
|
827
|
+
userId: request.userId,
|
|
828
|
+
role: 'admin',
|
|
829
|
+
});
|
|
830
|
+
if (list.length <= 0) {
|
|
831
|
+
return {
|
|
832
|
+
success: false,
|
|
833
|
+
errorCode: 'not_authorized',
|
|
834
|
+
errorMessage: 'You are not authorized to perform this operation.',
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
yield this._store.removeStudioAssignment(request.studioId, request.removedUserId);
|
|
838
|
+
return {
|
|
839
|
+
success: true,
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
catch (err) {
|
|
843
|
+
console.error('[RecordsController] [removeStudioMember] An error occurred while removing a studio member:', err);
|
|
844
|
+
return {
|
|
845
|
+
success: false,
|
|
846
|
+
errorCode: 'server_error',
|
|
847
|
+
errorMessage: 'A server error occurred.',
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
});
|
|
851
|
+
}
|
|
334
852
|
_createSalt() {
|
|
335
853
|
return fromByteArray(randomBytes(16));
|
|
336
854
|
}
|