No require any ENV_VAR
This commit is contained in:
@@ -7,7 +7,7 @@
|
|||||||
"lint-fix": "eslint . --ext .ts --fix",
|
"lint-fix": "eslint . --ext .ts --fix",
|
||||||
"update-client-hash": "node -e \"require('fs').writeFileSync('public/misc/hash.txt', require('randomstring').generate())\"",
|
"update-client-hash": "node -e \"require('fs').writeFileSync('public/misc/hash.txt', require('randomstring').generate())\"",
|
||||||
"debug-client": "tsc-watch --project ./public/js-source",
|
"debug-client": "tsc-watch --project ./public/js-source",
|
||||||
"debug-server": "tsc-watch --project ./src --onSuccess \"node --enable-source-maps --use-openssl-ca --unhandled-rejections=strict ./out/index\"",
|
"debug-server": "tsc-watch --project . --onSuccess \"node --enable-source-maps --use-openssl-ca --unhandled-rejections=strict ./out/index\"",
|
||||||
"debug": "concurrently npm:debug-*",
|
"debug": "concurrently npm:debug-*",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"production": "node --use-openssl-ca --unhandled-rejections=strict ./out/index",
|
"production": "node --use-openssl-ca --unhandled-rejections=strict ./out/index",
|
||||||
|
|||||||
10
src/app.ts
10
src/app.ts
@@ -5,7 +5,7 @@ import * as path from 'path';
|
|||||||
import * as sassMiddleware from 'node-sass-middleware';
|
import * as sassMiddleware from 'node-sass-middleware';
|
||||||
import * as compression from 'compression';
|
import * as compression from 'compression';
|
||||||
import indexRouter from './routes';
|
import indexRouter from './routes';
|
||||||
import {HttpLogger, Config, AuthProxy, AutoReloader, Polyfill, Session} from './utils';
|
import {AuthProxy, AutoReloader, DefaultConfig, HttpLogger, Polyfill} from './utils';
|
||||||
|
|
||||||
export const app = express();
|
export const app = express();
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ export const app = express();
|
|||||||
app.set('views', path.join(__dirname, '../views'));
|
app.set('views', path.join(__dirname, '../views'));
|
||||||
app.set('view engine', 'pug');
|
app.set('view engine', 'pug');
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
res.locals.Config = Config;
|
res.locals.Config = DefaultConfig;
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ router.use(AuthProxy.router);
|
|||||||
router.use(AutoReloader.router);
|
router.use(AutoReloader.router);
|
||||||
|
|
||||||
// session
|
// session
|
||||||
router.use(Session.getRouter());
|
//router.use(Session.getRouter());
|
||||||
|
|
||||||
// static config
|
// static config
|
||||||
router.use("/js/polyfill.js", Polyfill.router);
|
router.use("/js/polyfill.js", Polyfill.router);
|
||||||
@@ -46,7 +46,7 @@ router.use(sassMiddleware({
|
|||||||
router.use(express.static(path.join(__dirname, '../public')));
|
router.use(express.static(path.join(__dirname, '../public')));
|
||||||
|
|
||||||
router.use(indexRouter);
|
router.use(indexRouter);
|
||||||
app.use(Config.BASE_PATH, router);
|
app.use(DefaultConfig.BASE_PATH, router);
|
||||||
|
|
||||||
// catch 404 and forward to error handler
|
// catch 404 and forward to error handler
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
@@ -60,7 +60,7 @@ app.use((err: Error&{status?: number}, req: Request, res: Response, next: NextFu
|
|||||||
}
|
}
|
||||||
// set locals, only providing error in development
|
// set locals, only providing error in development
|
||||||
res.locals.message = err.message;
|
res.locals.message = err.message;
|
||||||
res.locals.error = !Config.isProduction ? err : {};
|
res.locals.error = !DefaultConfig.isProduction ? err : {};
|
||||||
|
|
||||||
// render the error page
|
// render the error page
|
||||||
res.status(err.status || 500);
|
res.status(err.status || 500);
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
/* eslint-disable no-process-exit,no-console */
|
/* eslint-disable no-process-exit,no-console */
|
||||||
import * as http from "http";
|
import * as http from "http";
|
||||||
import urlJoin from 'proper-url-join';
|
import urlJoin from 'proper-url-join';
|
||||||
import {Config} from './utils';
|
import {DefaultConfig} from './utils';
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
host: Config.HOSTNAME,
|
host: DefaultConfig.HOSTNAME,
|
||||||
port: Config.PORT,
|
port: DefaultConfig.PORT,
|
||||||
path: urlJoin(Config.BASE_PATH, '/health'),
|
path: urlJoin(DefaultConfig.BASE_PATH, '/health'),
|
||||||
timeout: 2000,
|
timeout: 2000,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
10
src/index.ts
10
src/index.ts
@@ -4,12 +4,12 @@
|
|||||||
import * as http from 'http';
|
import * as http from 'http';
|
||||||
import {app} from './app';
|
import {app} from './app';
|
||||||
import {HttpError} from 'http-errors';
|
import {HttpError} from 'http-errors';
|
||||||
import {Config, Logger} from './utils';
|
import {DefaultConfig, Logger} from './utils';
|
||||||
|
|
||||||
app.set('port', Config.PORT);
|
app.set('port', DefaultConfig.PORT);
|
||||||
const server = http.createServer(app);
|
const server = http.createServer(app);
|
||||||
|
|
||||||
app.listen(Config.PORT);
|
app.listen(DefaultConfig.PORT);
|
||||||
server.on('error', onError);
|
server.on('error', onError);
|
||||||
server.on('listening', onListening);
|
server.on('listening', onListening);
|
||||||
|
|
||||||
@@ -20,11 +20,11 @@ function onError(error: HttpError): void {
|
|||||||
|
|
||||||
switch (error.code) {
|
switch (error.code) {
|
||||||
case 'EACCES':
|
case 'EACCES':
|
||||||
Logger.error(Config.PORT + ' requires elevated privileges');
|
Logger.error(DefaultConfig.PORT + ' requires elevated privileges');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
break;
|
break;
|
||||||
case 'EADDRINUSE':
|
case 'EADDRINUSE':
|
||||||
Logger.error(Config.PORT + ' is already in use');
|
Logger.error(DefaultConfig.PORT + ' is already in use');
|
||||||
process.exit(2);
|
process.exit(2);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import * as express from 'express';
|
import * as express from 'express';
|
||||||
import userRouter from './api/user';
|
import userRouter from './api/user';
|
||||||
import healthRouter from './healthcheck';
|
import healthRouter from './healthcheck';
|
||||||
import {Config} from '../utils';
|
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -1,21 +1,22 @@
|
|||||||
import {Request, RequestHandler} from 'express';
|
import {Request, RequestHandler} from 'express';
|
||||||
import {Config, Logger} from '.';
|
import {DefaultConfig, Logger} from '.';
|
||||||
import {Resolvable} from './helpers/resolvable';
|
import {Resolvable} from './helpers/resolvable';
|
||||||
import fetch from 'node-fetch';
|
import fetch from 'node-fetch';
|
||||||
import {urlJoin} from './helpers/urlJoin';
|
import {urlJoin} from './helpers/urlJoin';
|
||||||
|
|
||||||
const router: RequestHandler = (req: Request, res, next) => {
|
const router: RequestHandler = (req: Request, res, next) => {
|
||||||
const resolvable = new Resolvable(async () => {
|
const resolvable = new Resolvable(async () => {
|
||||||
if (!Config.USERINFO_HEADER) {
|
if (!DefaultConfig.USERINFO_HEADER) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const token = req.header(Config.USERINFO_HEADER);
|
const token = req.header(DefaultConfig.USERINFO_HEADER);
|
||||||
const url = Config.AUTH_PROXY_USERINFO_URL || Config.AUTH_PROXY_URL && urlJoin(Config.AUTH_PROXY_URL, "userinfo");
|
const url = DefaultConfig.AUTH_PROXY_USERINFO_URL ||
|
||||||
|
DefaultConfig.AUTH_PROXY_URL && urlJoin(DefaultConfig.AUTH_PROXY_URL, "userinfo");
|
||||||
if (token === undefined || url === undefined) {
|
if (token === undefined || url === undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const res = await fetch(url, {headers: [[Config.USERINFO_HEADER, token]]});
|
const res = await fetch(url, {headers: [[DefaultConfig.USERINFO_HEADER, token]]});
|
||||||
return await res.json() as UserInfo;
|
return await res.json() as UserInfo;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Logger.warn(e);
|
Logger.warn(e);
|
||||||
@@ -25,7 +26,8 @@ const router: RequestHandler = (req: Request, res, next) => {
|
|||||||
|
|
||||||
req.getUserInfo = () => resolvable.resolve();
|
req.getUserInfo = () => resolvable.resolve();
|
||||||
res.initLogout = function() {
|
res.initLogout = function() {
|
||||||
const url = Config.AUTH_PROXY_INIT_LOGOUT_URL || Config.AUTH_PROXY_URL && urlJoin(Config.AUTH_PROXY_URL, "init-logout");
|
const url = DefaultConfig.AUTH_PROXY_INIT_LOGOUT_URL ||
|
||||||
|
DefaultConfig.AUTH_PROXY_URL && urlJoin(DefaultConfig.AUTH_PROXY_URL, "init-logout");
|
||||||
if (url === undefined) {
|
if (url === undefined) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import {Router} from 'express';
|
import {Router} from 'express';
|
||||||
import {Config, Logger} from '.';
|
import {DefaultConfig, Logger} from '.';
|
||||||
import {urlJoin} from './helpers/urlJoin';
|
import {urlJoin} from './helpers/urlJoin';
|
||||||
import {v4} from 'uuid';
|
import {v4} from 'uuid';
|
||||||
import Timeout = NodeJS.Timeout;
|
import Timeout = NodeJS.Timeout;
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
if (!Config.isProduction) {
|
if (!DefaultConfig.isProduction) {
|
||||||
let uuid = v4();
|
let uuid = v4();
|
||||||
let updateTimeout: Timeout|undefined = undefined;
|
let updateTimeout: Timeout|undefined = undefined;
|
||||||
import("node-watch").then((watch) => {
|
import("node-watch").then((watch) => {
|
||||||
|
|||||||
@@ -32,10 +32,10 @@ const envs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function requireEnv(name: string, onlyInProduction = false): void {
|
function requireEnv(name: string, onlyInProduction = false): void {
|
||||||
env.get(name).required(!onlyInProduction || isProduction);
|
env.get(name).required(!onlyInProduction || isProduction).asString();
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Config = {
|
export const DefaultConfig = {
|
||||||
...envs,
|
...envs,
|
||||||
EXTERNAL_BASE_URL:
|
EXTERNAL_BASE_URL:
|
||||||
envs.EXTERNAL_BASE_URL ||
|
envs.EXTERNAL_BASE_URL ||
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export {Config} from './config';
|
export {DefaultConfig} from './config';
|
||||||
export {Redis} from './redis';
|
export {Redis} from './redis';
|
||||||
export {Logger, HttpLogger} from './logging';
|
export {Logger, HttpLogger} from './logging';
|
||||||
export {AuthProxy} from './auth-proxy';
|
export {AuthProxy} from './auth-proxy';
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import {LeveledLogMethod} from 'winston';
|
|||||||
import prune = require('json-prune');
|
import prune = require('json-prune');
|
||||||
import {RequestHandler} from 'express';
|
import {RequestHandler} from 'express';
|
||||||
import * as colors from 'colors';
|
import * as colors from 'colors';
|
||||||
import {Config} from '.';
|
import {DefaultConfig} from '.';
|
||||||
|
|
||||||
|
|
||||||
const logger = winston.createLogger({
|
const logger = winston.createLogger({
|
||||||
level: Config.isProduction ? "info" : "silly",
|
level: DefaultConfig.isProduction ? "info" : "silly",
|
||||||
format: winston.format.json(),
|
format: winston.format.json(),
|
||||||
transports: [
|
transports: [
|
||||||
new winston.transports.Console({
|
new winston.transports.Console({
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {AccessControl, AccessControlError, IQueryInfo, Permission, Query} from 'role-acl';
|
import {AccessControl, AccessControlError, IQueryInfo, Permission} from 'role-acl';
|
||||||
|
import {Query} from 'role-acl/lib/src/core/Query';
|
||||||
import {Request, RequestHandler} from 'express';
|
import {Request, RequestHandler} from 'express';
|
||||||
|
|
||||||
// see https://www.npmjs.com/package/role-acl
|
// see https://www.npmjs.com/package/role-acl
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {RequestHandler} from 'express';
|
import {RequestHandler} from 'express';
|
||||||
import * as polyfillLibrary from 'polyfill-library';
|
import * as polyfillLibrary from 'polyfill-library';
|
||||||
import {Config, Logger} from '.';
|
import {DefaultConfig, Logger} from '.';
|
||||||
import {PolyfillFeatureList} from 'polyfill-library';
|
import {PolyfillFeatureList} from 'polyfill-library';
|
||||||
import {WaitForSync} from './helpers/resolvable';
|
import {WaitForSync} from './helpers/resolvable';
|
||||||
import {spawn, Thread, Worker} from 'threads';
|
import {spawn, Thread, Worker} from 'threads';
|
||||||
@@ -26,7 +26,7 @@ const router: RequestHandler = async (req, res) => {
|
|||||||
const polyfillBundle = await polyfillLibrary.getPolyfillString({
|
const polyfillBundle = await polyfillLibrary.getPolyfillString({
|
||||||
uaString: req.header("user-agent"),
|
uaString: req.header("user-agent"),
|
||||||
features: await features.resolve(),
|
features: await features.resolve(),
|
||||||
minify: Config.isProduction,
|
minify: DefaultConfig.isProduction,
|
||||||
unknown: "polyfill",
|
unknown: "polyfill",
|
||||||
stream: false,
|
stream: false,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,38 +1,70 @@
|
|||||||
import * as redis from 'redis';
|
import * as redis from 'redis';
|
||||||
import {Config} from '.';
|
import {DefaultConfig} from '.';
|
||||||
import {promisify} from 'util';
|
import {promisify} from 'util';
|
||||||
|
|
||||||
Config.requireEnv('REDIS_URL', true);
|
interface RedisType {
|
||||||
|
client: redis.RedisClient | undefined;
|
||||||
const redisClient = Config.REDIS_URL && redis.createClient({url: Config.REDIS_URL});
|
get(key: string): Promise<string | undefined>;
|
||||||
const inMemory: {[key: string]: string} = {};
|
set(key: string, value: string): Promise<void>;
|
||||||
|
del(...keys: string[]): Promise<number>;
|
||||||
type RedisType = {
|
|
||||||
client: redis.RedisClient | undefined,
|
|
||||||
get: (key: string) => Promise<string | undefined>,
|
|
||||||
set: (key: string, value: string) => Promise<void>,
|
|
||||||
del: (...keys: string[]) => Promise<void>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Redis: RedisType = redisClient ? {
|
class RealRedis implements RedisType {
|
||||||
client: redisClient,
|
private _client: redis.RedisClient|undefined;
|
||||||
get: (async (key: string) => {
|
private _promGet: ((key: string) => Promise<string|null>)|undefined;
|
||||||
const value = await promisify(redisClient.get)(key);
|
private _promSet: RedisType["set"]|undefined;
|
||||||
|
private _promDel: RedisType["del"]|undefined;
|
||||||
|
|
||||||
|
get client(): redis.RedisClient {
|
||||||
|
if (this._client !== undefined) return this._client;
|
||||||
|
DefaultConfig.requireEnv('REDIS_URL');
|
||||||
|
this._client = redis.createClient({url: DefaultConfig.REDIS_URL});
|
||||||
|
return this._client;
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(key: string): Promise<string | undefined> {
|
||||||
|
if (!this._promGet) this._promGet = promisify(this.client.get);
|
||||||
|
const value = await this._promGet(key);
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
return value === null ? undefined : value;
|
return value === null ? undefined : value;
|
||||||
}),
|
|
||||||
set: promisify(redisClient.set),
|
|
||||||
del: promisify(redisClient.del) as unknown as (...keys: string[]) => Promise<void>,
|
|
||||||
} : {
|
|
||||||
client: undefined,
|
|
||||||
get: (async (key: string) => inMemory[key] || undefined),
|
|
||||||
set: (async (key: string, value: string) => {
|
|
||||||
inMemory[key] = value;
|
|
||||||
}),
|
|
||||||
del: (async (...keys: string[]) => {
|
|
||||||
for (const key of keys) {
|
|
||||||
delete inMemory[key];
|
|
||||||
}
|
}
|
||||||
}),
|
|
||||||
};
|
async set(key: string, value: string): Promise<void> {
|
||||||
|
if (!this._promSet) this._promSet = promisify(this.client.set);
|
||||||
|
await this._promSet(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
async del(...keys: string[]): Promise<number> {
|
||||||
|
if (!this._promDel) this._promDel = promisify(this.client.del);
|
||||||
|
return await this._promDel(...keys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockRedis implements RedisType {
|
||||||
|
private inMemory: {[key: string]: string} = {};
|
||||||
|
|
||||||
|
get client(): undefined {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(key: string): Promise<string | undefined> {
|
||||||
|
return this.inMemory[key] || undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async set(key: string, value: string): Promise<void> {
|
||||||
|
this.inMemory[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
async del(...keys: string[]): Promise<number> {
|
||||||
|
for (const key of keys) {
|
||||||
|
delete this.inMemory[key];
|
||||||
|
}
|
||||||
|
return keys.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Redis: RedisType =
|
||||||
|
DefaultConfig.REDIS_URL || DefaultConfig.isProduction
|
||||||
|
? new RealRedis()
|
||||||
|
: new MockRedis();
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,22 @@
|
|||||||
import {Store} from 'express-session';
|
import {Store} from 'express-session';
|
||||||
import {Redis, Config} from '.';
|
import {Redis, DefaultConfig} from '.';
|
||||||
import * as redisStore from "connect-redis";
|
import * as redisStore from "connect-redis";
|
||||||
import * as session from 'express-session';
|
import * as session from 'express-session';
|
||||||
import {RequestHandler} from 'express';
|
import {RequestHandler} from 'express';
|
||||||
|
|
||||||
Config.requireEnv('SESSION_SECRET', true);
|
|
||||||
|
|
||||||
let sessionStore: Store|undefined = undefined;
|
let sessionStore: Store|undefined = undefined;
|
||||||
|
|
||||||
function getRouter(options?: Partial<session.SessionOptions>): RequestHandler {
|
function getRouter(options?: Partial<session.SessionOptions>): RequestHandler {
|
||||||
|
DefaultConfig.requireEnv('SESSION_SECRET', true);
|
||||||
if (Redis.client && sessionStore !== undefined) {
|
if (Redis.client && sessionStore !== undefined) {
|
||||||
const RedisStore = redisStore(session);
|
const RedisStore = redisStore(session);
|
||||||
sessionStore = new RedisStore({client: Redis.client});
|
sessionStore = new RedisStore({client: Redis.client});
|
||||||
}
|
}
|
||||||
return session({
|
return session({
|
||||||
store: sessionStore,
|
store: sessionStore,
|
||||||
secret: Config.SESSION_SECRET || 'keyboard cat',
|
secret: DefaultConfig.SESSION_SECRET || 'keyboard cat',
|
||||||
resave: false,
|
resave: false,
|
||||||
saveUninitialized: true,
|
saveUninitialized: true,
|
||||||
cookie: {secure: false},
|
cookie: {secure: false},
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
- const baseUrl = Config.EXTERNAL_BASE_URL;
|
- const baseUrl = DefaultConfig.EXTERNAL_BASE_URL;
|
||||||
doctype html
|
doctype html
|
||||||
html
|
html
|
||||||
head
|
head
|
||||||
@@ -13,5 +13,5 @@ html
|
|||||||
require(['src/index'], function (index) {
|
require(['src/index'], function (index) {
|
||||||
index.setConfig({EXTERNAL_BASE_URL: '#{baseUrl}'});
|
index.setConfig({EXTERNAL_BASE_URL: '#{baseUrl}'});
|
||||||
});
|
});
|
||||||
if !Config.isProduction
|
if !DefaultConfig.isProduction
|
||||||
script(type='text/javascript', src=baseUrl+'/auto-reload/client.js')
|
script(type='text/javascript', src=baseUrl+'/auto-reload/client.js')
|
||||||
|
|||||||
Reference in New Issue
Block a user