Database Proxy with Access Control
database-proxy is a core component within the Laf serverless ecosystem, designed to function as a 'super API' that allows client applications to securely and directly interact with databases (like MongoDB, with future support for MySQL as indicated by keywords) via HTTP. It enables frontend developers to perform database operations without needing a dedicated backend API layer for common CRUD actions. The security and access patterns are enforced through a set of configurable Access Control List (ACL) rules. Currently, the package is in active beta development, with version `1.0.0-beta.14` being the most recent significant release. Releases are frequent, indicating rapid development and feature iteration. Its key differentiator is simplifying backend development by shifting database access control to the proxy layer, significantly reducing the boilerplate traditionally associated with data APIs in BaaS and serverless architectures.
Common errors
-
TypeError: (0 , database_proxy_1.Proxy) is not a constructor
cause Attempting to use ES module `import` syntax for a CommonJS package.fixChange your import statements from `import { Proxy } from 'database-proxy'` to `const { Proxy } = require('database-proxy')`. -
Error: MongoParseError: options is not a function
cause An incorrect MongoDB connection string format or attempting to pass connection options in an incompatible way to `MongoClient`.fixEnsure your MongoDB connection string (`MONGO_URI`) is correctly formatted, e.g., `mongodb://localhost:27017` or `mongodb+srv://user:pass@cluster.mongodb.net/`. -
code: 1, error: 'Access denied: operation not permitted by policy'
cause The incoming database operation (read, update, add, remove) violates the defined `Policy` rules for the collection or document, given the injected user context.fixReview your `rules` configuration for the affected collection and the `injections` provided to `proxy.validate()`. Ensure the user's role and data context satisfy the policy conditions for the requested action.
Warnings
- breaking A critical security vulnerability (CVE-2023-48225) was fixed in `v1.0.0-beta.14`. All private deployments running older versions of Laf (which includes this package) are strongly advised to update immediately to mitigate potential risks.
- gotcha The `database-proxy` package is designed for CommonJS environments, as indicated by its `package.json` configuration. Attempting to use ES module `import` syntax will result in runtime errors.
- gotcha The effectiveness of the database proxy relies entirely on robust Access Control List (ACL) policies and secure handling of user identity injections. Inadequate or insecure policies can expose your database to unauthorized access or data manipulation.
Install
-
npm install database-proxy -
yarn add database-proxy -
pnpm add database-proxy
Imports
- Proxy
import { Proxy } from 'database-proxy'const { Proxy } = require('database-proxy') - MongoAccessor
import MongoAccessor from 'database-proxy'
const { MongoAccessor } = require('database-proxy') - Policy
const Policy = require('database-proxy').Policyconst { Policy } = require('database-proxy')
Quickstart
const express = require('express');
const { Proxy, MongoAccessor, Policy } = require('database-proxy');
const { MongoClient } = require('mongodb');
const app = express();
app.use(express.json());
// Dummy function for token parsing (replace with actual implementation)
function parseToken(authorizationHeader) {
// In a real application, this would validate a JWT and extract user info
if (authorizationHeader && authorizationHeader.startsWith('Bearer ')) {
const token = authorizationHeader.substring(7);
// For demonstration, let's assume a valid token means uid is 'testuser'
// In production, decode and verify the token properly.
return { uid: 'testuser', admin: true }; // Example: always admin for demo
}
return { uid: null, admin: false };
}
// Design the access control policy rules
const rules = {
categories: {
"read": true,
"update": "$admin === true", // Only admin can update
"add": "$admin === true", // Only admin can add
"remove": "$admin === true"
},
articles: {
"read": true,
"update": "$userid && $userid === query.createdBy",
"add": "$userid && data.createdBy === $userid",
"remove": "$userid === query.createdBy || $admin === true"
}
};
const mongoUri = process.env.MONGO_URI ?? 'mongodb://localhost:27017';
const client = new MongoClient(mongoUri);
async function setupDatabaseProxy() {
try {
await client.connect();
console.log('Connected to MongoDB');
const accessor = new MongoAccessor(client);
const policy = new Policy(accessor);
policy.load(rules);
const proxy = new Proxy(accessor, policy);
app.post('/proxy', async (req, res) => {
const { uid, admin } = parseToken(req.headers['authorization']);
const injections = {
uid: uid,
admin: admin
};
const params = proxy.parseParams(req.body);
const result = await proxy.validate(params, injections);
if (result.errors) {
return res.status(403).send({
code: 1,
error: result.errors
});
}
const data = await proxy.execute(params);
return res.send({
code: 0,
data
});
});
const port = 8080;
app.listen(port, () => console.log(`Database Proxy listening on http://localhost:${port}/proxy`));
} catch (error) {
console.error('Failed to connect to MongoDB or start server:', error);
process.exit(1);
}
}
setupDatabaseProxy();