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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IntegrationKind, ServiceProviderType, StreamByConfig, UserIntegration } from '../types';
|
|
1
|
+
import { IntegrationKind, ProjectInfo, ServiceProviderType, StreamByConfig, UserIntegration } from '../types';
|
|
2
2
|
export declare function isOAuthServiceProvider(provider: string): boolean;
|
|
3
3
|
export declare class OAuthRefreshError extends Error {
|
|
4
4
|
provider: string;
|
|
@@ -34,4 +34,4 @@ export interface ResolvedCredential {
|
|
|
34
34
|
credential: unknown;
|
|
35
35
|
}
|
|
36
36
|
export declare function getDecryptedIntegrationCredential(userId: string, integrationId: string, config: StreamByConfig): Promise<ResolvedCredential | null>;
|
|
37
|
-
export declare function getDecryptedIntegrationCredentialById(integrationId: string, config: StreamByConfig): Promise<ResolvedCredential | null>;
|
|
37
|
+
export declare function getDecryptedIntegrationCredentialById(integrationId: string, config: StreamByConfig, project: Pick<ProjectInfo, 'members'>): Promise<ResolvedCredential | null>;
|
|
@@ -15,6 +15,7 @@ exports.getDecryptedIntegrationCredentialById = getDecryptedIntegrationCredentia
|
|
|
15
15
|
const crypto_1 = __importDefault(require("crypto"));
|
|
16
16
|
const manager_1 = require("../models/manager");
|
|
17
17
|
const encryption_1 = require("../utils/encryption");
|
|
18
|
+
const auth_1 = require("../utils/auth");
|
|
18
19
|
const oauthClient_1 = require("./oauthClient");
|
|
19
20
|
const OAUTH_SERVICE_PROVIDERS = new Set(['jira', 'google']);
|
|
20
21
|
function isOAuthServiceProvider(provider) {
|
|
@@ -157,9 +158,14 @@ async function getDecryptedIntegrationCredential(userId, integrationId, config)
|
|
|
157
158
|
// stored on the connection itself. `config` is required (not just for OAuth's sake) so a
|
|
158
159
|
// near-expiry jira/google token can be refreshed using the host-injected client
|
|
159
160
|
// credentials in config.oauthProviders.
|
|
160
|
-
|
|
161
|
+
// A member's own integration, used by a project it was connected to: it resolves only while its
|
|
162
|
+
// owner is an active member of that project. A project its owner isn't in (never was, left it,
|
|
163
|
+
// or a connection pointing at someone else's integration) never gets the credential.
|
|
164
|
+
async function getDecryptedIntegrationCredentialById(integrationId, config, project) {
|
|
161
165
|
const UserIntegrationModel = (0, manager_1.getModel)('user_integrations');
|
|
162
166
|
const integration = await UserIntegrationModel.findOne({ id: integrationId });
|
|
167
|
+
if (!integration || !(0, auth_1.activeMemberOf)(project, integration.userId))
|
|
168
|
+
return null;
|
|
163
169
|
return resolveCredential(integration, config);
|
|
164
170
|
}
|
|
165
171
|
async function decryptIntegration(integration) {
|
package/dist/types/index.d.ts
CHANGED
|
@@ -63,6 +63,7 @@ export interface ProjectInfo {
|
|
|
63
63
|
role: 'viewer' | 'editor' | 'admin';
|
|
64
64
|
userId: string;
|
|
65
65
|
archived?: boolean;
|
|
66
|
+
status?: 'active' | 'pending';
|
|
66
67
|
}[];
|
|
67
68
|
description?: string;
|
|
68
69
|
exports?: {
|
|
@@ -131,6 +132,7 @@ export interface StreamByConfig {
|
|
|
131
132
|
databases?: DatabaseCredential[];
|
|
132
133
|
adapter?: StorageAdapter;
|
|
133
134
|
encrypt?: string;
|
|
135
|
+
encryptPreviousKeys?: string[];
|
|
134
136
|
websocket?: {
|
|
135
137
|
server: WebSocketServer;
|
|
136
138
|
};
|
package/dist/utils/auth.d.ts
CHANGED
|
@@ -1,2 +1,16 @@
|
|
|
1
1
|
import { ProjectInfo } from '../types';
|
|
2
|
-
|
|
2
|
+
type ProjectMembers = Pick<ProjectInfo, 'members'> | null | undefined;
|
|
3
|
+
export declare function activeMemberOf(project: ProjectMembers, userId: string | undefined): {
|
|
4
|
+
role: "viewer" | "editor" | "admin";
|
|
5
|
+
userId: string;
|
|
6
|
+
archived?: boolean;
|
|
7
|
+
status?: "active" | "pending";
|
|
8
|
+
} | undefined;
|
|
9
|
+
export declare const projectRoleOf: (project: ProjectMembers, userId: string | undefined) => "viewer" | "editor" | "admin" | undefined;
|
|
10
|
+
/** An active member, whatever their role: can see the project. */
|
|
11
|
+
export declare const isProjectMember: (project: ProjectMembers, userId: string | undefined) => boolean;
|
|
12
|
+
/** An editor or an admin of the project: can change it (its content, its connections). */
|
|
13
|
+
export declare const canEditProject: (project: ProjectMembers, userId: string | undefined) => boolean;
|
|
14
|
+
/** An admin of the project: can manage its members, and delete it. */
|
|
15
|
+
export declare const isProjectAdmin: (project: ProjectMembers, userId: string | undefined) => boolean;
|
|
16
|
+
export {};
|
package/dist/utils/auth.js
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isProjectMember =
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
exports.isProjectAdmin = exports.canEditProject = exports.isProjectMember = exports.projectRoleOf = void 0;
|
|
4
|
+
exports.activeMemberOf = activeMemberOf;
|
|
5
|
+
// A project's member who's in it now: invited and accepted (status 'active'). (A member's
|
|
6
|
+
// `archived` is only that member having archived the project in their own list.)
|
|
7
|
+
function activeMemberOf(project, userId) {
|
|
8
|
+
if (!project?.members || !userId)
|
|
9
|
+
return undefined;
|
|
10
|
+
return project.members.find(member => member.userId?.toString() === userId && member.status === 'active');
|
|
9
11
|
}
|
|
12
|
+
// What a user can do in a project is their role in it, as an active member -- never their
|
|
13
|
+
// role on the platform (Auth.role): a pending invitation is no access yet.
|
|
14
|
+
const projectRoleOf = (project, userId) => activeMemberOf(project, userId)?.role;
|
|
15
|
+
exports.projectRoleOf = projectRoleOf;
|
|
16
|
+
/** An active member, whatever their role: can see the project. */
|
|
17
|
+
const isProjectMember = (project, userId) => !!(0, exports.projectRoleOf)(project, userId);
|
|
18
|
+
exports.isProjectMember = isProjectMember;
|
|
19
|
+
/** An editor or an admin of the project: can change it (its content, its connections). */
|
|
20
|
+
const canEditProject = (project, userId) => ['editor', 'admin'].includes((0, exports.projectRoleOf)(project, userId) ?? '');
|
|
21
|
+
exports.canEditProject = canEditProject;
|
|
22
|
+
/** An admin of the project: can manage its members, and delete it. */
|
|
23
|
+
const isProjectAdmin = (project, userId) => (0, exports.projectRoleOf)(project, userId) === 'admin';
|
|
24
|
+
exports.isProjectAdmin = isProjectAdmin;
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
/** The key credentials are encrypted with, and the previous ones they may still be in. */
|
|
2
|
+
export declare const setEncryptionKey: (key: string, previousKeys?: string[]) => void;
|
|
2
3
|
export declare const encrypt: (text: string) => string;
|
|
3
4
|
export declare const decrypt: (text: string) => string;
|
|
5
|
+
/** Whether a stored value is encrypted the current way (GCM, with the current key). */
|
|
6
|
+
export declare const isCurrentEncryption: (text: string) => boolean;
|
|
7
|
+
/** Whether a stored value is in this module's own format (GCM, any key). */
|
|
8
|
+
export declare const isVersionedEncryption: (text: string) => boolean;
|
|
4
9
|
export declare const isEncryptionKeySet: () => boolean;
|
package/dist/utils/encryption.js
CHANGED
|
@@ -33,43 +33,96 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.isEncryptionKeySet = exports.decrypt = exports.encrypt = exports.setEncryptionKey = void 0;
|
|
36
|
+
exports.isEncryptionKeySet = exports.isVersionedEncryption = exports.isCurrentEncryption = exports.decrypt = exports.encrypt = exports.setEncryptionKey = void 0;
|
|
37
37
|
const crypto = __importStar(require("crypto"));
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
// Stored credentials, encrypted with AES-256-GCM (authenticated: an altered value fails to
|
|
39
|
+
// decrypt instead of decrypting to garbage), each value naming the key it was encrypted with:
|
|
40
|
+
//
|
|
41
|
+
// v2:<key id>:<iv>:<auth tag>:<ciphertext> (base64url parts; a 12-byte iv)
|
|
42
|
+
//
|
|
43
|
+
// A key's id is a fingerprint of it, so rotating is only configuring the new key as the
|
|
44
|
+
// current one and the old ones as previous keys: what's stored keeps decrypting, and is
|
|
45
|
+
// re-encrypted with the current key (see services/reencryptCredentials).
|
|
46
|
+
//
|
|
47
|
+
// Values stored before (AES-256-CBC, "<iv hex>:<ciphertext hex>", no key id) still decrypt,
|
|
48
|
+
// with the current key or a previous one.
|
|
49
|
+
const GCM = 'aes-256-gcm';
|
|
50
|
+
const GCM_IV_LENGTH = 12;
|
|
51
|
+
const LEGACY_CBC = 'aes-256-cbc';
|
|
52
|
+
const VERSION = 'v2';
|
|
53
|
+
let currentKey;
|
|
54
|
+
const keysById = new Map();
|
|
55
|
+
const keyBuffer = (hex) => {
|
|
56
|
+
if (typeof hex !== 'string' || !/^[0-9a-fA-F]{64}$/.test(hex)) {
|
|
43
57
|
throw new Error('Invalid encryption key length. Key must be 32 bytes (64 hexadecimal characters).');
|
|
44
58
|
}
|
|
45
|
-
|
|
59
|
+
return Buffer.from(hex, 'hex');
|
|
60
|
+
};
|
|
61
|
+
// A key's id: a fingerprint of it (never the key, nor enough to find it).
|
|
62
|
+
const keyIdOf = (key) => crypto.createHash('sha256').update('streamby-encryption-key:').update(key).digest('hex').slice(0, 16);
|
|
63
|
+
/** The key credentials are encrypted with, and the previous ones they may still be in. */
|
|
64
|
+
const setEncryptionKey = (key, previousKeys = []) => {
|
|
65
|
+
const current = keyBuffer(key);
|
|
66
|
+
const previous = previousKeys.map(keyBuffer);
|
|
67
|
+
keysById.clear();
|
|
68
|
+
for (const k of [...previous, current])
|
|
69
|
+
keysById.set(keyIdOf(k), k);
|
|
70
|
+
currentKey = { id: keyIdOf(current), key: current };
|
|
46
71
|
};
|
|
47
72
|
exports.setEncryptionKey = setEncryptionKey;
|
|
73
|
+
const requireKey = () => {
|
|
74
|
+
if (!currentKey)
|
|
75
|
+
throw new Error('Encryption key is not set. Cannot encrypt or decrypt credentials.');
|
|
76
|
+
return currentKey;
|
|
77
|
+
};
|
|
48
78
|
const encrypt = (text) => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
encrypted = Buffer.concat([encrypted, cipher.final()]);
|
|
56
|
-
return iv.toString('hex') + ':' + encrypted.toString('hex');
|
|
79
|
+
const { id, key } = requireKey();
|
|
80
|
+
const iv = crypto.randomBytes(GCM_IV_LENGTH);
|
|
81
|
+
const cipher = crypto.createCipheriv(GCM, key, iv);
|
|
82
|
+
const encrypted = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]);
|
|
83
|
+
const tag = cipher.getAuthTag();
|
|
84
|
+
return [VERSION, id, iv.toString('base64url'), tag.toString('base64url'), encrypted.toString('base64url')].join(':');
|
|
57
85
|
};
|
|
58
86
|
exports.encrypt = encrypt;
|
|
87
|
+
const decryptLegacy = (text, key) => {
|
|
88
|
+
const [ivHex, ...rest] = text.split(':');
|
|
89
|
+
const decipher = crypto.createDecipheriv(LEGACY_CBC, key, Buffer.from(ivHex, 'hex'));
|
|
90
|
+
return Buffer.concat([decipher.update(Buffer.from(rest.join(':'), 'hex')), decipher.final()]).toString();
|
|
91
|
+
};
|
|
59
92
|
const decrypt = (text) => {
|
|
60
|
-
|
|
61
|
-
|
|
93
|
+
requireKey();
|
|
94
|
+
if (text.startsWith(`${VERSION}:`)) {
|
|
95
|
+
const [, id, iv, tag, encrypted] = text.split(':');
|
|
96
|
+
const key = keysById.get(id);
|
|
97
|
+
if (!key)
|
|
98
|
+
throw new Error('This credential was encrypted with a key that is not configured.');
|
|
99
|
+
const decipher = crypto.createDecipheriv(GCM, key, Buffer.from(iv, 'base64url'));
|
|
100
|
+
decipher.setAuthTag(Buffer.from(tag, 'base64url'));
|
|
101
|
+
try {
|
|
102
|
+
return Buffer.concat([decipher.update(Buffer.from(encrypted, 'base64url')), decipher.final()]).toString('utf8');
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
throw new Error('This credential could not be decrypted: it was altered or damaged.');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Stored before: CBC, with whichever configured key it was encrypted with.
|
|
109
|
+
let lastError;
|
|
110
|
+
for (const key of [currentKey.key, ...[...keysById.values()].filter(k => k !== currentKey.key)]) {
|
|
111
|
+
try {
|
|
112
|
+
return decryptLegacy(text, key);
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
lastError = err;
|
|
116
|
+
}
|
|
62
117
|
}
|
|
63
|
-
|
|
64
|
-
const iv = Buffer.from(textParts.shift(), 'hex');
|
|
65
|
-
const encryptedText = Buffer.from(textParts.join(':'), 'hex');
|
|
66
|
-
const decipher = crypto.createDecipheriv(ALGORITHM, Buffer.from(ENCRYPTION_KEY, 'hex'), iv);
|
|
67
|
-
let decrypted = decipher.update(encryptedText);
|
|
68
|
-
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
|
69
|
-
return decrypted.toString();
|
|
118
|
+
throw lastError instanceof Error ? lastError : new Error('This credential could not be decrypted.');
|
|
70
119
|
};
|
|
71
120
|
exports.decrypt = decrypt;
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
121
|
+
/** Whether a stored value is encrypted the current way (GCM, with the current key). */
|
|
122
|
+
const isCurrentEncryption = (text) => !!currentKey && text.startsWith(`${VERSION}:${currentKey.id}:`);
|
|
123
|
+
exports.isCurrentEncryption = isCurrentEncryption;
|
|
124
|
+
/** Whether a stored value is in this module's own format (GCM, any key). */
|
|
125
|
+
const isVersionedEncryption = (text) => text.startsWith(`${VERSION}:`);
|
|
126
|
+
exports.isVersionedEncryption = isVersionedEncryption;
|
|
127
|
+
const isEncryptionKeySet = () => !!currentKey;
|
|
75
128
|
exports.isEncryptionKeySet = isEncryptionKeySet;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isServiceEndpoint = isServiceEndpoint;
|
|
4
|
+
// Where a service integration's credential may be sent: its own provider's API, over https.
|
|
5
|
+
// A node chooses the endpoint it calls, and anyone who can edit the node could otherwise point
|
|
6
|
+
// it at a server of their own and receive the credential of whoever connected the integration.
|
|
7
|
+
const PROVIDER_HOSTS = {
|
|
8
|
+
claude: (host) => host === 'api.anthropic.com',
|
|
9
|
+
jira: (host) => host === 'api.atlassian.com' || host.endsWith('.atlassian.net'),
|
|
10
|
+
google: (host) => host === 'googleapis.com' || host.endsWith('.googleapis.com'),
|
|
11
|
+
};
|
|
12
|
+
function isServiceEndpoint(provider, endpoint) {
|
|
13
|
+
if (typeof endpoint !== 'string')
|
|
14
|
+
return false;
|
|
15
|
+
let url;
|
|
16
|
+
try {
|
|
17
|
+
url = new URL(endpoint);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
if (url.protocol !== 'https:' || url.username || url.password || url.port)
|
|
23
|
+
return false;
|
|
24
|
+
const allowed = PROVIDER_HOSTS[provider];
|
|
25
|
+
return !!allowed && allowed(url.hostname.toLowerCase());
|
|
26
|
+
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "streamby-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"description": "StreamBy middleware framework for media storage management",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"files": [
|
|
8
|
-
"dist"
|
|
8
|
+
"dist",
|
|
9
|
+
"CHANGELOG.md"
|
|
9
10
|
],
|
|
10
11
|
"scripts": {
|
|
11
12
|
"build": "tsc",
|
|
@@ -27,7 +28,7 @@
|
|
|
27
28
|
"typescript"
|
|
28
29
|
],
|
|
29
30
|
"author": "TerminalCore Labs",
|
|
30
|
-
"license": "
|
|
31
|
+
"license": "AGPL-3.0-only",
|
|
31
32
|
"dependencies": {
|
|
32
33
|
"@aws-sdk/client-s3": "^3.528.0",
|
|
33
34
|
"@aws-sdk/s3-request-presigner": "^3.787.0",
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.apiConnectionRouter = apiConnectionRouter;
|
|
4
|
-
const express_1 = require("express");
|
|
5
|
-
const manager_1 = require("../../models/manager");
|
|
6
|
-
const auth_1 = require("../../utils/auth");
|
|
7
|
-
const apiConnection_1 = require("../../services/apiConnection");
|
|
8
|
-
const VALID_METHODS = ['GET', 'POST', 'PATCH', 'PUT', 'DELETE'];
|
|
9
|
-
function apiConnectionRouter(config) {
|
|
10
|
-
const router = (0, express_1.Router)();
|
|
11
|
-
const Project = (0, manager_1.getModel)('projects');
|
|
12
|
-
// List API connections for a project
|
|
13
|
-
router.get('/projects/:id/api/connections', async (req, res) => {
|
|
14
|
-
try {
|
|
15
|
-
const auth = req.auth;
|
|
16
|
-
const projectId = req.params.id;
|
|
17
|
-
const project = await Project.findOne({ _id: projectId });
|
|
18
|
-
if (!project) {
|
|
19
|
-
return res.status(404).json({ message: 'Project not found' });
|
|
20
|
-
}
|
|
21
|
-
if (!(0, auth_1.isProjectMember)(project, auth.userId)) {
|
|
22
|
-
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
23
|
-
}
|
|
24
|
-
return res.status(200).json({ data: project.apiConnections || [] });
|
|
25
|
-
}
|
|
26
|
-
catch (err) {
|
|
27
|
-
res.status(500).json({ message: 'Failed to fetch API connections', details: err.message });
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
// Add a new API connection to a project
|
|
31
|
-
router.post('/projects/:id/api/connections', async (req, res) => {
|
|
32
|
-
try {
|
|
33
|
-
const auth = req.auth;
|
|
34
|
-
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
35
|
-
return res.status(403).json({ message: 'Permission denied' });
|
|
36
|
-
}
|
|
37
|
-
const projectId = req.params.id;
|
|
38
|
-
const { name, baseUrl, method, description, credentialId, prefix } = req.body;
|
|
39
|
-
if (!name || !baseUrl) {
|
|
40
|
-
return res.status(400).json({ message: 'name and baseUrl are required' });
|
|
41
|
-
}
|
|
42
|
-
if (!VALID_METHODS.includes(method)) {
|
|
43
|
-
return res.status(400).json({ message: `method must be one of: ${VALID_METHODS.join(', ')}` });
|
|
44
|
-
}
|
|
45
|
-
const project = await Project.findOne({ _id: projectId });
|
|
46
|
-
if (!project) {
|
|
47
|
-
return res.status(404).json({ message: 'Project not found' });
|
|
48
|
-
}
|
|
49
|
-
if (!(0, auth_1.isProjectMember)(project, auth.userId)) {
|
|
50
|
-
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
51
|
-
}
|
|
52
|
-
if (credentialId) {
|
|
53
|
-
const credExists = project.credentials?.some((c) => c.id === credentialId);
|
|
54
|
-
if (!credExists) {
|
|
55
|
-
return res.status(400).json({ message: 'Credential not found in project' });
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
const connection = await (0, apiConnection_1.addApiConnection)(config, projectId, {
|
|
59
|
-
name,
|
|
60
|
-
baseUrl,
|
|
61
|
-
method,
|
|
62
|
-
prefix,
|
|
63
|
-
...(description !== undefined && { description }),
|
|
64
|
-
...(credentialId !== undefined && { credentialId }),
|
|
65
|
-
});
|
|
66
|
-
return res.status(201).json({ data: connection });
|
|
67
|
-
}
|
|
68
|
-
catch (err) {
|
|
69
|
-
res.status(500).json({ message: 'Failed to add API connection', details: err.message });
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
// Delete an API connection from a project
|
|
73
|
-
router.delete('/projects/:id/api/connections/:connectionId', async (req, res) => {
|
|
74
|
-
try {
|
|
75
|
-
const auth = req.auth;
|
|
76
|
-
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
77
|
-
return res.status(403).json({ message: 'Permission denied' });
|
|
78
|
-
}
|
|
79
|
-
const projectId = req.params.id;
|
|
80
|
-
const { connectionId } = req.params;
|
|
81
|
-
const project = await Project.findOne({ _id: projectId });
|
|
82
|
-
if (!project) {
|
|
83
|
-
return res.status(404).json({ message: 'Project not found' });
|
|
84
|
-
}
|
|
85
|
-
if (!(0, auth_1.isProjectMember)(project, auth.userId)) {
|
|
86
|
-
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
87
|
-
}
|
|
88
|
-
const exists = project.apiConnections?.some((c) => c.id === connectionId);
|
|
89
|
-
if (!exists) {
|
|
90
|
-
return res.status(404).json({ message: 'API connection not found' });
|
|
91
|
-
}
|
|
92
|
-
await (0, apiConnection_1.deleteApiConnection)(config, projectId, connectionId);
|
|
93
|
-
return res.status(200).json({ message: 'API connection deleted successfully.' });
|
|
94
|
-
}
|
|
95
|
-
catch (err) {
|
|
96
|
-
res.status(500).json({ message: 'Failed to delete API connection', details: err.message });
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
return router;
|
|
100
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.credentialRouter = credentialRouter;
|
|
4
|
-
const express_1 = require("express");
|
|
5
|
-
const manager_1 = require("../../models/manager");
|
|
6
|
-
const auth_1 = require("../../utils/auth");
|
|
7
|
-
const sanitize_1 = require("../../utils/sanitize");
|
|
8
|
-
const credential_1 = require("../../services/credential");
|
|
9
|
-
function credentialRouter(config) {
|
|
10
|
-
const router = (0, express_1.Router)();
|
|
11
|
-
const Project = (0, manager_1.getModel)('projects');
|
|
12
|
-
// Add a new credential to a project
|
|
13
|
-
router.post('/projects/:id/credentials', async (req, res) => {
|
|
14
|
-
try {
|
|
15
|
-
const auth = req.auth;
|
|
16
|
-
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
17
|
-
return res.status(403).json({ message: 'Permission denied' });
|
|
18
|
-
}
|
|
19
|
-
const projectId = req.params.id;
|
|
20
|
-
const { id, key, value } = req.body;
|
|
21
|
-
if (!key || !value) {
|
|
22
|
-
return res.status(400).json({ message: 'Missing credential key or value' });
|
|
23
|
-
}
|
|
24
|
-
const project = await Project.findOne({ _id: projectId });
|
|
25
|
-
if (!project || !(0, auth_1.isProjectMember)(project, auth.userId)) {
|
|
26
|
-
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
27
|
-
}
|
|
28
|
-
const updatedProject = await (0, credential_1.addCredential)(config, projectId, { id, key, value });
|
|
29
|
-
res.status(201).json({ message: 'Credential added successfully', project: (0, sanitize_1.sanitizeProject)(updatedProject) });
|
|
30
|
-
}
|
|
31
|
-
catch (err) {
|
|
32
|
-
res.status(500).json({ message: 'Failed to add credential', details: err.message });
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
// Update an existing credential in a project
|
|
36
|
-
router.patch('/projects/:id/credentials/:credentialId', async (req, res) => {
|
|
37
|
-
try {
|
|
38
|
-
const auth = req.auth;
|
|
39
|
-
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
40
|
-
return res.status(403).json({ message: 'Permission denied' });
|
|
41
|
-
}
|
|
42
|
-
const projectId = req.params.id;
|
|
43
|
-
const credentialId = req.params.credentialId;
|
|
44
|
-
const { key, value } = req.body;
|
|
45
|
-
if (!key && !value) {
|
|
46
|
-
return res.status(400).json({ message: 'No update fields provided' });
|
|
47
|
-
}
|
|
48
|
-
const project = await Project.findOne({ _id: projectId });
|
|
49
|
-
if (!project || !(0, auth_1.isProjectMember)(project, auth.userId)) {
|
|
50
|
-
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
51
|
-
}
|
|
52
|
-
const updatedProject = await (0, credential_1.updateCredential)(config, projectId, credentialId, { key, value });
|
|
53
|
-
res.status(200).json({ message: 'Credential updated successfully', project: (0, sanitize_1.sanitizeProject)(updatedProject) });
|
|
54
|
-
}
|
|
55
|
-
catch (err) {
|
|
56
|
-
res.status(500).json({ message: 'Failed to update credential', details: err.message });
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
// Delete a credential from a project
|
|
60
|
-
router.delete('/projects/:id/credentials/:credentialId', async (req, res) => {
|
|
61
|
-
try {
|
|
62
|
-
const auth = req.auth;
|
|
63
|
-
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
64
|
-
return res.status(403).json({ message: 'Permission denied' });
|
|
65
|
-
}
|
|
66
|
-
const projectId = req.params.id;
|
|
67
|
-
const credentialId = req.params.credentialId;
|
|
68
|
-
const project = await Project.findOne({ _id: projectId });
|
|
69
|
-
if (!project || !(0, auth_1.isProjectMember)(project, auth.userId)) {
|
|
70
|
-
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
71
|
-
}
|
|
72
|
-
const updatedProject = await (0, credential_1.deleteCredential)(config, projectId, credentialId);
|
|
73
|
-
res.status(200).json({ message: 'Credential deleted successfully', project: (0, sanitize_1.sanitizeProject)(updatedProject) });
|
|
74
|
-
}
|
|
75
|
-
catch (err) {
|
|
76
|
-
res.status(500).json({ message: 'Failed to delete credential', details: err.message });
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
return router;
|
|
80
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { StreamByConfig, ApiConnection, ApiConnectionMethod } from '../types';
|
|
2
|
-
export declare function addApiConnection(config: StreamByConfig, projectId: string, data: {
|
|
3
|
-
name: string;
|
|
4
|
-
apiUrl: string;
|
|
5
|
-
method: ApiConnectionMethod;
|
|
6
|
-
prefix?: string;
|
|
7
|
-
description?: string;
|
|
8
|
-
credentialId?: string;
|
|
9
|
-
}): Promise<ApiConnection>;
|
|
10
|
-
export declare function updateApiConnection(config: StreamByConfig, projectId: string, connectionId: string, data: {
|
|
11
|
-
name?: string;
|
|
12
|
-
apiUrl?: string;
|
|
13
|
-
method?: ApiConnectionMethod;
|
|
14
|
-
prefix?: string;
|
|
15
|
-
description?: string;
|
|
16
|
-
credentialId?: string;
|
|
17
|
-
}): Promise<ApiConnection>;
|
|
18
|
-
export declare function deleteApiConnection(config: StreamByConfig, projectId: string, connectionId: string): Promise<void>;
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.addApiConnection = addApiConnection;
|
|
4
|
-
exports.updateApiConnection = updateApiConnection;
|
|
5
|
-
exports.deleteApiConnection = deleteApiConnection;
|
|
6
|
-
const mongodb_1 = require("mongodb");
|
|
7
|
-
const manager_1 = require("../models/manager");
|
|
8
|
-
async function addApiConnection(config, projectId, data) {
|
|
9
|
-
const ProjectModel = (0, manager_1.getModel)('projects');
|
|
10
|
-
const project = await ProjectModel.findOne({ _id: projectId });
|
|
11
|
-
if (!project) {
|
|
12
|
-
throw new Error('Project not found.');
|
|
13
|
-
}
|
|
14
|
-
const connection = {
|
|
15
|
-
id: new mongodb_1.ObjectId().toHexString(),
|
|
16
|
-
name: data.name,
|
|
17
|
-
prefix: data.prefix,
|
|
18
|
-
apiUrl: data.apiUrl,
|
|
19
|
-
method: data.method,
|
|
20
|
-
projectId,
|
|
21
|
-
createdAt: new Date(),
|
|
22
|
-
...(data.description !== undefined && { description: data.description }),
|
|
23
|
-
...(data.credentialId !== undefined && { credentialId: data.credentialId }),
|
|
24
|
-
};
|
|
25
|
-
const updatedProject = await ProjectModel.update({ _id: projectId }, { $push: { apiConnections: connection } });
|
|
26
|
-
if (!updatedProject) {
|
|
27
|
-
throw new Error('Failed to add API connection to project.');
|
|
28
|
-
}
|
|
29
|
-
return connection;
|
|
30
|
-
}
|
|
31
|
-
async function updateApiConnection(config, projectId, connectionId, data) {
|
|
32
|
-
const ProjectModel = (0, manager_1.getModel)('projects');
|
|
33
|
-
const project = await ProjectModel.findOne({ _id: projectId });
|
|
34
|
-
if (!project)
|
|
35
|
-
throw new Error('Project not found.');
|
|
36
|
-
const connections = project.apiConnections ?? [];
|
|
37
|
-
const index = connections.findIndex((c) => c.id === connectionId);
|
|
38
|
-
if (index === -1)
|
|
39
|
-
throw new Error('API connection not found.');
|
|
40
|
-
const updated = {
|
|
41
|
-
...connections[index],
|
|
42
|
-
...(data.name !== undefined && { name: data.name }),
|
|
43
|
-
...(data.apiUrl !== undefined && { apiUrl: data.apiUrl }),
|
|
44
|
-
...(data.method !== undefined && { method: data.method }),
|
|
45
|
-
...(data.prefix !== undefined && { prefix: data.prefix }),
|
|
46
|
-
...(data.description !== undefined && { description: data.description }),
|
|
47
|
-
...(data.credentialId !== undefined && { credentialId: data.credentialId }),
|
|
48
|
-
};
|
|
49
|
-
connections[index] = updated;
|
|
50
|
-
const result = await ProjectModel.update({ _id: projectId }, { $set: { apiConnections: connections } });
|
|
51
|
-
if (!result)
|
|
52
|
-
throw new Error('Failed to update API connection.');
|
|
53
|
-
return updated;
|
|
54
|
-
}
|
|
55
|
-
async function deleteApiConnection(config, projectId, connectionId) {
|
|
56
|
-
const ProjectModel = (0, manager_1.getModel)('projects');
|
|
57
|
-
const project = await ProjectModel.findOne({ _id: projectId });
|
|
58
|
-
if (!project) {
|
|
59
|
-
throw new Error('Project not found.');
|
|
60
|
-
}
|
|
61
|
-
const updatedProject = await ProjectModel.update({ _id: projectId }, { $pull: { apiConnections: { id: connectionId } } });
|
|
62
|
-
if (!updatedProject) {
|
|
63
|
-
throw new Error('Failed to delete API connection from project.');
|
|
64
|
-
}
|
|
65
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { StreamByConfig, Credential, ProjectInfo } from '../types';
|
|
2
|
-
export declare function addCredential(config: StreamByConfig, projectId: string, newCredential: Omit<Credential, 'encryptedValue'> & {
|
|
3
|
-
value: string;
|
|
4
|
-
}): Promise<ProjectInfo>;
|
|
5
|
-
export declare function updateCredential(config: StreamByConfig, projectId: string, credentialId: string, updates: Partial<Omit<Credential, 'encryptedValue'> & {
|
|
6
|
-
value: string;
|
|
7
|
-
}>): Promise<ProjectInfo>;
|
|
8
|
-
export declare function deleteCredential(config: StreamByConfig, projectId: string, credentialId: string): Promise<ProjectInfo>;
|