Skip to content

Commit fa3837e

Browse files
Malte Legenhausenbenjie
authored andcommitted
feat(postgraphql): inject custom settings into the database (graphile#399)
* (feat) Allow the injection of custom postgres settings * Making sure the value that gets set is a string * Linting errors fixed
1 parent bf57477 commit fa3837e

File tree

6 files changed

+81
-2
lines changed

6 files changed

+81
-2
lines changed

src/postgraphql/__tests__/withPostGraphQLContext-test.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ test('will throw an error if the JWT token does not have an appropriate audience
107107
expect(pgClient.query.mock.calls).toEqual([['begin'], ['commit']])
108108
})
109109

110-
test('will succeed with all the correct thigns', async () => {
110+
test('will succeed with all the correct things', async () => {
111111
const pgClient = { query: jest.fn(), release: jest.fn() }
112112
const pgPool = { connect: jest.fn(() => pgClient) }
113113
await withPostGraphQLContext({
@@ -140,6 +140,28 @@ test('will add extra claims as available', async () => {
140140
}], ['commit']])
141141
})
142142

143+
test('will add extra settings as available', async () => {
144+
const pgClient = { query: jest.fn(), release: jest.fn() }
145+
const pgPool = { connect: jest.fn(() => pgClient) }
146+
await withPostGraphQLContext({
147+
pgPool,
148+
jwtToken: jwt.sign({ aud: 'postgraphql' }, 'secret', { noTimestamp: true }),
149+
jwtSecret: 'secret',
150+
pgSettings: {
151+
'foo.bar': 'test1',
152+
'some.other.var': 'hello world',
153+
},
154+
}, () => {})
155+
expect(pgClient.query.mock.calls).toEqual([['begin'], [{
156+
text: 'select set_config($1, $2, true), set_config($3, $4, true), set_config($5, $6, true)',
157+
values: [
158+
'foo.bar', 'test1',
159+
'some.other.var', 'hello world',
160+
'jwt.claims.aud', 'postgraphql',
161+
],
162+
}], ['commit']])
163+
})
164+
143165
test('will set the default role if available', async () => {
144166
const pgClient = { query: jest.fn(), release: jest.fn() }
145167
const pgPool = { connect: jest.fn(() => pgClient) }

src/postgraphql/http/__tests__/createPostGraphQLHttpRequestHandler-test.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,5 +540,42 @@ for (const [name, createServerFromHandler] of Array.from(serverCreators)) {
540540
console.error = origConsoleError
541541
}
542542
})
543+
544+
test('will correctly hand over pgSettings to the withPostGraphQLContext call', async () => {
545+
pgPool.connect.mockClear()
546+
pgClient.query.mockClear()
547+
pgClient.release.mockClear()
548+
const server = createServer({
549+
pgSettings: {
550+
'foo.string': 'test1',
551+
'foo.number': 42,
552+
'foo.boolean': true,
553+
},
554+
})
555+
await (
556+
request(server)
557+
.post('/graphql')
558+
.send({ query: '{hello}' })
559+
.expect(200)
560+
.expect('Content-Type', /json/)
561+
.expect({ data: { hello: 'world' } })
562+
)
563+
expect(pgPool.connect.mock.calls).toEqual([[]])
564+
expect(pgClient.query.mock.calls).toEqual([
565+
['begin'],
566+
[
567+
{
568+
text: 'select set_config($1, $2, true), set_config($3, $4, true), set_config($5, $6, true)',
569+
values: [
570+
'foo.string', 'test1',
571+
'foo.number', '42',
572+
'foo.boolean', 'true',
573+
],
574+
},
575+
],
576+
['commit'],
577+
])
578+
expect(pgClient.release.mock.calls).toEqual([[]])
579+
})
543580
})
544581
}

src/postgraphql/http/createPostGraphQLHttpRequestHandler.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,9 @@ export default function createPostGraphQLHttpRequestHandler (config: {
7373
// The size can be given as a human-readable string, such as '200kB' or '5MB'
7474
// (case insensitive).
7575
bodySizeLimit?: string,
76+
77+
// Allows the definition of custom postgres settings which get injected in
78+
// each transaction via set_config. They can then be used via current_setting
79+
// in postgres functions.
80+
pgSettings?: { [key: string]: mixed },
7681
}): HttpRequestHandler

src/postgraphql/http/createPostGraphQLHttpRequestHandler.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ const origGraphiqlHtml = new Promise((resolve, reject) => {
6565
* @param {GraphQLSchema} graphqlSchema
6666
*/
6767
export default function createPostGraphQLHttpRequestHandler (options) {
68-
const { getGqlSchema, pgPool } = options
68+
const { getGqlSchema, pgPool, pgSettings } = options
6969

7070
// Gets the route names for our GraphQL endpoint, and our GraphiQL endpoint.
7171
const graphqlRoute = options.graphqlRoute || '/graphql'
@@ -395,6 +395,7 @@ export default function createPostGraphQLHttpRequestHandler (options) {
395395
jwtToken,
396396
jwtSecret: options.jwtSecret,
397397
pgDefaultRole: options.pgDefaultRole,
398+
pgSettings,
398399
}, context => {
399400
pgRole = context.pgRole
400401
return executeGraphql(

src/postgraphql/postgraphql.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ type PostGraphQLOptions = {
2525
exportJsonSchemaPath?: string,
2626
exportGqlSchemaPath?: string,
2727
bodySizeLimit?: string,
28+
pgSettings?: { [key: string]: mixed },
2829
}
2930

3031
/**

src/postgraphql/withPostGraphQLContext.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,13 @@ export default async function withPostGraphQLContext(
3636
jwtToken,
3737
jwtSecret,
3838
pgDefaultRole,
39+
pgSettings,
3940
}: {
4041
pgPool: Pool,
4142
jwtToken?: string,
4243
jwtSecret?: string,
4344
pgDefaultRole?: string,
45+
pgSettings?: { [key: string]: mixed },
4446
},
4547
callback: (context: mixed) => Promise<ExecutionResult>,
4648
): Promise<ExecutionResult> {
@@ -60,6 +62,7 @@ export default async function withPostGraphQLContext(
6062
jwtToken,
6163
jwtSecret,
6264
pgDefaultRole,
65+
pgSettings,
6366
})
6467

6568
return await callback({
@@ -89,11 +92,13 @@ async function setupPgClientTransaction ({
8992
jwtToken,
9093
jwtSecret,
9194
pgDefaultRole,
95+
pgSettings,
9296
}: {
9397
pgClient: Client,
9498
jwtToken?: string,
9599
jwtSecret?: string,
96100
pgDefaultRole?: string,
101+
pgSettings?: { [key: string]: mixed },
97102
}): Promise<string | undefined> {
98103
// Setup our default role. Once we decode our token, the role may change.
99104
let role = pgDefaultRole
@@ -135,6 +140,14 @@ async function setupPgClientTransaction ({
135140
// Sql query.
136141
const localSettings = new Map<string, mixed>()
137142

143+
// Set the custom provided settings before jwt claims and role are set
144+
// this prevents an accidentional overwriting
145+
if (typeof pgSettings === 'object') {
146+
for (const key of Object.keys(pgSettings)) {
147+
localSettings.set(key, String(pgSettings[key]))
148+
}
149+
}
150+
138151
// If there is a rule, we want to set the root `role` setting locally
139152
// to be our role. The role may only be null if we have no default role.
140153
if (typeof role === 'string') {

0 commit comments

Comments
 (0)