parse-dashboard 9.0.0-alpha.7 → 9.0.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/Parse-Dashboard/app.js
CHANGED
|
@@ -158,7 +158,8 @@ module.exports = function(config, options) {
|
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
if (typeof app.masterKey === 'function') {
|
|
161
|
-
|
|
161
|
+
const cacheKey = matchingAccess.readOnly ? 'readOnlyMasterKey' : 'masterKey';
|
|
162
|
+
app.masterKey = await ConfigKeyCache.get(app.appId, cacheKey, app.masterKeyTtl, app.masterKey);
|
|
162
163
|
}
|
|
163
164
|
|
|
164
165
|
return app;
|
|
@@ -197,9 +198,11 @@ module.exports = function(config, options) {
|
|
|
197
198
|
// In-memory conversation storage (consider using Redis in future)
|
|
198
199
|
const conversations = new Map();
|
|
199
200
|
|
|
200
|
-
// Agent API endpoint
|
|
201
|
-
|
|
201
|
+
// Agent API endpoint handler
|
|
202
|
+
async function agentHandler(req, res) {
|
|
202
203
|
try {
|
|
204
|
+
const authentication = req.user;
|
|
205
|
+
|
|
203
206
|
const { message, modelName, conversationId, permissions } = req.body || {};
|
|
204
207
|
const { appId } = req.params;
|
|
205
208
|
|
|
@@ -221,11 +224,40 @@ module.exports = function(config, options) {
|
|
|
221
224
|
}
|
|
222
225
|
|
|
223
226
|
// Find the app in the configuration
|
|
224
|
-
const
|
|
225
|
-
if (!
|
|
227
|
+
const appConfig = config.apps.find(a => (a.appNameForURL || a.appName) === appId);
|
|
228
|
+
if (!appConfig) {
|
|
226
229
|
return res.status(404).json({ error: `App "${appId}" not found` });
|
|
227
230
|
}
|
|
228
231
|
|
|
232
|
+
// Cross-app access control — restrict to apps the authenticated user has access to
|
|
233
|
+
const appsUserHasAccess = authentication && authentication.appsUserHasAccessTo;
|
|
234
|
+
let isPerAppReadOnly = false;
|
|
235
|
+
if (appsUserHasAccess) {
|
|
236
|
+
const matchingAccess = appsUserHasAccess.find(access => access.appId === appConfig.appId);
|
|
237
|
+
if (!matchingAccess) {
|
|
238
|
+
return res.status(403).json({ error: 'Forbidden: you do not have access to this app' });
|
|
239
|
+
}
|
|
240
|
+
isPerAppReadOnly = !!matchingAccess.readOnly;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Determine if the user is read-only (globally or per-app)
|
|
244
|
+
const isReadOnly = (authentication && authentication.isReadOnly) || isPerAppReadOnly;
|
|
245
|
+
|
|
246
|
+
// Build the app context — always shallow copy to avoid mutating the shared config
|
|
247
|
+
const appContext = { ...appConfig };
|
|
248
|
+
if (isReadOnly) {
|
|
249
|
+
if (!appConfig.readOnlyMasterKey) {
|
|
250
|
+
return res.status(400).json({ error: 'You need to provide a readOnlyMasterKey to use read-only features.' });
|
|
251
|
+
}
|
|
252
|
+
appContext.masterKey = appConfig.readOnlyMasterKey;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Resolve function-typed masterKey (supports dynamic key rotation via ConfigKeyCache)
|
|
256
|
+
if (typeof appContext.masterKey === 'function') {
|
|
257
|
+
const cacheKey = isReadOnly ? 'readOnlyMasterKey' : 'masterKey';
|
|
258
|
+
appContext.masterKey = await ConfigKeyCache.get(appContext.appId, cacheKey, appContext.masterKeyTtl, appContext.masterKey);
|
|
259
|
+
}
|
|
260
|
+
|
|
229
261
|
// Find the requested model
|
|
230
262
|
const modelConfig = config.agent.models.find(model => model.name === modelName);
|
|
231
263
|
if (!modelConfig) {
|
|
@@ -258,8 +290,12 @@ module.exports = function(config, options) {
|
|
|
258
290
|
// Array to track database operations for this request
|
|
259
291
|
const operationLog = [];
|
|
260
292
|
|
|
293
|
+
// Read-only users: override client permissions to deny all write operations,
|
|
294
|
+
// preventing privilege escalation via self-authorized permissions in the request body
|
|
295
|
+
const effectivePermissions = isReadOnly ? {} : (permissions || {});
|
|
296
|
+
|
|
261
297
|
// Make request to OpenAI API with app context and conversation history
|
|
262
|
-
const response = await makeOpenAIRequest(message, model, apiKey,
|
|
298
|
+
const response = await makeOpenAIRequest(message, model, apiKey, appContext, conversationHistory, operationLog, effectivePermissions);
|
|
263
299
|
|
|
264
300
|
// Update conversation history with user message and AI response
|
|
265
301
|
conversationHistory.push(
|
|
@@ -280,7 +316,7 @@ module.exports = function(config, options) {
|
|
|
280
316
|
conversationId: finalConversationId,
|
|
281
317
|
debug: {
|
|
282
318
|
timestamp: new Date().toISOString(),
|
|
283
|
-
appId:
|
|
319
|
+
appId: appContext.appId,
|
|
284
320
|
modelUsed: model,
|
|
285
321
|
operations: operationLog
|
|
286
322
|
}
|
|
@@ -291,7 +327,19 @@ module.exports = function(config, options) {
|
|
|
291
327
|
const errorMessage = error.message || 'Provider error';
|
|
292
328
|
res.status(500).json({ error: `Error: ${errorMessage}` });
|
|
293
329
|
}
|
|
294
|
-
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Agent API endpoint — middleware chain: auth check (401) → CSRF validation (403) → handler
|
|
333
|
+
app.post('/apps/:appId/agent',
|
|
334
|
+
(req, res, next) => {
|
|
335
|
+
if (users && (!req.user || !req.user.isAuthenticated)) {
|
|
336
|
+
return res.status(401).json({ error: 'Unauthorized' });
|
|
337
|
+
}
|
|
338
|
+
next();
|
|
339
|
+
},
|
|
340
|
+
Authentication.csrfProtection,
|
|
341
|
+
agentHandler
|
|
342
|
+
);
|
|
295
343
|
|
|
296
344
|
/**
|
|
297
345
|
* Database function tools for the AI agent
|
|
@@ -1115,7 +1163,7 @@ You have direct access to the Parse database through function calls, so you can
|
|
|
1115
1163
|
});
|
|
1116
1164
|
|
|
1117
1165
|
// For every other request, go to index.html. Let client-side handle the rest.
|
|
1118
|
-
app.get('{*splat}', function(req, res) {
|
|
1166
|
+
app.get('{*splat}', Authentication.csrfProtection, function(req, res) {
|
|
1119
1167
|
if (users && (!req.user || !req.user.isAuthenticated)) {
|
|
1120
1168
|
const redirect = req.url.replace('/login', '');
|
|
1121
1169
|
if (redirect.length > 1) {
|
|
@@ -1139,6 +1187,7 @@ You have direct access to the Parse database through function calls, so you can
|
|
|
1139
1187
|
</head>
|
|
1140
1188
|
<body>
|
|
1141
1189
|
<div id="browser_mount"></div>
|
|
1190
|
+
<script id="csrf" type="application/json">"${req.csrfToken()}"</script>
|
|
1142
1191
|
<script src="${mountPath}bundles/dashboard.bundle.js"></script>
|
|
1143
1192
|
</body>
|
|
1144
1193
|
</html>
|