UNPKG

@coko/server

Version:

Reusable server for use by Coko's projects

244 lines 10.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.shutdownFn = exports.startServer = void 0; const express_1 = __importDefault(require("express")); const util_1 = require("util"); const http_1 = __importDefault(require("http")); const passport_1 = __importDefault(require("passport")); const cookie_parser_1 = __importDefault(require("cookie-parser")); const helmet_1 = __importDefault(require("helmet")); const Sentry = __importStar(require("@sentry/node")); const pino_http_1 = __importDefault(require("pino-http")); const internals_1 = __importDefault(require("./logger/internals")); const db_1 = require("./db"); const pubsub_1 = __importDefault(require("./graphql/pubsub")); const healthcheck_1 = __importDefault(require("./healthcheck")); const filesystem_1 = require("./utils/filesystem"); const errorStatuses_1 = __importDefault(require("./startup/errorStatuses")); const config_1 = __importDefault(require("./configManager/config")); const fileStorage_1 = __importDefault(require("./fileStorage")); const urls_1 = require("./utils/urls"); const checkConnections_1 = require("./startup/checkConnections"); const seedGlobalTeams_1 = __importDefault(require("./startup/seedGlobalTeams")); const seedAdminUser_1 = __importDefault(require("./startup/seedAdminUser")); const customScripts_1 = require("./startup/customScripts"); const cors_1 = __importDefault(require("./startup/cors")); const authentication_1 = __importDefault(require("./authentication")); const static_1 = __importDefault(require("./startup/static")); const registerComponents_1 = __importDefault(require("./startup/registerComponents")); const setup_1 = __importDefault(require("./graphql/setup")); const jobManager_1 = require("./jobManager"); const env_1 = require("./utils/env"); let server; const nodeEnv = (0, env_1.env)('NODE_ENV'); const isDevelopment = nodeEnv === 'development'; /** * startServer is run with no parameters, but we allow a testConfig so that * tests can run the server in a specified setup. */ const startServer = async (testConfig) => { if (server) return server; const startTime = performance.now(); internals_1.default.init('Coko server init tasks', { newLineBefore: true }); internals_1.default.section('Load config'); await config_1.default.init(testConfig); internals_1.default.success('Configuration valid!'); internals_1.default.section(`Ensure tmp folder exists`); await (0, filesystem_1.ensureTempFolderExists)(); internals_1.default.success(`tmp folder now exists`); db_1.db.init(); pubsub_1.default.init(); fileStorage_1.default.init(); (0, urls_1.initUrls)(); await (0, checkConnections_1.checkConnections)(); await db_1.migrationManager.migrate(); await (0, seedGlobalTeams_1.default)(); await (0, seedAdminUser_1.default)(config_1.default.get('adminUser')); await (0, customScripts_1.runCustomStartupScripts)(); const app = (0, express_1.default)(); const port = config_1.default.get('port') || 3000; app.set('port', port); const httpServer = http_1.default.createServer(app); app.use(express_1.default.json({ limit: '50mb' })); app.use(express_1.default.urlencoded({ extended: false })); app.use((0, cookie_parser_1.default)()); /** * Perhaps in the future, we can add a config option to make this 'same-site' * in some cases. (eg. client running at myapp.com and server running at * server.myapp.com can use a stricter 'same-site' policy without issues.) * Or maybe someone is not mounting static folders at all and they want to * restrict even further to 'same-origin'. */ let helmetConfig = { crossOriginResourcePolicy: { policy: 'cross-origin' }, }; /** * This makes apollo explorer work in development * See https://docs.nestjs.com/security/helmet#use-with-express-default */ if (isDevelopment && config_1.default.get('useGraphQLServer')) { helmetConfig = { ...helmetConfig, crossOriginEmbedderPolicy: false, contentSecurityPolicy: { directives: { imgSrc: [ `'self'`, 'data:', 'apollo-server-landing-page.cdn.apollographql.com', ], scriptSrc: [`'self'`, `https: 'unsafe-inline'`], manifestSrc: [ `'self'`, 'apollo-server-landing-page.cdn.apollographql.com', ], frameSrc: [`'self'`, 'sandbox.embed.apollographql.com'], }, }, }; } app.use((0, helmet_1.default)(helmetConfig)); app.use((0, cors_1.default)()); app.use((0, pino_http_1.default)({ transport: isDevelopment ? { target: 'pino-pretty', options: { colorize: true, messageFormat: '{req.method} {req.url} {res.statusCode} - {responseTime}ms - request ID: {req.id}', ignore: 'req,res,err,responseTime', }, } : undefined, customProps: (req, _res) => { if (!(req.url === '/graphql' && req.body)) return {}; const { variables, operationName } = req.body; return { gql: { operation: operationName || 'unnamed', // Do not log variables in production due to PII/GDPR etc. // Maybe at some point we could have a whitelist for variables that are explictly OK to log variables: isDevelopment ? variables : undefined, }, }; }, redact: { paths: [ 'req.headers.cookie', 'req.headers.authorization', 'gql.variables.token', 'gql.variables.email', 'gql.variables.password', 'gql.variables.currentPassword', 'gql.variables.newPassword', 'gql.variables.input.token', 'gql.variables.input.email', 'gql.variables.input.password', 'gql.variables.input.currentPassword', 'gql.variables.input.newPassword', ], censor: '*****', }, })); // @ts-ignore app.use(passport_1.default.initialize()); passport_1.default.use('bearer', authentication_1.default.strategies.bearer); passport_1.default.use('anonymous', authentication_1.default.strategies.anonymous); passport_1.default.use('local', authentication_1.default.strategies.local); app.get('/healthcheck', healthcheck_1.default); (0, static_1.default)(app); await (0, registerComponents_1.default)(app); if (config_1.default.get('useGraphQLServer')) { await (0, setup_1.default)(httpServer, app, passport_1.default); } await jobManager_1.jobManager.init(); internals_1.default.section(`Starting HTTP server`); const sentryDsn = config_1.default.get('sentry.dsn'); if (sentryDsn) { Sentry.setupExpressErrorHandler(app); internals_1.default.success('Sentry initialized'); } else { internals_1.default.warn('Skipping sentry initialization: no dsn key found in config'); } (0, errorStatuses_1.default)(app); const startListening = (0, util_1.promisify)(httpServer.listen).bind(httpServer); await startListening(port); internals_1.default.point(`App is listening on port ${port}`); server = httpServer; const endTime = performance.now(); const durationInSeconds = (endTime - startTime) / 1000; // Convert to seconds internals_1.default.init(`Coko server init finished in ${durationInSeconds.toFixed(4)} seconds`, { newLineBefore: true, newLineAfter: true, }); return server; }; exports.startServer = startServer; const shutdownFn = async () => { await (0, customScripts_1.runCustomShutdownScripts)(); internals_1.default.section('Shut down http server'); server.close(); server = undefined; internals_1.default.success('Http server successfully shut down'); await jobManager_1.jobManager.stop(); if (config_1.default.get('useGraphQLServer')) { internals_1.default.section('Shut down subscription client'); await pubsub_1.default.client.end(); internals_1.default.success('Subscription client successfully shut down'); } internals_1.default.section('Shut down database connection'); await db_1.db.destroy(); internals_1.default.success('Database connection successfully shut down'); }; exports.shutdownFn = shutdownFn; const shutdown = async (signal) => { internals_1.default.init(`Coko server graceful shutdown after receiving signal ${signal}`); const startTime = performance.now(); await (0, exports.shutdownFn)(); const endTime = performance.now(); const durationInSeconds = (endTime - startTime) / 1000; // Convert to seconds internals_1.default.init(`Coko server graceful shutdown finished in ${durationInSeconds.toFixed(4)} seconds`); process.exit(); }; process.on('SIGINT', () => shutdown('SIGINT')); process.on('SIGTERM', () => shutdown('SIGTERM')); //# sourceMappingURL=startServer.js.map