Skip to content

Commit

Permalink
chore: refactor environment variables
Browse files Browse the repository at this point in the history
  • Loading branch information
DylanVann committed Jan 14, 2021
1 parent 05344c5 commit 12e6dca
Show file tree
Hide file tree
Showing 20 changed files with 226 additions and 103 deletions.
4 changes: 2 additions & 2 deletions .env.ci
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ DATABASE_OWNER_PASSWORD=cisecret1
DATABASE_AUTHENTICATOR=graphile_starter_authenticator
DATABASE_AUTHENTICATOR_PASSWORD=cisecret2
DATABASE_VISITOR=graphile_starter_visitor
SECRET=cisecret3
JWT_SECRET=cisecret4
SECRET=cbGscssSrcSDmrOYm5vVvQIUmGoexyZuVcQVlpsn63XIIqofk63eGnl8WXPj2B0R
JWT_SECRET=cbGscssSrcSDmrOYm5vVvQIUmGoexyZuVcQVlpsn63XIIqofk63eGnl8WXPj2B0R
PORT=5678
ROOT_URL=http://localhost:5678
ENABLE_CYPRESS_COMMANDS=1
Expand Down
120 changes: 114 additions & 6 deletions @app/config/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,121 @@
import { defaultNumber, minLength, parseInteger, required } from "./validate";

// @ts-ignore
const packageJson = require("../../../package.json");

// TODO: customise this with your own settings!

export const fromEmail =
'"PostGraphile Starter" <[email protected]>';
export const awsRegion = "us-east-1";
export const projectName = packageJson.name.replace(/[-_]/g, " ");
export const companyName = projectName; // For copyright ownership
export const emailLegalText =
export const fromEmail: string = `"PostGraphile Starter" <[email protected]>`;
export const awsRegion: string = "us-east-1";
export const projectName: string = packageJson.name.replace(/[-_]/g, " ");
export const companyName: string = projectName; // For copyright ownership
export const emailLegalText: string =
// Envvar here so we can override on the demo website
process.env.LEGAL_TEXT || "<Insert legal email footer text here >";

export const SECRET: string = minLength(
required({ value: process.env.SECRET, name: "SECRET" }),
64
).value;

export const JWT_SECRET: string = minLength(
required({
value: process.env.JWT_SECRET,
name: "JWT_SECRET",
}),
64
).value;

export const REDIS_URL: string | undefined = process.env.REDIS_URL;

export const DATABASE_URL: string = required({
value: process.env.DATABASE_URL,
name: "DATABASE_URL",
}).value;

export const AUTH_DATABASE_URL: string = required({
value: process.env.AUTH_DATABASE_URL,
name: "AUTH_DATABASE_URL",
}).value;

export const NODE_ENV: string = required({
value: process.env.NODE_ENV,
name: "NODE_ENV",
}).value;

export const isDev: boolean = NODE_ENV === "development";

export const isTest: boolean = NODE_ENV === "test";

export const isDevOrTest: boolean = isDev || isTest;

export const ROOT_URL: string = required({
value: process.env.ROOT_URL,
name: "ROOT_URL",
}).value;

export const TRUST_PROXY: string | undefined = process.env.TRUST_PROXY;

export const ENABLE_GRAPHIQL: boolean = !!process.env.ENABLE_GRAPHIQL;

export const GRAPHILE_LICENSE: string | undefined =
process.env.GRAPHILE_LICENSE;
// Pro plugin options (requires process.env.GRAPHILE_LICENSE)
export const GRAPHQL_PAGINATION_CAP: number = defaultNumber(
parseInteger({
value: process.env.GRAPHQL_PAGINATION_CAP,
name: "GRAPHQL_PAGINATION_CAP",
}),
50
).value;
export const GRAPHQL_DEPTH_LIMIT: number = defaultNumber(
parseInteger({
value: process.env.GRAPHQL_DEPTH_LIMIT,
name: "GRAPHQL_DEPTH_LIMIT",
}),
12
).value;
export const GRAPHQL_COST_LIMIT: number = defaultNumber(
parseInteger({
value: process.env.GRAPHQL_COST_LIMIT,
name: "GRAPHQL_COST_LIMIT",
}),
30000
).value;
export const HIDE_QUERY_COST: boolean =
defaultNumber(
parseInteger({
value: process.env.HIDE_QUERY_COST,
name: "HIDE_QUERY_COST",
}),
0
).value < 1;

export const PORT: number = defaultNumber(
parseInteger({ value: process.env.PORT, name: "PORT" }),
3000
).value;

export const ENABLE_CYPRESS_COMMANDS: boolean =
process.env.ENABLE_CYPRESS_COMMANDS === "1";

export const GITHUB_KEY: string | undefined = process.env.GITHUB_KEY;
export const GITHUB_SECRET: string | undefined = process.env.GITHUB_SECRET;

export const DATABASE_VISITOR: string | undefined =
process.env.DATABASE_VISITOR;

const MILLISECOND = 1;
const SECOND = 1000 * MILLISECOND;
const MINUTE = 60 * SECOND;
const HOUR = 60 * MINUTE;
const DAY = 24 * HOUR;
export const MAXIMUM_SESSION_DURATION_IN_MILLISECONDS: number = defaultNumber(
parseInteger({
value: process.env.MAXIMUM_SESSION_DURATION_IN_MILLISECONDS,
name: "MAXIMUM_SESSION_DURATION_IN_MILLISECONDS",
}),
3 * DAY
).value;

export const T_AND_C_URL: string | undefined = process.env.T_AND_C_URL;
51 changes: 51 additions & 0 deletions @app/config/src/validate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* Validation functions for environment variables.
*
* `name` is passed with `value` so that bundlers that replace
* process.env.MY_VAR with the value will still provide useful error messages.
*/

export const required = (v: {
value?: string;
name: string;
}): { value: string; name: string } => {
const { value, name } = v;
if (!value || value === "") {
throw new Error(
`process.env.${name} is required but not defined - did you remember to run the setup script? Have you sourced the environmental variables file '.env'?`
);
}
return v as any;
};

export const minLength = (
v: { value: string; name: string },
minLength: number
): { value: string; name: string } => {
const { value, name } = v;
if (value.length < minLength) {
throw new Error(
`process.env.${name} should have minimum length ${minLength}.`
);
}
return v;
};

export const parseInteger = (v: {
value?: string;
name: string;
}): { value: number; name: string } => ({
value: parseInt(v.value || "", 10),
name: v.name,
});

export const defaultNumber = (
v: { value?: number; name: string },
defaultValue: number
): { value: number; name: string } => {
const { value, name } = v;
return {
value: value === undefined || isNaN(value) ? defaultValue : value,
name,
};
};
15 changes: 5 additions & 10 deletions @app/server/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { isDev, isTest, TRUST_PROXY } from "@app/config";
import express, { Express } from "express";
import { Server } from "http";
import { Middleware } from "postgraphile";

import { cloudflareIps } from "./cloudflare";
import * as middleware from "./middleware";
import { makeShutdownActions, ShutdownAction } from "./shutdownActions";
import { sanitizeEnv } from "./utils";

// Server may not always be supplied, e.g. where mounting on a sub-route
export function getHttpServer(app: Express): Server | void {
Expand All @@ -27,11 +27,6 @@ export async function makeApp({
}: {
httpServer?: Server;
} = {}): Promise<Express> {
sanitizeEnv();

const isTest = process.env.NODE_ENV === "test";
const isDev = process.env.NODE_ENV === "development";

const shutdownActions = makeShutdownActions();

if (isDev) {
Expand All @@ -50,7 +45,7 @@ export async function makeApp({
* server knows it's running in SSL mode, and so the logs can log the true
* IP address of the client rather than the IP address of our proxy.
*/
if (process.env.TRUST_PROXY) {
if (TRUST_PROXY) {
/*
We recommend you set TRUST_PROXY to the following:
Expand All @@ -63,11 +58,11 @@ export async function makeApp({
*/
app.set(
"trust proxy",
process.env.TRUST_PROXY === "1"
TRUST_PROXY === "1"
? true
: process.env.TRUST_PROXY === "cloudflare"
: TRUST_PROXY === "cloudflare"
? ["loopback", "linklocal", "uniquelocal", ...cloudflareIps]
: process.env.TRUST_PROXY.split(",")
: TRUST_PROXY.split(",")
);
}

Expand Down
2 changes: 1 addition & 1 deletion @app/server/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env node
/* eslint-disable no-console */
import { PORT } from "@app/config";
import chalk from "chalk";
import { createServer } from "http";

Expand All @@ -19,7 +20,6 @@ async function main() {
httpServer.addListener("request", app);

// And finally, we open the listen port
const PORT = parseInt(process.env.PORT || "", 10) || 3000;
httpServer.listen(PORT, () => {
const address = httpServer.address();
const actualPort: string =
Expand Down
5 changes: 3 additions & 2 deletions @app/server/src/middleware/installCSRFProtection.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ROOT_URL } from "@app/config";
import csrf from "csurf";
import { Express } from "express";

Expand All @@ -17,8 +18,8 @@ export default (app: Express) => {
if (
req.method === "POST" &&
req.path === "/graphql" &&
(req.headers.referer === `${process.env.ROOT_URL}/graphiql` ||
req.headers.origin === process.env.ROOT_URL)
(req.headers.referer === `${ROOT_URL}/graphiql` ||
req.headers.origin === ROOT_URL)
) {
// Bypass CSRF for GraphiQL
next();
Expand Down
5 changes: 3 additions & 2 deletions @app/server/src/middleware/installCypressServerCommand.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ENABLE_CYPRESS_COMMANDS, isDevOrTest } from "@app/config";
import { urlencoded } from "body-parser";
import { Express, Request, RequestHandler, Response } from "express";
import { Pool } from "pg";
Expand All @@ -6,7 +7,7 @@ import { getRootPgPool } from "./installDatabasePools";

export default (app: Express) => {
// Only enable this in test/development mode
if (!["test", "development"].includes(process.env.NODE_ENV || "")) {
if (!isDevOrTest) {
throw new Error("This code must not run in production");
}

Expand All @@ -15,7 +16,7 @@ export default (app: Express) => {
* to be set; this gives us extra protection against accidental XSS/CSRF
* attacks.
*/
const safeToRun = process.env.ENABLE_CYPRESS_COMMANDS === "1";
const safeToRun = ENABLE_CYPRESS_COMMANDS;

const rootPgPool = getRootPgPool(app);

Expand Down
5 changes: 3 additions & 2 deletions @app/server/src/middleware/installDatabasePools.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AUTH_DATABASE_URL, DATABASE_URL } from "@app/config";
import { Express } from "express";
import { Pool } from "pg";

Expand Down Expand Up @@ -26,14 +27,14 @@ function swallowPoolError(_error: Error) {
export default (app: Express) => {
// This pool runs as the database owner, so it can do anything.
const rootPgPool = new Pool({
connectionString: process.env.DATABASE_URL,
connectionString: DATABASE_URL,
});
rootPgPool.on("error", swallowPoolError);
app.set("rootPgPool", rootPgPool);

// This pool runs as the unprivileged user, it's what PostGraphile uses.
const authPgPool = new Pool({
connectionString: process.env.AUTH_DATABASE_URL,
connectionString: AUTH_DATABASE_URL,
});
authPgPool.on("error", swallowPoolError);
app.set("authPgPool", authPgPool);
Expand Down
3 changes: 1 addition & 2 deletions @app/server/src/middleware/installErrorHandler.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { isDev } from "@app/config";
import { ErrorRequestHandler, Express } from "express";
import * as fs from "fs";
import { template, TemplateExecutor } from "lodash";
import { resolve } from "path";

const isDev = process.env.NODE_ENV === "development";

interface ParsedError {
message: string;
status: number;
Expand Down
4 changes: 1 addition & 3 deletions @app/server/src/middleware/installHelmet.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { isDevOrTest } from "@app/config";
import { Express } from "express";
import helmet from "helmet";

const isDevOrTest =
process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test";

export default function installHelmet(app: Express) {
app.use(
helmet(
Expand Down
3 changes: 1 addition & 2 deletions @app/server/src/middleware/installLogging.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { isDev } from "@app/config";
import { Express } from "express";
import morgan from "morgan";

const isDev = process.env.NODE_ENV === "development";

export default (app: Express) => {
if (isDev) {
// To enable logging on development, uncomment the next line:
Expand Down
7 changes: 4 additions & 3 deletions @app/server/src/middleware/installPassport.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { GITHUB_KEY, GITHUB_SECRET } from "@app/config";
import { Express } from "express";
import { get } from "lodash";
import passport from "passport";
Expand Down Expand Up @@ -40,14 +41,14 @@ export default async (app: Express) => {
res.redirect("/");
});

if (process.env.GITHUB_KEY) {
if (GITHUB_KEY) {
await installPassportStrategy(
app,
"github",
GitHubStrategy,
{
clientID: process.env.GITHUB_KEY,
clientSecret: process.env.GITHUB_SECRET,
clientID: GITHUB_KEY,
clientSecret: GITHUB_SECRET,
scope: ["user:email"],
},
{},
Expand Down
3 changes: 2 additions & 1 deletion @app/server/src/middleware/installPassportStrategy.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ROOT_URL } from "@app/config";
import { Express, Request, RequestHandler } from "express";
import passport from "passport";

Expand Down Expand Up @@ -81,7 +82,7 @@ export default (
new Strategy(
{
...strategyConfig,
callbackURL: `${process.env.ROOT_URL}/auth/${service}/callback`,
callbackURL: `${ROOT_URL}/auth/${service}/callback`,
passReqToCallback: true,
},
async (
Expand Down
Loading

0 comments on commit 12e6dca

Please sign in to comment.