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
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @streamby/core
|
|
2
2
|
|
|
3
|
+
[](https://www.gnu.org/licenses/agpl-3.0)
|
|
4
|
+
|
|
3
5
|
Middleware framework for building storage-agnostic media APIs.
|
|
4
6
|
Part of the [StreamBy](https://streamby.nhexa.cl) ecosystem by TerminalCore Labs.
|
|
5
7
|
|
|
@@ -59,7 +61,7 @@ app.use('/streamby', express.json(), createStreamByRouter({
|
|
|
59
61
|
},
|
|
60
62
|
},
|
|
61
63
|
],
|
|
62
|
-
encrypt: process.env.STREAMBY_ENCRYPTION_KEY,
|
|
64
|
+
encrypt: process.env.STREAMBY_ENCRYPTION_KEY, // 64 hex characters; see "Encryption and key rotation"
|
|
63
65
|
websocket: { server: wss },
|
|
64
66
|
}));
|
|
65
67
|
|
|
@@ -71,6 +73,51 @@ server.listen(3000);
|
|
|
71
73
|
|
|
72
74
|
---
|
|
73
75
|
|
|
76
|
+
## 🔑 Encryption and key rotation
|
|
77
|
+
|
|
78
|
+
Credentials stored by StreamBy (connection secrets, export credentials, user integrations,
|
|
79
|
+
distribution and render farm credentials) are encrypted with the key you pass as `encrypt`:
|
|
80
|
+
32 bytes, written as 64 hexadecimal characters. Each stored value records the id of the key
|
|
81
|
+
that encrypted it (a fingerprint, never the key itself), so you can rotate the key:
|
|
82
|
+
|
|
83
|
+
1. Generate a new key, for example with `openssl rand -hex 32`.
|
|
84
|
+
2. Pass the new key as `encrypt` and the previous one in `encryptPreviousKeys`.
|
|
85
|
+
3. Restart. Values encrypted with a previous key still decrypt, and at startup StreamBy
|
|
86
|
+
re-encrypts all of them with the new key.
|
|
87
|
+
4. Once that's done (the startup log says how many records it re-encrypted, and none are left
|
|
88
|
+
on the old key), you can remove the old key from `encryptPreviousKeys`.
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
createStreamByRouter({
|
|
92
|
+
// ...
|
|
93
|
+
encrypt: process.env.STREAMBY_ENCRYPTION_KEY, // the current key
|
|
94
|
+
encryptPreviousKeys: [process.env.STREAMBY_PREVIOUS_KEY!], // only while rotating
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
> Upgrading from a version before 0.34.0: deploy with your current key first, and rotate only
|
|
99
|
+
> after that deploy (see the [changelog](CHANGELOG.md)).
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 👥 Project roles
|
|
104
|
+
|
|
105
|
+
What a user can do in a project depends on their role in that project, as an active member
|
|
106
|
+
(an invitation counts once it's accepted). It never depends on the platform role your
|
|
107
|
+
`authProvider` returns.
|
|
108
|
+
|
|
109
|
+
| Role | Can |
|
|
110
|
+
|---|---|
|
|
111
|
+
| `viewer` | See the project and its content, and archive the project in their own list. |
|
|
112
|
+
| `editor` | Everything a viewer can, plus change the project: its details, connections, exports, files and folders, workflow, pipelines, production, reviews, rights, and start jobs. |
|
|
113
|
+
| `admin` | Everything an editor can, plus manage the project's members and delete the project. |
|
|
114
|
+
|
|
115
|
+
A project's public exports (`GET /streamby/:projectId/export/:exportName`) are read without a
|
|
116
|
+
session. They only run nodes that read data: never a node that uses an account's credential,
|
|
117
|
+
nor one that starts a job.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
74
121
|
## 🗄️ Database Schema Setup
|
|
75
122
|
|
|
76
123
|
`@streamby/core` requires a pre-configured database schema for its internal operations. It does not manage database connections directly; instead, it expects the host application to provide initialized connection instances. This ensures full control and flexibility for the host environment.
|
|
@@ -279,5 +326,9 @@ Visit us [here](https://terminalcore.cl)
|
|
|
279
326
|
|
|
280
327
|
## 📟 License
|
|
281
328
|
|
|
282
|
-
|
|
329
|
+
Copyright (C) 2026 TerminalCore Labs
|
|
330
|
+
|
|
331
|
+
Licensed under the [GNU Affero General Public License v3.0](./LICENSE) (AGPL-3.0-only).
|
|
332
|
+
|
|
333
|
+
> **Note for consumers:** unlike most installable packages, this one is intentionally AGPL rather than MIT. AGPL's copyleft applies to any project that depends on it -- if you install `@streamby/core`, your own project must also be licensed under AGPL-3.0 (or a compatible copyleft license). Confirm this is acceptable for your use case before adding this dependency.
|
|
283
334
|
|
|
@@ -9,6 +9,7 @@ const manager_1 = require("../models/manager");
|
|
|
9
9
|
const storage_1 = require("../providers/storage");
|
|
10
10
|
const connectionManager_1 = require("../adapters/database/connectionManager");
|
|
11
11
|
const backfillBuiltinConnections_1 = require("../services/backfillBuiltinConnections");
|
|
12
|
+
const reencryptCredentials_1 = require("../services/reencryptCredentials");
|
|
12
13
|
const wsHub_1 = require("../services/wsHub");
|
|
13
14
|
const auth_1 = require("../services/auth");
|
|
14
15
|
const encryption_1 = require("../utils/encryption");
|
|
@@ -38,7 +39,7 @@ const pipeline_1 = require("./routes/pipeline");
|
|
|
38
39
|
function createStreamByRouter(config) {
|
|
39
40
|
const router = express_1.default.Router();
|
|
40
41
|
if (config.encrypt)
|
|
41
|
-
(0, encryption_1.setEncryptionKey)(config.encrypt);
|
|
42
|
+
(0, encryption_1.setEncryptionKey)(config.encrypt, config.encryptPreviousKeys);
|
|
42
43
|
// Establish DB connections asynchronously (Postgres/Mongo + schema migrations).
|
|
43
44
|
// Until they're ready, HOLD incoming requests instead of letting them through —
|
|
44
45
|
// route handlers resolve projects/exports (e.g. the /menu-list export) against a
|
|
@@ -52,6 +53,16 @@ function createStreamByRouter(config) {
|
|
|
52
53
|
.then(() => (0, backfillBuiltinConnections_1.backfillBuiltinConnections)(config))
|
|
53
54
|
.catch((err) => {
|
|
54
55
|
console.error('❌ StreamBy: failed to backfill built-in connections:', err);
|
|
56
|
+
})
|
|
57
|
+
.then(() => (0, reencryptCredentials_1.reencryptStoredCredentials)())
|
|
58
|
+
.then(({ updated, failed }) => {
|
|
59
|
+
if (updated)
|
|
60
|
+
console.log(`✅ StreamBy: re-encrypted the credentials of ${updated} record(s).`);
|
|
61
|
+
if (failed)
|
|
62
|
+
console.error(`❌ StreamBy: ${failed} stored credential(s) could not be decrypted with the configured keys; left as they are.`);
|
|
63
|
+
})
|
|
64
|
+
.catch((err) => {
|
|
65
|
+
console.error('❌ StreamBy: failed to re-encrypt stored credentials:', err);
|
|
55
66
|
})
|
|
56
67
|
.finally(() => {
|
|
57
68
|
ready = true;
|
|
@@ -89,10 +100,12 @@ function createStreamByRouter(config) {
|
|
|
89
100
|
if (config.websocket?.server) {
|
|
90
101
|
(0, wsHub_1.initWsHub)(config.websocket.server, config);
|
|
91
102
|
}
|
|
92
|
-
|
|
103
|
+
// /auth answers whether there's a session (it reads it itself); everything after needs one,
|
|
104
|
+
// but for the public export route (see authenticate).
|
|
93
105
|
router.use((0, auth_2.authRouter)(config));
|
|
94
106
|
router.use((0, auth_1.authenticate)(config));
|
|
95
107
|
router.use(subscription_1.ensureSubscription);
|
|
108
|
+
router.use((0, user_1.userRouter)(config));
|
|
96
109
|
router.use((0, export_1.exportRouter)(config));
|
|
97
110
|
router.use((0, member_1.memberRouter)(config));
|
|
98
111
|
router.use((0, storage_2.storageRouter)(config));
|
|
@@ -29,7 +29,7 @@ function aiRouter(_config) {
|
|
|
29
29
|
const project = await Project.findOne({ _id: projectId });
|
|
30
30
|
if (!project)
|
|
31
31
|
return res.status(404).json({ message: 'Project not found' });
|
|
32
|
-
if (!(0, auth_1.
|
|
32
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
33
33
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
34
34
|
const job = (0, jobQueue_1.createJob)('transcription', auth.userId, projectId, { fileId, model, sourceLanguage, outputFormats, provider, credentialId });
|
|
35
35
|
setImmediate(() => (0, aiProcessor_1.runTranscriptionJob)(job.jobId, fileId, projectId, { model, sourceLanguage, outputFormats, provider, credentialId }));
|
|
@@ -50,7 +50,7 @@ function aiRouter(_config) {
|
|
|
50
50
|
const project = await Project.findOne({ _id: projectId });
|
|
51
51
|
if (!project)
|
|
52
52
|
return res.status(404).json({ message: 'Project not found' });
|
|
53
|
-
if (!(0, auth_1.
|
|
53
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
54
54
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
55
55
|
const job = (0, jobQueue_1.createJob)('upscale', auth.userId, projectId, { fileId, scale, model, mode, credentialId });
|
|
56
56
|
setImmediate(() => (0, aiProcessor_1.runUpscaleJob)(job.jobId, fileId, projectId, { scale, model, mode, credentialId }));
|
|
@@ -71,7 +71,7 @@ function aiRouter(_config) {
|
|
|
71
71
|
const project = await Project.findOne({ _id: projectId });
|
|
72
72
|
if (!project)
|
|
73
73
|
return res.status(404).json({ message: 'Project not found' });
|
|
74
|
-
if (!(0, auth_1.
|
|
74
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
75
75
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
76
76
|
const job = (0, jobQueue_1.createJob)('generate-asset', auth.userId, projectId, { assetType, provider, prompt, seed, credentialId });
|
|
77
77
|
setImmediate(() => (0, aiProcessor_1.runGenerateAssetJob)(job.jobId, projectId, { assetType, provider, prompt, seed, credentialId }));
|
|
@@ -65,9 +65,6 @@ function connectionRouter(config) {
|
|
|
65
65
|
router.post('/projects/:id/connections', async (req, res) => {
|
|
66
66
|
try {
|
|
67
67
|
const auth = req.auth;
|
|
68
|
-
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
69
|
-
return res.status(403).json({ message: 'Permission denied' });
|
|
70
|
-
}
|
|
71
68
|
const projectId = req.params.id;
|
|
72
69
|
const { name, apiUrl, method, description, token, prefix } = req.body;
|
|
73
70
|
if (!name || !apiUrl) {
|
|
@@ -80,7 +77,7 @@ function connectionRouter(config) {
|
|
|
80
77
|
if (!project) {
|
|
81
78
|
return res.status(404).json({ message: 'Project not found' });
|
|
82
79
|
}
|
|
83
|
-
if (!(0, auth_1.
|
|
80
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId)) {
|
|
84
81
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
85
82
|
}
|
|
86
83
|
const connection = await (0, connection_1.addConnection)(config, projectId, {
|
|
@@ -101,9 +98,6 @@ function connectionRouter(config) {
|
|
|
101
98
|
router.patch('/projects/:id/connections/:connectionId', async (req, res) => {
|
|
102
99
|
try {
|
|
103
100
|
const auth = req.auth;
|
|
104
|
-
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
105
|
-
return res.status(403).json({ message: 'Permission denied' });
|
|
106
|
-
}
|
|
107
101
|
const { id: projectId, connectionId } = req.params;
|
|
108
102
|
const { name, apiUrl, method, description, token, prefix } = req.body;
|
|
109
103
|
if (method && !VALID_METHODS.includes(method)) {
|
|
@@ -112,7 +106,7 @@ function connectionRouter(config) {
|
|
|
112
106
|
const project = await Project.findOne({ _id: projectId });
|
|
113
107
|
if (!project)
|
|
114
108
|
return res.status(404).json({ message: 'Project not found' });
|
|
115
|
-
if (!(0, auth_1.
|
|
109
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId)) {
|
|
116
110
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
117
111
|
}
|
|
118
112
|
const updated = await (0, connection_1.updateConnection)(config, projectId, connectionId, {
|
|
@@ -170,16 +164,13 @@ function connectionRouter(config) {
|
|
|
170
164
|
router.delete('/projects/:id/connections/:connectionId', async (req, res) => {
|
|
171
165
|
try {
|
|
172
166
|
const auth = req.auth;
|
|
173
|
-
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
174
|
-
return res.status(403).json({ message: 'Permission denied' });
|
|
175
|
-
}
|
|
176
167
|
const projectId = req.params.id;
|
|
177
168
|
const { connectionId } = req.params;
|
|
178
169
|
const project = await Project.findOne({ _id: projectId });
|
|
179
170
|
if (!project) {
|
|
180
171
|
return res.status(404).json({ message: 'Project not found' });
|
|
181
172
|
}
|
|
182
|
-
if (!(0, auth_1.
|
|
173
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId)) {
|
|
183
174
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
184
175
|
}
|
|
185
176
|
const exists = project.connections?.some((c) => c.id === connectionId);
|
|
@@ -49,9 +49,6 @@ function dbConnectionRouter(config) {
|
|
|
49
49
|
router.post('/projects/:id/connections/db', async (req, res) => {
|
|
50
50
|
try {
|
|
51
51
|
const auth = req.auth;
|
|
52
|
-
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
53
|
-
return res.status(403).json({ message: 'Permission denied' });
|
|
54
|
-
}
|
|
55
52
|
const { name, dbType, credential, integrationId, description } = req.body;
|
|
56
53
|
if (!name || !dbType) {
|
|
57
54
|
return res.status(400).json({ message: 'name and dbType are required' });
|
|
@@ -68,7 +65,7 @@ function dbConnectionRouter(config) {
|
|
|
68
65
|
const project = await Project.findOne({ _id: req.params.id });
|
|
69
66
|
if (!project)
|
|
70
67
|
return res.status(404).json({ message: 'Project not found' });
|
|
71
|
-
if (!(0, auth_1.
|
|
68
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
72
69
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
73
70
|
let encryptedCredential;
|
|
74
71
|
if (integrationId) {
|
|
@@ -106,13 +103,10 @@ function dbConnectionRouter(config) {
|
|
|
106
103
|
router.delete('/projects/:id/connections/db/:connId', async (req, res) => {
|
|
107
104
|
try {
|
|
108
105
|
const auth = req.auth;
|
|
109
|
-
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
110
|
-
return res.status(403).json({ message: 'Permission denied' });
|
|
111
|
-
}
|
|
112
106
|
const project = await Project.findOne({ _id: req.params.id });
|
|
113
107
|
if (!project)
|
|
114
108
|
return res.status(404).json({ message: 'Project not found' });
|
|
115
|
-
if (!(0, auth_1.
|
|
109
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
116
110
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
117
111
|
const conn = (0, connectionResolver_1.findDbConnection)(project, req.params.connId);
|
|
118
112
|
if (!conn)
|
|
@@ -149,13 +143,10 @@ function dbConnectionRouter(config) {
|
|
|
149
143
|
router.post('/projects/:id/connections/db/:connId/tables', async (req, res) => {
|
|
150
144
|
try {
|
|
151
145
|
const auth = req.auth;
|
|
152
|
-
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
153
|
-
return res.status(403).json({ message: 'Permission denied' });
|
|
154
|
-
}
|
|
155
146
|
const project = await Project.findOne({ _id: req.params.id });
|
|
156
147
|
if (!project)
|
|
157
148
|
return res.status(404).json({ message: 'Project not found' });
|
|
158
|
-
if (!(0, auth_1.
|
|
149
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
159
150
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
160
151
|
const schema = req.body;
|
|
161
152
|
if (!schema.tableName)
|
|
@@ -177,13 +168,10 @@ function dbConnectionRouter(config) {
|
|
|
177
168
|
router.delete('/projects/:id/connections/db/:connId/tables/:tableName', async (req, res) => {
|
|
178
169
|
try {
|
|
179
170
|
const auth = req.auth;
|
|
180
|
-
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
181
|
-
return res.status(403).json({ message: 'Permission denied' });
|
|
182
|
-
}
|
|
183
171
|
const project = await Project.findOne({ _id: req.params.id });
|
|
184
172
|
if (!project)
|
|
185
173
|
return res.status(404).json({ message: 'Project not found' });
|
|
186
|
-
if (!(0, auth_1.
|
|
174
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
187
175
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
188
176
|
await withResolvedDbClient(project, req.params.connId, config, auth, res, async (resolved) => {
|
|
189
177
|
await (0, dbConnection_1.deleteTableOrCollectionInternal)(resolved.client, resolved.type, req.params.tableName, req.params.id);
|
|
@@ -218,13 +206,10 @@ function dbConnectionRouter(config) {
|
|
|
218
206
|
router.post('/projects/:id/connections/db/:connId/tables/:tableName', async (req, res) => {
|
|
219
207
|
try {
|
|
220
208
|
const auth = req.auth;
|
|
221
|
-
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
222
|
-
return res.status(403).json({ message: 'Permission denied' });
|
|
223
|
-
}
|
|
224
209
|
const project = await Project.findOne({ _id: req.params.id });
|
|
225
210
|
if (!project)
|
|
226
211
|
return res.status(404).json({ message: 'Project not found' });
|
|
227
|
-
if (!(0, auth_1.
|
|
212
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
228
213
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
229
214
|
const record = req.body;
|
|
230
215
|
if (!record || typeof record !== 'object' || Array.isArray(record)) {
|
|
@@ -243,12 +228,10 @@ function dbConnectionRouter(config) {
|
|
|
243
228
|
router.put('/projects/:id/connections/db/:connId/tables/:tableName/:recordId', async (req, res) => {
|
|
244
229
|
try {
|
|
245
230
|
const auth = req.auth;
|
|
246
|
-
if (auth.role !== 'admin' && auth.role !== 'editor')
|
|
247
|
-
return res.status(403).json({ message: 'Permission denied' });
|
|
248
231
|
const project = await Project.findOne({ _id: req.params.id });
|
|
249
232
|
if (!project)
|
|
250
233
|
return res.status(404).json({ message: 'Project not found' });
|
|
251
|
-
if (!(0, auth_1.
|
|
234
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
252
235
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
253
236
|
const updates = req.body;
|
|
254
237
|
if (!updates || typeof updates !== 'object' || Array.isArray(updates)) {
|
|
@@ -267,12 +250,10 @@ function dbConnectionRouter(config) {
|
|
|
267
250
|
router.delete('/projects/:id/connections/db/:connId/tables/:tableName/:recordId', async (req, res) => {
|
|
268
251
|
try {
|
|
269
252
|
const auth = req.auth;
|
|
270
|
-
if (auth.role !== 'admin' && auth.role !== 'editor')
|
|
271
|
-
return res.status(403).json({ message: 'Permission denied' });
|
|
272
253
|
const project = await Project.findOne({ _id: req.params.id });
|
|
273
254
|
if (!project)
|
|
274
255
|
return res.status(404).json({ message: 'Project not found' });
|
|
275
|
-
if (!(0, auth_1.
|
|
256
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
276
257
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
277
258
|
await withResolvedDbClient(project, req.params.connId, config, auth, res, async (resolved) => {
|
|
278
259
|
await (0, dbConnection_1.deleteRecordInternal)(resolved.client, resolved.type, req.params.tableName, req.params.recordId, req.params.id);
|
|
@@ -11,6 +11,7 @@ const wsHub_1 = require("../../services/wsHub");
|
|
|
11
11
|
const connectionManager_1 = require("../../adapters/database/connectionManager");
|
|
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 getDb() {
|
|
15
16
|
const ids = (0, connectionManager_1.getConnectedIds)().filter(id => (0, connectionManager_1.getConnection)(id).type === 'nosql');
|
|
16
17
|
if (!ids.length)
|
|
@@ -44,13 +45,19 @@ function distributionRouter(_config) {
|
|
|
44
45
|
try {
|
|
45
46
|
const auth = req.auth;
|
|
46
47
|
const { projectId } = req.params;
|
|
47
|
-
const { name, target, encryptedCredential, config, description } = req.body;
|
|
48
|
+
const { name, target, credential: credentialValue, encryptedCredential: legacyCredential, config, description } = req.body;
|
|
49
|
+
// The credential, as given (`encryptedCredential` was its old name: it was always the
|
|
50
|
+
// value itself), stored encrypted.
|
|
51
|
+
const credential = credentialValue ?? legacyCredential;
|
|
52
|
+
if (credential && !(0, encryption_1.isEncryptionKeySet)())
|
|
53
|
+
return res.status(500).json({ message: 'Encryption key is not set' });
|
|
54
|
+
const encryptedCredential = credential ? (0, encryption_1.encrypt)(String(credential)) : undefined;
|
|
48
55
|
if (!name || !target)
|
|
49
56
|
return res.status(400).json({ message: 'name and target are required' });
|
|
50
57
|
const project = await Project.findOne({ _id: projectId });
|
|
51
58
|
if (!project)
|
|
52
59
|
return res.status(404).json({ message: 'Project not found' });
|
|
53
|
-
if (!(0, auth_1.
|
|
60
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
54
61
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
55
62
|
const connection = {
|
|
56
63
|
id: crypto_1.default.randomUUID(),
|
|
@@ -76,7 +83,7 @@ function distributionRouter(_config) {
|
|
|
76
83
|
const project = await Project.findOne({ _id: projectId });
|
|
77
84
|
if (!project)
|
|
78
85
|
return res.status(404).json({ message: 'Project not found' });
|
|
79
|
-
if (!(0, auth_1.
|
|
86
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
80
87
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
81
88
|
await Project.update({ _id: projectId }, { $pull: { distributionConnections: { id: connectionId } } });
|
|
82
89
|
res.json({ message: 'Connection deleted' });
|
|
@@ -96,7 +103,7 @@ function distributionRouter(_config) {
|
|
|
96
103
|
const project = await Project.findOne({ _id: projectId });
|
|
97
104
|
if (!project)
|
|
98
105
|
return res.status(404).json({ message: 'Project not found' });
|
|
99
|
-
if (!(0, auth_1.
|
|
106
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
100
107
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
101
108
|
const exportDoc = await Export.findOne({ id: exportId, projectId });
|
|
102
109
|
if (!exportDoc)
|
|
@@ -169,7 +176,7 @@ function distributionRouter(_config) {
|
|
|
169
176
|
const project = await Project.findOne({ _id: projectId });
|
|
170
177
|
if (!project)
|
|
171
178
|
return res.status(404).json({ message: 'Project not found' });
|
|
172
|
-
if (!(0, auth_1.
|
|
179
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
173
180
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
174
181
|
const db = getDb();
|
|
175
182
|
if (!db)
|
|
@@ -237,7 +237,7 @@ function exportRouter(config) {
|
|
|
237
237
|
const resolvedNodeSchema = nodeSchema ?? DEFAULT_NODE_SCHEMA;
|
|
238
238
|
const exportType = useConnections ? 'externalApi' : 'json';
|
|
239
239
|
const project = await Project.findOne({ _id: projectId });
|
|
240
|
-
if (!project || !(0, auth_1.
|
|
240
|
+
if (!project || !(0, auth_1.canEditProject)(project, auth.userId)) {
|
|
241
241
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
242
242
|
}
|
|
243
243
|
const mainDb = config.databases?.find(db => db.main) ?? config.databases?.[0];
|
|
@@ -258,7 +258,7 @@ function exportRouter(config) {
|
|
|
258
258
|
const { id: projectId, export_id: exportId } = req.params;
|
|
259
259
|
const { name, description, isPrivate, allowedOrigin, devMode, devPorts, useConnections, useCredentials, nodeSchema } = req.body;
|
|
260
260
|
const project = await Project.findOne({ _id: projectId });
|
|
261
|
-
if (!project || !(0, auth_1.
|
|
261
|
+
if (!project || !(0, auth_1.canEditProject)(project, auth.userId)) {
|
|
262
262
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
263
263
|
}
|
|
264
264
|
const exportMetadata = project.exports?.find((e) => e.id.toString() === exportId);
|
|
@@ -284,7 +284,7 @@ function exportRouter(config) {
|
|
|
284
284
|
}
|
|
285
285
|
const { id: projectId, export_id: exportId } = req.params;
|
|
286
286
|
const project = await Project.findOne({ _id: projectId });
|
|
287
|
-
if (!project || !(0, auth_1.
|
|
287
|
+
if (!project || !(0, auth_1.canEditProject)(project, auth.userId)) {
|
|
288
288
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
289
289
|
}
|
|
290
290
|
const exportMetadata = project.exports.find((e) => e.id.toString() === exportId);
|
|
@@ -352,7 +352,8 @@ function exportRouter(config) {
|
|
|
352
352
|
}
|
|
353
353
|
let data;
|
|
354
354
|
if (exportMetadata.nodeSchema) {
|
|
355
|
-
data
|
|
355
|
+
// Read without a session: only the nodes that read data run (see executeExport).
|
|
356
|
+
data = await (0, executor_1.executeExport)(exportMetadata.nodeSchema, project, config, { public: true });
|
|
356
357
|
return res.json(data ?? null);
|
|
357
358
|
}
|
|
358
359
|
// Legacy path: raw JSON or external API export
|
|
@@ -25,11 +25,24 @@ function mediaRouter(config) {
|
|
|
25
25
|
const adapter = config.adapter || (0, storage_1.createStorageProvider)(config.storageProviders);
|
|
26
26
|
const Project = (0, manager_1.getModel)('projects');
|
|
27
27
|
// GET /jobs/:jobId — poll job status
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
// Only for whoever started it, or a member of its project: someone else's job is as if it
|
|
29
|
+
// didn't exist.
|
|
30
|
+
router.get('/jobs/:jobId', async (req, res) => {
|
|
31
|
+
try {
|
|
32
|
+
const auth = req.auth;
|
|
33
|
+
const job = (0, jobQueue_1.getJob)(req.params.jobId);
|
|
34
|
+
if (!job)
|
|
35
|
+
return res.status(404).json({ message: 'Job not found' });
|
|
36
|
+
if (job.userId !== auth.userId) {
|
|
37
|
+
const project = await Project.findOne({ _id: job.projectId });
|
|
38
|
+
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
39
|
+
return res.status(404).json({ message: 'Job not found' });
|
|
40
|
+
}
|
|
41
|
+
res.json({ job });
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
res.status(500).json({ message: 'Failed to fetch job', details: err.message });
|
|
45
|
+
}
|
|
33
46
|
});
|
|
34
47
|
// POST /projects/:projectId/jobs/ingest — queue an ingest job for an already-uploaded file
|
|
35
48
|
router.post('/projects/:projectId/jobs/ingest', async (req, res) => {
|
|
@@ -42,7 +55,7 @@ function mediaRouter(config) {
|
|
|
42
55
|
const project = await Project.findOne({ _id: projectId });
|
|
43
56
|
if (!project)
|
|
44
57
|
return res.status(404).json({ message: 'Project not found' });
|
|
45
|
-
if (!(0, auth_1.
|
|
58
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
46
59
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
47
60
|
const job = (0, jobQueue_1.createJob)('ingest', auth.userId, projectId, { fileId });
|
|
48
61
|
// Run async — response returns immediately with jobId
|
|
@@ -64,7 +77,7 @@ function mediaRouter(config) {
|
|
|
64
77
|
const project = await Project.findOne({ _id: projectId });
|
|
65
78
|
if (!project)
|
|
66
79
|
return res.status(404).json({ message: 'Project not found' });
|
|
67
|
-
if (!(0, auth_1.
|
|
80
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
68
81
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
69
82
|
const job = (0, jobQueue_1.createJob)('transcode', auth.userId, projectId, {
|
|
70
83
|
fileId, codec, resolution, outputFormat, bitrate, audioCodec,
|
|
@@ -87,7 +100,7 @@ function mediaRouter(config) {
|
|
|
87
100
|
const project = await Project.findOne({ _id: projectId });
|
|
88
101
|
if (!project)
|
|
89
102
|
return res.status(404).json({ message: 'Project not found' });
|
|
90
|
-
if (!(0, auth_1.
|
|
103
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
91
104
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
92
105
|
const job = (0, jobQueue_1.createJob)('thumbnail', auth.userId, projectId, { fileId, timecode, resolution, strategy });
|
|
93
106
|
setImmediate(() => (0, mediaProcessor_1.runThumbnailJob)(job.jobId, fileId, projectId, { timecode, resolution, strategy }));
|
|
@@ -108,7 +121,7 @@ function mediaRouter(config) {
|
|
|
108
121
|
const project = await Project.findOne({ _id: projectId });
|
|
109
122
|
if (!project)
|
|
110
123
|
return res.status(404).json({ message: 'Project not found' });
|
|
111
|
-
if (!(0, auth_1.
|
|
124
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
112
125
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
113
126
|
const job = (0, jobQueue_1.createJob)('caption', auth.userId, projectId, { fileId, sourceLanguage, outputFormat, provider });
|
|
114
127
|
setImmediate(() => (0, mediaProcessor_1.runCaptionJob)(job.jobId, fileId, projectId, { sourceLanguage, outputFormat, provider }));
|
|
@@ -150,7 +163,7 @@ function mediaRouter(config) {
|
|
|
150
163
|
const project = await Project.findOne({ _id: projectId });
|
|
151
164
|
if (!project)
|
|
152
165
|
return res.status(404).json({ message: 'Project not found' });
|
|
153
|
-
if (!(0, auth_1.
|
|
166
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
154
167
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
155
168
|
const col = getMetadataCollection();
|
|
156
169
|
if (!col)
|
|
@@ -52,9 +52,6 @@ function memberRouter(config) {
|
|
|
52
52
|
router.post('/projects/:id/members', async (req, res) => {
|
|
53
53
|
try {
|
|
54
54
|
const auth = req.auth;
|
|
55
|
-
if (auth.role !== 'admin') {
|
|
56
|
-
return res.status(403).json({ message: 'Only admins can invite members' });
|
|
57
|
-
}
|
|
58
55
|
const projectId = req.params.id;
|
|
59
56
|
const { userId, role } = req.body;
|
|
60
57
|
if (!userId || !role) {
|
|
@@ -67,7 +64,7 @@ function memberRouter(config) {
|
|
|
67
64
|
if (!project) {
|
|
68
65
|
return res.status(404).json({ message: 'Project not found' });
|
|
69
66
|
}
|
|
70
|
-
if (!(0, auth_1.
|
|
67
|
+
if (!(0, auth_1.isProjectAdmin)(project, auth.userId)) {
|
|
71
68
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
72
69
|
}
|
|
73
70
|
const alreadyMember = project.members?.some((m) => m.userId?.toString() === userId?.toString());
|
|
@@ -107,9 +104,6 @@ function memberRouter(config) {
|
|
|
107
104
|
router.patch('/projects/:id/members/:userId', async (req, res) => {
|
|
108
105
|
try {
|
|
109
106
|
const auth = req.auth;
|
|
110
|
-
if (auth.role !== 'admin') {
|
|
111
|
-
return res.status(403).json({ message: 'Only admins can update member roles' });
|
|
112
|
-
}
|
|
113
107
|
const projectId = req.params.id;
|
|
114
108
|
const targetUserId = req.params.userId;
|
|
115
109
|
const { role } = req.body;
|
|
@@ -120,7 +114,7 @@ function memberRouter(config) {
|
|
|
120
114
|
if (!project) {
|
|
121
115
|
return res.status(404).json({ message: 'Project not found' });
|
|
122
116
|
}
|
|
123
|
-
if (!(0, auth_1.
|
|
117
|
+
if (!(0, auth_1.isProjectAdmin)(project, auth.userId)) {
|
|
124
118
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
125
119
|
}
|
|
126
120
|
const memberExists = project.members?.some((m) => m.userId?.toString() === targetUserId?.toString());
|
|
@@ -282,9 +276,6 @@ function memberRouter(config) {
|
|
|
282
276
|
router.delete('/projects/:id/members/:userId', async (req, res) => {
|
|
283
277
|
try {
|
|
284
278
|
const auth = req.auth;
|
|
285
|
-
if (auth.role !== 'admin') {
|
|
286
|
-
return res.status(403).json({ message: 'Only admins can remove members' });
|
|
287
|
-
}
|
|
288
279
|
const projectId = req.params.id;
|
|
289
280
|
const targetUserId = req.params.userId;
|
|
290
281
|
if (targetUserId === auth.userId) {
|
|
@@ -294,7 +285,7 @@ function memberRouter(config) {
|
|
|
294
285
|
if (!project) {
|
|
295
286
|
return res.status(404).json({ message: 'Project not found' });
|
|
296
287
|
}
|
|
297
|
-
if (!(0, auth_1.
|
|
288
|
+
if (!(0, auth_1.isProjectAdmin)(project, auth.userId)) {
|
|
298
289
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
299
290
|
}
|
|
300
291
|
const memberExists = project.members?.some((m) => m.userId?.toString() === targetUserId?.toString());
|
|
@@ -67,7 +67,7 @@ function pipelineRouter(_config) {
|
|
|
67
67
|
const project = await Project.findOne({ _id: projectId });
|
|
68
68
|
if (!project)
|
|
69
69
|
return res.status(404).json({ message: 'Project not found' });
|
|
70
|
-
if (!(0, auth_1.
|
|
70
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
71
71
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
72
72
|
const db = getDb();
|
|
73
73
|
if (!db)
|
|
@@ -120,7 +120,7 @@ function pipelineRouter(_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)
|
|
@@ -154,7 +154,7 @@ function pipelineRouter(_config) {
|
|
|
154
154
|
const project = await Project.findOne({ _id: projectId });
|
|
155
155
|
if (!project)
|
|
156
156
|
return res.status(404).json({ message: 'Project not found' });
|
|
157
|
-
if (!(0, auth_1.
|
|
157
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
158
158
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
159
159
|
const db = getDb();
|
|
160
160
|
if (!db)
|
|
@@ -51,7 +51,7 @@ function productionRouter(_config) {
|
|
|
51
51
|
const project = await Project.findOne({ _id: projectId });
|
|
52
52
|
if (!project)
|
|
53
53
|
return res.status(404).json({ message: 'Project not found' });
|
|
54
|
-
if (!(0, auth_1.
|
|
54
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
55
55
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
56
56
|
const db = getDb();
|
|
57
57
|
if (!db)
|
|
@@ -81,7 +81,7 @@ function productionRouter(_config) {
|
|
|
81
81
|
const project = await Project.findOne({ _id: projectId });
|
|
82
82
|
if (!project)
|
|
83
83
|
return res.status(404).json({ message: 'Project not found' });
|
|
84
|
-
if (!(0, auth_1.
|
|
84
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
85
85
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
86
86
|
const db = getDb();
|
|
87
87
|
if (!db)
|
|
@@ -110,7 +110,7 @@ function productionRouter(_config) {
|
|
|
110
110
|
const project = await Project.findOne({ _id: projectId });
|
|
111
111
|
if (!project)
|
|
112
112
|
return res.status(404).json({ message: 'Project not found' });
|
|
113
|
-
if (!(0, auth_1.
|
|
113
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
114
114
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
115
115
|
const db = getDb();
|
|
116
116
|
if (!db)
|
|
@@ -156,7 +156,7 @@ function productionRouter(_config) {
|
|
|
156
156
|
const project = await Project.findOne({ _id: projectId });
|
|
157
157
|
if (!project)
|
|
158
158
|
return res.status(404).json({ message: 'Project not found' });
|
|
159
|
-
if (!(0, auth_1.
|
|
159
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
160
160
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
161
161
|
const db = getDb();
|
|
162
162
|
if (!db)
|
|
@@ -192,7 +192,7 @@ function productionRouter(_config) {
|
|
|
192
192
|
const project = await Project.findOne({ _id: projectId });
|
|
193
193
|
if (!project)
|
|
194
194
|
return res.status(404).json({ message: 'Project not found' });
|
|
195
|
-
if (!(0, auth_1.
|
|
195
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
196
196
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
197
197
|
const db = getDb();
|
|
198
198
|
if (!db)
|
|
@@ -231,7 +231,7 @@ function productionRouter(_config) {
|
|
|
231
231
|
const project = await Project.findOne({ _id: projectId });
|
|
232
232
|
if (!project)
|
|
233
233
|
return res.status(404).json({ message: 'Project not found' });
|
|
234
|
-
if (!(0, auth_1.
|
|
234
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
235
235
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
236
236
|
const db = getDb();
|
|
237
237
|
if (!db)
|
|
@@ -277,7 +277,7 @@ function productionRouter(_config) {
|
|
|
277
277
|
const project = await Project.findOne({ _id: projectId });
|
|
278
278
|
if (!project)
|
|
279
279
|
return res.status(404).json({ message: 'Project not found' });
|
|
280
|
-
if (!(0, auth_1.
|
|
280
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
281
281
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
282
282
|
const db = getDb();
|
|
283
283
|
if (!db)
|
|
@@ -311,7 +311,7 @@ function productionRouter(_config) {
|
|
|
311
311
|
const project = await Project.findOne({ _id: projectId });
|
|
312
312
|
if (!project)
|
|
313
313
|
return res.status(404).json({ message: 'Project not found' });
|
|
314
|
-
if (!(0, auth_1.
|
|
314
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
315
315
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
316
316
|
const db = getDb();
|
|
317
317
|
if (!db)
|
|
@@ -346,7 +346,7 @@ function productionRouter(_config) {
|
|
|
346
346
|
const project = await Project.findOne({ _id: projectId });
|
|
347
347
|
if (!project)
|
|
348
348
|
return res.status(404).json({ message: 'Project not found' });
|
|
349
|
-
if (!(0, auth_1.
|
|
349
|
+
if (!(0, auth_1.canEditProject)(project, auth.userId))
|
|
350
350
|
return res.status(403).json({ message: 'Unauthorized' });
|
|
351
351
|
const db = getDb();
|
|
352
352
|
if (!db)
|