streamby-core 0.33.0 → 0.34.0
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/CHANGELOG.md +67 -0
- package/LICENSE +661 -0
- package/README.md +53 -2
- package/dist/middleware/createRouter.js +15 -2
- package/dist/middleware/routes/ai.js +3 -3
- package/dist/middleware/routes/connection.js +3 -12
- package/dist/middleware/routes/dbConnection.js +7 -26
- package/dist/middleware/routes/distribution.js +12 -5
- package/dist/middleware/routes/export.js +5 -4
- package/dist/middleware/routes/media.js +23 -10
- package/dist/middleware/routes/member.js +3 -12
- package/dist/middleware/routes/pipeline.js +3 -3
- package/dist/middleware/routes/production.js +9 -9
- package/dist/middleware/routes/project.js +8 -25
- package/dist/middleware/routes/renderFarm.js +13 -6
- package/dist/middleware/routes/review.js +4 -4
- package/dist/middleware/routes/rights.js +1 -1
- package/dist/middleware/routes/storage.js +21 -15
- package/dist/middleware/routes/storageConnection.js +2 -8
- package/dist/middleware/routes/workflow.js +1 -1
- package/dist/services/auth.d.ts +1 -2
- package/dist/services/auth.js +9 -11
- package/dist/services/connectionResolver.js +2 -2
- package/dist/services/executor.d.ts +4 -1
- package/dist/services/executor.js +29 -11
- package/dist/services/reencryptCredentials.d.ts +5 -0
- package/dist/services/reencryptCredentials.js +74 -0
- package/dist/services/userIntegration.d.ts +2 -2
- package/dist/services/userIntegration.js +7 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/utils/auth.d.ts +15 -1
- package/dist/utils/auth.js +21 -6
- package/dist/utils/encryption.d.ts +6 -1
- package/dist/utils/encryption.js +80 -27
- package/dist/utils/serviceEndpoints.d.ts +2 -0
- package/dist/utils/serviceEndpoints.js +26 -0
- package/package.json +4 -3
- package/dist/middleware/routes/apiConnection.d.ts +0 -3
- package/dist/middleware/routes/apiConnection.js +0 -100
- package/dist/middleware/routes/credential.d.ts +0 -3
- package/dist/middleware/routes/credential.js +0 -80
- package/dist/services/apiConnection.d.ts +0 -18
- package/dist/services/apiConnection.js +0 -65
- package/dist/services/credential.d.ts +0 -8
- package/dist/services/credential.js +0 -70
- package/dist/services/pipeline.d.ts +0 -2
- package/dist/services/pipeline.js +0 -292
|
@@ -72,9 +72,6 @@ function projectRouter(config) {
|
|
|
72
72
|
if (req.subscription === 'freemium') {
|
|
73
73
|
return res.status(403).json({ error: 'Freemium users cannot create projects' });
|
|
74
74
|
}
|
|
75
|
-
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
76
|
-
return res.status(403).json({ message: 'Permission denied' });
|
|
77
|
-
}
|
|
78
75
|
const { name, description, image, allowedOrigin, public: isPublic = true, category, integrationIds } = req.body;
|
|
79
76
|
if (isPublic === false && req.subscription === 'freemium') {
|
|
80
77
|
return res.status(403).json({ error: 'Private projects require a subscriber plan' });
|
|
@@ -208,9 +205,6 @@ function projectRouter(config) {
|
|
|
208
205
|
router.patch('/projects/:id', async (req, res) => {
|
|
209
206
|
try {
|
|
210
207
|
const auth = req.auth;
|
|
211
|
-
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
212
|
-
return res.status(403).json({ message: 'Permission denied' });
|
|
213
|
-
}
|
|
214
208
|
const projectId = req.params.id;
|
|
215
209
|
const updates = req.body;
|
|
216
210
|
const project = await Project.findOne({ _id: projectId });
|
|
@@ -220,7 +214,7 @@ function projectRouter(config) {
|
|
|
220
214
|
if (!updates || typeof updates !== 'object') {
|
|
221
215
|
return res.status(400).json({ message: 'Missing updates payload' });
|
|
222
216
|
}
|
|
223
|
-
if (!(0, auth_1.
|
|
217
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId)) {
|
|
224
218
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
225
219
|
}
|
|
226
220
|
const updated = await Project.update({ _id: projectId }, updates);
|
|
@@ -250,8 +244,9 @@ function projectRouter(config) {
|
|
|
250
244
|
if (!project) {
|
|
251
245
|
return res.status(404).json({ message: 'Project not found' });
|
|
252
246
|
}
|
|
253
|
-
|
|
254
|
-
|
|
247
|
+
// Only an admin of the project deletes it.
|
|
248
|
+
if (!(0, auth_1.isProjectAdmin)(project, auth.userId)) {
|
|
249
|
+
return res.status(403).json({ message: 'Only a project admin can delete it' });
|
|
255
250
|
}
|
|
256
251
|
await Project.useDbType(project.dbType).delete({ _id: projectId });
|
|
257
252
|
const allProjects = await Project.find({});
|
|
@@ -348,9 +343,6 @@ function projectRouter(config) {
|
|
|
348
343
|
router.post('/projects/:id/image', async (req, res) => {
|
|
349
344
|
try {
|
|
350
345
|
const auth = req.auth;
|
|
351
|
-
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
352
|
-
return res.status(403).json({ message: 'Permission denied' });
|
|
353
|
-
}
|
|
354
346
|
const projectId = req.params.id;
|
|
355
347
|
const { imageUrl } = req.body;
|
|
356
348
|
if (!projectId || !imageUrl) {
|
|
@@ -360,7 +352,7 @@ function projectRouter(config) {
|
|
|
360
352
|
if (!project) {
|
|
361
353
|
return res.status(404).json({ message: 'Project not found' });
|
|
362
354
|
}
|
|
363
|
-
if (!(0, auth_1.
|
|
355
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId)) {
|
|
364
356
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
365
357
|
}
|
|
366
358
|
const updatedProject = await Project.update({ _id: projectId }, { image: imageUrl });
|
|
@@ -376,16 +368,13 @@ function projectRouter(config) {
|
|
|
376
368
|
router.patch('/projects/:id/origins', async (req, res) => {
|
|
377
369
|
try {
|
|
378
370
|
const auth = req.auth;
|
|
379
|
-
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
380
|
-
return res.status(403).json({ message: 'Permission denied' });
|
|
381
|
-
}
|
|
382
371
|
const projectId = req.params.id;
|
|
383
372
|
const { origins } = req.body;
|
|
384
373
|
const project = await Project.findOne({ _id: projectId });
|
|
385
374
|
if (!project) {
|
|
386
375
|
return res.status(404).json({ message: 'Project not found' });
|
|
387
376
|
}
|
|
388
|
-
if (!(0, auth_1.
|
|
377
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId)) {
|
|
389
378
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
390
379
|
}
|
|
391
380
|
const updated = await Project.update({ _id: projectId }, { allowedOrigin: origins });
|
|
@@ -402,9 +391,6 @@ function projectRouter(config) {
|
|
|
402
391
|
router.post('/projects/:id/integrations', async (req, res) => {
|
|
403
392
|
try {
|
|
404
393
|
const auth = req.auth;
|
|
405
|
-
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
406
|
-
return res.status(403).json({ message: 'Permission denied' });
|
|
407
|
-
}
|
|
408
394
|
const projectId = req.params.id;
|
|
409
395
|
const { integrationId } = req.body;
|
|
410
396
|
if (!integrationId)
|
|
@@ -412,7 +398,7 @@ function projectRouter(config) {
|
|
|
412
398
|
const project = await Project.findOne({ _id: projectId });
|
|
413
399
|
if (!project)
|
|
414
400
|
return res.status(404).json({ message: 'Project not found' });
|
|
415
|
-
if (!(0, auth_1.
|
|
401
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
416
402
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
417
403
|
const alreadyConnected = [...(project.dbConnections ?? []), ...(project.storageConnections ?? []), ...(project.integrationConnections ?? [])]
|
|
418
404
|
.some((c) => c.integrationId === integrationId);
|
|
@@ -438,15 +424,12 @@ function projectRouter(config) {
|
|
|
438
424
|
router.delete('/projects/:id/integrations/:integrationId', async (req, res) => {
|
|
439
425
|
try {
|
|
440
426
|
const auth = req.auth;
|
|
441
|
-
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
442
|
-
return res.status(403).json({ message: 'Permission denied' });
|
|
443
|
-
}
|
|
444
427
|
const projectId = req.params.id;
|
|
445
428
|
const { integrationId } = req.params;
|
|
446
429
|
const project = await Project.findOne({ _id: projectId });
|
|
447
430
|
if (!project)
|
|
448
431
|
return res.status(404).json({ message: 'Project not found' });
|
|
449
|
-
if (!(0, auth_1.
|
|
432
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
450
433
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
451
434
|
const dbConn = (project.dbConnections ?? []).find((c) => c.integrationId === integrationId);
|
|
452
435
|
const storageConn = (project.storageConnections ?? []).find((c) => c.integrationId === integrationId);
|
|
@@ -11,6 +11,7 @@ const jobQueue_1 = require("../../services/jobQueue");
|
|
|
11
11
|
const vfxProcessor_1 = require("../../services/vfxProcessor");
|
|
12
12
|
const sanitize_1 = require("../../utils/sanitize");
|
|
13
13
|
const crypto_1 = __importDefault(require("crypto"));
|
|
14
|
+
const encryption_1 = require("../../utils/encryption");
|
|
14
15
|
function renderFarmRouter(_config) {
|
|
15
16
|
const router = (0, express_1.Router)();
|
|
16
17
|
const Project = (0, manager_1.getModel)('projects');
|
|
@@ -34,14 +35,20 @@ function renderFarmRouter(_config) {
|
|
|
34
35
|
try {
|
|
35
36
|
const auth = req.auth;
|
|
36
37
|
const { projectId } = req.params;
|
|
37
|
-
const { name, provider, apiUrl, encryptedCredential, description } = req.body;
|
|
38
|
+
const { name, provider, apiUrl, credential: credentialValue, encryptedCredential: legacyCredential, description } = req.body;
|
|
39
|
+
// The credential, as given (`encryptedCredential` was its old name: it was always the
|
|
40
|
+
// value itself), stored encrypted.
|
|
41
|
+
const credential = credentialValue ?? legacyCredential;
|
|
42
|
+
if (credential && !(0, encryption_1.isEncryptionKeySet)())
|
|
43
|
+
return res.status(500).json({ message: 'Encryption key is not set' });
|
|
44
|
+
const encryptedCredential = credential ? (0, encryption_1.encrypt)(String(credential)) : undefined;
|
|
38
45
|
if (!name || !provider || !apiUrl) {
|
|
39
46
|
return res.status(400).json({ message: 'name, provider and apiUrl are required' });
|
|
40
47
|
}
|
|
41
48
|
const project = await Project.findOne({ _id: projectId });
|
|
42
49
|
if (!project)
|
|
43
50
|
return res.status(404).json({ message: 'Project not found' });
|
|
44
|
-
if (!(0, auth_1.
|
|
51
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
45
52
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
46
53
|
const connection = {
|
|
47
54
|
id: crypto_1.default.randomUUID(),
|
|
@@ -67,7 +74,7 @@ function renderFarmRouter(_config) {
|
|
|
67
74
|
const project = await Project.findOne({ _id: projectId });
|
|
68
75
|
if (!project)
|
|
69
76
|
return res.status(404).json({ message: 'Project not found' });
|
|
70
|
-
if (!(0, auth_1.
|
|
77
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
71
78
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
72
79
|
await Project.update({ _id: projectId }, { $pull: { renderFarmConnections: { id: connectionId } } });
|
|
73
80
|
res.json({ message: 'Connection deleted' });
|
|
@@ -87,7 +94,7 @@ function renderFarmRouter(_config) {
|
|
|
87
94
|
const project = await Project.findOne({ _id: projectId });
|
|
88
95
|
if (!project)
|
|
89
96
|
return res.status(404).json({ message: 'Project not found' });
|
|
90
|
-
if (!(0, auth_1.
|
|
97
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
91
98
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
92
99
|
const job = (0, jobQueue_1.createJob)('render', auth.userId, projectId, { fileId, renderer, renderFarmConnectionId, frameRange, resolution, samples, outputFormat });
|
|
93
100
|
setImmediate(() => (0, vfxProcessor_1.runRenderJob)(job.jobId, fileId, projectId, { renderer, renderFarmConnectionId, frameRange, resolution, samples, outputFormat }));
|
|
@@ -107,7 +114,7 @@ function renderFarmRouter(_config) {
|
|
|
107
114
|
const project = await Project.findOne({ _id: projectId });
|
|
108
115
|
if (!project)
|
|
109
116
|
return res.status(404).json({ message: 'Project not found' });
|
|
110
|
-
if (!(0, auth_1.
|
|
117
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
111
118
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
112
119
|
const job = (0, jobQueue_1.createJob)('format-convert', auth.userId, projectId, { fileId, inputFormat, outputFormat, applyTransforms, embedTextures });
|
|
113
120
|
setImmediate(() => (0, vfxProcessor_1.runFormatConvertJob)(job.jobId, fileId, projectId, { inputFormat, outputFormat, applyTransforms, embedTextures }));
|
|
@@ -127,7 +134,7 @@ function renderFarmRouter(_config) {
|
|
|
127
134
|
const project = await Project.findOne({ _id: projectId });
|
|
128
135
|
if (!project)
|
|
129
136
|
return res.status(404).json({ message: 'Project not found' });
|
|
130
|
-
if (!(0, auth_1.
|
|
137
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
131
138
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
132
139
|
const job = (0, jobQueue_1.createJob)('lod', auth.userId, projectId, { fileId, levels, reductionRatios, algorithm, outputFormat });
|
|
133
140
|
setImmediate(() => (0, vfxProcessor_1.runLodJob)(job.jobId, fileId, projectId, { levels, reductionRatios, algorithm, outputFormat }));
|
|
@@ -60,7 +60,7 @@ function reviewRouter(_config) {
|
|
|
60
60
|
const project = await Project.findOne({ _id: projectId });
|
|
61
61
|
if (!project)
|
|
62
62
|
return res.status(404).json({ message: 'Project not found' });
|
|
63
|
-
if (!(0, auth_1.
|
|
63
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
64
64
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
65
65
|
const db = getDb();
|
|
66
66
|
if (!db)
|
|
@@ -120,7 +120,7 @@ function reviewRouter(_config) {
|
|
|
120
120
|
const project = await Project.findOne({ _id: projectId });
|
|
121
121
|
if (!project)
|
|
122
122
|
return res.status(404).json({ message: 'Project not found' });
|
|
123
|
-
if (!(0, auth_1.
|
|
123
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
124
124
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
125
125
|
const db = getDb();
|
|
126
126
|
if (!db)
|
|
@@ -190,7 +190,7 @@ function reviewRouter(_config) {
|
|
|
190
190
|
const project = await Project.findOne({ _id: projectId });
|
|
191
191
|
if (!project)
|
|
192
192
|
return res.status(404).json({ message: 'Project not found' });
|
|
193
|
-
if (!(0, auth_1.
|
|
193
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
194
194
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
195
195
|
const db = getDb();
|
|
196
196
|
if (!db)
|
|
@@ -224,7 +224,7 @@ function reviewRouter(_config) {
|
|
|
224
224
|
const project = await Project.findOne({ _id: projectId });
|
|
225
225
|
if (!project)
|
|
226
226
|
return res.status(404).json({ message: 'Project not found' });
|
|
227
|
-
if (!(0, auth_1.
|
|
227
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
228
228
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
229
229
|
const db = getDb();
|
|
230
230
|
if (!db)
|
|
@@ -42,7 +42,7 @@ function rightsRouter(_config) {
|
|
|
42
42
|
const project = await Project.findOne({ _id: projectId });
|
|
43
43
|
if (!project)
|
|
44
44
|
return res.status(404).json({ message: 'Project not found' });
|
|
45
|
-
if (!(0, auth_1.
|
|
45
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
46
46
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
47
47
|
const db = getDb();
|
|
48
48
|
if (!db)
|
|
@@ -71,11 +71,14 @@ function storageRouter(config) {
|
|
|
71
71
|
router.get('/upload-project-image-url/:id', async (req, res) => {
|
|
72
72
|
try {
|
|
73
73
|
const auth = req.auth;
|
|
74
|
-
if (auth.role !== 'admin' && auth.role !== 'editor')
|
|
75
|
-
return res.status(403).json({ message: 'Permission denied' });
|
|
76
74
|
const projectId = req.params.id;
|
|
77
75
|
if (!projectId)
|
|
78
76
|
return res.status(400).json({ message: 'Missing projectId' });
|
|
77
|
+
const project = await Project.findOne({ _id: projectId });
|
|
78
|
+
if (!project)
|
|
79
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
80
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
81
|
+
return res.status(403).json({ message: 'Permission denied' });
|
|
79
82
|
const response = await (0, presign_1.getPresignedProjectImageUrl)(adapter, projectId);
|
|
80
83
|
res.status(200).json({ ...response, message: 'Presigned URL generated successfully' });
|
|
81
84
|
}
|
|
@@ -86,11 +89,14 @@ function storageRouter(config) {
|
|
|
86
89
|
router.delete('/delete-project-image-url/:id', async (req, res) => {
|
|
87
90
|
try {
|
|
88
91
|
const auth = req.auth;
|
|
89
|
-
if (auth.role !== 'admin' && auth.role !== 'editor')
|
|
90
|
-
return res.status(403).json({ message: 'Permission denied' });
|
|
91
92
|
const projectId = req.params.id;
|
|
92
93
|
if (!projectId)
|
|
93
94
|
return res.status(400).json({ message: 'Missing projectId' });
|
|
95
|
+
const project = await Project.findOne({ _id: projectId });
|
|
96
|
+
if (!project)
|
|
97
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
98
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
99
|
+
return res.status(403).json({ message: 'Permission denied' });
|
|
94
100
|
await (0, file_1.deleteProjectImage)(adapter, projectId);
|
|
95
101
|
const updated = await Project.update({ _id: projectId }, { image: '' });
|
|
96
102
|
res.status(201).json({ ...updated, message: 'Image deleted successfully' });
|
|
@@ -119,7 +125,7 @@ function storageRouter(config) {
|
|
|
119
125
|
const project = await Project.findOne({ _id: projectId });
|
|
120
126
|
if (!project)
|
|
121
127
|
return res.status(404).json({ message: 'Project not found' });
|
|
122
|
-
if (!(0, auth_1.
|
|
128
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
123
129
|
return res.status(403).json({ message: 'Unauthorized access' });
|
|
124
130
|
if (!adapter.getPresignedUploadUrl) {
|
|
125
131
|
return res.status(501).json({ message: 'Storage adapter does not support presigned uploads' });
|
|
@@ -192,7 +198,7 @@ function storageRouter(config) {
|
|
|
192
198
|
const project = await Project.findOne({ _id: projectId });
|
|
193
199
|
if (!project)
|
|
194
200
|
return res.status(404).json({ message: 'Project not found' });
|
|
195
|
-
if (!(0, auth_1.
|
|
201
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
196
202
|
return res.status(403).json({ message: 'Unauthorized access' });
|
|
197
203
|
const collection = getRawFilesCollection();
|
|
198
204
|
if (!collection)
|
|
@@ -213,7 +219,7 @@ function storageRouter(config) {
|
|
|
213
219
|
const project = await Project.findOne({ _id: projectId });
|
|
214
220
|
if (!project)
|
|
215
221
|
return res.status(404).json({ message: 'Project not found' });
|
|
216
|
-
if (!(0, auth_1.
|
|
222
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
217
223
|
return res.status(403).json({ message: 'Unauthorized access' });
|
|
218
224
|
const collection = getRawFilesCollection();
|
|
219
225
|
if (!collection)
|
|
@@ -291,7 +297,7 @@ function storageRouter(config) {
|
|
|
291
297
|
const project = await Project.findOne({ _id: projectId });
|
|
292
298
|
if (!project)
|
|
293
299
|
return res.status(404).json({ message: 'Project not found' });
|
|
294
|
-
if (!(0, auth_1.
|
|
300
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
295
301
|
return res.status(403).json({ message: 'Unauthorized access' });
|
|
296
302
|
const connAdapter = await resolveConnAdapter(connId, project, auth);
|
|
297
303
|
if ('error' in connAdapter)
|
|
@@ -419,7 +425,7 @@ function storageRouter(config) {
|
|
|
419
425
|
const project = await Project.findOne({ _id: projectId });
|
|
420
426
|
if (!project)
|
|
421
427
|
return res.status(404).json({ message: 'Project not found' });
|
|
422
|
-
if (!(0, auth_1.
|
|
428
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
423
429
|
return res.status(403).json({ message: 'Unauthorized access' });
|
|
424
430
|
const collection = getRawFilesCollection();
|
|
425
431
|
if (!collection)
|
|
@@ -440,7 +446,7 @@ function storageRouter(config) {
|
|
|
440
446
|
const project = await Project.findOne({ _id: projectId });
|
|
441
447
|
if (!project)
|
|
442
448
|
return res.status(404).json({ message: 'Project not found' });
|
|
443
|
-
if (!(0, auth_1.
|
|
449
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
444
450
|
return res.status(403).json({ message: 'Unauthorized access' });
|
|
445
451
|
const connAdapter = await resolveConnAdapter(connId, project, auth);
|
|
446
452
|
if ('error' in connAdapter)
|
|
@@ -539,7 +545,7 @@ function storageRouter(config) {
|
|
|
539
545
|
const project = await Project.findOne({ _id: projectId });
|
|
540
546
|
if (!project)
|
|
541
547
|
return res.status(404).json({ message: 'Project not found' });
|
|
542
|
-
if (!(0, auth_1.
|
|
548
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
543
549
|
return res.status(403).json({ message: 'Unauthorized access' });
|
|
544
550
|
const conn = findStorageConn(project, connId);
|
|
545
551
|
const isBuiltin = conn?.source === 'builtin';
|
|
@@ -590,7 +596,7 @@ function storageRouter(config) {
|
|
|
590
596
|
const project = await Project.findOne({ _id: projectId });
|
|
591
597
|
if (!project)
|
|
592
598
|
return res.status(404).json({ message: 'Project not found' });
|
|
593
|
-
if (!(0, auth_1.
|
|
599
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
594
600
|
return res.status(403).json({ message: 'Unauthorized access' });
|
|
595
601
|
const collection = getRawFoldersCollection();
|
|
596
602
|
if (!collection)
|
|
@@ -624,7 +630,7 @@ function storageRouter(config) {
|
|
|
624
630
|
const project = await Project.findOne({ _id: projectId });
|
|
625
631
|
if (!project)
|
|
626
632
|
return res.status(404).json({ message: 'Project not found' });
|
|
627
|
-
if (!(0, auth_1.
|
|
633
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
628
634
|
return res.status(403).json({ message: 'Unauthorized access' });
|
|
629
635
|
const collection = getRawFoldersCollection();
|
|
630
636
|
if (!collection)
|
|
@@ -654,7 +660,7 @@ function storageRouter(config) {
|
|
|
654
660
|
const project = await Project.findOne({ _id: projectId });
|
|
655
661
|
if (!project)
|
|
656
662
|
return res.status(404).json({ message: 'Project not found' });
|
|
657
|
-
if (!(0, auth_1.
|
|
663
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
658
664
|
return res.status(403).json({ message: 'Unauthorized access' });
|
|
659
665
|
const foldersCollection = getRawFoldersCollection();
|
|
660
666
|
if (!foldersCollection)
|
|
@@ -682,7 +688,7 @@ function storageRouter(config) {
|
|
|
682
688
|
const project = await Project.findOne({ _id: projectId });
|
|
683
689
|
if (!project)
|
|
684
690
|
return res.status(404).json({ message: 'Project not found' });
|
|
685
|
-
if (!(0, auth_1.
|
|
691
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
686
692
|
return res.status(403).json({ message: 'Unauthorized access' });
|
|
687
693
|
if (folderId != null) {
|
|
688
694
|
const foldersCollection = getRawFoldersCollection();
|
|
@@ -40,9 +40,6 @@ function storageConnectionRouter(config) {
|
|
|
40
40
|
router.post('/projects/:id/connections/storage', async (req, res) => {
|
|
41
41
|
try {
|
|
42
42
|
const auth = req.auth;
|
|
43
|
-
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
44
|
-
return res.status(403).json({ message: 'Permission denied' });
|
|
45
|
-
}
|
|
46
43
|
const { name, type, credential, integrationId, description } = req.body;
|
|
47
44
|
if (!name || !type) {
|
|
48
45
|
return res.status(400).json({ message: 'name and type are required' });
|
|
@@ -59,7 +56,7 @@ function storageConnectionRouter(config) {
|
|
|
59
56
|
const project = await Project.findOne({ _id: req.params.id });
|
|
60
57
|
if (!project)
|
|
61
58
|
return res.status(404).json({ message: 'Project not found' });
|
|
62
|
-
if (!(0, auth_1.
|
|
59
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
63
60
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
64
61
|
let encryptedCredential;
|
|
65
62
|
if (integrationId) {
|
|
@@ -98,13 +95,10 @@ function storageConnectionRouter(config) {
|
|
|
98
95
|
try {
|
|
99
96
|
const { connId } = req.params;
|
|
100
97
|
const auth = req.auth;
|
|
101
|
-
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
102
|
-
return res.status(403).json({ message: 'Permission denied' });
|
|
103
|
-
}
|
|
104
98
|
const project = await Project.findOne({ _id: req.params.id });
|
|
105
99
|
if (!project)
|
|
106
100
|
return res.status(404).json({ message: 'Project not found' });
|
|
107
|
-
if (!(0, auth_1.
|
|
101
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
108
102
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
109
103
|
const conn = project.storageConnections?.find((c) => c.id === connId);
|
|
110
104
|
if (!conn)
|
|
@@ -38,7 +38,7 @@ function workflowRouter(config) {
|
|
|
38
38
|
return res.status(401).json({ message: 'Unauthorized' });
|
|
39
39
|
const projectId = req.params.id;
|
|
40
40
|
const project = await Project.findOne({ _id: projectId });
|
|
41
|
-
if (!project || !(0, auth_1.
|
|
41
|
+
if (!project || !(0, auth_1.canEditProject)(project, auth.userId)) {
|
|
42
42
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
43
43
|
}
|
|
44
44
|
const now = new Date();
|
package/dist/services/auth.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { Auth } from '../types';
|
|
2
1
|
import { Request, Response, NextFunction } from 'express';
|
|
3
2
|
import { StreamByConfig } from '../types';
|
|
3
|
+
export declare const isPublicRoute: (method: string, path: string) => boolean;
|
|
4
4
|
export declare const authenticate: (config: StreamByConfig) => (req: Request, res: Response, next: NextFunction) => Promise<void | Response<any, Record<string, any>>>;
|
|
5
|
-
export declare function checkRole(auth: Auth, required: 'viewer' | 'editor' | 'admin'): void;
|
package/dist/services/auth.js
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.authenticate = void 0;
|
|
4
|
-
|
|
3
|
+
exports.authenticate = exports.isPublicRoute = void 0;
|
|
4
|
+
// The one route open without a session: a project's public export, read
|
|
5
|
+
// (GET /:projectId/export/:exportName, see routes/export). Exactly that path -- not any path
|
|
6
|
+
// that has an "export" segment somewhere.
|
|
7
|
+
const PUBLIC_EXPORT = /^\/[^/]+\/export\/[^/]+\/?$/;
|
|
8
|
+
const READ_METHODS = ['GET', 'HEAD', 'OPTIONS'];
|
|
9
|
+
const isPublicRoute = (method, path) => READ_METHODS.includes(method.toUpperCase()) && PUBLIC_EXPORT.test(path);
|
|
10
|
+
exports.isPublicRoute = isPublicRoute;
|
|
5
11
|
const authenticate = (config) => async (req, res, next) => {
|
|
6
|
-
if (req.path
|
|
12
|
+
if ((0, exports.isPublicRoute)(req.method, req.path)) {
|
|
7
13
|
return next();
|
|
8
14
|
}
|
|
9
15
|
try {
|
|
@@ -20,11 +26,3 @@ const authenticate = (config) => async (req, res, next) => {
|
|
|
20
26
|
}
|
|
21
27
|
};
|
|
22
28
|
exports.authenticate = authenticate;
|
|
23
|
-
function checkRole(auth, required) {
|
|
24
|
-
const roles = ['viewer', 'editor', 'admin'];
|
|
25
|
-
const userIdx = roles.indexOf(auth.role);
|
|
26
|
-
const requiredIdx = roles.indexOf(required);
|
|
27
|
-
if (userIdx < requiredIdx) {
|
|
28
|
-
throw new Error(`Insufficient role: requires ${required}, found ${auth.role}`);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
@@ -52,7 +52,7 @@ async function resolveDbConnectionClient(project, connId, config, auth) {
|
|
|
52
52
|
if (conn.source === 'integration') {
|
|
53
53
|
if (!conn.integrationId)
|
|
54
54
|
return { error: 'Connection is missing its integrationId', status: 500 };
|
|
55
|
-
const resolved = await (0, userIntegration_1.getDecryptedIntegrationCredentialById)(conn.integrationId, config);
|
|
55
|
+
const resolved = await (0, userIntegration_1.getDecryptedIntegrationCredentialById)(conn.integrationId, config, project);
|
|
56
56
|
if (!resolved)
|
|
57
57
|
return { error: 'Integration not found', status: 400 };
|
|
58
58
|
connectionString = resolved.credential;
|
|
@@ -109,7 +109,7 @@ async function resolveStorageAdapter(project, connId, config, auth) {
|
|
|
109
109
|
if (!conn.integrationId)
|
|
110
110
|
return { error: 'Connection is missing its integrationId', status: 500 };
|
|
111
111
|
try {
|
|
112
|
-
const resolved = await (0, userIntegration_1.getDecryptedIntegrationCredentialById)(conn.integrationId, config);
|
|
112
|
+
const resolved = await (0, userIntegration_1.getDecryptedIntegrationCredentialById)(conn.integrationId, config, project);
|
|
113
113
|
if (!resolved)
|
|
114
114
|
return { error: 'Integration not found', status: 400 };
|
|
115
115
|
return new s3_1.S3Adapter(resolved.credential);
|
|
@@ -4,4 +4,7 @@ export declare class NodeExecutionError extends Error {
|
|
|
4
4
|
nodeType: string;
|
|
5
5
|
constructor(nodeId: string, nodeType: string, message: string);
|
|
6
6
|
}
|
|
7
|
-
export
|
|
7
|
+
export interface ExecuteExportOptions {
|
|
8
|
+
public?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function executeExport(nodeSchema: NodeSchema, project: any, config: StreamByConfig, { public: isPublic }?: ExecuteExportOptions): Promise<any>;
|
|
@@ -12,6 +12,7 @@ const mediaProcessor_1 = require("./mediaProcessor");
|
|
|
12
12
|
const vfxProcessor_1 = require("./vfxProcessor");
|
|
13
13
|
const aiProcessor_1 = require("./aiProcessor");
|
|
14
14
|
const storage_1 = require("../providers/storage");
|
|
15
|
+
const serviceEndpoints_1 = require("../utils/serviceEndpoints");
|
|
15
16
|
function applyFilterConfig(payload, config) {
|
|
16
17
|
const isArr = Array.isArray(payload);
|
|
17
18
|
let r = isArr ? [...payload] : payload;
|
|
@@ -87,13 +88,19 @@ class NodeExecutionError extends Error {
|
|
|
87
88
|
}
|
|
88
89
|
}
|
|
89
90
|
exports.NodeExecutionError = NodeExecutionError;
|
|
90
|
-
// Export execution
|
|
91
|
-
// export
|
|
92
|
-
// resolveDbConnectionClient's signature
|
|
93
|
-
//
|
|
94
|
-
// already-published export.
|
|
91
|
+
// Export execution has no user behind it (a public export is read without a session -- see
|
|
92
|
+
// routes/export) -- there is no real Auth to pass through. This sentinel satisfies
|
|
93
|
+
// resolveDbConnectionClient's signature; canUseBuiltin gating is for connection *management*
|
|
94
|
+
// by a user, not for serving an already-published export.
|
|
95
95
|
const SYSTEM_AUTH = { userId: 'system', username: 'system', role: 'admin' };
|
|
96
|
-
|
|
96
|
+
// Nodes that start a job (transcoding, rendering, generating...): work, and cost, on the
|
|
97
|
+
// server or a provider, for whoever asks.
|
|
98
|
+
const JOB_NODES = new Set([
|
|
99
|
+
'transcodeNode', 'captionNode', 'thumbnailNode', 'ingestNode', 'renderJobNode', 'formatConvertNode',
|
|
100
|
+
'lodNode', 'transcriptionNode', 'upscaleNode', 'proceduralAssetNode',
|
|
101
|
+
]);
|
|
102
|
+
const notPublic = (node) => new NodeExecutionError(node.id, node.type, `Node '${node.id}' (${node.type}) can't run in a public export.`);
|
|
103
|
+
async function executeExport(nodeSchema, project, config, { public: isPublic = true } = {}) {
|
|
97
104
|
const nodes = nodeSchema.nodes;
|
|
98
105
|
const edges = nodeSchema.edges;
|
|
99
106
|
// 1. Data layer — nodes connected to streamby in-bottom
|
|
@@ -119,6 +126,8 @@ async function executeExport(nodeSchema, project, config) {
|
|
|
119
126
|
if (!connectionId) {
|
|
120
127
|
throw new NodeExecutionError(node.id, node.type, `Node '${node.id}' (${node.type}) has no database connection — connect one on the node.`);
|
|
121
128
|
}
|
|
129
|
+
if (isPublic && (0, connectionResolver_1.findDbConnection)(project, connectionId)?.source === 'integration')
|
|
130
|
+
throw notPublic(node);
|
|
122
131
|
const recordId = node.data?.recordId;
|
|
123
132
|
const projectIdentifier = project._id?.toString() ?? project.id;
|
|
124
133
|
const resolved = await (0, connectionResolver_1.resolveDbConnectionClient)(project, connectionId, config, SYSTEM_AUTH);
|
|
@@ -158,6 +167,8 @@ async function executeExport(nodeSchema, project, config) {
|
|
|
158
167
|
// NodeExecutionError (matching dataSourceNode/ingestNode's intentional pattern), since
|
|
159
168
|
// a silently-skipped service action would produce a workflow that "succeeds" while
|
|
160
169
|
// quietly missing the one thing the node exists to do.
|
|
170
|
+
if (isPublic)
|
|
171
|
+
throw notPublic(node);
|
|
161
172
|
const connectionId = node.data?.connectionId;
|
|
162
173
|
if (!connectionId) {
|
|
163
174
|
throw new NodeExecutionError(node.id, node.type, `Node '${node.id}' (${node.type}) has no integration connection — connect one on the node.`);
|
|
@@ -166,21 +177,26 @@ async function executeExport(nodeSchema, project, config) {
|
|
|
166
177
|
if (!connection) {
|
|
167
178
|
throw new NodeExecutionError(node.id, node.type, `Node '${node.id}' (${node.type}): integration connection not found on this project.`);
|
|
168
179
|
}
|
|
169
|
-
const resolved = await (0, userIntegration_1.getDecryptedIntegrationCredentialById)(connection.integrationId, config);
|
|
170
|
-
if (!resolved) {
|
|
171
|
-
throw new NodeExecutionError(node.id, node.type, `Node '${node.id}' (${node.type}): integration credential could not be resolved — it may have been disconnected.`);
|
|
172
|
-
}
|
|
173
180
|
const endpoint = node.data?.endpoint;
|
|
174
181
|
if (!endpoint) {
|
|
175
182
|
throw new NodeExecutionError(node.id, node.type, `Node '${node.id}' (${node.type}) has no endpoint configured.`);
|
|
176
183
|
}
|
|
184
|
+
// Checked before the credential is even resolved: it's only ever sent to its provider.
|
|
185
|
+
if (!(0, serviceEndpoints_1.isServiceEndpoint)(connection.provider, endpoint)) {
|
|
186
|
+
throw new NodeExecutionError(node.id, node.type, `Node '${node.id}' (${node.type}): ${endpoint} is not a ${connection.provider} endpoint.`);
|
|
187
|
+
}
|
|
188
|
+
const resolved = await (0, userIntegration_1.getDecryptedIntegrationCredentialById)(connection.integrationId, config, project);
|
|
189
|
+
if (!resolved) {
|
|
190
|
+
throw new NodeExecutionError(node.id, node.type, `Node '${node.id}' (${node.type}): integration credential could not be resolved — it may have been disconnected.`);
|
|
191
|
+
}
|
|
177
192
|
const method = node.data?.method ?? 'GET';
|
|
178
193
|
const headers = {
|
|
179
194
|
'Content-Type': 'application/json',
|
|
180
195
|
...(0, oauthClient_1.buildServiceAuthHeaders)(connection.provider, resolved.credential),
|
|
181
196
|
};
|
|
182
197
|
const body = node.data?.body ? JSON.stringify(node.data.body) : undefined;
|
|
183
|
-
|
|
198
|
+
// A redirect would carry the credential's headers on to wherever it points.
|
|
199
|
+
const response = await fetch(endpoint, { method, headers, body, redirect: 'error' });
|
|
184
200
|
if (!response.ok) {
|
|
185
201
|
throw new NodeExecutionError(node.id, node.type, `Node '${node.id}' (${node.type}): integration action failed (${response.statusText}).`);
|
|
186
202
|
}
|
|
@@ -197,6 +213,8 @@ async function executeExport(nodeSchema, project, config) {
|
|
|
197
213
|
while (processNode) {
|
|
198
214
|
const nodeData = processNode.data ?? {};
|
|
199
215
|
const fileId = nodeData.fileId;
|
|
216
|
+
if (isPublic && JOB_NODES.has(processNode.type))
|
|
217
|
+
throw notPublic(processNode);
|
|
200
218
|
if (processNode.type === 'transcodeNode' && fileId) {
|
|
201
219
|
const job = (0, jobQueue_1.createJob)('transcode', systemUserId, projectId, nodeData);
|
|
202
220
|
setImmediate(() => (0, mediaProcessor_1.runTranscodeJob)(job.jobId, fileId, projectId, {
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.reencryptStoredCredentials = reencryptStoredCredentials;
|
|
4
|
+
const manager_1 = require("../models/manager");
|
|
5
|
+
const encryption_1 = require("../utils/encryption");
|
|
6
|
+
// Brings every stored credential to the current encryption (AES-256-GCM with the current key,
|
|
7
|
+
// see utils/encryption): those encrypted before (CBC, or GCM with a previous key) are
|
|
8
|
+
// re-encrypted, and the distribution and render farm credentials, stored as they were sent,
|
|
9
|
+
// are encrypted. Non-destructive: a value is replaced only by its own re-encryption; one that
|
|
10
|
+
// can't be decrypted is left as it is (and counted). Runs at startup; once everything is
|
|
11
|
+
// current, it changes nothing.
|
|
12
|
+
// A project's lists of connections with a credential each, encrypted before.
|
|
13
|
+
const ENCRYPTED_LISTS = ['connections', 'dbConnections', 'storageConnections', 'exports'];
|
|
14
|
+
// The ones whose credentials were stored as sent, never encrypted.
|
|
15
|
+
const PLAIN_LISTS = ['distributionConnections', 'renderFarmConnections'];
|
|
16
|
+
const INTEGRATION_FIELDS = ['encryptedCredential', 'encryptedAccessToken', 'encryptedRefreshToken'];
|
|
17
|
+
async function reencryptStoredCredentials() {
|
|
18
|
+
const result = { updated: 0, failed: 0 };
|
|
19
|
+
if (!(0, encryption_1.isEncryptionKeySet)())
|
|
20
|
+
return result;
|
|
21
|
+
// The current encryption of a stored value, or undefined when it's current already or
|
|
22
|
+
// can't be read (left as it is).
|
|
23
|
+
const upgrade = (value, storedPlain) => {
|
|
24
|
+
if (typeof value !== 'string' || !value || (0, encryption_1.isCurrentEncryption)(value))
|
|
25
|
+
return undefined;
|
|
26
|
+
if (storedPlain && !(0, encryption_1.isVersionedEncryption)(value))
|
|
27
|
+
return (0, encryption_1.encrypt)(value);
|
|
28
|
+
try {
|
|
29
|
+
return (0, encryption_1.encrypt)((0, encryption_1.decrypt)(value));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
result.failed++;
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const Project = (0, manager_1.getModel)('projects');
|
|
37
|
+
for (const project of (await Project.find({}))) {
|
|
38
|
+
const changes = {};
|
|
39
|
+
for (const list of [...ENCRYPTED_LISTS, ...PLAIN_LISTS]) {
|
|
40
|
+
const entries = project[list];
|
|
41
|
+
if (!Array.isArray(entries))
|
|
42
|
+
continue;
|
|
43
|
+
const storedPlain = PLAIN_LISTS.includes(list);
|
|
44
|
+
let changed = false;
|
|
45
|
+
const next = entries.map((entry) => {
|
|
46
|
+
const upgraded = upgrade(entry?.encryptedCredential, storedPlain);
|
|
47
|
+
if (upgraded === undefined)
|
|
48
|
+
return entry;
|
|
49
|
+
changed = true;
|
|
50
|
+
return { ...entry, encryptedCredential: upgraded };
|
|
51
|
+
});
|
|
52
|
+
if (changed)
|
|
53
|
+
changes[list] = next;
|
|
54
|
+
}
|
|
55
|
+
if (Object.keys(changes).length) {
|
|
56
|
+
await Project.update({ _id: project._id ?? project.id }, changes);
|
|
57
|
+
result.updated++;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const UserIntegration = (0, manager_1.getModel)('user_integrations');
|
|
61
|
+
for (const integration of (await UserIntegration.find({}))) {
|
|
62
|
+
const changes = {};
|
|
63
|
+
for (const field of INTEGRATION_FIELDS) {
|
|
64
|
+
const upgraded = upgrade(integration[field], false);
|
|
65
|
+
if (upgraded !== undefined)
|
|
66
|
+
changes[field] = upgraded;
|
|
67
|
+
}
|
|
68
|
+
if (Object.keys(changes).length) {
|
|
69
|
+
await UserIntegration.update({ id: integration.id }, changes);
|
|
70
|
+
result.updated++;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
}
|