From c0af902e2b2f2ceec7570c9dd14735a77b3bc3ed Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Thu, 10 Jan 2019 14:14:20 +0000 Subject: [PATCH] fix(jwt): handle bigint / numeric in JWTs (#376) --- .../src/plugins/PgJWTPlugin.js | 16 +++++++++- .../src/plugins/makeProcField.js | 5 +++- .../graphile-build-pg/src/plugins/pgField.js | 17 ++++++++++- .../__snapshots__/queries-jwt.test.js.snap | 21 ++++++++++--- .../__tests__/integration/queries-jwt.test.js | 30 +++++++++++++++---- .../__snapshots__/defaultOptions.test.js.snap | 16 +++++----- .../__snapshots__/function-clash.test.js.snap | 16 +++++----- .../schema/__snapshots__/indexes.test.js.snap | 16 +++++----- .../schema/__snapshots__/jwt.test.js.snap | 12 ++++---- .../schema/__snapshots__/rbac.test.js.snap | 16 +++++----- .../__snapshots__/skipNodePlugin.test.js.snap | 16 +++++----- .../__tests__/kitchen-sink-schema.sql | 10 +++---- 12 files changed, 127 insertions(+), 64 deletions(-) diff --git a/packages/graphile-build-pg/src/plugins/PgJWTPlugin.js b/packages/graphile-build-pg/src/plugins/PgJWTPlugin.js index e97207d04..bba084d26 100644 --- a/packages/graphile-build-pg/src/plugins/PgJWTPlugin.js +++ b/packages/graphile-build-pg/src/plugins/PgJWTPlugin.js @@ -14,6 +14,7 @@ export default (function PgJWTPlugin( pgRegisterGqlTypeByTypeId, pg2GqlMapper, pgTweaksByTypeId, + pgTweakFragmentForTypeAndModifier, graphql: { GraphQLScalarType }, inflection, pgParseIdentifier: parseIdentifier, @@ -118,7 +119,20 @@ export default (function PgJWTPlugin( }; pgTweaksByTypeId[compositeType.id] = fragment => - sql.fragment`to_json(${fragment})`; + sql.fragment`json_build_object(${sql.join( + compositeClass.attributes.map( + attr => + sql.fragment`${sql.literal( + attr.name + )}::text, ${pgTweakFragmentForTypeAndModifier( + sql.fragment`(${fragment}).${sql.identifier(attr.name)}`, + attr.type, + attr.typeModifier, + {} + )}` + ), + ", " + )})`; }); return _; }); diff --git a/packages/graphile-build-pg/src/plugins/makeProcField.js b/packages/graphile-build-pg/src/plugins/makeProcField.js index 4f4ded625..f39487edb 100644 --- a/packages/graphile-build-pg/src/plugins/makeProcField.js +++ b/packages/graphile-build-pg/src/plugins/makeProcField.js @@ -475,7 +475,10 @@ export default function makeProcField( : null), }, {}, - false + false, + { + pgType: returnType, + } ), // Result } diff --git a/packages/graphile-build-pg/src/plugins/pgField.js b/packages/graphile-build-pg/src/plugins/pgField.js index b5565aecd..c2c26dd05 100644 --- a/packages/graphile-build-pg/src/plugins/pgField.js +++ b/packages/graphile-build-pg/src/plugins/pgField.js @@ -12,6 +12,7 @@ export default function pgField( pgQueryFromResolveData: queryFromResolveData, getSafeAliasFromAlias, getSafeAliasFromResolveInfo, + pgTweakFragmentForTypeAndModifier, } = build; return fieldWithHooks( fieldName, @@ -26,6 +27,13 @@ export default function pgField( const isListType = nullableType !== namedType && nullableType.constructor === build.graphql.GraphQLList; + const isLeafType = build.graphql.isLeafType(FieldType); + if (isLeafType && !options.pgType) { + // eslint-disable-next-line no-console + throw new Error( + "pgField call omits options.pgType for a leaf type; certain tweaks may not be applied!" + ); + } const { getDataFromParsedResolveInfoFragment, addDataGenerator, @@ -52,7 +60,14 @@ export default function pgField( : sql.identifier(Symbol()); const query = queryFromResolveData( whereFrom ? whereFrom(queryBuilder) : sql.identifier(Symbol()), - tableAlias, + isLeafType && options.pgType + ? pgTweakFragmentForTypeAndModifier( + tableAlias, + options.pgType, + options.pgTypeModifier, + {} + ) + : tableAlias, resolveData, whereFrom === false ? { onlyJsonField: true } diff --git a/packages/postgraphile-core/__tests__/integration/__snapshots__/queries-jwt.test.js.snap b/packages/postgraphile-core/__tests__/integration/__snapshots__/queries-jwt.test.js.snap index 78f2d4671..59008b4aa 100644 --- a/packages/postgraphile-core/__tests__/integration/__snapshots__/queries-jwt.test.js.snap +++ b/packages/postgraphile-core/__tests__/integration/__snapshots__/queries-jwt.test.js.snap @@ -16,8 +16,8 @@ Object { "authenticate": Object { "jwtToken": Object { "a": 1, - "b": 2, - "c": 3, + "b": "2", + "c": "3", "exp": 2130969600, "role": "yay", }, @@ -30,8 +30,8 @@ exports[`jwt pgJwtTypeIdentifier 1`] = ` Object { "a": 1, "aud": "postgraphile", - "b": 2, - "c": 3, + "b": "2", + "c": "3", "exp": 2130969600, "iat": "[timestamp]", "iss": "postgraphile", @@ -55,3 +55,16 @@ Object { }, } `; + +exports[`jwt pgJwtTypeIdentifier, big numbers 1`] = ` +Object { + "a": 1, + "aud": "postgraphile", + "b": "1234567890123456789.123456789", + "c": "987654321098765432", + "exp": 2130969600, + "iat": "[timestamp]", + "iss": "postgraphile", + "role": "yay", +} +`; diff --git a/packages/postgraphile-core/__tests__/integration/queries-jwt.test.js b/packages/postgraphile-core/__tests__/integration/queries-jwt.test.js index 177e2ee3d..383189ad5 100644 --- a/packages/postgraphile-core/__tests__/integration/queries-jwt.test.js +++ b/packages/postgraphile-core/__tests__/integration/queries-jwt.test.js @@ -2,8 +2,8 @@ const { graphql } = require("graphql"); const { withPgClient } = require("../helpers"); const { createPostGraphileSchema } = require("../.."); const { readFile: rawReadFile } = require("fs"); -const { printSchema } = require("graphql/utilities"); -const debug = require("debug")("graphile-build:schema"); +//const { printSchema } = require("graphql/utilities"); +//const debug = require("debug")("graphile-build:schema"); const jwt = require("jsonwebtoken"); function readFile(filename, encoding) { @@ -27,7 +27,7 @@ const tests = [ { name: "jwt normal", query: `mutation { - authenticate(input: {a: 1, b: 2, c: 3}) { + authenticate(input: {a: 1, b: "2", c: "3"}) { jwtToken { role exp @@ -42,7 +42,25 @@ const tests = [ { name: "jwt pgJwtTypeIdentifier", query: `mutation { - authenticate(input: {a: 1, b: 2, c: 3}) { + authenticate(input: {a: 1, b: "2", c: "3"}) { + jwtToken + } + }`, + schema: "withJwt", + process: ({ + data: { + authenticate: { jwtToken: str }, + }, + }) => { + return Object.assign(jwt.verify(str, jwtSecret), { + iat: "[timestamp]", + }); + }, + }, + { + name: "jwt pgJwtTypeIdentifier, big numbers", + query: `mutation { + authenticate(input: {a: 1, b: "1234567890123456789.123456789", c: "987654321098765432"}) { jwtToken } }`, @@ -69,7 +87,7 @@ const tests = [ { name: "jwt pgJwtTypeIdentifier with payload", query: `mutation { - authenticatePayload(input: {a: 1, b: 2, c: 3}) { + authenticatePayload(input: {a: 1, b: "2", c: "3"}) { authPayload { jwt id @@ -106,7 +124,7 @@ beforeAll(() => { jwtSecret: jwtSecret, }), ]); - debug(printSchema(withJwt)); + //debug(printSchema(withJwt)); return { normal, withJwt, diff --git a/packages/postgraphile-core/__tests__/integration/schema/__snapshots__/defaultOptions.test.js.snap b/packages/postgraphile-core/__tests__/integration/schema/__snapshots__/defaultOptions.test.js.snap index 7bae3f9cf..fcfba8136 100644 --- a/packages/postgraphile-core/__tests__/integration/schema/__snapshots__/defaultOptions.test.js.snap +++ b/packages/postgraphile-core/__tests__/integration/schema/__snapshots__/defaultOptions.test.js.snap @@ -228,8 +228,8 @@ type AuthenticateFailPayload { \\"\\"\\"All input for the \`authenticate\` mutation.\\"\\"\\" input AuthenticateInput { a: Int - b: Int - c: Int + b: BigFloat + c: BigInt \\"\\"\\" An arbitrary string value with no semantic meaning. Will be included in the @@ -241,8 +241,8 @@ input AuthenticateInput { \\"\\"\\"All input for the \`authenticateMany\` mutation.\\"\\"\\" input AuthenticateManyInput { a: Int - b: Int - c: Int + b: BigFloat + c: BigInt \\"\\"\\" An arbitrary string value with no semantic meaning. Will be included in the @@ -284,8 +284,8 @@ type AuthenticatePayload { \\"\\"\\"All input for the \`authenticatePayload\` mutation.\\"\\"\\" input AuthenticatePayloadInput { a: Int - b: Int - c: Int + b: BigFloat + c: BigInt \\"\\"\\" An arbitrary string value with no semantic meaning. Will be included in the @@ -3220,8 +3220,8 @@ type JsonIdentityMutationPayload { type JwtToken { a: Int - b: Int - c: Int + b: BigFloat + c: BigInt exp: Int role: String } diff --git a/packages/postgraphile-core/__tests__/integration/schema/__snapshots__/function-clash.test.js.snap b/packages/postgraphile-core/__tests__/integration/schema/__snapshots__/function-clash.test.js.snap index c717393e0..7460b9cba 100644 --- a/packages/postgraphile-core/__tests__/integration/schema/__snapshots__/function-clash.test.js.snap +++ b/packages/postgraphile-core/__tests__/integration/schema/__snapshots__/function-clash.test.js.snap @@ -228,8 +228,8 @@ type AuthenticateFailPayload { \\"\\"\\"All input for the \`authenticate\` mutation.\\"\\"\\" input AuthenticateInput { a: Int - b: Int - c: Int + b: BigFloat + c: BigInt \\"\\"\\" An arbitrary string value with no semantic meaning. Will be included in the @@ -241,8 +241,8 @@ input AuthenticateInput { \\"\\"\\"All input for the \`authenticateMany\` mutation.\\"\\"\\" input AuthenticateManyInput { a: Int - b: Int - c: Int + b: BigFloat + c: BigInt \\"\\"\\" An arbitrary string value with no semantic meaning. Will be included in the @@ -284,8 +284,8 @@ type AuthenticatePayload { \\"\\"\\"All input for the \`authenticatePayload\` mutation.\\"\\"\\" input AuthenticatePayloadInput { a: Int - b: Int - c: Int + b: BigFloat + c: BigInt \\"\\"\\" An arbitrary string value with no semantic meaning. Will be included in the @@ -3220,8 +3220,8 @@ type JsonIdentityMutationPayload { type JwtToken { a: Int - b: Int - c: Int + b: BigFloat + c: BigInt exp: Int role: String } diff --git a/packages/postgraphile-core/__tests__/integration/schema/__snapshots__/indexes.test.js.snap b/packages/postgraphile-core/__tests__/integration/schema/__snapshots__/indexes.test.js.snap index 3fbb24561..2d98d9d12 100644 --- a/packages/postgraphile-core/__tests__/integration/schema/__snapshots__/indexes.test.js.snap +++ b/packages/postgraphile-core/__tests__/integration/schema/__snapshots__/indexes.test.js.snap @@ -228,8 +228,8 @@ type AuthenticateFailPayload { \\"\\"\\"All input for the \`authenticate\` mutation.\\"\\"\\" input AuthenticateInput { a: Int - b: Int - c: Int + b: BigFloat + c: BigInt \\"\\"\\" An arbitrary string value with no semantic meaning. Will be included in the @@ -241,8 +241,8 @@ input AuthenticateInput { \\"\\"\\"All input for the \`authenticateMany\` mutation.\\"\\"\\" input AuthenticateManyInput { a: Int - b: Int - c: Int + b: BigFloat + c: BigInt \\"\\"\\" An arbitrary string value with no semantic meaning. Will be included in the @@ -284,8 +284,8 @@ type AuthenticatePayload { \\"\\"\\"All input for the \`authenticatePayload\` mutation.\\"\\"\\" input AuthenticatePayloadInput { a: Int - b: Int - c: Int + b: BigFloat + c: BigInt \\"\\"\\" An arbitrary string value with no semantic meaning. Will be included in the @@ -3108,8 +3108,8 @@ type JsonIdentityMutationPayload { type JwtToken { a: Int - b: Int - c: Int + b: BigFloat + c: BigInt exp: Int role: String } diff --git a/packages/postgraphile-core/__tests__/integration/schema/__snapshots__/jwt.test.js.snap b/packages/postgraphile-core/__tests__/integration/schema/__snapshots__/jwt.test.js.snap index 2af921f7f..cc86fe905 100644 --- a/packages/postgraphile-core/__tests__/integration/schema/__snapshots__/jwt.test.js.snap +++ b/packages/postgraphile-core/__tests__/integration/schema/__snapshots__/jwt.test.js.snap @@ -72,8 +72,8 @@ type AuthenticateFailPayload { \\"\\"\\"All input for the \`authenticate\` mutation.\\"\\"\\" input AuthenticateInput { a: Int - b: Int - c: Int + b: BigFloat + c: BigInt \\"\\"\\" An arbitrary string value with no semantic meaning. Will be included in the @@ -85,8 +85,8 @@ input AuthenticateInput { \\"\\"\\"All input for the \`authenticateMany\` mutation.\\"\\"\\" input AuthenticateManyInput { a: Int - b: Int - c: Int + b: BigFloat + c: BigInt \\"\\"\\" An arbitrary string value with no semantic meaning. Will be included in the @@ -128,8 +128,8 @@ type AuthenticatePayload { \\"\\"\\"All input for the \`authenticatePayload\` mutation.\\"\\"\\" input AuthenticatePayloadInput { a: Int - b: Int - c: Int + b: BigFloat + c: BigInt \\"\\"\\" An arbitrary string value with no semantic meaning. Will be included in the diff --git a/packages/postgraphile-core/__tests__/integration/schema/__snapshots__/rbac.test.js.snap b/packages/postgraphile-core/__tests__/integration/schema/__snapshots__/rbac.test.js.snap index b72a0ab02..16b8d0d06 100644 --- a/packages/postgraphile-core/__tests__/integration/schema/__snapshots__/rbac.test.js.snap +++ b/packages/postgraphile-core/__tests__/integration/schema/__snapshots__/rbac.test.js.snap @@ -1543,8 +1543,8 @@ type AuthenticateFailPayload { \\"\\"\\"All input for the \`authenticate\` mutation.\\"\\"\\" input AuthenticateInput { a: Int - b: Int - c: Int + b: BigFloat + c: BigInt \\"\\"\\" An arbitrary string value with no semantic meaning. Will be included in the @@ -1556,8 +1556,8 @@ input AuthenticateInput { \\"\\"\\"All input for the \`authenticateMany\` mutation.\\"\\"\\" input AuthenticateManyInput { a: Int - b: Int - c: Int + b: BigFloat + c: BigInt \\"\\"\\" An arbitrary string value with no semantic meaning. Will be included in the @@ -1599,8 +1599,8 @@ type AuthenticatePayload { \\"\\"\\"All input for the \`authenticatePayload\` mutation.\\"\\"\\" input AuthenticatePayloadInput { a: Int - b: Int - c: Int + b: BigFloat + c: BigInt \\"\\"\\" An arbitrary string value with no semantic meaning. Will be included in the @@ -4535,8 +4535,8 @@ type JsonIdentityMutationPayload { type JwtToken { a: Int - b: Int - c: Int + b: BigFloat + c: BigInt exp: Int role: String } diff --git a/packages/postgraphile-core/__tests__/integration/schema/__snapshots__/skipNodePlugin.test.js.snap b/packages/postgraphile-core/__tests__/integration/schema/__snapshots__/skipNodePlugin.test.js.snap index 433c1a084..ee6060d66 100644 --- a/packages/postgraphile-core/__tests__/integration/schema/__snapshots__/skipNodePlugin.test.js.snap +++ b/packages/postgraphile-core/__tests__/integration/schema/__snapshots__/skipNodePlugin.test.js.snap @@ -228,8 +228,8 @@ type AuthenticateFailPayload { \\"\\"\\"All input for the \`authenticate\` mutation.\\"\\"\\" input AuthenticateInput { a: Int - b: Int - c: Int + b: BigFloat + c: BigInt \\"\\"\\" An arbitrary string value with no semantic meaning. Will be included in the @@ -241,8 +241,8 @@ input AuthenticateInput { \\"\\"\\"All input for the \`authenticateMany\` mutation.\\"\\"\\" input AuthenticateManyInput { a: Int - b: Int - c: Int + b: BigFloat + c: BigInt \\"\\"\\" An arbitrary string value with no semantic meaning. Will be included in the @@ -284,8 +284,8 @@ type AuthenticatePayload { \\"\\"\\"All input for the \`authenticatePayload\` mutation.\\"\\"\\" input AuthenticatePayloadInput { a: Int - b: Int - c: Int + b: BigFloat + c: BigInt \\"\\"\\" An arbitrary string value with no semantic meaning. Will be included in the @@ -2962,8 +2962,8 @@ type JsonIdentityMutationPayload { type JwtToken { a: Int - b: Int - c: Int + b: BigFloat + c: BigInt exp: Int role: String } diff --git a/packages/postgraphile-core/__tests__/kitchen-sink-schema.sql b/packages/postgraphile-core/__tests__/kitchen-sink-schema.sql index 09f1dae8c..8d82947f3 100644 --- a/packages/postgraphile-core/__tests__/kitchen-sink-schema.sql +++ b/packages/postgraphile-core/__tests__/kitchen-sink-schema.sql @@ -363,12 +363,12 @@ create type b.jwt_token as ( role text, exp integer, a integer, - b integer, - c integer + b numeric, + c bigint ); -create function b.authenticate(a integer, b integer, c integer) returns b.jwt_token as $$ select ('yay', extract(epoch from '2037-07-12'::timestamp), a, b, c)::b.jwt_token $$ language sql; -create function b.authenticate_many(a integer, b integer, c integer) returns b.jwt_token[] as $$ select array[('foo', 1, a, b, c)::b.jwt_token, ('bar', 2, a + 1, b + 1, c + 1)::b.jwt_token, ('baz', 3, a + 2, b + 2, c + 2)::b.jwt_token] $$ language sql; +create function b.authenticate(a integer, b numeric, c bigint) returns b.jwt_token as $$ select ('yay', extract(epoch from '2037-07-12'::timestamp), a, b, c)::b.jwt_token $$ language sql; +create function b.authenticate_many(a integer, b numeric, c bigint) returns b.jwt_token[] as $$ select array[('foo', 1, a, b, c)::b.jwt_token, ('bar', 2, a + 1, b + 1, c + 1)::b.jwt_token, ('baz', 3, a + 2, b + 2, c + 2)::b.jwt_token] $$ language sql; create function b.authenticate_fail() returns b.jwt_token as $$ select null::b.jwt_token $$ language sql; create type b.auth_payload as ( @@ -377,7 +377,7 @@ create type b.auth_payload as ( admin bool ); -create function b.authenticate_payload(a integer, b integer, c integer) returns b.auth_payload as $$ select (('yay', extract(epoch from '2037-07-12'::timestamp), a, b, c)::b.jwt_token, 1, true)::b.auth_payload $$ language sql; +create function b.authenticate_payload(a integer, b numeric, c bigint) returns b.auth_payload as $$ select (('yay', extract(epoch from '2037-07-12'::timestamp), a, b, c)::b.jwt_token, 1, true)::b.auth_payload $$ language sql; create table a.similar_table_1 ( id serial primary key,