Skip to content

Instantly share code, notes, and snippets.

@pszafer
Last active December 17, 2020 09:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pszafer/12b91a4cd62aee2736978cdd5de007a2 to your computer and use it in GitHub Desktop.
Save pszafer/12b91a4cd62aee2736978cdd5de007a2 to your computer and use it in GitHub Desktop.
postgraphile server
import PgUpsertPlugin from "./PgUpsertPlugin";
import PostGraphileFulltextFilterPlugin from "./PostgraphileFullTextFilterPlugin"
import PgSimplifyInflectorPlugin from "@graphile-contrib/pg-simplify-inflector";
import ConnectionFilterPlugin from "postgraphile-plugin-connection-filter";
import PgOrderByRelatedPlugin from "@graphile-contrib/pg-order-by-related";
import PgOrderByMultiColumnIndexPlugin from "./PgOrderByMultiColumnIndexPlugin";
import { SHAREDTOKEN, CORS_OPTIONS, FORMTOKEN, hosts, adminPayload, workerPayload } from "../config";
import express from "express";
import UnauthorizedError from "./UnauthorizedError";
import { postgraphile, makePluginHook } from "postgraphile";
import { OAuth2Client } from "google-auth-library";
import bearerToken from "express-bearer-token";
import cors from "cors";
import PgPubsub from '@graphile/pg-pubsub';
import { makeExtendSchemaPlugin, gql } from "graphile-utils";
const pluginHook = makePluginHook([PgPubsub]);
let requestCount = 0;
const morgan = require('morgan');
const compression = require("compression")
const bearer = bearerToken()
const client = new OAuth2Client(process.env.GOOGLE_CLIENT_ID);
const port = process.env.PORT || 8081;
const host = `localhost:${port}`;
const referers = [...hosts.map(x => (`http://${x}:${port}/graphiql`))]
const extractToken = ({token, connectionParams: { authToken } = {}}) => token ? token : authToken ? authToken : null
const asyncJWT = async (req, res, next) => {
++requestCount;
console.log(`Request number: ${requestCount}`)
var wsProto = false;
if (req.headers['sec-websocket-protocol']){
wsProto= true;
}
const idToken = extractToken(req);
if (idToken === FORMTOKEN) {
req.payload = workerPayload(req)
req.form = true;
console.log("Finishing form NextJS")
return next();
}
if (
(referers.includes(req.headers.referer) && SHAREDTOKEN == idToken || host === req.headers.host)
) {
req.payload = adminPayload;
req.admin = true;
console.log("Admin, giving access.")
return next();
}
if (idToken) {
console.log("Ticker", idToken)
try {
const ticket = await client.verifyIdToken({
idToken: idToken,
audience: process.env.GOOGLE_CLIENT_ID,
});
const payload = ticket.getPayload();
if (payload["hd"] === process.env.GOOGLE_DOMAIN) {
req["payload"] = payload;
console.log("Google user. Giving access.")
return next();
}
} catch (error) {
console.error("Error with token.")
return next(
new UnauthorizedError("too_old_token", {
message: "Token is too old " + error,
})
);
}
}
console.error("No credentials.")
return next(
new UnauthorizedError("credentials_bad_format", {
message: "Format is Authorization: Bearer [token]",
})
);
};
const authErrors = (err, req, res, next) => {
if (
err.name === "UnauthorizedError" ||
err.name === "UnhandledPromiseRejectionWarning"
) {
res.status(err.status).json({ errors: [{ message: err.message }] });
res.end();
}
};
const errorsLevel = ["hint", "detail", "errcode", "notice"];
// const errorsLevel = [ "severity", "code", "detail", "hint", "position",
// "internalPosition", "internalQuery", "where", "schema",
// "table", "column", "dataType", "constraint", "file", "line", "routine" ]
const postgraphileOptions = {
pgSettings: (req) => {
const settings = {};
if (req.payload) {
settings["role"] = req.admin ? "admin" : req.form ? "workperson" : "person";
settings["user.sub"] = req.payload["sub"];
settings["user.firstname"] = req.payload["given_name"];
settings["user.lastname"] = req.payload["family_name"];
settings["user.email"] = req.payload["email"];
} else {
settings["role"] = "intranet_anonymous";
}
return settings;
},
pluginHook,
subscriptions: true,
exportGqlSchemaPath: "schema.graphql",
watchPg: true,
ignoreRBAC: false,
graphiql: true,
enhanceGraphiql: true,
enableQueryBatching: true,
showErrorStack: "json",
dynamicJson: true,
setofFunctionsContainNulls: false,
websocketMiddlewares:
[
bearer,
asyncJWT
],
allowExplain(req) {
return true;
},
disableQueryLog: true,
extendedErrors: ["hint", "detail", "errcode", "notice"],
appendPlugins: [
MySubscriptionPlugin,
PgSimplifyInflectorPlugin,
ConnectionFilterPlugin,
PgOrderByRelatedPlugin,
PgOrderByMultiColumnIndexPlugin,
PostGraphileFulltextFilterPlugin,
PgUpsertPlugin,
],
graphileBuildOptions: {
orderByNullsLast: true,
connectionFilterAllowNullInput: true, // default: false
connectionFilterAllowEmptyObjectInput: true,
connectionFilterRelations: true,
connectionFilterSetofFunctions: true,
connectionFilterComputedColumns: true
},
}
const postgraphileMiddleware = postgraphile(process.env.DATABASE_URL, process.env.DB_SCHEMA, postgraphileOptions)
const app = express();
app.use(cors(CORS_OPTIONS));
app.use(compression())
app.use(morgan(':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"'))
app.use("/graphql", bearer);
app.use("/graphql", asyncJWT);
app.use("/graphql", authErrors);
app.use(postgraphileMiddleware);
app.listen(port, () => {
console.log(`Pricing service listening on http://localhost:${port}/graphiql`);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment