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

Add support for restricted graphql users #84

Merged
merged 4 commits into from
Apr 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
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
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
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo: 'for 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);
}
8 changes: 0 additions & 8 deletions postgraphile/vendor/postgraphile-supporter/LICENSE.md

This file was deleted.

Loading