u
This commit is contained in:
@@ -4,6 +4,7 @@ import multer from 'multer';
|
|||||||
import JSZip from 'jszip';
|
import JSZip from 'jszip';
|
||||||
import env from '../../config/env.js';
|
import env from '../../config/env.js';
|
||||||
import * as db from '../../db/company.js';
|
import * as db from '../../db/company.js';
|
||||||
|
import { requireCompanyAdmin } from '../../db/companyuser.js';
|
||||||
import * as googleAuth from '../../db/googleAuth.js';
|
import * as googleAuth from '../../db/googleAuth.js';
|
||||||
import { clearCachedUser } from '../middleware/auth.js';
|
import { clearCachedUser } from '../middleware/auth.js';
|
||||||
import { emit } from '../sse.js';
|
import { emit } from '../sse.js';
|
||||||
@@ -499,13 +500,25 @@ export function register(app, basePath = '') {
|
|||||||
|
|
||||||
app.post(
|
app.post(
|
||||||
`${basePath}/company/import`,
|
`${basePath}/company/import`,
|
||||||
|
async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const companyId = requireSelectedCompany(req);
|
||||||
|
await requireCompanyAdmin(req.user.id, companyId);
|
||||||
|
next();
|
||||||
|
} catch (error) {
|
||||||
|
const status = companyErrorStatus(error);
|
||||||
|
return res.status(status).json({ error: error.message });
|
||||||
|
}
|
||||||
|
},
|
||||||
uploadCompanyImportFile,
|
uploadCompanyImportFile,
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
let companyId;
|
let companyId;
|
||||||
try {
|
try {
|
||||||
companyId = requireSelectedCompany(req);
|
companyId = requireSelectedCompany(req);
|
||||||
|
await requireCompanyAdmin(req.user.id, companyId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return res.status(error.statusCode || 400).json({ error: error.message });
|
const status = companyErrorStatus(error);
|
||||||
|
return res.status(status).json({ error: error.message });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeImportsByCompany.has(companyId)) {
|
if (activeImportsByCompany.has(companyId)) {
|
||||||
@@ -968,7 +981,7 @@ export function register(app, basePath = '') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const company = await db.updateCompany(parsedId.companyId, body);
|
const company = await db.updateCompany(parsedId.companyId, body, req.user.id);
|
||||||
emit('company', { companyId: parsedId.companyId, data: { companyId: parsedId.companyId } });
|
emit('company', { companyId: parsedId.companyId, data: { companyId: parsedId.companyId } });
|
||||||
res.status(200).json({ company });
|
res.status(200).json({ company });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -989,10 +1002,14 @@ export function register(app, basePath = '') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await db.deleteCompany(parsedId.companyId);
|
await db.deleteCompany(parsedId.companyId, req.user.id);
|
||||||
clearCompanyPrinters(parsedId.companyId);
|
clearCompanyPrinters(parsedId.companyId);
|
||||||
res.sendStatus(204);
|
res.sendStatus(204);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
const status = companyErrorStatus(error);
|
||||||
|
if (status !== 500) {
|
||||||
|
return res.status(status).json({ error: error.message });
|
||||||
|
}
|
||||||
console.error('Delete company failed:', error);
|
console.error('Delete company failed:', error);
|
||||||
res.status(500).json({ error: 'Failed to delete company' });
|
res.status(500).json({ error: 'Failed to delete company' });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ import fs from 'fs';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import multer from 'multer';
|
import multer from 'multer';
|
||||||
import { emit } from '../sse.js';
|
import { emit } from '../sse.js';
|
||||||
|
import { requireCompanyAdmin } from '../../db/companyuser.js';
|
||||||
import {
|
import {
|
||||||
prepareVfs,
|
prepareVfs,
|
||||||
resolveVfsRoot,
|
resolveVfsRoot,
|
||||||
isReadOnlyDatabasePath,
|
isReadOnlyDatabasePath,
|
||||||
|
isProtectedAdminVfsPath,
|
||||||
|
directoryContainsProtectedAdminVfsFile,
|
||||||
getMimeType,
|
getMimeType,
|
||||||
restoreDefaultSkills,
|
restoreDefaultSkills,
|
||||||
restoreDefaultApplets,
|
restoreDefaultApplets,
|
||||||
@@ -586,6 +589,8 @@ export function register(app, basePath = '') {
|
|||||||
: '';
|
: '';
|
||||||
const uploaded = [];
|
const uploaded = [];
|
||||||
|
|
||||||
|
const parsedEntries = [];
|
||||||
|
let touchesProtected = false;
|
||||||
for (const f of files) {
|
for (const f of files) {
|
||||||
const origName = (f.originalname || 'upload').replace(/[\0\\]/g, '/');
|
const origName = (f.originalname || 'upload').replace(/[\0\\]/g, '/');
|
||||||
const relPath = targetDir ? `${targetDir}/${origName}` : origName;
|
const relPath = targetDir ? `${targetDir}/${origName}` : origName;
|
||||||
@@ -596,6 +601,18 @@ export function register(app, basePath = '') {
|
|||||||
|
|
||||||
const absPath = path.resolve(vfsRoot, parsed.name);
|
const absPath = path.resolve(vfsRoot, parsed.name);
|
||||||
if (!absPath.startsWith(vfsRoot)) continue;
|
if (!absPath.startsWith(vfsRoot)) continue;
|
||||||
|
|
||||||
|
if (isProtectedAdminVfsPath(parsed.name)) {
|
||||||
|
touchesProtected = true;
|
||||||
|
}
|
||||||
|
parsedEntries.push({ f, parsed, absPath });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (touchesProtected) {
|
||||||
|
await requireCompanyAdmin(req.user.id, selected.companyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const { f, parsed, absPath } of parsedEntries) {
|
||||||
fs.mkdirSync(path.dirname(absPath), { recursive: true });
|
fs.mkdirSync(path.dirname(absPath), { recursive: true });
|
||||||
fs.writeFileSync(absPath, f.buffer);
|
fs.writeFileSync(absPath, f.buffer);
|
||||||
|
|
||||||
@@ -815,6 +832,15 @@ export function register(app, basePath = '') {
|
|||||||
return res.status(404).json({ error: 'Source file or directory not found' });
|
return res.status(404).json({ error: 'Source file or directory not found' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
isProtectedAdminVfsPath(parsedOld.name) ||
|
||||||
|
isProtectedAdminVfsPath(parsedNew.name) ||
|
||||||
|
directoryContainsProtectedAdminVfsFile(absOld) ||
|
||||||
|
directoryContainsProtectedAdminVfsFile(absNew)
|
||||||
|
) {
|
||||||
|
await requireCompanyAdmin(req.user.id, selected.companyId);
|
||||||
|
}
|
||||||
|
|
||||||
fs.mkdirSync(path.dirname(absNew), { recursive: true });
|
fs.mkdirSync(path.dirname(absNew), { recursive: true });
|
||||||
fs.renameSync(absOld, absNew);
|
fs.renameSync(absOld, absNew);
|
||||||
|
|
||||||
@@ -961,6 +987,9 @@ export function register(app, basePath = '') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (isProtectedAdminVfsPath(parsed.name)) {
|
||||||
|
await requireCompanyAdmin(req.user.id, selected.companyId);
|
||||||
|
}
|
||||||
const vfsRoot = await resolveVfsRoot(req.user.id);
|
const vfsRoot = await resolveVfsRoot(req.user.id);
|
||||||
const absPath = path.resolve(vfsRoot, parsed.name);
|
const absPath = path.resolve(vfsRoot, parsed.name);
|
||||||
if (!absPath.startsWith(vfsRoot)) {
|
if (!absPath.startsWith(vfsRoot)) {
|
||||||
@@ -1058,6 +1087,9 @@ export function register(app, basePath = '') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (isProtectedAdminVfsPath(parsed.name)) {
|
||||||
|
await requireCompanyAdmin(req.user.id, selected.companyId);
|
||||||
|
}
|
||||||
const vfsRoot = await resolveVfsRoot(req.user.id);
|
const vfsRoot = await resolveVfsRoot(req.user.id);
|
||||||
const absPath = path.resolve(vfsRoot, parsed.name);
|
const absPath = path.resolve(vfsRoot, parsed.name);
|
||||||
if (!absPath.startsWith(vfsRoot)) {
|
if (!absPath.startsWith(vfsRoot)) {
|
||||||
@@ -1066,6 +1098,9 @@ export function register(app, basePath = '') {
|
|||||||
if (!fs.existsSync(absPath)) {
|
if (!fs.existsSync(absPath)) {
|
||||||
return res.status(404).json({ error: 'File not found' });
|
return res.status(404).json({ error: 'File not found' });
|
||||||
}
|
}
|
||||||
|
if (directoryContainsProtectedAdminVfsFile(absPath)) {
|
||||||
|
await requireCompanyAdmin(req.user.id, selected.companyId);
|
||||||
|
}
|
||||||
const stat = fs.statSync(absPath);
|
const stat = fs.statSync(absPath);
|
||||||
if (stat.isDirectory()) {
|
if (stat.isDirectory()) {
|
||||||
fs.rmSync(absPath, { recursive: true, force: true });
|
fs.rmSync(absPath, { recursive: true, force: true });
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { getStore } from './store.js';
|
import { getStore } from './store.js';
|
||||||
|
import { requireSelectedCompanyAdmin } from './companyuser.js';
|
||||||
|
|
||||||
export async function list(googleAuthId) {
|
export async function list(googleAuthId) {
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
@@ -6,11 +7,13 @@ export async function list(googleAuthId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function create(googleAuthId, data) {
|
export async function create(googleAuthId, data) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.account.create(googleAuthId, data);
|
return store.account.create(googleAuthId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function remove(googleAuthId, id) {
|
export async function remove(googleAuthId, id) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.account.delete(googleAuthId, id);
|
return store.account.delete(googleAuthId, id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { getStore } from './store.js';
|
import { getStore } from './store.js';
|
||||||
|
import { requireSelectedCompanyAdmin } from './companyuser.js';
|
||||||
|
|
||||||
export async function list(googleAuthId, printeragentId) {
|
export async function list(googleAuthId, printeragentId) {
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
@@ -16,16 +17,19 @@ export async function findOrCreate(printeragentId, data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function update(googleAuthId, data) {
|
export async function update(googleAuthId, data) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.agentprinter.update(googleAuthId, data);
|
return store.agentprinter.update(googleAuthId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function order(googleAuthId, data) {
|
export async function order(googleAuthId, data) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.agentprinter.order(googleAuthId, data);
|
return store.agentprinter.order(googleAuthId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function remove(googleAuthId, id) {
|
export async function remove(googleAuthId, id) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.agentprinter.delete(googleAuthId, id);
|
return store.agentprinter.delete(googleAuthId, id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { getStore } from './store.js';
|
import { getStore } from './store.js';
|
||||||
|
import { requireSelectedCompanyAdmin } from './companyuser.js';
|
||||||
|
|
||||||
export async function list(googleAuthId) {
|
export async function list(googleAuthId) {
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
@@ -11,21 +12,25 @@ export async function getById(googleAuthId, id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function create(googleAuthId, data) {
|
export async function create(googleAuthId, data) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.carrier.create(googleAuthId, data);
|
return store.carrier.create(googleAuthId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function update(googleAuthId, id, data) {
|
export async function update(googleAuthId, id, data) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.carrier.update(googleAuthId, id, data);
|
return store.carrier.update(googleAuthId, id, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function order(googleAuthId, data) {
|
export async function order(googleAuthId, data) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.carrier.order(googleAuthId, data);
|
return store.carrier.order(googleAuthId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function remove(googleAuthId, id) {
|
export async function remove(googleAuthId, id) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.carrier.delete(googleAuthId, id);
|
return store.carrier.delete(googleAuthId, id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { getStore, getProvider } from './store.js';
|
import { getStore, getProvider } from './store.js';
|
||||||
|
import { requireCompanyAdmin } from './companyuser.js';
|
||||||
|
|
||||||
export async function listCompanies(authId) {
|
export async function listCompanies(authId) {
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
@@ -15,12 +16,18 @@ export async function createCompany(googleAuthId, data) {
|
|||||||
return store.company.create({ ...data, googleAuthId });
|
return store.company.create({ ...data, googleAuthId });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateCompany(id, data) {
|
export async function updateCompany(id, data, googleAuthId) {
|
||||||
|
if (googleAuthId != null) {
|
||||||
|
await requireCompanyAdmin(googleAuthId, id);
|
||||||
|
}
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.company.update(id, data);
|
return store.company.update(id, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteCompany(companyId) {
|
export async function deleteCompany(companyId, googleAuthId) {
|
||||||
|
if (googleAuthId != null) {
|
||||||
|
await requireCompanyAdmin(googleAuthId, companyId);
|
||||||
|
}
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
await store.company.delete(companyId);
|
await store.company.delete(companyId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,15 @@
|
|||||||
import { getStore } from './store.js';
|
import { getStore } from './store.js';
|
||||||
|
import { requireSelectedCompanyAdmin } from './companyuser.js';
|
||||||
|
|
||||||
|
export const ADMIN_ONLY_SETTING_KEYS = new Set([
|
||||||
|
'ui.theme',
|
||||||
|
'keys.tavily',
|
||||||
|
'keys.openrouter',
|
||||||
|
'keys.serper',
|
||||||
|
'modelslugimage',
|
||||||
|
'systemprompt-prefix',
|
||||||
|
'systemprompt-suffix',
|
||||||
|
]);
|
||||||
|
|
||||||
export async function list(googleAuthId, options) {
|
export async function list(googleAuthId, options) {
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
@@ -11,6 +22,9 @@ export async function getByKey(googleAuthId, key, options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function set(googleAuthId, input) {
|
export async function set(googleAuthId, input) {
|
||||||
|
if (ADMIN_ONLY_SETTING_KEYS.has(input?.key)) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
|
}
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.companysetting.set(googleAuthId, input);
|
return store.companysetting.set(googleAuthId, input);
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,53 @@
|
|||||||
import { getStore } from './store.js';
|
import { getStore, getProvider } from './store.js';
|
||||||
|
|
||||||
|
export async function resolveCompanyAccess(googleAuthId, companyId) {
|
||||||
|
const provider = await getProvider();
|
||||||
|
const access = await provider.query(
|
||||||
|
`SELECT company_id, role FROM companyuser WHERE company_id = @companyId AND google_auth_id = @googleAuthId`,
|
||||||
|
{ companyId, googleAuthId }
|
||||||
|
);
|
||||||
|
const accessRow = access.rows[0] ?? null;
|
||||||
|
if (!accessRow) {
|
||||||
|
const exists = await provider.query(
|
||||||
|
`SELECT id FROM company WHERE id = @companyId`,
|
||||||
|
{ companyId }
|
||||||
|
);
|
||||||
|
if (!exists.rows[0]) {
|
||||||
|
throw new Error('Company not found');
|
||||||
|
}
|
||||||
|
throw new Error('Not authorized');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
companyId: accessRow.company_id,
|
||||||
|
role: accessRow.role ?? 'user',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resolveSelectedCompanyAccess(googleAuthId) {
|
||||||
|
const store = await getStore();
|
||||||
|
const user = await store.google_auth.getById(googleAuthId);
|
||||||
|
const companyId = user?.selectedCompanyId ?? null;
|
||||||
|
if (companyId == null) {
|
||||||
|
throw new Error('No company selected');
|
||||||
|
}
|
||||||
|
return resolveCompanyAccess(googleAuthId, companyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function requireCompanyAdmin(googleAuthId, companyId) {
|
||||||
|
const ctx = await resolveCompanyAccess(googleAuthId, companyId);
|
||||||
|
if (ctx.role !== 'admin') {
|
||||||
|
throw new Error('Not authorized');
|
||||||
|
}
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function requireSelectedCompanyAdmin(googleAuthId) {
|
||||||
|
const ctx = await resolveSelectedCompanyAccess(googleAuthId);
|
||||||
|
if (ctx.role !== 'admin') {
|
||||||
|
throw new Error('Not authorized');
|
||||||
|
}
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
export async function list(googleAuthId) {
|
export async function list(googleAuthId) {
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { getStore } from './store.js';
|
import { getStore } from './store.js';
|
||||||
|
import { requireSelectedCompanyAdmin } from './companyuser.js';
|
||||||
|
|
||||||
export async function list(googleAuthId) {
|
export async function list(googleAuthId) {
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
@@ -6,21 +7,25 @@ export async function list(googleAuthId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function create(googleAuthId, data) {
|
export async function create(googleAuthId, data) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.customergroup.create(googleAuthId, data);
|
return store.customergroup.create(googleAuthId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function update(googleAuthId, id, data) {
|
export async function update(googleAuthId, id, data) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.customergroup.update(googleAuthId, id, data);
|
return store.customergroup.update(googleAuthId, id, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function order(googleAuthId, data) {
|
export async function order(googleAuthId, data) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.customergroup.order(googleAuthId, data);
|
return store.customergroup.order(googleAuthId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function remove(googleAuthId, id) {
|
export async function remove(googleAuthId, id) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.customergroup.delete(googleAuthId, id);
|
return store.customergroup.delete(googleAuthId, id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { getStore } from './store.js';
|
import { getStore } from './store.js';
|
||||||
|
import { requireSelectedCompanyAdmin } from './companyuser.js';
|
||||||
|
|
||||||
export async function list(googleAuthId) {
|
export async function list(googleAuthId) {
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
@@ -6,6 +7,7 @@ export async function list(googleAuthId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function update(googleAuthId, id, input) {
|
export async function update(googleAuthId, id, input) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.numbers.update(googleAuthId, id, input);
|
return store.numbers.update(googleAuthId, id, input);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { getStore, getProvider } from './store.js';
|
import { getStore, getProvider } from './store.js';
|
||||||
|
import { requireSelectedCompanyAdmin } from './companyuser.js';
|
||||||
|
|
||||||
export async function list(googleAuthId) {
|
export async function list(googleAuthId) {
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
@@ -6,11 +7,13 @@ export async function list(googleAuthId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function upsert(googleAuthId, data) {
|
export async function upsert(googleAuthId, data) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.posterminal.upsert(googleAuthId, data);
|
return store.posterminal.upsert(googleAuthId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function remove(googleAuthId, id) {
|
export async function remove(googleAuthId, id) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.posterminal.delete(googleAuthId, id);
|
return store.posterminal.delete(googleAuthId, id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { getStore } from './store.js';
|
import { getStore } from './store.js';
|
||||||
|
import { requireSelectedCompanyAdmin } from './companyuser.js';
|
||||||
|
|
||||||
export async function list(googleAuthId) {
|
export async function list(googleAuthId) {
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
@@ -11,21 +12,25 @@ export async function getById(googleAuthId, id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function create(googleAuthId, data) {
|
export async function create(googleAuthId, data) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.printer.create(googleAuthId, data);
|
return store.printer.create(googleAuthId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function update(googleAuthId, id, data) {
|
export async function update(googleAuthId, id, data) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.printer.update(googleAuthId, id, data);
|
return store.printer.update(googleAuthId, id, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function order(googleAuthId, data) {
|
export async function order(googleAuthId, data) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.printer.order(googleAuthId, data);
|
return store.printer.order(googleAuthId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function remove(googleAuthId, id) {
|
export async function remove(googleAuthId, id) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.printer.delete(googleAuthId, id);
|
return store.printer.delete(googleAuthId, id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { getStore } from './store.js';
|
import { getStore } from './store.js';
|
||||||
|
import { requireSelectedCompanyAdmin } from './companyuser.js';
|
||||||
|
|
||||||
export async function list(googleAuthId) {
|
export async function list(googleAuthId) {
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
@@ -6,16 +7,19 @@ export async function list(googleAuthId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function create(googleAuthId, data) {
|
export async function create(googleAuthId, data) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.printeragent.create(googleAuthId, data);
|
return store.printeragent.create(googleAuthId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function update(googleAuthId, data) {
|
export async function update(googleAuthId, data) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.printeragent.update(googleAuthId, data);
|
return store.printeragent.update(googleAuthId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function remove(googleAuthId, id) {
|
export async function remove(googleAuthId, id) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.printeragent.delete(googleAuthId, id);
|
return store.printeragent.delete(googleAuthId, id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { getStore } from './store.js';
|
import { getStore } from './store.js';
|
||||||
|
import { requireSelectedCompanyAdmin } from './companyuser.js';
|
||||||
|
|
||||||
export async function list(googleAuthId) {
|
export async function list(googleAuthId) {
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
@@ -11,21 +12,25 @@ export async function getById(googleAuthId, id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function create(googleAuthId, data) {
|
export async function create(googleAuthId, data) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.warehouse.create(googleAuthId, data);
|
return store.warehouse.create(googleAuthId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function update(googleAuthId, id, data) {
|
export async function update(googleAuthId, id, data) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.warehouse.update(googleAuthId, id, data);
|
return store.warehouse.update(googleAuthId, id, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function order(googleAuthId, data) {
|
export async function order(googleAuthId, data) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.warehouse.order(googleAuthId, data);
|
return store.warehouse.order(googleAuthId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function remove(googleAuthId, id) {
|
export async function remove(googleAuthId, id) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.warehouse.delete(googleAuthId, id);
|
return store.warehouse.delete(googleAuthId, id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -344,8 +344,8 @@ export async function runSandboxJob(options) {
|
|||||||
throw new Error('Sandbox worker pool is shutting down');
|
throw new Error('Sandbox worker pool is shutting down');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { poolKey, googleAuthId, code, timeoutMs, dashbackToken, appletInput, appletFile } = options;
|
const { poolKey, googleAuthId, isAdmin = true, code, timeoutMs, dashbackToken, appletInput, appletFile } = options;
|
||||||
const job = { code, timeoutMs, dashbackToken, appletInput, appletFile, poolKey, googleAuthId };
|
const job = { code, timeoutMs, dashbackToken, appletInput, appletFile, poolKey, googleAuthId, isAdmin };
|
||||||
|
|
||||||
const entry = await acquireWorker(poolKey, googleAuthId);
|
const entry = await acquireWorker(poolKey, googleAuthId);
|
||||||
if (entry) {
|
if (entry) {
|
||||||
|
|||||||
@@ -544,7 +544,7 @@ async function runJob(job) {
|
|||||||
const appletInput = job.appletInput;
|
const appletInput = job.appletInput;
|
||||||
const appletFile = job.appletFile;
|
const appletFile = job.appletFile;
|
||||||
|
|
||||||
const vfs = createVfs(vfsDir, { googleAuthId });
|
const vfs = createVfs(vfsDir, { googleAuthId, isAdmin: job.isAdmin !== false });
|
||||||
|
|
||||||
const bridgedExcel = bridgeExcelJS(vfs);
|
const bridgedExcel = bridgeExcelJS(vfs);
|
||||||
const bridgedPDF = bridgePDFKit(vfs);
|
const bridgedPDF = bridgePDFKit(vfs);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { defineTool, ToolError } from 'libllmorbiter';
|
|||||||
import env from '../config/env.js';
|
import env from '../config/env.js';
|
||||||
import { TOOL_DESCRIPTIONS, TOOL_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
import { TOOL_DESCRIPTIONS, TOOL_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
||||||
import { createVfs, prepareVfs } from './vfs.js';
|
import { createVfs, prepareVfs } from './vfs.js';
|
||||||
|
import { resolveSelectedCompanyAccess } from '../db/companyuser.js';
|
||||||
import { runSandboxJob } from '../services/javascriptSandbox.js';
|
import { runSandboxJob } from '../services/javascriptSandbox.js';
|
||||||
|
|
||||||
const DEFAULT_TIMEOUT_MS = 20_000;
|
const DEFAULT_TIMEOUT_MS = 20_000;
|
||||||
@@ -184,11 +185,21 @@ function toTypedCsv(result, columns) {
|
|||||||
*
|
*
|
||||||
* @param {{ code: string, timeoutMs: number, vfsRoot: string, googleAuthId: number, appletInput?: object, appletFile?: string }} ctx
|
* @param {{ code: string, timeoutMs: number, vfsRoot: string, googleAuthId: number, appletInput?: object, appletFile?: string }} ctx
|
||||||
*/
|
*/
|
||||||
function runInWorker({ code, timeoutMs, vfsRoot, googleAuthId, appletInput, appletFile }) {
|
async function runInWorker({ code, timeoutMs, vfsRoot, googleAuthId, appletInput, appletFile }) {
|
||||||
|
let isAdmin = true;
|
||||||
|
if (googleAuthId != null) {
|
||||||
|
try {
|
||||||
|
const access = await resolveSelectedCompanyAccess(googleAuthId);
|
||||||
|
isAdmin = access.role === 'admin';
|
||||||
|
} catch {
|
||||||
|
isAdmin = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
const dashbackToken = jwt.sign({ id: googleAuthId }, env.jwtSecret, { expiresIn: '5m' });
|
const dashbackToken = jwt.sign({ id: googleAuthId }, env.jwtSecret, { expiresIn: '5m' });
|
||||||
return runSandboxJob({
|
return runSandboxJob({
|
||||||
poolKey: vfsRoot,
|
poolKey: vfsRoot,
|
||||||
googleAuthId,
|
googleAuthId,
|
||||||
|
isAdmin,
|
||||||
code,
|
code,
|
||||||
timeoutMs,
|
timeoutMs,
|
||||||
dashbackToken,
|
dashbackToken,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { defineTool, ToolError } from 'libllmorbiter';
|
import { defineTool, ToolError } from 'libllmorbiter';
|
||||||
import { isReadOnlyDatabasePath } from './vfs.js';
|
import { isReadOnlyDatabasePath, isProtectedAdminVfsPath, prepareVfs } from './vfs.js';
|
||||||
|
import { requireSelectedCompanyAdmin } from '../db/companyuser.js';
|
||||||
import { isSkillPath, renderSkill, parseSkill, SKILL_EXT, skillFileName } from './skill.js';
|
import { isSkillPath, renderSkill, parseSkill, SKILL_EXT, skillFileName } from './skill.js';
|
||||||
import { prepareVfs } from './vfs.js';
|
|
||||||
import { TOOL_DESCRIPTIONS, PATCH_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
import { TOOL_DESCRIPTIONS, PATCH_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
||||||
import { getStore } from '../db/store.js';
|
import { getStore } from '../db/store.js';
|
||||||
|
|
||||||
@@ -155,6 +155,10 @@ export function createPatchTool({ googleAuthId }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isProtectedAdminVfsPath(clean)) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
|
}
|
||||||
|
|
||||||
// A SKILL file must be flat — reject an attempted nested path up front.
|
// A SKILL file must be flat — reject an attempted nested path up front.
|
||||||
const looksSkillByExt = clean.toLowerCase().endsWith(SKILL_EXT.toLowerCase());
|
const looksSkillByExt = clean.toLowerCase().endsWith(SKILL_EXT.toLowerCase());
|
||||||
if (looksSkillByExt && clean.includes('/')) {
|
if (looksSkillByExt && clean.includes('/')) {
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import fs from 'fs';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { defineTool } from 'libllmorbiter';
|
import { defineTool } from 'libllmorbiter';
|
||||||
import { isSkillPath } from './skill.js';
|
import { isSkillPath } from './skill.js';
|
||||||
import { resolveVfsRoot } from './vfs.js';
|
import { resolveVfsRoot, isProtectedAdminVfsPath, directoryContainsProtectedAdminVfsFile } from './vfs.js';
|
||||||
|
import { resolveSelectedCompanyAccess, requireSelectedCompanyAdmin } from '../db/companyuser.js';
|
||||||
import { TOOL_DESCRIPTIONS, TOOL_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
import { TOOL_DESCRIPTIONS, TOOL_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,6 +41,11 @@ export function createResetVfsTool({ googleAuthId }) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
invoke: async ({ include_skills = false } = {}) => {
|
invoke: async ({ include_skills = false } = {}) => {
|
||||||
|
if (include_skills === true) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
|
}
|
||||||
|
const access = await resolveSelectedCompanyAccess(googleAuthId);
|
||||||
|
const isAdmin = access.role === 'admin';
|
||||||
const keepSkills = include_skills !== true;
|
const keepSkills = include_skills !== true;
|
||||||
const keptSkills = [];
|
const keptSkills = [];
|
||||||
const vfsRoot = await resolveVfsRoot(googleAuthId);
|
const vfsRoot = await resolveVfsRoot(googleAuthId);
|
||||||
@@ -51,8 +57,12 @@ export function createResetVfsTool({ googleAuthId }) {
|
|||||||
if (keepSkills && isSkillPath(entry)) {
|
if (keepSkills && isSkillPath(entry)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
const fullPath = path.join(vfsRoot, entry);
|
||||||
|
if (!isAdmin && (isProtectedAdminVfsPath(entry) || directoryContainsProtectedAdminVfsFile(fullPath))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
fs.rmSync(path.join(vfsRoot, entry), { recursive: true, force: true });
|
fs.rmSync(fullPath, { recursive: true, force: true });
|
||||||
deletedCount += 1;
|
deletedCount += 1;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(`[reset_vfs] could not remove "${entry}": ${err.message}`);
|
console.warn(`[reset_vfs] could not remove "${entry}": ${err.message}`);
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { defineTool, ToolError } from 'libllmorbiter';
|
import { defineTool, ToolError } from 'libllmorbiter';
|
||||||
import { isReadOnlyDatabasePath, prepareVfs } from './vfs.js';
|
import { isReadOnlyDatabasePath, isProtectedAdminVfsPath, prepareVfs } from './vfs.js';
|
||||||
|
import { requireSelectedCompanyAdmin } from '../db/companyuser.js';
|
||||||
import { isSkillPath } from './skill.js';
|
import { isSkillPath } from './skill.js';
|
||||||
import { TOOL_DESCRIPTIONS, TOOL_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
import { TOOL_DESCRIPTIONS, TOOL_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
||||||
|
|
||||||
@@ -94,6 +95,10 @@ export function createRmTool({ googleAuthId }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isProtectedAdminVfsPath(clean)) {
|
||||||
|
await requireSelectedCompanyAdmin(googleAuthId);
|
||||||
|
}
|
||||||
|
|
||||||
const vfsRoot = await prepareVfs({ googleAuthId });
|
const vfsRoot = await prepareVfs({ googleAuthId });
|
||||||
const { abs, size } = deleteVfsFile(vfsRoot, clean);
|
const { abs, size } = deleteVfsFile(vfsRoot, clean);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { getStore } from '../db/store.js';
|
import { getStore } from '../db/store.js';
|
||||||
|
import { requireSelectedCompanyAdmin } from '../db/companyuser.js';
|
||||||
import { getDefaultSkills } from './skill.js';
|
import { getDefaultSkills } from './skill.js';
|
||||||
import { getDefaultApplets, DEFAULT_APPLETS_VERSION } from './applet.js';
|
import { getDefaultApplets, DEFAULT_APPLETS_VERSION } from './applet.js';
|
||||||
|
|
||||||
@@ -97,6 +98,39 @@ export function isReadOnlyDatabasePath(filePath) {
|
|||||||
return lower === 'image' || lower.startsWith('image/') || Boolean(extractFullHash(clean));
|
return lower === 'image' || lower.startsWith('image/') || Boolean(extractFullHash(clean));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isProtectedAdminVfsPath(filePath) {
|
||||||
|
if (typeof filePath !== 'string' || !filePath.trim()) return false;
|
||||||
|
const clean = cleanVirtualPath(filePath, true);
|
||||||
|
const lower = clean.toLowerCase();
|
||||||
|
return (
|
||||||
|
lower.endsWith('.skill') ||
|
||||||
|
lower.endsWith('.applet.js') ||
|
||||||
|
lower.endsWith('.apple.js') ||
|
||||||
|
lower === 'apple.js' ||
|
||||||
|
lower.endsWith('/apple.js')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function directoryContainsProtectedAdminVfsFile(absDirPath) {
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(absDirPath)) return false;
|
||||||
|
const stat = fs.statSync(absDirPath);
|
||||||
|
if (!stat.isDirectory()) return isProtectedAdminVfsPath(absDirPath);
|
||||||
|
const entries = fs.readdirSync(absDirPath, { withFileTypes: true });
|
||||||
|
for (const entry of entries) {
|
||||||
|
const fullPath = path.join(absDirPath, entry.name);
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
if (directoryContainsProtectedAdminVfsFile(fullPath)) return true;
|
||||||
|
} else if (isProtectedAdminVfsPath(entry.name)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function assertNotReadOnlyDatabasePath(filePath) {
|
function assertNotReadOnlyDatabasePath(filePath) {
|
||||||
if (isReadOnlyDatabasePath(filePath)) {
|
if (isReadOnlyDatabasePath(filePath)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -190,6 +224,9 @@ export async function loadDefaultSkills(ctx = {}) {
|
|||||||
* @returns {Promise<Array<string>>}
|
* @returns {Promise<Array<string>>}
|
||||||
*/
|
*/
|
||||||
export async function restoreDefaultSkills(ctx = {}) {
|
export async function restoreDefaultSkills(ctx = {}) {
|
||||||
|
if (ctx.googleAuthId != null) {
|
||||||
|
await requireSelectedCompanyAdmin(ctx.googleAuthId);
|
||||||
|
}
|
||||||
return seedVfsFiles(ctx, getDefaultSkills(), { overwrite: true, label: 'skill' });
|
return seedVfsFiles(ctx, getDefaultSkills(), { overwrite: true, label: 'skill' });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,6 +245,9 @@ export async function loadDefaultApplets(ctx = {}) {
|
|||||||
* @returns {Promise<Array<string>>}
|
* @returns {Promise<Array<string>>}
|
||||||
*/
|
*/
|
||||||
export async function restoreDefaultApplets(ctx = {}) {
|
export async function restoreDefaultApplets(ctx = {}) {
|
||||||
|
if (ctx.googleAuthId != null) {
|
||||||
|
await requireSelectedCompanyAdmin(ctx.googleAuthId);
|
||||||
|
}
|
||||||
return seedVfsFiles(ctx, getVersionedApplets(), { overwrite: true, label: 'applet' });
|
return seedVfsFiles(ctx, getVersionedApplets(), { overwrite: true, label: 'applet' });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,7 +333,14 @@ function cbWrap(syncFn, arityWithOpt = 3) {
|
|||||||
* @param {{ googleAuthId: number }} [options]
|
* @param {{ googleAuthId: number }} [options]
|
||||||
*/
|
*/
|
||||||
export function createVfs(vfsRoot, options = {}) {
|
export function createVfs(vfsRoot, options = {}) {
|
||||||
const { googleAuthId } = options;
|
const { googleAuthId, isAdmin = true } = options;
|
||||||
|
|
||||||
|
const assertWritablePath = (filePath) => {
|
||||||
|
assertNotReadOnlyDatabasePath(filePath);
|
||||||
|
if (!isAdmin && isProtectedAdminVfsPath(filePath)) {
|
||||||
|
throw new Error('Not authorized');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// #region Internal Sandbox Helpers
|
// #region Internal Sandbox Helpers
|
||||||
// MARK: - Internal Sandbox Helpers
|
// MARK: - Internal Sandbox Helpers
|
||||||
@@ -405,7 +452,7 @@ export function createVfs(vfsRoot, options = {}) {
|
|||||||
|
|
||||||
// --- Write & Append Operations ---
|
// --- Write & Append Operations ---
|
||||||
writeFileSync: (p, data, opt) => {
|
writeFileSync: (p, data, opt) => {
|
||||||
assertNotReadOnlyDatabasePath(p);
|
assertWritablePath(p);
|
||||||
const diskPath = safeResolve(p);
|
const diskPath = safeResolve(p);
|
||||||
fs.mkdirSync(path.dirname(diskPath), { recursive: true });
|
fs.mkdirSync(path.dirname(diskPath), { recursive: true });
|
||||||
try {
|
try {
|
||||||
@@ -416,14 +463,14 @@ export function createVfs(vfsRoot, options = {}) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
createWriteStream: (p, opt) => {
|
createWriteStream: (p, opt) => {
|
||||||
assertNotReadOnlyDatabasePath(p);
|
assertWritablePath(p);
|
||||||
const diskPath = safeResolve(p);
|
const diskPath = safeResolve(p);
|
||||||
fs.mkdirSync(path.dirname(diskPath), { recursive: true });
|
fs.mkdirSync(path.dirname(diskPath), { recursive: true });
|
||||||
return fs.createWriteStream(diskPath, opt);
|
return fs.createWriteStream(diskPath, opt);
|
||||||
},
|
},
|
||||||
|
|
||||||
appendFileSync: (p, data, opt) => {
|
appendFileSync: (p, data, opt) => {
|
||||||
assertNotReadOnlyDatabasePath(p);
|
assertWritablePath(p);
|
||||||
const diskPath = safeResolve(p);
|
const diskPath = safeResolve(p);
|
||||||
fs.mkdirSync(path.dirname(diskPath), { recursive: true });
|
fs.mkdirSync(path.dirname(diskPath), { recursive: true });
|
||||||
try {
|
try {
|
||||||
@@ -435,7 +482,7 @@ export function createVfs(vfsRoot, options = {}) {
|
|||||||
|
|
||||||
// --- Copy, Rename & Truncate Operations ---
|
// --- Copy, Rename & Truncate Operations ---
|
||||||
copyFileSync: (src, dest, mode) => {
|
copyFileSync: (src, dest, mode) => {
|
||||||
assertNotReadOnlyDatabasePath(dest);
|
assertWritablePath(dest);
|
||||||
const hash = extractFullHash(src);
|
const hash = extractFullHash(src);
|
||||||
if (hash) {
|
if (hash) {
|
||||||
const buf = syncFetchImage(hash);
|
const buf = syncFetchImage(hash);
|
||||||
@@ -452,7 +499,7 @@ export function createVfs(vfsRoot, options = {}) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
cpSync: (src, dest, opt) => {
|
cpSync: (src, dest, opt) => {
|
||||||
assertNotReadOnlyDatabasePath(dest);
|
assertWritablePath(dest);
|
||||||
const srcDisk = safeResolve(src);
|
const srcDisk = safeResolve(src);
|
||||||
const destDisk = safeResolve(dest);
|
const destDisk = safeResolve(dest);
|
||||||
fs.mkdirSync(path.dirname(destDisk), { recursive: true });
|
fs.mkdirSync(path.dirname(destDisk), { recursive: true });
|
||||||
@@ -464,10 +511,13 @@ export function createVfs(vfsRoot, options = {}) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
renameSync: (oldPath, newPath) => {
|
renameSync: (oldPath, newPath) => {
|
||||||
assertNotReadOnlyDatabasePath(oldPath);
|
assertWritablePath(oldPath);
|
||||||
assertNotReadOnlyDatabasePath(newPath);
|
assertWritablePath(newPath);
|
||||||
const oldDisk = safeResolve(oldPath);
|
const oldDisk = safeResolve(oldPath);
|
||||||
const newDisk = safeResolve(newPath);
|
const newDisk = safeResolve(newPath);
|
||||||
|
if (!isAdmin && (directoryContainsProtectedAdminVfsFile(oldDisk) || directoryContainsProtectedAdminVfsFile(newDisk))) {
|
||||||
|
throw new Error('Not authorized');
|
||||||
|
}
|
||||||
fs.mkdirSync(path.dirname(newDisk), { recursive: true });
|
fs.mkdirSync(path.dirname(newDisk), { recursive: true });
|
||||||
try {
|
try {
|
||||||
return fs.renameSync(oldDisk, newDisk);
|
return fs.renameSync(oldDisk, newDisk);
|
||||||
@@ -477,7 +527,7 @@ export function createVfs(vfsRoot, options = {}) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
truncateSync: (p, len) => {
|
truncateSync: (p, len) => {
|
||||||
assertNotReadOnlyDatabasePath(p);
|
assertWritablePath(p);
|
||||||
const diskPath = safeResolve(p);
|
const diskPath = safeResolve(p);
|
||||||
try {
|
try {
|
||||||
return fs.truncateSync(diskPath, len);
|
return fs.truncateSync(diskPath, len);
|
||||||
@@ -600,7 +650,7 @@ export function createVfs(vfsRoot, options = {}) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
unlinkSync: (p) => {
|
unlinkSync: (p) => {
|
||||||
assertNotReadOnlyDatabasePath(p);
|
assertWritablePath(p);
|
||||||
const diskPath = safeResolve(p);
|
const diskPath = safeResolve(p);
|
||||||
try {
|
try {
|
||||||
return fs.unlinkSync(diskPath);
|
return fs.unlinkSync(diskPath);
|
||||||
@@ -610,8 +660,11 @@ export function createVfs(vfsRoot, options = {}) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
rmdirSync: (p, opt) => {
|
rmdirSync: (p, opt) => {
|
||||||
assertNotReadOnlyDatabasePath(p);
|
assertWritablePath(p);
|
||||||
const diskPath = safeResolve(p);
|
const diskPath = safeResolve(p);
|
||||||
|
if (!isAdmin && directoryContainsProtectedAdminVfsFile(diskPath)) {
|
||||||
|
throw new Error('Not authorized');
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
return fs.rmdirSync(diskPath, opt);
|
return fs.rmdirSync(diskPath, opt);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -620,8 +673,11 @@ export function createVfs(vfsRoot, options = {}) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
rmSync: (p, opt) => {
|
rmSync: (p, opt) => {
|
||||||
assertNotReadOnlyDatabasePath(p);
|
assertWritablePath(p);
|
||||||
const diskPath = safeResolve(p);
|
const diskPath = safeResolve(p);
|
||||||
|
if (!isAdmin && directoryContainsProtectedAdminVfsFile(diskPath)) {
|
||||||
|
throw new Error('Not authorized');
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
return fs.rmSync(diskPath, opt);
|
return fs.rmSync(diskPath, opt);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -662,7 +718,7 @@ export function createVfs(vfsRoot, options = {}) {
|
|||||||
writeFile: async (p, data, opt) => vfs.writeFileSync(p, data, opt),
|
writeFile: async (p, data, opt) => vfs.writeFileSync(p, data, opt),
|
||||||
appendFile: async (p, data, opt) => vfs.appendFileSync(p, data, opt),
|
appendFile: async (p, data, opt) => vfs.appendFileSync(p, data, opt),
|
||||||
copyFile: async (src, dest, mode) => {
|
copyFile: async (src, dest, mode) => {
|
||||||
assertNotReadOnlyDatabasePath(dest);
|
assertWritablePath(dest);
|
||||||
const hash = extractFullHash(src);
|
const hash = extractFullHash(src);
|
||||||
if (hash) {
|
if (hash) {
|
||||||
const buf = await vfs.promises.readFile(src);
|
const buf = await vfs.promises.readFile(src);
|
||||||
@@ -695,7 +751,7 @@ export function createVfs(vfsRoot, options = {}) {
|
|||||||
* containment + read-only guards as `writeFileSync`.
|
* containment + read-only guards as `writeFileSync`.
|
||||||
*/
|
*/
|
||||||
resolveWritePath: (p) => {
|
resolveWritePath: (p) => {
|
||||||
assertNotReadOnlyDatabasePath(p);
|
assertWritablePath(p);
|
||||||
return safeResolve(p);
|
return safeResolve(p);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user