Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion src/postgraphql/__tests__/withPostGraphQLContext-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ test('will throw an error if the JWT token does not have an appropriate audience
expect(pgClient.query.mock.calls).toEqual([['begin'], ['commit']])
})

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

test('will add extra settings as available', async () => {
const pgClient = { query: jest.fn(), release: jest.fn() }
const pgPool = { connect: jest.fn(() => pgClient) }
await withPostGraphQLContext({
pgPool,
jwtToken: jwt.sign({ aud: 'postgraphql' }, 'secret', { noTimestamp: true }),
jwtSecret: 'secret',
pgSettings: {
'foo.bar': 'test1',
'some.other.var': 'hello world',
},
}, () => {})
expect(pgClient.query.mock.calls).toEqual([['begin'], [{
text: 'select set_config($1, $2, true), set_config($3, $4, true), set_config($5, $6, true)',
values: [
'foo.bar', 'test1',
'some.other.var', 'hello world',
'jwt.claims.aud', 'postgraphql',
],
}], ['commit']])
})

test('will set the default role if available', async () => {
const pgClient = { query: jest.fn(), release: jest.fn() }
const pgPool = { connect: jest.fn(() => pgClient) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -540,5 +540,36 @@ for (const [name, createServerFromHandler] of Array.from(serverCreators)) {
console.error = origConsoleError
}
})

test('will correctly hand over pgSettings to the withPostGraphQLContext call', async () => {
pgPool.connect.mockClear()
pgClient.query.mockClear()
pgClient.release.mockClear()
const server = createServer({
pgSettings: {
'foo.bar': 'test1',
},
})
await (
request(server)
.post('/graphql')
.send({ query: '{hello}' })
.expect(200)
.expect('Content-Type', /json/)
.expect({ data: { hello: 'world' } })
)
expect(pgPool.connect.mock.calls).toEqual([[]])
expect(pgClient.query.mock.calls).toEqual([
['begin'],
[
{
text: 'select set_config($1, $2, true)',
values: ['foo.bar', 'test1'],
},
],
['commit'],
])
expect(pgClient.release.mock.calls).toEqual([[]])
})
})
}
5 changes: 5 additions & 0 deletions src/postgraphql/http/createPostGraphQLHttpRequestHandler.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,9 @@ export default function createPostGraphQLHttpRequestHandler (config: {
// The size can be given as a human-readable string, such as '200kB' or '5MB'
// (case insensitive).
bodySizeLimit?: string,

// Allows the definition of custom postgres settings which get injected in
// each transaction via set_config. They can then be used via current_setting
// in postgres functions.
pgSettings?: { [key: string]: mixed },
}): HttpRequestHandler
3 changes: 2 additions & 1 deletion src/postgraphql/http/createPostGraphQLHttpRequestHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const origGraphiqlHtml = new Promise((resolve, reject) => {
* @param {GraphQLSchema} graphqlSchema
*/
export default function createPostGraphQLHttpRequestHandler (options) {
const { getGqlSchema, pgPool } = options
const { getGqlSchema, pgPool, pgSettings } = options

// Gets the route names for our GraphQL endpoint, and our GraphiQL endpoint.
const graphqlRoute = options.graphqlRoute || '/graphql'
Expand Down Expand Up @@ -395,6 +395,7 @@ export default function createPostGraphQLHttpRequestHandler (options) {
jwtToken,
jwtSecret: options.jwtSecret,
pgDefaultRole: options.pgDefaultRole,
pgSettings,
}, context => {
pgRole = context.pgRole
return executeGraphql(
Expand Down
1 change: 1 addition & 0 deletions src/postgraphql/postgraphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type PostGraphQLOptions = {
exportJsonSchemaPath?: string,
exportGqlSchemaPath?: string,
bodySizeLimit?: string,
pgSettings?: { [key: string]: mixed },
}

/**
Expand Down
13 changes: 13 additions & 0 deletions src/postgraphql/withPostGraphQLContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@ export default async function withPostGraphQLContext(
jwtToken,
jwtSecret,
pgDefaultRole,
pgSettings,
}: {
pgPool: Pool,
jwtToken?: string,
jwtSecret?: string,
pgDefaultRole?: string,
pgSettings?: { [key: string]: mixed },
},
callback: (context: mixed) => Promise<ExecutionResult>,
): Promise<ExecutionResult> {
Expand All @@ -60,6 +62,7 @@ export default async function withPostGraphQLContext(
jwtToken,
jwtSecret,
pgDefaultRole,
pgSettings,
})

return await callback({
Expand Down Expand Up @@ -89,11 +92,13 @@ async function setupPgClientTransaction ({
jwtToken,
jwtSecret,
pgDefaultRole,
pgSettings,
}: {
pgClient: Client,
jwtToken?: string,
jwtSecret?: string,
pgDefaultRole?: string,
pgSettings?: { [key: string]: mixed },
}): Promise<string | undefined> {
// Setup our default role. Once we decode our token, the role may change.
let role = pgDefaultRole
Expand Down Expand Up @@ -135,6 +140,14 @@ async function setupPgClientTransaction ({
// Sql query.
const localSettings = new Map<string, mixed>()

// Set the custom provided settings before jwt claims and role are set
// this prevents an accidentional overwriting
if (typeof pgSettings === 'object') {
for (const key of Object.keys(pgSettings)) {
localSettings.set(key, pgSettings[key])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please wrap the value with String constructor: localSettings.set(key, String(pgSettings[key]))

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will. Any thoughts about objects that can be serialized to a json string and injected in postgres?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the user should handle that themself with JSON.serialize. I just want to ensure that the types are clear so that set_config(text, text, bool) isn't mistaken for a potential different user-defined function such as set_config(test, json, bool) etc.

}
}

// If there is a rule, we want to set the root `role` setting locally
// to be our role. The role may only be null if we have no default role.
if (typeof role === 'string') {
Expand Down