我正在考虑使用我们的 Node Express + pg-promise + postgres 服务来实现行级安全性。
我们尝试了一些方法但没有成功:
有人(希望是vitaly-t :))请建议在连接内运行所有sql查询之前注入当前租户的最佳策略。
非常感谢
这是一个简短的代码示例:
const promise = require('bluebird');
const initOptions = {
promiseLib: promise,
connect: async (client, dc, useCount) => {
try {
// "hook" into the db connect event - and set the tenantId so all future sql queries in this connection
// have an implied WHERE tenant_id = app.current_setting('app.current_tenant')::UUID (aka PostGres Row Level Security)
const tenantId = client.$ctx?.cn?.tenantId || client.$ctx?.cnOptions?.tenantId;
if (tenantId) {
await client.query(`SET app.current_tenant = '${tenantId}';`);
}
} catch (ex) {
log.error('error in db.js initOptions', {ex});
}
}
};
const pgp = require('pg-promise')(initOptions);
const options = tenantIdOptional => {
return {
user: process.env.POSTGRES_USER,
host: process.env.POSTGRES_HOST,
database: process.env.POSTGRES_DATABASE,
password: process.env.POSTGRES_PASSWORD,
port: process.env.POSTGRES_PORT,
max: 100,
tenantId: tenantIdOptional
};
};
const db = pgp(options());
const getDb = tenantId => {
// how to inject tenantId into the db object
// 1. this was getting an error "WARNING: Creating a duplicate database object for the same connection and Error: write EPIPE"
// const tmpDb = pgp(options(tenantId));
// return tmpDb;
// 2. this was running the set app.current_tenant BEFORE the database connection was established
// const setTenantId = async () => {
// await db.query(`SET app.current_tenant = '${tenantId}';`);
// };
// setTenantId();
// return db;
// 3. this is bypassing the connection pool management - and is not working
// db.connect(options(tenantId));
// return db;
return db;
};
// Exporting the global database object for shared use:
const exportFunctions = {
getDb,
db // have to also export db for the legacy non-Row level security areas of the service
};
module.exports = exportFunctions;
SET
操作是连接绑定的,即该操作仅在当前连接会话持续时有效。对于池生成的新连接,您需要重新应用设置。
控制当前连接会话的标准方法是通过任务:
await db.task('my-task', async t => {
await t.none('SET app.current_tenant = ${tenantId}', {tenantId});
// ... run all session-related queries here
});
或者,如果需要交易,您可以使用方法
tx
来代替。
但是,如果您的
tenantId
是全局已知的,并且您希望它自动通过所有连接传播,那么您可以使用事件 connect 来代替:
const initOptions = {
connect({client}) {
client.query('SET app.current_tenant = $1', [tenantId]);
}
};
后者是一种事后考虑的解决方法,但它确实工作可靠,具有最佳性能,并且避免创建额外的任务。
尚未找到获取当前请求对象的方法(然后设置tenant_id)
这对于任何 HTTP 库来说都应该非常简单,但超出了本文的范围。