@pixelbyte-software/pixcode 1.51.4 → 1.51.5

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.
Files changed (39) hide show
  1. package/dist/assets/{index-HfGHXhD6.js → index-CpbSjzK7.js} +179 -182
  2. package/dist/assets/index-oDq76nSq.css +32 -0
  3. package/dist/index.html +2 -2
  4. package/dist-server/server/cli.js +1 -1
  5. package/dist-server/server/cli.js.map +1 -1
  6. package/dist-server/server/index.js +530 -34
  7. package/dist-server/server/index.js.map +1 -1
  8. package/dist-server/server/middleware/auth.js +20 -3
  9. package/dist-server/server/middleware/auth.js.map +1 -1
  10. package/dist-server/server/modules/providers/provider.routes.js +33 -17
  11. package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
  12. package/dist-server/server/routes/commands.js +20 -9
  13. package/dist-server/server/routes/commands.js.map +1 -1
  14. package/dist-server/server/routes/live-view.js +30 -7
  15. package/dist-server/server/routes/live-view.js.map +1 -1
  16. package/dist-server/server/routes/messages.js +30 -0
  17. package/dist-server/server/routes/messages.js.map +1 -1
  18. package/dist-server/server/routes/network.js +3 -2
  19. package/dist-server/server/routes/network.js.map +1 -1
  20. package/dist-server/server/routes/projects.js +4 -3
  21. package/dist-server/server/routes/projects.js.map +1 -1
  22. package/dist-server/server/routes/remote.js +2 -1
  23. package/dist-server/server/routes/remote.js.map +1 -1
  24. package/dist-server/server/services/platformization.js +73 -0
  25. package/dist-server/server/services/platformization.js.map +1 -1
  26. package/package.json +1 -1
  27. package/scripts/update-git-install.mjs +15 -10
  28. package/server/cli.js +4 -4
  29. package/server/index.js +552 -34
  30. package/server/middleware/auth.js +26 -2
  31. package/server/modules/providers/provider.routes.ts +91 -53
  32. package/server/routes/commands.js +43 -32
  33. package/server/routes/live-view.js +47 -24
  34. package/server/routes/messages.js +47 -12
  35. package/server/routes/network.js +9 -8
  36. package/server/routes/projects.js +7 -6
  37. package/server/routes/remote.js +6 -5
  38. package/server/services/platformization.js +86 -13
  39. package/dist/assets/index-B9N-gfOQ.css +0 -32
@@ -3,9 +3,10 @@ import path from 'path';
3
3
  import { spawn } from 'child_process';
4
4
  import os from 'os';
5
5
 
6
- import express from 'express';
7
-
8
- import { addProjectManually, extractProjectDirectory } from '../projects.js';
6
+ import express from 'express';
7
+
8
+ import { addProjectManually, extractProjectDirectory } from '../projects.js';
9
+ import { requireAdmin } from '../middleware/auth.js';
9
10
 
10
11
  const router = express.Router();
11
12
 
@@ -302,7 +303,7 @@ async function projectHasAnySessions(workspacePath) {
302
303
  * composer and surface a "directory deleted" warning instead of letting
303
304
  * the user fire prompts into a void.
304
305
  */
305
- router.get('/:projectName/dir-status', async (req, res) => {
306
+ router.get('/:projectName/dir-status', requireAdmin, async (req, res) => {
306
307
  const { projectName } = req.params;
307
308
  try {
308
309
  const actualPath = await extractProjectDirectory(projectName);
@@ -340,7 +341,7 @@ router.get('/:projectName/dir-status', async (req, res) => {
340
341
  * matches ChatGPT's "New chat" which reuses the empty canvas until the
341
342
  * user actually commits a message.
342
343
  */
343
- router.post('/quick-start', async (req, res) => {
344
+ router.post('/quick-start', requireAdmin, async (req, res) => {
344
345
  try {
345
346
  await fs.mkdir(WORKSPACES_BASE, { recursive: true });
346
347
 
@@ -424,7 +425,7 @@ router.post('/quick-start', async (req, res) => {
424
425
  * - githubTokenId?: number (optional, ID of stored token)
425
426
  * - newGithubToken?: string (optional, one-time token)
426
427
  */
427
- router.post('/create-workspace', async (req, res) => {
428
+ router.post('/create-workspace', requireAdmin, async (req, res) => {
428
429
  try {
429
430
  const { workspaceType, path: workspacePath, githubUrl, githubTokenId, newGithubToken, subfolderName } = req.body;
430
431
 
@@ -5,10 +5,11 @@ import {
5
5
  getPublicRemoteConnectionConfig,
6
6
  saveRemoteConnectionConfig,
7
7
  } from '../services/remote-connection.js';
8
- import {
9
- buildControlRoomSnapshot,
10
- buildMobileConsoleLayout,
11
- } from '../services/control-room.js';
8
+ import {
9
+ buildControlRoomSnapshot,
10
+ buildMobileConsoleLayout,
11
+ } from '../services/control-room.js';
12
+ import { requireAdmin } from '../middleware/auth.js';
12
13
 
13
14
  const router = express.Router();
14
15
 
@@ -16,7 +17,7 @@ router.get('/config', (req, res) => {
16
17
  res.json({ success: true, connection: getPublicRemoteConnectionConfig() });
17
18
  });
18
19
 
19
- router.put('/config', (req, res) => {
20
+ router.put('/config', requireAdmin, (req, res) => {
20
21
  try {
21
22
  const connection = saveRemoteConnectionConfig(req.body || {});
22
23
  res.json({ success: true, connection });
@@ -1,6 +1,7 @@
1
- import crypto from 'node:crypto';
2
- import os from 'node:os';
3
- import { execFile } from 'node:child_process';
1
+ import crypto from 'node:crypto';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { execFile } from 'node:child_process';
4
5
  import { promisify } from 'node:util';
5
6
 
6
7
  import bcrypt from 'bcryptjs';
@@ -174,6 +175,25 @@ function projectMatches(collaborator, project = {}) {
174
175
  );
175
176
  }
176
177
 
178
+ function isPathInside(basePath, targetPath) {
179
+ const relative = path.relative(path.resolve(basePath), path.resolve(targetPath));
180
+ return relative === '' || (relative && !relative.startsWith('..') && !path.isAbsolute(relative));
181
+ }
182
+
183
+ function normalizeAllowedRoots(input) {
184
+ const roots = Array.isArray(input) ? input : [];
185
+ const normalized = roots
186
+ .filter((entry) => typeof entry === 'string')
187
+ .map((entry) => entry.trim().replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/+$/g, ''))
188
+ .map((entry) => entry || '.')
189
+ .filter((entry) => !entry.includes('..'));
190
+ return Array.from(new Set(normalized.length > 0 ? normalized : ['.']));
191
+ }
192
+
193
+ function collaboratorAllowedRoots(collaborator) {
194
+ return normalizeAllowedRoots(collaborator.allowedRoots || collaborator.allowedFolders || ['.']);
195
+ }
196
+
177
197
  export function userHasProjectAccess(user, project, capability = 'viewFiles') {
178
198
  if (isAdminUser(user)) return true;
179
199
  if (!user?.id && !user?.userId) return false;
@@ -198,6 +218,55 @@ export function userHasProjectAccess(user, project, capability = 'viewFiles') {
198
218
  });
199
219
  }
200
220
 
221
+ export function getProjectAccessForUser(user, project, capability = 'viewFiles') {
222
+ if (isAdminUser(user)) {
223
+ return { unrestricted: true, allowedRoots: ['.'] };
224
+ }
225
+ if (!user?.id && !user?.userId) return { unrestricted: false, allowedRoots: [] };
226
+
227
+ const userId = Number(user.id ?? user.userId);
228
+ const username = String(user.username || '').toLowerCase();
229
+ const store = readStore();
230
+ const allowedRoots = [];
231
+
232
+ for (const collaborator of store.projectCollaborators) {
233
+ if (collaborator.status === 'disabled') continue;
234
+ if (!projectMatches(collaborator, project)) continue;
235
+ const sameUser = Number(collaborator.userId) === userId ||
236
+ String(collaborator.userRef || '').toLowerCase() === username;
237
+ if (!sameUser) continue;
238
+ const capabilityAllowed = capability === 'viewFiles'
239
+ ? collaborator.capabilities?.viewFiles !== false
240
+ : collaborator.capabilities?.[capability] === true;
241
+ if (!capabilityAllowed) continue;
242
+ allowedRoots.push(...collaboratorAllowedRoots(collaborator));
243
+ }
244
+
245
+ return { unrestricted: false, allowedRoots: Array.from(new Set(allowedRoots)) };
246
+ }
247
+
248
+ export function userHasProjectPathAccess(user, project, targetPath, capability = 'viewFiles') {
249
+ if (isAdminUser(user)) return true;
250
+ const projectPath = project?.fullPath || project?.path || project?.projectPath;
251
+ if (!projectPath || !targetPath) return false;
252
+ const access = getProjectAccessForUser(user, project, capability);
253
+ return access.allowedRoots.some((root) => {
254
+ const allowedPath = root === '.' ? projectPath : path.resolve(projectPath, root);
255
+ return isPathInside(allowedPath, targetPath);
256
+ });
257
+ }
258
+
259
+ export function filterFileTreeForUser(files = [], user, project, capability = 'viewFiles') {
260
+ if (isAdminUser(user)) return files;
261
+ const projectPath = project?.fullPath || project?.path || project?.projectPath;
262
+ if (!projectPath) return [];
263
+ return files.filter((entry) => {
264
+ const entryPath = entry?.path || entry?.fullPath || entry?.relativePath || '';
265
+ const absoluteEntryPath = path.isAbsolute(entryPath) ? entryPath : path.resolve(projectPath, entryPath);
266
+ return userHasProjectPathAccess(user, { ...project, fullPath: projectPath }, absoluteEntryPath, capability);
267
+ });
268
+ }
269
+
201
270
  export function filterProjectsForUser(projects = [], user) {
202
271
  if (isAdminUser(user)) return projects;
203
272
  return projects.filter((project) => userHasProjectAccess(user, project, 'viewFiles'));
@@ -427,11 +496,12 @@ export function createProjectCollaborator(input = {}, actorId = null) {
427
496
  userId: targetUser.id,
428
497
  userRef,
429
498
  role,
430
- capabilities: {
431
- ...capabilities,
432
- ...(input.capabilities && typeof input.capabilities === 'object' ? input.capabilities : {}),
433
- },
434
- status: input.status || 'active',
499
+ capabilities: {
500
+ ...capabilities,
501
+ ...(input.capabilities && typeof input.capabilities === 'object' ? input.capabilities : {}),
502
+ },
503
+ allowedRoots: normalizeAllowedRoots(input.allowedRoots || input.allowedFolders || ['.']),
504
+ status: input.status || 'active',
435
505
  createdAt: nowIso(),
436
506
  updatedAt: nowIso(),
437
507
  };
@@ -451,11 +521,14 @@ export function updateProjectCollaborator(collaboratorId, patch = {}, actorId =
451
521
  ...collaborator,
452
522
  ...patch,
453
523
  id: collaborator.id,
454
- capabilities: {
455
- ...collaborator.capabilities,
456
- ...(patch.capabilities && typeof patch.capabilities === 'object' ? patch.capabilities : {}),
457
- },
458
- updatedAt: nowIso(),
524
+ capabilities: {
525
+ ...collaborator.capabilities,
526
+ ...(patch.capabilities && typeof patch.capabilities === 'object' ? patch.capabilities : {}),
527
+ },
528
+ allowedRoots: patch.allowedRoots || patch.allowedFolders
529
+ ? normalizeAllowedRoots(patch.allowedRoots || patch.allowedFolders)
530
+ : collaboratorAllowedRoots(collaborator),
531
+ updatedAt: nowIso(),
459
532
  };
460
533
  return updated;
461
534
  });