Skip to content
This repository has been archived by the owner on Aug 31, 2021. It is now read-only.

Commit

Permalink
Merge pull request #84 from vulcanize/improve-postgraphile-interaction
Browse files Browse the repository at this point in the history
Add support for restricted graphql users
  • Loading branch information
rmulhol authored Apr 18, 2019
2 parents dbd2208 + 7d2542a commit 13cd712
Show file tree
Hide file tree
Showing 12 changed files with 200 additions and 120 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ vulcanizedb.log
db/migrations/20*.sql
plugins/*.so
postgraphile/*.toml
postgraphile/schema.graphql
27 changes: 19 additions & 8 deletions postgraphile/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,31 @@ Build the docker image in this directory. Start the `GraphiQL` frontend by:
* Setting the env variables for the database connection: `DATABASE_HOST`,
`DATABASE_NAME`, `DATABASE_USER`, `DATABASE_PASSWORD` (and optionally
`DATABASE_PORT` if running on non-standard port).
* The specified user needs to be `superuser` on the vulcanizeDB database
* Run the container (ex. `docker run -e DATABASE_HOST=localhost -e DATABASE_NAME=vulcanize_public -e DATABASE_USER=vulcanize -e DATABASE_PASSWORD=vulcanize -d m0ar/images:postgraphile-alpine`)
* GraphiQL is available at `:3000/graphiql`

By default, this build will expose only the "public" schema - to add other schemas, use a config file `config.toml` and set the env var `CONFIG_PATH` to point to its location. Example `toml`:
* The specified user needs to be `superuser` on the vulcanizeDB database,
so postgraphile can setup watch fixtures keeping track of live schema
changes.
* To limit the amount of available queries in GraphQL, a restricted user can be used
for postgraphile introspection by adding env variables `GQ_USER` and `GQ_PASSWORD`.
* By doing `GRANT [SELECT | EXECUTE]` on tables/functions for this user,
you can selectively assign things you want available in GraphQL.
* You still need to pass in a superuser with `DATABASE_USER` & `DATABASE_PASSWORD` for
the postgraphile watch fixtures to work.
* By default, postgraphile publishes the `public` schema. This can be expanded with for example `GQ_SCHEMAS=public,maker`
* Run the container (ex. `docker run -e DATABASE_HOST=localhost -e DATABASE_NAME=my_database -e DATABASE_USER=superuser -e DATABASE_PASSWORD=superuser -e GQ_USER=graphql -e GQ_PASSWORD=graphql -e GQ_SCHEMAS=public,anotherSchema -d my-postgraphile-image`)
* GraphiQL frontend is available at `:3000/graphiql`
GraphQL endpoint is available at `:3000/graphql`

By default, this build will expose only the "public" schema - to add other schemas, use either the env variables,
or a config file `config.toml` and set the env var `CONFIG_PATH` to point to its location. Example `toml`:

```
[database]
name = "vulcanize_public"
hostname = "localhost"
port = 5432
schemas = ["public", "yourschema"]
gq_schemas = ["public", "yourschema"]
gq_user = "graphql"
gq_password = "graphql"
```

## Building
Expand All @@ -29,8 +42,6 @@ By default, this build will expose only the "public" schema - to add other schem

Install dependencies with `yarn` and execute `yarn build`. The bundle produced by Webpack will be present in `build/dist/`.

This application currently uses the Postgraphile supporter plugin. This plugin is present in the `vendor/` directory and is copied to `node_modules/` after installation of packages. It is a fresh checkout of the plugin as of August 31st, 2018.

## Running

Provide the built bundle to node as a runnable script: `node ./build/dist/vulcanize-postgraphile-server.js`
Expand Down
4 changes: 2 additions & 2 deletions postgraphile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
"scripts": {
"build": "rm -rf ./build/dist && webpack --config=./webpack.config.js",
"lint": "tslint --project ./tsconfig.json --config ./tslint.json",
"postinstall": "rm -rf node_modules/@graphile && mkdir node_modules/@graphile && cp -R ./vendor/postgraphile-supporter/ ./node_modules/@graphile/plugin-supporter/",
"start": "npm run build && node ./build/dist/vulcanize-postgraphile-server.js",
"dev": "./node_modules/typescript/bin/tsc && node build/dist/src/index.js",
"test": "rm -rf ./build/spec && tsc --build ./tsconfig.test.json && jasmine --config=./spec/support/jasmine.json",
Expand All @@ -28,7 +27,7 @@
"passport": "0.4.0",
"pg": "6.4.2",
"pg-native": "3.0.0",
"postgraphile": "4.0.0-rc.4",
"postgraphile": "4.4.0-beta.11",
"subscriptions-transport-ws": "0.9.14",
"toml": "2.3.3"
},
Expand All @@ -40,6 +39,7 @@
"@types/lodash": "4.14.116",
"@types/node": "^10.12.21",
"@types/passport": "0.4.6",
"@graphile-contrib/pg-simplify-inflector": "3.0.0",
"awesome-typescript-loader": "5.2.0",
"jasmine": "3.2.0",
"jasmine-ts-console-reporter": "3.1.1",
Expand Down
10 changes: 3 additions & 7 deletions postgraphile/spec/server/config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ describe('buildServerConfig', () => {
databaseConfig = {
host: 'example.com',
database: 'example_database',
schemas: ['public']
schemas: ['public'],
ownerConnectionString: 'postgres://admin:admin@host'
};

postgraphileMiddleware = jasmine
.createSpyObj<PostgraphileMiddleware>(['call']),
.createSpyObj<PostgraphileMiddleware>(['call']);

serverUtilities = {
pluginHook: jasmine.createSpy('pluginHook'),
enableSubscriptions: jasmine.createSpy('enableSubscriptions'),
express: jasmine.createSpy('express'),
expressSession: jasmine.createSpy('expressSession'),
httpServerFactory: jasmine.createSpy('httpServerFactory'),
Expand Down Expand Up @@ -66,10 +66,6 @@ describe('buildServerConfig', () => {
expect(serverConfig.options).not.toBeNull();
});

it('enables simple subscriptions', () => {
expect(serverConfig.options.simpleSubscriptions).toBe(true);
});

it('it adds the express session handler as the first middleware', () => {
expect(serverConfig.options.webSocketMiddlewares[0])
.toBe(expressSessionHandler);
Expand Down
21 changes: 8 additions & 13 deletions postgraphile/spec/server/runtime.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ describe('bootServer', () => {
beforeEach(() => {
serverUtilities = {
pluginHook: jasmine.createSpy('pluginHook'),
enableSubscriptions: jasmine.createSpy('enableSubscriptions'),
express: jasmine.createSpy('express'),
expressSession: jasmine.createSpy('expressSession'),
httpServerFactory: jasmine.createSpy('httpServerFactory'),
Expand All @@ -25,12 +24,16 @@ describe('bootServer', () => {

serverConfig = {
middleware: jasmine.createSpyObj<PostgraphileMiddleware>(['call']),
options: {
pluginHook: jasmine.createSpy('pluginHook'),
watchPg: true,
options: {
appendPlugins: [],
disableDefaultMutations: false,
enableCors: true,
simpleSubscriptions: true,
exportGqlSchemaPath: '',
graphiql: true,
ignoreRBAC: false,
ownerConnectionString: '',
pluginHook: jasmine.createSpy('pluginHook'),
watchPg: true,
webSocketMiddlewares: [] },
port: 5678
};
Expand All @@ -56,14 +59,6 @@ describe('bootServer', () => {
expect(useSpy).toHaveBeenCalledWith(serverConfig.middleware);
});

it('enahances the Node HTTP server with Postgraphile subscriptions', () => {
expect(serverUtilities.enableSubscriptions)
.toHaveBeenCalledWith(
mockHttpServer,
serverConfig.middleware,
serverConfig.options);
});

it('instructs the server to listen on the given port', () => {
const listenSpy = mockHttpServer.listen as jasmine.Spy;
expect(listenSpy).toHaveBeenCalledWith(serverConfig.port);
Expand Down
21 changes: 10 additions & 11 deletions postgraphile/src/adapters/postgraphile.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { RequestHandler } from 'express';
import { Server } from 'http';
import { PluginHookFn } from 'postgraphile/build/postgraphile/pluginHook';
import {PluginHookFn } from 'postgraphile/build/postgraphile/pluginHook';
import {Plugin} from 'postgraphile';

// NOTE: Shape of the middleware is not
// currently important to this application, but if a need arises,
Expand All @@ -9,12 +9,16 @@ import { PluginHookFn } from 'postgraphile/build/postgraphile/pluginHook';
export interface PostgraphileMiddleware extends RequestHandler {}

export interface PostgraphileOptions {
pluginHook: PluginHookFn;
simpleSubscriptions: boolean;
watchPg: boolean;
appendPlugins: Plugin[];
disableDefaultMutations: boolean;
enableCors: boolean;
exportGqlSchemaPath: string;
graphiql: boolean;
// NOTE: Shape of the middlewares is not
ignoreRBAC: boolean;
ownerConnectionString: string;
pluginHook: PluginHookFn;
watchPg: boolean;
// NOTE Shape of the middlewares is not
// currently important to this application, but if a need arises,
// any needed shape can be assigned from a custom type here.
webSocketMiddlewares: object[];
Expand All @@ -26,8 +30,3 @@ export type PostgraphileInitCallback = (
options: PostgraphileOptions
) => PostgraphileMiddleware;

export type AddSubscriptionsCallback = (
httpServer: Server,
middleware: PostgraphileMiddleware,
options: PostgraphileOptions
) => void;
20 changes: 16 additions & 4 deletions postgraphile/src/config/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ export function parseConfig(
let database = '';
let user = '';
let password = '';
let schemas = ['public'];
let gqSchemas = ['public'];
let gqUser = '';
let gqPassword = '';

if (configPath) {
const tomlContents = readCallback(`${configPath}`).toString();
Expand All @@ -32,7 +34,9 @@ export function parseConfig(
database = parsedToml['database']['name'];
user = parsedToml['database']['user'];
password = parsedToml['database']['password'];
schemas = parsedToml['database']['schemas'];
gqSchemas = parsedToml['database']['gq_schemas'];
gqUser = parsedToml['database']['gq_user'] || gqUser;
gqPassword = parsedToml['database']['gq_password'] || gqPassword;
}

// Overwrite config values with env. vars if such are set
Expand All @@ -41,6 +45,11 @@ export function parseConfig(
database = process.env.DATABASE_NAME || database;
user = process.env.DATABASE_USER || user;
password = process.env.DATABASE_PASSWORD || password;
gqSchemas = process.env.GQ_SCHEMAS
? process.env.GQ_SCHEMAS.split(',')
: gqSchemas;
gqUser = process.env.GQ_USER || gqUser;
gqPassword = process.env.GQ_PASSWORD || gqPassword;

if (!host) {
throw new Error(MISSING_HOST_MESSAGE);
Expand All @@ -55,8 +64,11 @@ export function parseConfig(
}

return {
host: `postgres://${user}:${password}@${host}:${port}`,
host: gqUser && gqPassword
? `postgres://${gqUser}:${gqPassword}@${host}:${port}`
: `postgres://${user}:${password}@${host}:${port}`,
database,
schemas
schemas: gqSchemas,
ownerConnectionString: `postgres://${user}:${password}@${host}:${port}/${database}`
};
}
7 changes: 1 addition & 6 deletions postgraphile/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@ import passport = require('passport');
import session = require('express-session');
import toml = require('toml');

const {
default: PostGraphileSupporter,
enhanceHttpServerWithSubscriptions,
} = require('@graphile/plugin-supporter');
const pluginHook = makePluginHook([PostGraphileSupporter]);
const pluginHook = makePluginHook([]);

import { ServerUtilities } from './server/interface';
import { bootServer } from './server/runtime';
Expand All @@ -27,7 +23,6 @@ const configPath = process.env[CONFIG_PATH_KEY];
const serverPort = process.env[SERVER_PORT_KEY];

const serverUtilities: ServerUtilities = {
enableSubscriptions: enhanceHttpServerWithSubscriptions,
express,
expressSession: session,
httpServerFactory: createServer,
Expand Down
11 changes: 8 additions & 3 deletions postgraphile/src/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,18 @@ export function buildServerConfig(
const passportInitializer = utilities.passport.initialize();
const passportSessionHandler = utilities.passport.session();
const pluginHook = utilities.pluginHook;
const PgSimplifyInflectorPlugin = require('@graphile-contrib/pg-simplify-inflector');

const options: PostgraphileOptions = {
pluginHook: pluginHook,
simpleSubscriptions: true,
watchPg: true,
appendPlugins: [PgSimplifyInflectorPlugin],
disableDefaultMutations: true,
enableCors: true,
exportGqlSchemaPath: 'schema.graphql',
graphiql: true,
ignoreRBAC: false,
ownerConnectionString: databaseConfig.ownerConnectionString,
pluginHook: pluginHook,
watchPg: true,
webSocketMiddlewares: [
expressSessionHandler,
passportInitializer,
Expand Down
3 changes: 1 addition & 2 deletions postgraphile/src/server/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
} from '../adapters/session';

import {
AddSubscriptionsCallback,
PostgraphileInitCallback,
PostgraphileMiddleware,
PostgraphileOptions
Expand All @@ -18,6 +17,7 @@ export interface DatabaseConfig {
host: string;
database: string;
schemas: string[];
ownerConnectionString: string;
}

export interface ServerConfig {
Expand All @@ -27,7 +27,6 @@ export interface ServerConfig {
}

export interface ServerUtilities {
enableSubscriptions: AddSubscriptionsCallback;
express: ExpressInitCallback;
expressSession: ExpressSessionInitCallback;
httpServerFactory: CreateHttpServerCallback;
Expand Down
5 changes: 0 additions & 5 deletions postgraphile/src/server/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@ export function bootServer(
expressApp.use(config.middleware);

const httpServer = utilities.httpServerFactory(expressApp);

utilities.enableSubscriptions(
httpServer,
config.middleware,
config.options);

httpServer.listen(config.port);
}
Loading

0 comments on commit 13cd712

Please sign in to comment.