New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Setting application_name to current application user on global db object #418
Comments
Do I understand it right that you want to extend the protocol according to what database it is? If so, then you should use the
The
You add new methods that can call an existing method + do something else. |
Sorry if my question was misleading, or perhaps I've been looking at the wrong solutions. In my case there's only 1 database and the goal is to be able to set the logged-in application user's id in some way so that this can be used for an audit trail. |
I just did a test against the latest const pgp = require('pg-promise')();
const appName = 'my-app';
const db = pgp({
database: 'pg_promise_test',
port: 5432,
user: 'postgres',
application_name: appName
});
db.any('SELECT application_name FROM pg_stat_activity WHERE application_name = $1', appName)
.then(data => {
console.log(data);
pgp.end();
}); Outputs:
|
That would only work if I created a new db object for each request with the user id from that request but my understanding is that's horrible for performance. |
You want the interactive user from the request? That's quite unrelated to all of this. If you have the user from the request, you log right there, so what's the problem? And your question in this case is completely misleading. It got nothing to do with event |
Also, what do you think your method You can either execute two queries, like log insert + the actual query, or you can use a template for the query, like from the external SQL file that would expect the extra parameters, so it can amend it. |
I hadn't figured out what the I have the user id at the application layer so in the business logic I could insert into the I think I follow your suggestion of using a template but the audit for inserts will need the id returned, so two statements (or a common table expression) are required. After insert/update trigger functions seem to solve things nicely however the issue is having access to the user id from the application layer — and pg-promise is that bridge. If there's not an elegant way to achieve this then I'll go with one of the other options. |
I cannot advise you on how to template it into a single query, as it depends on the database architecture, and what it is exactly you are expecting. At best, I can show you how you can do it via 2 queries and event var options = {
extend: obj => {
obj.logContext = context => {
return obj.none('INSERT INTO logs(context) VALUES($1)', context);
};
obj.queryWithContext = (query, values, context) => {
return obj.logContext(context).then(() => {
return obj.query(query, values);
});
}
}
}; |
Thanks for the help. |
@vitaly-t: Hello again! Since this keeps coming up, for the record: what's the recommend way to prepend/append a single *Allowing re-use of connections/pg-instances between users/requests and use of SET application_name = <per-request-unique-uidentifier> I've been version locked with my nasty hack and am just now trying to fix this the correct way :-) |
@jmealo you want to execute The only way it won't override the setting is by using a transaction. But using a transaction for every HTTP request is a horrible idea, because transactions are blocking operations. So I don't think you'd want to do it 😉 |
Anyhow, as a pure exercise, you can play with it. One approach is to extend the protocol with your custom const initOptions = {
extend: obj => {
obj.appTask = (appName, cb) => {
return obj.task('app-task', t => {
cb = cb.bind(t, t);
return t.none('SET application_name = $1', appName).then(cb);
});
}
}
}; Then you can call: db.appTask('app-name', t => {
// do the task....
}).then().catch(); |
@vitaly-t Use case: if you set the application name GUC to a combination of a session id and correlation id you can match any PostgreSQL log output to a given correlation id or web application session. The So to be clear, I do not need a transaction per query, I need a SET statement prepended to allow queries issued by pg-promise so that the query is attributed to the correct correlation id. It's strictly for correlating logs and can be used in RLS rules to reference the web application user. If there is something I misunderstand about the internals of pg-promise that would break the above use case please let me know, I've had a shim in place in production for 2 years but I'd really like to get rid of it and use the latest version of pg-promise. The shim wraps each method and munges the input strings. I only have very limited use of the library because of this. |
@jmealo The example I showed above is a task, not a transaction. And of course you can alternatively prepend a set to each query either manually or by extending the protocol. Here, for example, we introduce a new special const initOptions = {
extend: obj => {
obj.appQuery = (appName, query, values, qrm) => {
const sql = pgp.helpers.concat([
{query: 'SET application_name = $1', values: appName},
{query, values}
]);
return obj.query(sql, [], qrm);
}
}
}; It would be well-performing, as the query is concatenated and executed only once. See also method helper.concat ;) |
You can also set the |
@amenadiel @vitaly-t Thank you for your suggestions, however, they do not accomplish the stated goal, just to re-iterate:
Every suggestion that I've received does not accomplish the goals above. I had to wrap pg-promise and am locked into an older version which allowed such monkey patching. When using something like Koa where we can have a middleware that presents pg-promise to the request context it would be hepful to set these state variables without entirely wrapping pg-promise. |
Another thought, if the number of apps is very limited, you can throw each app-specific stuff into its own schema, and then use separate database objects ;) |
@vitaly-t: This works with older versions of pg-promise but I cannot upgrade, hopefully this makes my user case clear: https://gist.github.com/jmealo/58c96de7add64ebbab86a637a46d3417 guc = {
'spark.user_id': ctx.userId,
'spark.role': ctx.role,
'spark.request_id': requestId,
application_name: `spark-api_${ctx.username}_${requestId}`
}; This populates a set of GUC variables that can be used both for logging and row-level security. The goal here is to enforce that a given set of GUC variables be pg-promise has tons of facilities for modifying the query, however, I have been unable to find one that will work appropriately within a |
@jmealo This is an old ticket now, but I want to do the same, did you ever land on a good solution? I'm using Koa2 and want to be able to set GUC variables within the request workflow. It seems a transaction per request is the way to go :? |
@abeluck: I just stayed locked at an older version. If a transaction per-request works for your application, that should be safe but may not meet your performance requirements. This gist may be a good starting point. IIRC, @vitaly-t warned that this wrapper might break some guarantees with tasks. I encountered no issues by prepending the queries. It's worth noting that I limited most requests to a single round trip using a CTE where I would grab everything in one go. If you design your app/schema/queries around the premise that you want end-to-end logging, multi-tenancy (using schemas) and RLS using application roles rather than PostgreSQL roles and are going to prepend each query with the correct application name you should be fine. You can enforce this pretty easily by requiring the GUC to be set via RLS and then any logic errors should be caught in development. YMMV. Good luck! |
I generally advise against wrapping base methods like that, because it won't work within tasks and transactions. If you want to extend the protocol, you should use event extend instead. |
@vitaly-t: I hope that you are healthy/well. Please consider the use case of row-level-security that uses web application roles instead of database roles: https://www.graphile.org/postgraphile/security/#how-it-works We need an API that allows passing in an application context (in our case, an http request) that will be used to populate settings (GUC) based upon that context by prepending Here is an example of one such API. . How can we implement this with pg-promise? I still don't see a way to use I'm working on a new project and I'd really like to have this functionality as both postgraphile and postgrest support using GUC settings to enforce RLS rules based on application roles to great success. |
@jmealo Something like this might help?
Do you intend to pretend It is better to do inside a task or transaction, because there you can check for a fresh connection, and execute await db.task('my-task', async t => {
const isFreshConnection = t.ctx.useCount === 0;
if(isFreshConnection) {
await t.none('SET ...');
// etc.
}
// then your queries...
}); |
@vitaly-t: Thanks for the quick reply. I looked over #710 and what you have here and I'm still not sure how to expose full-functionality of pg-promise while requiring every query to execute a "pre-query" first. For my use case it's critical that there's no way to issue a query without the Here's what I get by prepending queries:
My primary concern is that I want to make it impossible for an outgoing query to be made without setting the search_path and settings. If at all possible, I don't want it to be on developers to only use a subset of pg-promise features, or through later advanced usage somehow make a query without the SET statement. |
this is killing me, I've been reading issues in circles and I still don't know how to solve this problem |
I do not believe that this library creates any kind of limitation by itself. If you can do it within PostgreSQL, then you can do it here. There hasn't been a clear formulation of the solution within the server architecture, which made it impossible to offer any usable guidance as to how to approach this within the library. |
@vitaly-t: Many projects are allowing for this, the server supports it just fine using GUC: With the API surface exposed right now, there's no way to do this with The only alternative I could see which seems like a reach would be to use async local storage: https://nodejs.org/api/async_hooks.html @csrapr: You could store the user/session in async local storage and then pull it out using one of the supported means of extending |
@vitaly-t: Would it be possible to modify the outgoing query from a query event? If you can manipulate the const http = require('http');
const { AsyncLocalStorage } = require('async_hooks');
const asyncLocalStorage = new AsyncLocalStorage();
// Instead of logWithId, this is your query event handler, where you'd PREPEND your `SET` statement
function logWithId(msg) {
const id = asyncLocalStorage.getStore();
console.log(`${id !== undefined ? id : '-'}:`, msg);
}
let idSeq = 0;
http.createServer((req, res) => {
asyncLocalStorage.run(idSeq++, () => {
logWithId('start');
// Imagine any chain of async operations here
setImmediate(() => {
logWithId('finish');
res.end();
});
});
}).listen(8080); I didn't see anything else in the docs that would accomplish this. It seems like |
@csrapr: If there's no For context, I store my |
When following the repository pattern, as shown pg-promise-demo, you can have repository methods extending existing methods, whereby any of your repository methods can invoke methods from any other repository. This would propagate the current task/transaction context automatically. |
@vitaly-t: thanks, for those not using the repository pattern who want to prepend a query with a dynamic string with request context from async local storage, should we be using the query event and mutating the context or just monkey patch node-postgres? |
Event If you want to prepend a query in front of each other query, you can use |
I want to use the existing methods and I want it to happen uniformly; I think that's what everyone wants. Anything else is a security risk if one developer uses the wrong method. It would bypass auth. You can be smart and set up things so that the query will fail with the role you use with pg-promise if no GUC is set but it's relying on a lot of moving pieces versus just being able to unilaterally make sure every query can be authenticated based on the application user so that authorization can be enforced in DB. |
I gave you one solution, via repositories, you didn't want it. I gave you another, with an extended method, you didn't like it. I'm not gonna suggest any more solutions here of how to circumvent things that you would like.
I do not believe that this is what everyone wants. And even if somebody needs it, I have provided two approaches of how this can be done. After all, what you are doing is very non-standard, trying to hack queries like that. |
@csrapr: for what it's worth, I'd like to find a solution and will give this a shot when I can, but do see if postgrest/postgraphile meet your needs. They provide an API either REST or GraphQL and both support using JWT/sessions for Row-Level Security and auditing by reflecting over your database. @vitaly-t: this keeps coming up. I'm not sure why you've been so critical of this idea but I respect your choice for how you handle your library and appreciate your time proposing solutions but none of the solutions address the problems that we're trying to address. I pioneered this pattern and other projects I collaborated with picked it up or discovered it independently. It's a good pattern that's been verified in production and your library does actively prevent it IMO. I think I'm qualified to make that assessment. I don't think locking down the prototype breaking my original solution of wrapping the methods differs from what you're suggesting in any meaningful way. It seems like a control issue :( Library authors don't need to save everyone from themselves when they're working on the next big thing. A library preventing extensibility forces circumventing those preventions to implement this pattern. I'm sorry that we weren't able to meet somewhere in the middle but I'm done arguing the merits of this approach on your repo out of respect for all your hard work on an otherwise brilliant library. |
I'm trying to achieve something similar to #71, #100 — setting the
application_name
to include the current application user's id for use in audit tables (via trigger functions).I haven't been able to set the
application_name
on a configured db object (despite this SO post) and the furthest I've got is this example from Vitaly of using event extend:How can I set
application_name
and would use of extend mean having to usedb.myMethodWithParam
vs.db.any
,db.one
etc. or does it 'extend' the base methods?Thanks,
Lloyd
The text was updated successfully, but these errors were encountered: