fix(gateway,inventory): trust model hardening (#5)
- Renamed SERVICE_LEVEL_AUTH to GATEWAY_KEY_SERVICES (clarifies intent) - Removed /debug-nocodb endpoint from inventory (exposed full table dump) - Hardened NocoDB search filter construction: strip (), ~, , chars to prevent filter injection. Reject queries under 2 chars. Files: gateway/dashboard.py, services/inventory/server.js
This commit is contained in:
@@ -187,7 +187,8 @@ def handle_dashboard(handler, user):
|
|||||||
apps = conn.execute("SELECT * FROM apps WHERE enabled = 1 ORDER BY sort_order").fetchall()
|
apps = conn.execute("SELECT * FROM apps WHERE enabled = 1 ORDER BY sort_order").fetchall()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
SERVICE_LEVEL_AUTH = {"inventory", "reader", "books", "music", "budget"}
|
# Services that use gateway-injected API keys (not per-user tokens)
|
||||||
|
GATEWAY_KEY_SERVICES = {"inventory", "reader", "books", "music", "budget"}
|
||||||
widgets = []
|
widgets = []
|
||||||
futures = {}
|
futures = {}
|
||||||
|
|
||||||
@@ -289,9 +290,9 @@ def handle_dashboard(handler, user):
|
|||||||
for app in apps:
|
for app in apps:
|
||||||
app = dict(app)
|
app = dict(app)
|
||||||
svc_token = get_service_token(user["id"], app["id"])
|
svc_token = get_service_token(user["id"], app["id"])
|
||||||
is_service_level = app["id"] in SERVICE_LEVEL_AUTH
|
uses_gateway_key = app["id"] in GATEWAY_KEY_SERVICES
|
||||||
|
|
||||||
if not svc_token and not is_service_level:
|
if not svc_token and not uses_gateway_key:
|
||||||
widgets.append({"app": app["id"], "name": app["name"], "widget": app["dashboard_widget"], "connected": False, "data": None})
|
widgets.append({"app": app["id"], "name": app["name"], "widget": app["dashboard_widget"], "connected": False, "data": None})
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|||||||
@@ -142,7 +142,11 @@ app.get('/search-records', async (req, res) => {
|
|||||||
return res.status(400).json({ error: 'Search term required' });
|
return res.status(400).json({ error: 'Search term required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const term = searchTerm.replace(/[%_]/g, ''); // sanitize wildcards
|
// Sanitize: strip NocoDB filter operators and special chars to prevent injection
|
||||||
|
const term = searchTerm.replace(/[%_()~,]/g, '').trim();
|
||||||
|
if (!term || term.length < 2) {
|
||||||
|
return res.json({ results: [] });
|
||||||
|
}
|
||||||
|
|
||||||
// Server-side search across key fields using NocoDB like filter
|
// Server-side search across key fields using NocoDB like filter
|
||||||
const where = [
|
const where = [
|
||||||
@@ -358,64 +362,6 @@ app.get('/summary', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Debug endpoint to check NocoDB connection and data
|
|
||||||
app.get('/debug-nocodb', async (req, res) => {
|
|
||||||
try {
|
|
||||||
console.log('Debug: Testing NocoDB connection...');
|
|
||||||
console.log('Config:', {
|
|
||||||
url: config.ncodbUrl,
|
|
||||||
baseId: config.baseId,
|
|
||||||
tableId: config.tableId,
|
|
||||||
hasToken: !!config.apiToken
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fetch all records with pagination
|
|
||||||
let allRows = [];
|
|
||||||
let offset = 0;
|
|
||||||
const limit = 1000;
|
|
||||||
let hasMore = true;
|
|
||||||
|
|
||||||
while (hasMore && offset < 10000) {
|
|
||||||
const response = await axios.get(
|
|
||||||
config.ncodbUrl + '/api/v1/db/data/noco/' + config.baseId + '/' + config.tableId,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
'xc-token': config.apiToken,
|
|
||||||
'Accept': 'application/json'
|
|
||||||
},
|
|
||||||
params: {
|
|
||||||
limit: limit,
|
|
||||||
offset: offset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const pageRows = response.data.list || [];
|
|
||||||
allRows = allRows.concat(pageRows);
|
|
||||||
hasMore = pageRows.length === limit;
|
|
||||||
offset += limit;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
totalRowsRetrieved: allRows.length,
|
|
||||||
sampleRow: allRows[0] || null,
|
|
||||||
fieldNames: allRows.length > 0 ? Object.keys(allRows[0]) : [],
|
|
||||||
firstThreeRows: allRows.slice(0, 3),
|
|
||||||
message: `Successfully retrieved ${allRows.length} total records`
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Debug error:', error.message);
|
|
||||||
res.status(500).json({
|
|
||||||
error: 'Failed to connect to NocoDB',
|
|
||||||
message: error.message,
|
|
||||||
responseData: error.response?.data,
|
|
||||||
status: error.response?.status
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get items with issues (full details)
|
// Get items with issues (full details)
|
||||||
app.get('/issues', async (req, res) => {
|
app.get('/issues', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user