diff --git a/app/core/paths.ts b/app/core/paths.ts index 6c9946293..7891d77ad 100644 --- a/app/core/paths.ts +++ b/app/core/paths.ts @@ -20,7 +20,8 @@ export interface Paths { 'myservers-base': string; 'myservers-config': string; 'myservers-env': string; - 'machine-id': string; + 'ssl-certificate': string; + 'extra-origins': string; } const thisDir = __dirname; @@ -53,7 +54,8 @@ export const defaultPaths = new Map([ ['myservers-base', '/boot/config/plugins/dynamix.my.servers/'], ['myservers-config', '/boot/config/plugins/dynamix.my.servers/myservers.cfg'], ['myservers-env', '/boot/config/plugins/dynamix.my.servers/env'], - ['machine-id', '/etc/machine-id'] + ['ssl-certificate', '/boot/config/ssl/certs/certificate_bundle.pem'], + ['extra-origins', '/boot/config/plugins/dynamix.my.servers/extra-origins.json'] ]); /** diff --git a/app/server.ts b/app/server.ts index 117e29dcc..9ad62e01e 100644 --- a/app/server.ts +++ b/app/server.ts @@ -7,17 +7,20 @@ import fs from 'fs'; import net from 'net'; import path from 'path'; import execa from 'execa'; +import cors from 'cors'; import stoppable from 'stoppable'; import chokidar from 'chokidar'; import express from 'express'; import http from 'http'; import WebSocket from 'ws'; +import { pki } from 'node-forge'; import { ApolloServer } from 'apollo-server-express'; -import { log, config, utils, paths, pubsub, coreLogger } from './core'; +import { log, config, paths, pubsub, coreLogger } from './core'; import { getEndpoints, globalErrorHandler, exitApp, cleanStdout, sleep } from './core/utils'; import { graphql } from './graphql'; import packageJson from '../package.json'; import display from './graphql/resolvers/query/display'; +import { networkState, varState } from './core/states'; const configFilePath = path.join(paths.get('dynamix-base')!, 'case-model.cfg'); const customImageFilePath = path.join(paths.get('dynamix-base')!, 'case-model.png'); @@ -37,8 +40,64 @@ chokidar.watch(customImageFilePath).on('all', updatePubsub); */ const app = express(); +// Graphql port const port = process.env.PORT ?? String(config.get('port')); +const attemptJSONParse = (text: string, fallback: any) => { + try { + return JSON.parse(text); + } catch { + return fallback; + } +}; + +// Cors options +const invalidOrigin = 'The CORS policy for this site does not allow access from the specified Origin.'; +const certPem = fs.readFileSync(paths.get('ssl-certificate')!, 'utf-8'); +const { hash } = pki.certificateFromPem(certPem).subject; + +// Get extra origins from the user +const extraOriginPath = paths.get('extra-origins'); +// To add extra origins create a file at the "extra-origins" path +const extraOrigins = extraOriginPath ? attemptJSONParse(fs.readFileSync(extraOriginPath, 'utf-8'), []) : []; + +// Get local ip from first ethernet adapter in the "network" state +const localIp = networkState.data[0].ipaddr[0]; + +// Get webui port +const originPort = varState.data.port; + +// Allow http://tower.local:${port}, http://${ip}:${port} and https://${hash}.unraid.net:${port} +const allowedOrigins = [ + // The webui + 'http://tower.local', + `http://${localIp}${originPort}`, + `https://${hash}.unraid.net:${originPort}`, + + // Other endpoints should be added below + extraOrigins +]; + +// Cors +app.use(cors({ + origin: function (origin, callback) { + // Disallow requests with no origin + // (like mobile apps or curl requests) + if (!origin) { + callback(new Error(invalidOrigin), false); + return; + } + + // Only allow known origins + if (!allowedOrigins.includes(origin)) { + callback(new Error(invalidOrigin), false); + return; + } + + callback(null, true); + } +})); + // Add Unraid API version header app.use(async (_req, res, next) => { // Only get the machine ID on first request @@ -203,7 +262,7 @@ export const server = { if (isNaN(parseInt(port, 10))) { try { fs.unlinkSync(port); - } catch {} + } catch { } } // Run callback diff --git a/package-lock.json b/package-lock.json index 085728669..a7f08933f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10506,6 +10506,11 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" }, + "node-forge": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", diff --git a/package.json b/package.json index b245d0b22..ba3305ca8 100644 --- a/package.json +++ b/package.json @@ -103,6 +103,7 @@ "nchan": "^1.0.10", "node-cache": "5.1.2", "node-fetch": "^2.6.1", + "node-forge": "^0.10.0", "node-window-polyfill": "^1.0.2", "number-to-color": "^0.5.0", "observable-to-promise": "^1.0.0",