From 3dceb422c50baf1d64afd1e5deaf8e0767fa16df Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 8 Oct 2022 09:24:03 -0400 Subject: [PATCH 01/22] Added base config manager --- src/cache.js | 2 +- src/config-manager.js | 52 ++++++++++++++++++++++++++ src/database.js | 2 +- src/mailer.js | 2 +- src/server.js | 9 +++-- src/services/api/routes/v1/register.js | 2 +- src/services/api/routes/v1/user.js | 2 +- src/services/nnid/routes/miis.js | 2 +- src/util.js | 2 +- 9 files changed, 65 insertions(+), 10 deletions(-) create mode 100644 src/config-manager.js diff --git a/src/cache.js b/src/cache.js index 7d263e9..6cd90de 100644 --- a/src/cache.js +++ b/src/cache.js @@ -1,6 +1,6 @@ const fs = require('fs-extra'); const redis = require('redis'); -const config = require('../config.json'); +const { config } = require('./config-manager'); let client; const SERVICE_CERTS_BASE = `${__dirname}/../certs/service`; diff --git a/src/config-manager.js b/src/config-manager.js new file mode 100644 index 0000000..bfde2ea --- /dev/null +++ b/src/config-manager.js @@ -0,0 +1,52 @@ +const fs = require('fs-extra'); +const logger = require('../logger'); + +/** + * @typedef {Object} Config + * @property {object} http HTTP server settings + * @property {number} http.port HTTP port the server will listen on + * @property {object} mongoose Mongose connection settings + * @property {string} mongoose.uri URI Mongoose will connect to + * @property {string} mongoose.database MongoDB database name + * @property {object} mongoose.options MongoDB connection options + * @property {object} redis redis settings + * @property {string} redis.client redis client settings + * @property {string} redis.client.url redis server URL + * @property {object} email node-mailer client settings + * @property {string} email.host SMTP server address + * @property {number} email.port SMTP server port + * @property {boolean} email.secure Secure SMTP + * @property {string} email.from Email "from" name/address + * @property {object} email.auth Email authentication settings + * @property {string} email.auth.user Email username + * @property {string} email.auth.pass Email password + * @property {object} aws s3 client settings + * @property {object} aws.spaces Digital Ocean Spaces settings + * @property {string} aws.spaces.key s3 access key + * @property {string} aws.spaces.secret s3 access secret + * @property {object} hcaptcha hCaptcha settings + * @property {string} hcaptcha.secret hCaptcha secret + * @property {string} cdn_base Base URL for CDN location + * @property {string} website_base Base URL for service website (used with emails) + */ + +/** + * @type {Config} + */ +let config = {}; + +function configure() { + if (!fs.pathExistsSync(`${__dirname}/../config.json`)) { + logger.error('Failed to locate config.json file'); + process.exit(0); + } + + config = require(`${__dirname}/../config.json`); + + module.exports.config = config; +} + +module.exports = { + configure, + config +}; \ No newline at end of file diff --git a/src/database.js b/src/database.js index 53b9cdc..fcb136e 100644 --- a/src/database.js +++ b/src/database.js @@ -5,7 +5,7 @@ const util = require('./util'); const { PNID } = require('./models/pnid'); const { Server } = require('./models/server'); const logger = require('../logger'); -const config = require('../config.json'); +const { config } = require('./config-manager'); const { uri, database, options } = config.mongoose; // TODO: Extend this later with more settings diff --git a/src/mailer.js b/src/mailer.js index 6f00665..bf67714 100644 --- a/src/mailer.js +++ b/src/mailer.js @@ -1,5 +1,5 @@ const nodemailer = require('nodemailer'); -const config = require('../config.json'); +const { config } = require('./config-manager'); const transporter = nodemailer.createTransport(config.email); diff --git a/src/server.js b/src/server.js index 5711f6d..b3202c6 100644 --- a/src/server.js +++ b/src/server.js @@ -1,5 +1,9 @@ process.title = 'Pretendo - Account'; +const configManager = require('./config-manager'); + +configManager.configure(); + const express = require('express'); const morgan = require('morgan'); const xmlparser = require('./middleware/xml-parser'); @@ -7,7 +11,8 @@ const cache = require('./cache'); const database = require('./database'); const util = require('./util'); const logger = require('../logger'); -const config = require('../config.json'); + +const { config } = configManager; const { http: { port } } = config; const app = express(); @@ -19,8 +24,6 @@ const datastore = require('./services/datastore'); const api = require('./services/api'); // START APPLICATION -app.set('etag', false); -app.disable('x-powered-by'); // Create router logger.info('Setting up Middleware'); diff --git a/src/services/api/routes/v1/register.js b/src/services/api/routes/v1/register.js index 5f91aac..f2bfa06 100644 --- a/src/services/api/routes/v1/register.js +++ b/src/services/api/routes/v1/register.js @@ -12,7 +12,7 @@ const database = require('../../../../database'); const cache = require('../../../../cache'); const util = require('../../../../util'); const logger = require('../../../../../logger'); -const config = require('../../../../../config.json'); +const { config } = require('../../../../config-manager'); const PNID_VALID_CHARACTERS_REGEX = /^[\w\-\.]*$/gm; const PNID_PUNCTUATION_START_REGEX = /^[\_\-\.]/gm; diff --git a/src/services/api/routes/v1/user.js b/src/services/api/routes/v1/user.js index 07c014d..ee8ad4a 100644 --- a/src/services/api/routes/v1/user.js +++ b/src/services/api/routes/v1/user.js @@ -1,7 +1,7 @@ const router = require('express').Router(); const joi = require('joi'); const { PNID } = require('../../../../models/pnid'); -const config = require('../../../../../config.json'); +const { config } = require('../../../../config-manager'); // TODO: Extend this later with more settings const userSchema = joi.object({ diff --git a/src/services/nnid/routes/miis.js b/src/services/nnid/routes/miis.js index bc99ce9..82ff0d6 100644 --- a/src/services/nnid/routes/miis.js +++ b/src/services/nnid/routes/miis.js @@ -1,7 +1,7 @@ const router = require('express').Router(); const xmlbuilder = require('xmlbuilder'); const { PNID } = require('../../../models/pnid'); -const config = require('../../../../config.json'); +const { config } = require('../../../config-manager'); /** * [GET] diff --git a/src/util.js b/src/util.js index 32f3cab..c7073f6 100644 --- a/src/util.js +++ b/src/util.js @@ -3,7 +3,7 @@ const NodeRSA = require('node-rsa'); const aws = require('aws-sdk'); const mailer = require('./mailer'); const cache = require('./cache'); -const config = require('../config.json'); +const { config } = require('./config-manager'); const spacesEndpoint = new aws.Endpoint('nyc3.digitaloceanspaces.com'); const s3 = new aws.S3({ From 625bab0e25e6eaeefcc03ec8702dfa1a6f0cc3f8 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 8 Oct 2022 09:56:17 -0400 Subject: [PATCH 02/22] Added loading config from env --- example.env | 18 +++++++++++++ package-lock.json | 14 ++++++++++ package.json | 1 + src/config-manager.js | 62 ++++++++++++++++++++++++++++++++++++++----- 4 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 example.env diff --git a/example.env b/example.env new file mode 100644 index 0000000..5c34138 --- /dev/null +++ b/example.env @@ -0,0 +1,18 @@ +PN_ACT_PREFER_ENV_CONFIG=true # Load config from ENV instead of config.json +PN_ACT_CONFIG_HTTP_PORT=7070 +PN_ACT_CONFIG_MONGO_URI=mongodb://localhost:27017 +PN_ACT_CONFIG_MONGO_DB_NAME=database_name +PN_ACT_CONFIG_MONGOOSE_OPTION_useNewUrlParser=true +PN_ACT_CONFIG_MONGOOSE_OPTION_useUnifiedTopology=true +PN_ACT_CONFIG_REDIS_URL=redis://localhost:6379 +PN_ACT_CONFIG_EMAIL_HOST=smtp.gmail.com +PN_ACT_CONFIG_EMAIL_PORT=587 +PN_ACT_CONFIG_EMAIL_SECURE=false +PN_ACT_CONFIG_EMAIL_USERNAME=username +PN_ACT_CONFIG_EMAIL_PASSWORD=password +PN_ACT_CONFIG_EMAIL_FROM=Company Name +PN_ACT_CONFIG_S3_ACCESS_KEY=ACCESS_KEY +PN_ACT_CONFIG_S3_ACCESS_SECRET=ACCESS_SECRET +PN_ACT_CONFIG_HCAPTCHA_SECRET=0x0000000000000000000000000000000000000000 +PN_ACT_CONFIG_CDN_BASE=https://example.com +PN_ACT_CONFIG_WEBSITE_BASE=https://example.com \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 00a2f1b..bc218c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "colors": "^1.4.0", "cors": "^2.8.5", "dicer": "^0.2.5", + "dotenv": "^16.0.3", "email-validator": "^2.0.4", "express": "^4.17.1", "express-form-data": "^2.0.17", @@ -1083,6 +1084,14 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" }, + "node_modules/dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", + "engines": { + "node": ">=12" + } + }, "node_modules/dtype": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dtype/-/dtype-2.0.0.tgz", @@ -4540,6 +4549,11 @@ } } }, + "dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==" + }, "dtype": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dtype/-/dtype-2.0.0.tgz", diff --git a/package.json b/package.json index 1ef4be4..6c889d8 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "colors": "^1.4.0", "cors": "^2.8.5", "dicer": "^0.2.5", + "dotenv": "^16.0.3", "email-validator": "^2.0.4", "express": "^4.17.1", "express-form-data": "^2.0.17", diff --git a/src/config-manager.js b/src/config-manager.js index bfde2ea..51e2ec5 100644 --- a/src/config-manager.js +++ b/src/config-manager.js @@ -1,6 +1,8 @@ const fs = require('fs-extra'); const logger = require('../logger'); +require('dotenv').config(); + /** * @typedef {Object} Config * @property {object} http HTTP server settings @@ -16,7 +18,7 @@ const logger = require('../logger'); * @property {string} email.host SMTP server address * @property {number} email.port SMTP server port * @property {boolean} email.secure Secure SMTP - * @property {string} email.from Email "from" name/address + * @property {string} email.from Email 'from' name/address * @property {object} email.auth Email authentication settings * @property {string} email.auth.user Email username * @property {string} email.auth.pass Email password @@ -36,12 +38,60 @@ const logger = require('../logger'); let config = {}; function configure() { - if (!fs.pathExistsSync(`${__dirname}/../config.json`)) { - logger.error('Failed to locate config.json file'); - process.exit(0); - } + if (process.env.PN_ACT_PREFER_ENV_CONFIG === 'true') { + logger.info('Loading config from env'); - config = require(`${__dirname}/../config.json`); + config = { + http: { + port: Number(process.env.PN_ACT_CONFIG_HTTP_PORT) + }, + mongoose: { + uri: process.env.PN_ACT_CONFIG_MONGO_URI, + database: process.env.PN_ACT_CONFIG_MONGO_DB_NAME, + options: Object.keys(process.env) + .filter(key => key.startsWith('PN_ACT_CONFIG_MONGOOSE_OPTION_')) + .reduce((obj, key) => { + obj[key.split('_').pop()] = process.env[key]; + return obj; + }, {}) + }, + redis: { + client: { + url: process.env.PN_ACT_CONFIG_REDIS_URL + } + }, + email: { + host: process.env.PN_ACT_CONFIG_EMAIL_HOST, + port: Number(process.env.PN_ACT_CONFIG_EMAIL_PORT), + secure: Boolean(process.env.PN_ACT_CONFIG_EMAIL_SECURE), + auth: { + user: process.env.PN_ACT_CONFIG_EMAIL_USERNAME, + pass: process.env.PN_ACT_CONFIG_EMAIL_PASSWORD + }, + from: process.env.PN_ACT_CONFIG_EMAIL_FROM + }, + aws: { + spaces: { + key: process.env.PN_ACT_CONFIG_S3_ACCESS_KEY, + secret: process.env.PN_ACT_CONFIG_S3_ACCESS_SECRET + } + }, + hcaptcha: { + secret: process.env.PN_ACT_CONFIG_HCAPTCHA_SECRET + }, + cdn_base: process.env.PN_ACT_CONFIG_CDN_BASE, + website_base: process.env.PN_ACT_CONFIG_WEBSITE_BASE + }; + } else { + logger.info('Loading config from config.json'); + + if (!fs.pathExistsSync(`${__dirname}/../config.json`)) { + logger.error('Failed to locate config.json file'); + process.exit(0); + } + + config = require(`${__dirname}/../config.json`); + } module.exports.config = config; } From 3b6a4eeb076d8213f3eaf1ee6d2f11610a8739ca Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 8 Oct 2022 10:26:48 -0400 Subject: [PATCH 03/22] Added required fields check to config manager --- package-lock.json | 22 ++++++++++++++++++++++ package.json | 2 ++ src/config-manager.js | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+) diff --git a/package-lock.json b/package-lock.json index bc218c5..ce5f210 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,8 @@ "image-pixels": "^1.1.1", "joi": "^17.6.1", "kaitai-struct": "^0.9.0", + "lodash.has": "^4.5.2", + "lodash.set": "^4.3.2", "mii-js": "github:PretendoNetwork/mii-js", "moment": "^2.24.0", "moment-timezone": "^0.5.27", @@ -2055,6 +2057,16 @@ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" }, + "node_modules/lodash.has": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", + "integrity": "sha512-rnYUdIo6xRCJnQmbVFEwcxF144erlD+M3YcJUVesflU9paQaE8p+fJDcIQrlMYbxoANFL+AB9hZrzSBBk5PL+g==" + }, + "node_modules/lodash.set": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", + "integrity": "sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg==" + }, "node_modules/lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", @@ -5350,6 +5362,16 @@ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" }, + "lodash.has": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", + "integrity": "sha512-rnYUdIo6xRCJnQmbVFEwcxF144erlD+M3YcJUVesflU9paQaE8p+fJDcIQrlMYbxoANFL+AB9hZrzSBBk5PL+g==" + }, + "lodash.set": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", + "integrity": "sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg==" + }, "lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", diff --git a/package.json b/package.json index 6c889d8..931fbaf 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,8 @@ "image-pixels": "^1.1.1", "joi": "^17.6.1", "kaitai-struct": "^0.9.0", + "lodash.has": "^4.5.2", + "lodash.set": "^4.3.2", "mii-js": "github:PretendoNetwork/mii-js", "moment": "^2.24.0", "moment-timezone": "^0.5.27", diff --git a/src/config-manager.js b/src/config-manager.js index 51e2ec5..36dd277 100644 --- a/src/config-manager.js +++ b/src/config-manager.js @@ -1,4 +1,6 @@ const fs = require('fs-extra'); +const has = require('lodash.has'); +const set = require('lodash.set'); const logger = require('../logger'); require('dotenv').config(); @@ -37,6 +39,24 @@ require('dotenv').config(); */ let config = {}; +const requiredFields = [ + ['http.port', 'PN_ACT_CONFIG_HTTP_PORT', Number], + ['mongoose.uri', 'PN_ACT_CONFIG_MONGO_URI'], + ['mongoose.database', 'PN_ACT_CONFIG_MONGO_DB_NAME'], + ['redis.client.url', 'PN_ACT_CONFIG_REDIS_URL'], + ['email.host', 'PN_ACT_CONFIG_EMAIL_HOST'], + ['email.port', 'PN_ACT_CONFIG_EMAIL_PORT', Number], + ['email.secure', 'PN_ACT_CONFIG_EMAIL_SECURE', Boolean], + ['email.auth.user', 'PN_ACT_CONFIG_EMAIL_USERNAME'], + ['email.auth.pass', 'PN_ACT_CONFIG_EMAIL_PASSWORD'], + ['email.from', 'PN_ACT_CONFIG_EMAIL_FROM'], + ['aws.spaces.key', 'PN_ACT_CONFIG_S3_ACCESS_KEY'], + ['aws.spaces.secret', 'PN_ACT_CONFIG_S3_ACCESS_SECRET'], + ['hcaptcha.secret', 'PN_ACT_CONFIG_HCAPTCHA_SECRET'], + ['cdn_base', 'PN_ACT_CONFIG_CDN_BASE'], + ['website_base', 'PN_ACT_CONFIG_WEBSITE_BASE'], +]; + function configure() { if (process.env.PN_ACT_PREFER_ENV_CONFIG === 'true') { logger.info('Loading config from env'); @@ -93,6 +113,26 @@ function configure() { config = require(`${__dirname}/../config.json`); } + logger.info('Config loaded, checking integrity'); + + for (const requiredField of requiredFields) { + const [keyPath, env, convertType] = requiredField; + + if (!has(config, keyPath)) { + if (!process.env[env] || process.env[env].trim() === '') { + logger.error(`Failed to locate required field ${keyPath}. Set ${keyPath} in config.json or the ${env} environment variable`); + + process.exit(0); + } else { + logger.info(`${keyPath} not found in config, using environment variable ${env}`); + + const newValue = process.env[env]; + + set(config, keyPath, convertType ? convertType(newValue) : newValue); + } + } + } + module.exports.config = config; } From 1ed9b4c848095c48080ad6c395e7f3ecee493979 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 9 Oct 2022 13:45:39 -0400 Subject: [PATCH 04/22] Made redis cache optional --- src/cache.js | 32 +++++++++++++++++++++++--------- src/config-manager.js | 24 +++++++++++++++++++++++- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/cache.js b/src/cache.js index 6cd90de..d0b94d8 100644 --- a/src/cache.js +++ b/src/cache.js @@ -1,30 +1,44 @@ const fs = require('fs-extra'); const redis = require('redis'); -const { config } = require('./config-manager'); +const { config, disabledFeatures } = require('./config-manager'); let client; +const memoryCache = {}; + const SERVICE_CERTS_BASE = `${__dirname}/../certs/service`; const NEX_CERTS_BASE = `${__dirname}/../certs/nex`; async function connect() { - client = redis.createClient(config.redis.client); - client.on('error', (err) => console.log('Redis Client Error', err)); + if (!disabledFeatures.redis) { + client = redis.createClient(config.redis.client); + client.on('error', (err) => console.log('Redis Client Error', err)); - await client.connect(); + await client.connect(); + } } async function setCachedFile(type, name, fileName, value) { - await client.set(`${type}:${name}:${fileName}`, value); + if (disabledFeatures.redis) { + memoryCache[`${type}:${name}:${fileName}`] = value; + } else { + await client.set(`${type}:${name}:${fileName}`, value); + } } async function getCachedFile(type, name, fileName, encoding) { - const cachedFile = await client.get(`${type}:${name}:${fileName}`); + let cachedFile; + + if (disabledFeatures.redis) { + cachedFile = memoryCache[`${type}:${name}:${fileName}`]; + } else { + cachedFile = await client.get(`${type}:${name}:${fileName}`); + } if (cachedFile !== null) { - return Buffer.from(cachedFile, encoding); - } else { - return cachedFile; + cachedFile = Buffer.from(cachedFile, encoding); } + + return cachedFile; } // NEX server cache functions diff --git a/src/config-manager.js b/src/config-manager.js index 36dd277..bf95ae0 100644 --- a/src/config-manager.js +++ b/src/config-manager.js @@ -39,11 +39,23 @@ require('dotenv').config(); */ let config = {}; + +/** + * @typedef {Object} DisabledFeatures + * @property {boolean} redis true if redis is disabled + */ + +/** + * @type {DisabledFeatures} + */ +const disabledFeatures = { + redis: false +}; + const requiredFields = [ ['http.port', 'PN_ACT_CONFIG_HTTP_PORT', Number], ['mongoose.uri', 'PN_ACT_CONFIG_MONGO_URI'], ['mongoose.database', 'PN_ACT_CONFIG_MONGO_DB_NAME'], - ['redis.client.url', 'PN_ACT_CONFIG_REDIS_URL'], ['email.host', 'PN_ACT_CONFIG_EMAIL_HOST'], ['email.port', 'PN_ACT_CONFIG_EMAIL_PORT', Number], ['email.secure', 'PN_ACT_CONFIG_EMAIL_SECURE', Boolean], @@ -115,6 +127,7 @@ function configure() { logger.info('Config loaded, checking integrity'); + // * Check for required settings for (const requiredField of requiredFields) { const [keyPath, env, convertType] = requiredField; @@ -133,6 +146,15 @@ function configure() { } } + // * Check for optional settings + if (!has(config, 'redis.client.url')) { + if (!process.env.PN_ACT_CONFIG_REDIS_URL || process.env.PN_ACT_CONFIG_REDIS_URL.trim() === '') { + logger.warning('Failed to find Redis config. Disabling feature and using in-memory cache'); + + disabledFeatures.redis = true; + } + } + module.exports.config = config; } From 566cd987cf56d9b327b20447979cab12cf27f134 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 9 Oct 2022 13:48:02 -0400 Subject: [PATCH 05/22] Fixed redis client cache name issue --- src/cache.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/cache.js b/src/cache.js index d0b94d8..f20a8fe 100644 --- a/src/cache.js +++ b/src/cache.js @@ -17,21 +17,21 @@ async function connect() { } } -async function setCachedFile(type, name, fileName, value) { +async function setCachedFile(fileName, value) { if (disabledFeatures.redis) { - memoryCache[`${type}:${name}:${fileName}`] = value; + memoryCache[fileName] = value; } else { - await client.set(`${type}:${name}:${fileName}`, value); + await client.set(fileName, value); } } -async function getCachedFile(type, name, fileName, encoding) { +async function getCachedFile(fileName, encoding) { let cachedFile; if (disabledFeatures.redis) { - cachedFile = memoryCache[`${type}:${name}:${fileName}`]; + cachedFile = memoryCache[fileName]; } else { - cachedFile = await client.get(`${type}:${name}:${fileName}`); + cachedFile = await client.get(fileName); } if (cachedFile !== null) { @@ -90,19 +90,19 @@ async function getNEXAESKey(name, encoding) { } async function setNEXPublicKey(name, value) { - await setCachedFile('nex', name, 'public_key', value); + await setCachedFile(`nex:${name}:public_key`, value); } async function setNEXPrivateKey(name, value) { - await setCachedFile('nex', name, 'private_key', value); + await setCachedFile(`nex:${name}:private_key`, value); } async function setNEXSecretKey(name, value) { - await setCachedFile('nex', name, 'secret_key', value); + await setCachedFile(`nex:${name}:secret_key`, value); } async function setNEXAESKey(name, value) { - await setCachedFile('nex', name, 'aes_key', value); + await setCachedFile(`nex:${name}:aes_key`, value); } // 3rd party service cache functions @@ -154,19 +154,19 @@ async function getServiceAESKey(name, encoding) { } async function setServicePublicKey(name, value) { - await setCachedFile('service', name, 'public_key', value); + await setCachedFile(`service:${name}:public_key`, value); } async function setServicePrivateKey(name, value) { - await setCachedFile('service', name, 'private_key', value); + await setCachedFile(`service:${name}:private_key`, value); } async function setServiceSecretKey(name, value) { - await setCachedFile('service', name, 'secret_key', value); + await setCachedFile(`service:${name}:secret_key`, value); } async function setServiceAESKey(name, value) { - await setCachedFile('service', name, 'aes_key', value); + await setCachedFile(`service:${name}:aes_key`, value); } module.exports = { From 1b2141ff762d81c0b9ce9d83191e5981b5178848 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 9 Oct 2022 13:54:42 -0400 Subject: [PATCH 06/22] Improved config value set checks --- package-lock.json | 16 +++------------- package.json | 2 +- src/config-manager.js | 19 +++++++++++++------ 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index ce5f210..4fa0176 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "image-pixels": "^1.1.1", "joi": "^17.6.1", "kaitai-struct": "^0.9.0", - "lodash.has": "^4.5.2", + "lodash.get": "^4.4.2", "lodash.set": "^4.3.2", "mii-js": "github:PretendoNetwork/mii-js", "moment": "^2.24.0", @@ -2055,12 +2055,7 @@ "node_modules/lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" - }, - "node_modules/lodash.has": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", - "integrity": "sha512-rnYUdIo6xRCJnQmbVFEwcxF144erlD+M3YcJUVesflU9paQaE8p+fJDcIQrlMYbxoANFL+AB9hZrzSBBk5PL+g==" + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" }, "node_modules/lodash.set": { "version": "4.3.2", @@ -5360,12 +5355,7 @@ "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" - }, - "lodash.has": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", - "integrity": "sha512-rnYUdIo6xRCJnQmbVFEwcxF144erlD+M3YcJUVesflU9paQaE8p+fJDcIQrlMYbxoANFL+AB9hZrzSBBk5PL+g==" + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" }, "lodash.set": { "version": "4.3.2", diff --git a/package.json b/package.json index 931fbaf..bed4955 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "image-pixels": "^1.1.1", "joi": "^17.6.1", "kaitai-struct": "^0.9.0", - "lodash.has": "^4.5.2", + "lodash.get": "^4.4.2", "lodash.set": "^4.3.2", "mii-js": "github:PretendoNetwork/mii-js", "moment": "^2.24.0", diff --git a/src/config-manager.js b/src/config-manager.js index bf95ae0..009fe4a 100644 --- a/src/config-manager.js +++ b/src/config-manager.js @@ -1,5 +1,5 @@ const fs = require('fs-extra'); -const has = require('lodash.has'); +const get = require('lodash.get'); const set = require('lodash.set'); const logger = require('../logger'); @@ -131,15 +131,18 @@ function configure() { for (const requiredField of requiredFields) { const [keyPath, env, convertType] = requiredField; - if (!has(config, keyPath)) { - if (!process.env[env] || process.env[env].trim() === '') { + const configValue = get(config, keyPath); + const envValue = get(process.env, keyPath); + + if (!configValue || configValue.trim() === '') { + if (!envValue || envValue.trim() === '') { logger.error(`Failed to locate required field ${keyPath}. Set ${keyPath} in config.json or the ${env} environment variable`); process.exit(0); } else { logger.info(`${keyPath} not found in config, using environment variable ${env}`); - const newValue = process.env[env]; + const newValue = envValue; set(config, keyPath, convertType ? convertType(newValue) : newValue); } @@ -147,8 +150,12 @@ function configure() { } // * Check for optional settings - if (!has(config, 'redis.client.url')) { - if (!process.env.PN_ACT_CONFIG_REDIS_URL || process.env.PN_ACT_CONFIG_REDIS_URL.trim() === '') { + + const redisConfigValue = get(config, 'redis.client.url'); + const redisEnvValue = get(process.env, 'redis.client.url'); + + if (!redisConfigValue || redisConfigValue.trim() === '') { + if (!redisEnvValue || redisEnvValue.trim() === '') { logger.warning('Failed to find Redis config. Disabling feature and using in-memory cache'); disabledFeatures.redis = true; From 741a03edd99cbcea057a35681ddacbd07b26fb61 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 9 Oct 2022 14:10:59 -0400 Subject: [PATCH 07/22] Made email sending optional --- src/config-manager.js | 76 +++++++++++++++++++++++++++++++++++++++---- src/mailer.js | 14 +++++--- 2 files changed, 79 insertions(+), 11 deletions(-) diff --git a/src/config-manager.js b/src/config-manager.js index 009fe4a..7bb46c9 100644 --- a/src/config-manager.js +++ b/src/config-manager.js @@ -43,25 +43,21 @@ let config = {}; /** * @typedef {Object} DisabledFeatures * @property {boolean} redis true if redis is disabled + * @property {boolean} email true if email sending is disabled */ /** * @type {DisabledFeatures} */ const disabledFeatures = { - redis: false + redis: false, + email: false }; const requiredFields = [ ['http.port', 'PN_ACT_CONFIG_HTTP_PORT', Number], ['mongoose.uri', 'PN_ACT_CONFIG_MONGO_URI'], ['mongoose.database', 'PN_ACT_CONFIG_MONGO_DB_NAME'], - ['email.host', 'PN_ACT_CONFIG_EMAIL_HOST'], - ['email.port', 'PN_ACT_CONFIG_EMAIL_PORT', Number], - ['email.secure', 'PN_ACT_CONFIG_EMAIL_SECURE', Boolean], - ['email.auth.user', 'PN_ACT_CONFIG_EMAIL_USERNAME'], - ['email.auth.pass', 'PN_ACT_CONFIG_EMAIL_PASSWORD'], - ['email.from', 'PN_ACT_CONFIG_EMAIL_FROM'], ['aws.spaces.key', 'PN_ACT_CONFIG_S3_ACCESS_KEY'], ['aws.spaces.secret', 'PN_ACT_CONFIG_S3_ACCESS_SECRET'], ['hcaptcha.secret', 'PN_ACT_CONFIG_HCAPTCHA_SECRET'], @@ -162,6 +158,72 @@ function configure() { } } + const emailHostConfigValue = get(config, 'email.host'); + const emailHostEnvValue = get(process.env, 'PN_ACT_CONFIG_EMAIL_HOST'); + + if (!emailHostConfigValue || emailHostConfigValue.trim() === '') { + if (!emailHostEnvValue || emailHostEnvValue.trim() === '') { + logger.warning('Failed to find email SMTP host config. Disabling feature'); + + disabledFeatures.email = true; + } + } + + const emailPortConfigValue = get(config, 'email.port'); + const emailPortEnvValue = get(process.env, 'PN_ACT_CONFIG_EMAIL_PORT'); + + if (!emailPortConfigValue) { + if (!emailPortEnvValue || emailPortEnvValue.trim() === '') { + logger.warning('Failed to find email SMTP port config. Disabling feature'); + + disabledFeatures.email = true; + } + } + + const emailSecureConfigValue = get(config, 'email.secure'); + const emailSecureEnvValue = get(process.env, 'PN_ACT_CONFIG_EMAIL_SECURE'); + + if (emailSecureConfigValue === undefined) { + if (!emailSecureEnvValue || emailSecureEnvValue.trim() === '') { + logger.warning('Failed to find email SMTP secure config. Disabling feature'); + + disabledFeatures.email = true; + } + } + + const emailUsernameConfigValue = get(config, 'email.auth.user'); + const emailUsernameEnvValue = get(process.env, 'PN_ACT_CONFIG_EMAIL_USERNAME'); + + if (!emailUsernameConfigValue || emailUsernameConfigValue.trim() === '') { + if (!emailUsernameEnvValue || emailUsernameEnvValue.trim() === '') { + logger.warning('Failed to find email username config. Disabling feature'); + + disabledFeatures.email = true; + } + } + + const emailPasswordConfigValue = get(config, 'email.auth.pass'); + const emailPasswordEnvValue = get(process.env, 'PN_ACT_CONFIG_EMAIL_PASSWORD'); + + if (!emailPasswordConfigValue || emailPasswordConfigValue.trim() === '') { + if (!emailPasswordEnvValue || emailPasswordEnvValue.trim() === '') { + logger.warning('Failed to find email password config. Disabling feature'); + + disabledFeatures.email = true; + } + } + + const emailFromConfigValue = get(config, 'email.from'); + const emailFromEnvValue = get(process.env, 'PN_ACT_CONFIG_EMAIL_FROM'); + + if (!emailFromConfigValue || emailFromConfigValue.trim() === '') { + if (!emailFromEnvValue || emailFromEnvValue.trim() === '') { + logger.warning('Failed to find email from config. Disabling feature'); + + disabledFeatures.email = true; + } + } + module.exports.config = config; } diff --git a/src/mailer.js b/src/mailer.js index bf67714..d8228e2 100644 --- a/src/mailer.js +++ b/src/mailer.js @@ -1,12 +1,18 @@ const nodemailer = require('nodemailer'); -const { config } = require('./config-manager'); +const { config, disabledFeatures } = require('./config-manager'); -const transporter = nodemailer.createTransport(config.email); +let transporter; + +if (!disabledFeatures.email) { + transporter = nodemailer.createTransport(config.email); +} async function sendMail(options) { - options.from = config.email.from; + if (!disabledFeatures.email) { + options.from = config.email.from; - await transporter.sendMail(options); + await transporter.sendMail(options); + } } module.exports = { From 4809618b0206f8bc8be85a85272aaa766f22aefb Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 9 Oct 2022 14:11:27 -0400 Subject: [PATCH 08/22] logger.warning should be logger.warn --- src/config-manager.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/config-manager.js b/src/config-manager.js index 7bb46c9..86a97f1 100644 --- a/src/config-manager.js +++ b/src/config-manager.js @@ -152,7 +152,7 @@ function configure() { if (!redisConfigValue || redisConfigValue.trim() === '') { if (!redisEnvValue || redisEnvValue.trim() === '') { - logger.warning('Failed to find Redis config. Disabling feature and using in-memory cache'); + logger.warn('Failed to find Redis config. Disabling feature and using in-memory cache'); disabledFeatures.redis = true; } @@ -163,7 +163,7 @@ function configure() { if (!emailHostConfigValue || emailHostConfigValue.trim() === '') { if (!emailHostEnvValue || emailHostEnvValue.trim() === '') { - logger.warning('Failed to find email SMTP host config. Disabling feature'); + logger.warn('Failed to find email SMTP host config. Disabling feature'); disabledFeatures.email = true; } @@ -174,7 +174,7 @@ function configure() { if (!emailPortConfigValue) { if (!emailPortEnvValue || emailPortEnvValue.trim() === '') { - logger.warning('Failed to find email SMTP port config. Disabling feature'); + logger.warn('Failed to find email SMTP port config. Disabling feature'); disabledFeatures.email = true; } @@ -185,7 +185,7 @@ function configure() { if (emailSecureConfigValue === undefined) { if (!emailSecureEnvValue || emailSecureEnvValue.trim() === '') { - logger.warning('Failed to find email SMTP secure config. Disabling feature'); + logger.warn('Failed to find email SMTP secure config. Disabling feature'); disabledFeatures.email = true; } @@ -196,7 +196,7 @@ function configure() { if (!emailUsernameConfigValue || emailUsernameConfigValue.trim() === '') { if (!emailUsernameEnvValue || emailUsernameEnvValue.trim() === '') { - logger.warning('Failed to find email username config. Disabling feature'); + logger.warn('Failed to find email username config. Disabling feature'); disabledFeatures.email = true; } @@ -207,7 +207,7 @@ function configure() { if (!emailPasswordConfigValue || emailPasswordConfigValue.trim() === '') { if (!emailPasswordEnvValue || emailPasswordEnvValue.trim() === '') { - logger.warning('Failed to find email password config. Disabling feature'); + logger.warn('Failed to find email password config. Disabling feature'); disabledFeatures.email = true; } @@ -218,7 +218,7 @@ function configure() { if (!emailFromConfigValue || emailFromConfigValue.trim() === '') { if (!emailFromEnvValue || emailFromEnvValue.trim() === '') { - logger.warning('Failed to find email from config. Disabling feature'); + logger.warn('Failed to find email from config. Disabling feature'); disabledFeatures.email = true; } From 272e640a8b015b29c98065834f883f3b78ef0eb5 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 9 Oct 2022 14:12:35 -0400 Subject: [PATCH 09/22] Fixed required features type check --- src/config-manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config-manager.js b/src/config-manager.js index 86a97f1..0d55280 100644 --- a/src/config-manager.js +++ b/src/config-manager.js @@ -130,7 +130,7 @@ function configure() { const configValue = get(config, keyPath); const envValue = get(process.env, keyPath); - if (!configValue || configValue.trim() === '') { + if (!configValue || (typeof configValue === 'string' && configValue.trim() === '')) { if (!envValue || envValue.trim() === '') { logger.error(`Failed to locate required field ${keyPath}. Set ${keyPath} in config.json or the ${env} environment variable`); From 9bb83632db5959c75a24e9f3680a20d5ae99e6a7 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 9 Oct 2022 14:12:48 -0400 Subject: [PATCH 10/22] Actually export disabledFeatures --- src/config-manager.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/config-manager.js b/src/config-manager.js index 0d55280..b719e58 100644 --- a/src/config-manager.js +++ b/src/config-manager.js @@ -229,5 +229,6 @@ function configure() { module.exports = { configure, - config + config, + disabledFeatures }; \ No newline at end of file From 19222bbf2dba8090e8fc3faa5b0d014a50b39f07 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 9 Oct 2022 14:14:33 -0400 Subject: [PATCH 11/22] Fixed redis config env check --- src/config-manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config-manager.js b/src/config-manager.js index b719e58..95d5d2f 100644 --- a/src/config-manager.js +++ b/src/config-manager.js @@ -148,7 +148,7 @@ function configure() { // * Check for optional settings const redisConfigValue = get(config, 'redis.client.url'); - const redisEnvValue = get(process.env, 'redis.client.url'); + const redisEnvValue = get(process.env, 'PN_ACT_CONFIG_REDIS_URL'); if (!redisConfigValue || redisConfigValue.trim() === '') { if (!redisEnvValue || redisEnvValue.trim() === '') { From ce46c51c968a2a34aae801099797de99d0198da9 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 9 Oct 2022 14:19:37 -0400 Subject: [PATCH 12/22] Set config fallbacks using env --- src/config-manager.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/config-manager.js b/src/config-manager.js index 95d5d2f..b8cf5e6 100644 --- a/src/config-manager.js +++ b/src/config-manager.js @@ -155,6 +155,10 @@ function configure() { logger.warn('Failed to find Redis config. Disabling feature and using in-memory cache'); disabledFeatures.redis = true; + } else { + logger.info('redis.client.url not found in config, using environment variable PN_ACT_CONFIG_REDIS_URL'); + + set(config, 'redis.client.url', redisEnvValue); } } @@ -166,6 +170,10 @@ function configure() { logger.warn('Failed to find email SMTP host config. Disabling feature'); disabledFeatures.email = true; + } else { + logger.info('email.host not found in config, using environment variable PN_ACT_CONFIG_EMAIL_HOST'); + + set(config, 'email.host', emailHostEnvValue); } } @@ -177,6 +185,10 @@ function configure() { logger.warn('Failed to find email SMTP port config. Disabling feature'); disabledFeatures.email = true; + } else { + logger.info('email.port not found in config, using environment variable PN_ACT_CONFIG_EMAIL_PORT'); + + set(config, 'email.port', Number(emailPortEnvValue)); } } @@ -188,6 +200,10 @@ function configure() { logger.warn('Failed to find email SMTP secure config. Disabling feature'); disabledFeatures.email = true; + } else { + logger.info('email.secure not found in config, using environment variable PN_ACT_CONFIG_EMAIL_SECURE'); + + set(config, 'email.secure', Boolean(emailSecureEnvValue)); } } @@ -199,6 +215,10 @@ function configure() { logger.warn('Failed to find email username config. Disabling feature'); disabledFeatures.email = true; + } else { + logger.info('email.auth.user not found in config, using environment variable PN_ACT_CONFIG_EMAIL_USERNAME'); + + set(config, 'email.auth.user', emailUsernameEnvValue); } } @@ -210,6 +230,10 @@ function configure() { logger.warn('Failed to find email password config. Disabling feature'); disabledFeatures.email = true; + } else { + logger.info('email.pass not found in config, using environment variable PN_ACT_CONFIG_EMAIL_PASSWORD'); + + set(config, 'email.pass', emailPasswordEnvValue); } } @@ -221,6 +245,10 @@ function configure() { logger.warn('Failed to find email from config. Disabling feature'); disabledFeatures.email = true; + } else { + logger.info('email.from not found in config, using environment variable PN_ACT_CONFIG_EMAIL_FROM'); + + set(config, 'email.from', emailFromEnvValue); } } From 8f5dcb2f9ae4f876b868c7d531efaf34c0f4e49a Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 9 Oct 2022 14:23:34 -0400 Subject: [PATCH 13/22] Made captcha verification optional --- src/config-manager.js | 20 ++++++++++++++-- src/services/api/routes/v1/register.js | 32 ++++++++++++++------------ 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/src/config-manager.js b/src/config-manager.js index b8cf5e6..0a8733c 100644 --- a/src/config-manager.js +++ b/src/config-manager.js @@ -44,6 +44,7 @@ let config = {}; * @typedef {Object} DisabledFeatures * @property {boolean} redis true if redis is disabled * @property {boolean} email true if email sending is disabled + * @property {boolean} captcha true if captcha verification is disabled */ /** @@ -51,7 +52,8 @@ let config = {}; */ const disabledFeatures = { redis: false, - email: false + email: false, + captcha: false }; const requiredFields = [ @@ -60,7 +62,6 @@ const requiredFields = [ ['mongoose.database', 'PN_ACT_CONFIG_MONGO_DB_NAME'], ['aws.spaces.key', 'PN_ACT_CONFIG_S3_ACCESS_KEY'], ['aws.spaces.secret', 'PN_ACT_CONFIG_S3_ACCESS_SECRET'], - ['hcaptcha.secret', 'PN_ACT_CONFIG_HCAPTCHA_SECRET'], ['cdn_base', 'PN_ACT_CONFIG_CDN_BASE'], ['website_base', 'PN_ACT_CONFIG_WEBSITE_BASE'], ]; @@ -252,6 +253,21 @@ function configure() { } } + const captchaSecretConfigValue = get(config, 'hcaptcha.secret'); + const captchaSecretEnvValue = get(process.env, 'PN_ACT_CONFIG_HCAPTCHA_SECRET'); + + if (!captchaSecretConfigValue || captchaSecretConfigValue.trim() === '') { + if (!captchaSecretEnvValue || captchaSecretEnvValue.trim() === '') { + logger.warn('Failed to find captcha secret config. Disabling feature'); + + disabledFeatures.email = true; + } else { + logger.info('hcaptcha.secret not found in config, using environment variable PN_ACT_CONFIG_HCAPTCHA_SECRET'); + + set(config, 'hcaptcha.secret', emailFromEnvValue); + } + } + module.exports.config = config; } diff --git a/src/services/api/routes/v1/register.js b/src/services/api/routes/v1/register.js index f2bfa06..729bc41 100644 --- a/src/services/api/routes/v1/register.js +++ b/src/services/api/routes/v1/register.js @@ -12,7 +12,7 @@ const database = require('../../../../database'); const cache = require('../../../../cache'); const util = require('../../../../util'); const logger = require('../../../../../logger'); -const { config } = require('../../../../config-manager'); +const { config, disabledFeatures } = require('../../../../config-manager'); const PNID_VALID_CHARACTERS_REGEX = /^[\w\-\.]*$/gm; const PNID_PUNCTUATION_START_REGEX = /^[\_\-\.]/gm; @@ -42,22 +42,24 @@ router.post('/', async (request, response) => { const passwordConfirm = body.password_confirm?.trim(); const hCaptchaResponse = body.hCaptchaResponse?.trim(); - if (!hCaptchaResponse || hCaptchaResponse === '') { - return response.status(400).json({ - app: 'api', - status: 400, - error: 'Must fill in captcha' - }); - } + if (!disabledFeatures.captcha) { + if (!hCaptchaResponse || hCaptchaResponse === '') { + return response.status(400).json({ + app: 'api', + status: 400, + error: 'Must fill in captcha' + }); + } - const captchaVerify = await hcaptcha.verify(config.hcaptcha.secret, hCaptchaResponse); + const captchaVerify = await hcaptcha.verify(config.hcaptcha.secret, hCaptchaResponse); - if (!captchaVerify.success) { - return response.status(400).json({ - app: 'api', - status: 400, - error: 'Captcha verification failed' - }); + if (!captchaVerify.success) { + return response.status(400).json({ + app: 'api', + status: 400, + error: 'Captcha verification failed' + }); + } } if (!email || email === '') { From 8ab51d7f96496ef585c7d4a81c856e5fc52f01a5 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 9 Oct 2022 15:24:15 -0400 Subject: [PATCH 14/22] Made s3 optional --- example.env | 3 +- src/cache.js | 22 +++++++ src/config-manager.js | 89 ++++++++++++++++++++------ src/server.js | 2 + src/services/local-cdn/index.js | 30 +++++++++ src/services/local-cdn/routes/get.js | 16 +++++ src/services/local-cdn/routes/index.js | 3 + src/util.js | 49 +++++++++----- 8 files changed, 177 insertions(+), 37 deletions(-) create mode 100644 src/services/local-cdn/index.js create mode 100644 src/services/local-cdn/routes/get.js create mode 100644 src/services/local-cdn/routes/index.js diff --git a/example.env b/example.env index 5c34138..a5c1007 100644 --- a/example.env +++ b/example.env @@ -14,5 +14,6 @@ PN_ACT_CONFIG_EMAIL_FROM=Company Name PN_ACT_CONFIG_S3_ACCESS_KEY=ACCESS_KEY PN_ACT_CONFIG_S3_ACCESS_SECRET=ACCESS_SECRET PN_ACT_CONFIG_HCAPTCHA_SECRET=0x0000000000000000000000000000000000000000 -PN_ACT_CONFIG_CDN_BASE=https://example.com +PN_ACT_CONFIG_CDN_BASE=https://local-cdn.example.com +PN_ACT_CONFIG_CDN_SUBDOMAIN=local-cdn PN_ACT_CONFIG_WEBSITE_BASE=https://example.com \ No newline at end of file diff --git a/src/cache.js b/src/cache.js index f20a8fe..e889483 100644 --- a/src/cache.js +++ b/src/cache.js @@ -7,6 +7,7 @@ const memoryCache = {}; const SERVICE_CERTS_BASE = `${__dirname}/../certs/service`; const NEX_CERTS_BASE = `${__dirname}/../certs/nex`; +const LOCAL_CDN_BASE = `${__dirname}/../cdn`; async function connect() { if (!disabledFeatures.redis) { @@ -169,6 +170,25 @@ async function setServiceAESKey(name, value) { await setCachedFile(`service:${name}:aes_key`, value); } +// Local CDN cache functions + +async function getLocalCDNFile(name, encoding) { + let file = await getCachedFile(`local_cdn:${name}`, encoding); + + if (file === null) { + if (await fs.pathExists(`${LOCAL_CDN_BASE}/${name}`)) { + file = await fs.readFile(`${LOCAL_CDN_BASE}/${name}`, { encoding }); + await setLocalCDNFile(name, file); + } + } + + return file; +} + +async function setLocalCDNFile(name, value) { + await setCachedFile(`local_cdn:${name}`, value); +} + module.exports = { connect, getNEXPublicKey, @@ -187,4 +207,6 @@ module.exports = { setServicePrivateKey, setServiceSecretKey, setServiceAESKey, + getLocalCDNFile, + setLocalCDNFile }; \ No newline at end of file diff --git a/src/config-manager.js b/src/config-manager.js index 0a8733c..12615ac 100644 --- a/src/config-manager.js +++ b/src/config-manager.js @@ -13,23 +13,24 @@ require('dotenv').config(); * @property {string} mongoose.uri URI Mongoose will connect to * @property {string} mongoose.database MongoDB database name * @property {object} mongoose.options MongoDB connection options - * @property {object} redis redis settings - * @property {string} redis.client redis client settings - * @property {string} redis.client.url redis server URL - * @property {object} email node-mailer client settings - * @property {string} email.host SMTP server address - * @property {number} email.port SMTP server port - * @property {boolean} email.secure Secure SMTP - * @property {string} email.from Email 'from' name/address - * @property {object} email.auth Email authentication settings - * @property {string} email.auth.user Email username - * @property {string} email.auth.pass Email password - * @property {object} aws s3 client settings - * @property {object} aws.spaces Digital Ocean Spaces settings - * @property {string} aws.spaces.key s3 access key - * @property {string} aws.spaces.secret s3 access secret - * @property {object} hcaptcha hCaptcha settings - * @property {string} hcaptcha.secret hCaptcha secret + * @property {object} [redis] redis settings + * @property {string} [redis.client] redis client settings + * @property {string} [redis.client.url] redis server URL + * @property {object} [email] node-mailer client settings + * @property {string} [email.host] SMTP server address + * @property {number} [email.port] SMTP server port + * @property {boolean} [email.secure] Secure SMTP + * @property {string} [email.from] Email 'from' name/address + * @property {object} [email.auth] Email authentication settings + * @property {string} [email.auth.user] Email username + * @property {string} [email.auth.pass] Email password + * @property {object} [aws] s3 client settings + * @property {object} [aws.spaces] Digital Ocean Spaces settings + * @property {string} [aws.spaces.key] s3 access key + * @property {string} [aws.spaces.secret] s3 access secret + * @property {object} [hcaptcha] hCaptcha settings + * @property {string} [hcaptcha.secret] hCaptcha secret + * @property {string} [cdn_subdomain] Subdomain used for serving CDN contents when s3 is disabled * @property {string} cdn_base Base URL for CDN location * @property {string} website_base Base URL for service website (used with emails) */ @@ -45,6 +46,7 @@ let config = {}; * @property {boolean} redis true if redis is disabled * @property {boolean} email true if email sending is disabled * @property {boolean} captcha true if captcha verification is disabled + * @property {boolean} s3 true if s3 services is disabled */ /** @@ -53,15 +55,14 @@ let config = {}; const disabledFeatures = { redis: false, email: false, - captcha: false + captcha: false, + s3: false }; const requiredFields = [ ['http.port', 'PN_ACT_CONFIG_HTTP_PORT', Number], ['mongoose.uri', 'PN_ACT_CONFIG_MONGO_URI'], ['mongoose.database', 'PN_ACT_CONFIG_MONGO_DB_NAME'], - ['aws.spaces.key', 'PN_ACT_CONFIG_S3_ACCESS_KEY'], - ['aws.spaces.secret', 'PN_ACT_CONFIG_S3_ACCESS_SECRET'], ['cdn_base', 'PN_ACT_CONFIG_CDN_BASE'], ['website_base', 'PN_ACT_CONFIG_WEBSITE_BASE'], ]; @@ -268,6 +269,54 @@ function configure() { } } + const s3AccessKeyConfigValue = get(config, 'aws.spaces.key'); + const s3AccessKeyEnvValue = get(process.env, 'PN_ACT_CONFIG_S3_ACCESS_KEY'); + + if (!s3AccessKeyConfigValue || s3AccessKeyConfigValue.trim() === '') { + if (!s3AccessKeyEnvValue || s3AccessKeyEnvValue.trim() === '') { + logger.warn('Failed to find s3 access key config. Disabling feature'); + + disabledFeatures.s3 = true; + } else { + logger.info('aws.spaces.key not found in config, using environment variable PN_ACT_CONFIG_S3_ACCESS_KEY'); + + set(config, 'aws.spaces.key', s3AccessKeyEnvValue); + } + } + + const s3SecretKeyConfigValue = get(config, 'aws.spaces.secret'); + const s3SecretKeyEnvValue = get(process.env, 'PN_ACT_CONFIG_S3_ACCESS_SECRET'); + + if (!s3SecretKeyConfigValue || s3SecretKeyConfigValue.trim() === '') { + if (!s3SecretKeyEnvValue || s3SecretKeyEnvValue.trim() === '') { + logger.warn('Failed to find s3 secret key config. Disabling feature'); + + disabledFeatures.s3 = true; + } else { + logger.info('aws.spaces.secret not found in config, using environment variable PN_ACT_CONFIG_S3_ACCESS_SECRET'); + + set(config, 'aws.spaces.secret', s3AccessKeyEnvValue); + } + } + + if (disabledFeatures.s3) { + const cdnSubdomainConfigValue = get(config, 'cdn_subdomain'); + const cdnSubdomainEnvValue = get(process.env, 'PN_ACT_CONFIG_CDN_SUBDOMAIN'); + + if (!cdnSubdomainConfigValue || cdnSubdomainConfigValue.trim() === '') { + if (!cdnSubdomainEnvValue || cdnSubdomainEnvValue.trim() === '') { + logger.error('s3 file storage is disabled and no CDN subdomain was set. Set cdn_subdomain in config.json or the PN_ACT_CONFIG_CDN_SUBDOMAIN environment variable'); + process.exit(0); + } else { + logger.info('cdn_subdomain not found in config, using environment variable PN_ACT_CONFIG_CDN_SUBDOMAIN'); + + set(config, 'cdn_subdomain', cdnSubdomainEnvValue); + } + } + + logger.warn(`s3 file storage disabled. Using disk-based file storage. Please ensure cdn_base config or PN_ACT_CONFIG_CDN_BASE env variable is set to point to this server with the subdomain being ${config.cdn_subdomain}`); + } + module.exports.config = config; } diff --git a/src/server.js b/src/server.js index b3202c6..c4dcf6b 100644 --- a/src/server.js +++ b/src/server.js @@ -22,6 +22,7 @@ const nnid = require('./services/nnid'); const nasc = require('./services/nasc'); const datastore = require('./services/datastore'); const api = require('./services/api'); +const localcdn = require('./services/local-cdn'); // START APPLICATION @@ -40,6 +41,7 @@ app.use(nnid); app.use(nasc); app.use(datastore); app.use(api); +app.use(localcdn); // 404 handler logger.info('Creating 404 status handler'); diff --git a/src/services/local-cdn/index.js b/src/services/local-cdn/index.js new file mode 100644 index 0000000..1a0a9dc --- /dev/null +++ b/src/services/local-cdn/index.js @@ -0,0 +1,30 @@ +const express = require('express'); +const subdomain = require('express-subdomain'); +const logger = require('../../../logger'); +const { config, disabledFeatures } = require('../../config-manager'); + +if (!disabledFeatures.s3) { + // * s3 enabled, no need for this + + module.exports = express.Router(); + + return; +} + +const routes = require('./routes'); + +// Router to handle the subdomain +const localcdn = express.Router(); + +// Setup routes +logger.info('[LOCAL-CDN] Applying imported routes'); +localcdn.use(routes.GET); + +// Main router for endpoints +const router = express.Router(); + +// Create subdomains +logger.info(`[LOCAL-CDN] Creating '${config.cdn_subdomain}' subdomain`); +router.use(subdomain(config.cdn_subdomain, localcdn)); + +module.exports = router; \ No newline at end of file diff --git a/src/services/local-cdn/routes/get.js b/src/services/local-cdn/routes/get.js new file mode 100644 index 0000000..7ae69b9 --- /dev/null +++ b/src/services/local-cdn/routes/get.js @@ -0,0 +1,16 @@ +const router = require('express').Router(); +const cache = require('../../../cache'); + +router.get('/*', async (request, response) => { + const filePath = request.params[0]; + + const file = await cache.getLocalCDNFile(filePath); + + if (file) { + response.send(file); + } else { + response.sendStatus(404); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/src/services/local-cdn/routes/index.js b/src/services/local-cdn/routes/index.js new file mode 100644 index 0000000..033fca1 --- /dev/null +++ b/src/services/local-cdn/routes/index.js @@ -0,0 +1,3 @@ +module.exports = { + GET: require('./get.js'), +}; \ No newline at end of file diff --git a/src/util.js b/src/util.js index c7073f6..1f298d5 100644 --- a/src/util.js +++ b/src/util.js @@ -1,16 +1,21 @@ const crypto = require('crypto'); +const path = require('path'); const NodeRSA = require('node-rsa'); const aws = require('aws-sdk'); +const fs = require('fs-extra'); const mailer = require('./mailer'); const cache = require('./cache'); -const { config } = require('./config-manager'); +const { config, disabledFeatures } = require('./config-manager'); -const spacesEndpoint = new aws.Endpoint('nyc3.digitaloceanspaces.com'); -const s3 = new aws.S3({ - endpoint: spacesEndpoint, - accessKeyId: config.aws.spaces.key, - secretAccessKey: config.aws.spaces.secret -}); +let s3; + +if (!disabledFeatures.s3) { + s3 = new aws.S3({ + endpoint: new aws.Endpoint('nyc3.digitaloceanspaces.com'), + accessKeyId: config.aws.spaces.key, + secretAccessKey: config.aws.spaces.secret + }); +} function nintendoPasswordHash(password, pid) { const pidBuffer = Buffer.alloc(4); @@ -203,20 +208,32 @@ function unpackToken(token) { function fullUrl(request) { const protocol = request.protocol; const host = request.host; - const path = request.originalUrl; + const opath = request.originalUrl; - return `${protocol}://${host}${path}`; + return `${protocol}://${host}${opath}`; } async function uploadCDNAsset(bucket, key, data, acl) { - const awsPutParams = { - Body: data, - Key: key, - Bucket: bucket, - ACL: acl - }; + if (disabledFeatures.s3) { + await writeLocalCDNFile(key, data); + } else { + const awsPutParams = { + Body: data, + Key: key, + Bucket: bucket, + ACL: acl + }; - await s3.putObject(awsPutParams).promise(); + await s3.putObject(awsPutParams).promise(); + } +} + +async function writeLocalCDNFile(key, data) { + const filePath = `${__dirname}/../cdn/${key}`; + const folder = path.dirname(filePath); + + await fs.ensureDir(folder); + await fs.writeFile(filePath, data); } function nascError(errorCode) { From 15103a62c4d2a92d862a0e67a564c0203e026105 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 9 Oct 2022 15:27:06 -0400 Subject: [PATCH 15/22] Moved website_base config check --- src/config-manager.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/config-manager.js b/src/config-manager.js index 12615ac..bedf103 100644 --- a/src/config-manager.js +++ b/src/config-manager.js @@ -63,8 +63,7 @@ const requiredFields = [ ['http.port', 'PN_ACT_CONFIG_HTTP_PORT', Number], ['mongoose.uri', 'PN_ACT_CONFIG_MONGO_URI'], ['mongoose.database', 'PN_ACT_CONFIG_MONGO_DB_NAME'], - ['cdn_base', 'PN_ACT_CONFIG_CDN_BASE'], - ['website_base', 'PN_ACT_CONFIG_WEBSITE_BASE'], + ['cdn_base', 'PN_ACT_CONFIG_CDN_BASE'] ]; function configure() { @@ -254,6 +253,22 @@ function configure() { } } + if (!disabledFeatures.email) { + const websiteBaseConfigValue = get(config, 'website_base'); + const websiteBaseEnvValue = get(process.env, 'PN_ACT_CONFIG_WEBSITE_BASE'); + + if (!websiteBaseConfigValue || websiteBaseConfigValue.trim() === '') { + if (!websiteBaseEnvValue || websiteBaseEnvValue.trim() === '') { + logger.error('Email sending is not disabled and no website base was set. Set website_base in config.json or the PN_ACT_CONFIG_WEBSITE_BASE environment variable'); + process.exit(0); + } else { + logger.info('website_base not found in config, using environment variable PN_ACT_CONFIG_WEBSITE_BASE'); + + set(config, 'website_base', websiteBaseEnvValue); + } + } + } + const captchaSecretConfigValue = get(config, 'hcaptcha.secret'); const captchaSecretEnvValue = get(process.env, 'PN_ACT_CONFIG_HCAPTCHA_SECRET'); From 1d3fdd3af7b9d705e5df2e6147ee205458da4a64 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 9 Oct 2022 15:28:58 -0400 Subject: [PATCH 16/22] Fixed hcaptcha.secret setting wrong env variable --- src/config-manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config-manager.js b/src/config-manager.js index bedf103..b61e8a4 100644 --- a/src/config-manager.js +++ b/src/config-manager.js @@ -280,7 +280,7 @@ function configure() { } else { logger.info('hcaptcha.secret not found in config, using environment variable PN_ACT_CONFIG_HCAPTCHA_SECRET'); - set(config, 'hcaptcha.secret', emailFromEnvValue); + set(config, 'hcaptcha.secret', captchaSecretEnvValue); } } From 64488b061d2dd406500a70d1eebee15a59c3a7c2 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 9 Oct 2022 15:33:02 -0400 Subject: [PATCH 17/22] Added enable message to missing optional config values --- src/config-manager.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/config-manager.js b/src/config-manager.js index b61e8a4..2237783 100644 --- a/src/config-manager.js +++ b/src/config-manager.js @@ -153,7 +153,7 @@ function configure() { if (!redisConfigValue || redisConfigValue.trim() === '') { if (!redisEnvValue || redisEnvValue.trim() === '') { - logger.warn('Failed to find Redis config. Disabling feature and using in-memory cache'); + logger.warn('Failed to find Redis config. Disabling feature and using in-memory cache. To enable feature set redis.client.url in config.json or the PN_ACT_CONFIG_REDIS_URL environment variable'); disabledFeatures.redis = true; } else { @@ -168,7 +168,7 @@ function configure() { if (!emailHostConfigValue || emailHostConfigValue.trim() === '') { if (!emailHostEnvValue || emailHostEnvValue.trim() === '') { - logger.warn('Failed to find email SMTP host config. Disabling feature'); + logger.warn('Failed to find email SMTP host config. Disabling feature. To enable feature set email.host in config.json or the PN_ACT_CONFIG_EMAIL_HOST environment variable'); disabledFeatures.email = true; } else { @@ -183,7 +183,7 @@ function configure() { if (!emailPortConfigValue) { if (!emailPortEnvValue || emailPortEnvValue.trim() === '') { - logger.warn('Failed to find email SMTP port config. Disabling feature'); + logger.warn('Failed to find email SMTP port config. Disabling feature. To enable feature set email.port in config.json or the PN_ACT_CONFIG_EMAIL_PORT environment variable'); disabledFeatures.email = true; } else { @@ -198,7 +198,7 @@ function configure() { if (emailSecureConfigValue === undefined) { if (!emailSecureEnvValue || emailSecureEnvValue.trim() === '') { - logger.warn('Failed to find email SMTP secure config. Disabling feature'); + logger.warn('Failed to find email SMTP secure config. Disabling feature. To enable feature set email.secure in config.json or the PN_ACT_CONFIG_EMAIL_SECURE environment variable'); disabledFeatures.email = true; } else { @@ -213,7 +213,7 @@ function configure() { if (!emailUsernameConfigValue || emailUsernameConfigValue.trim() === '') { if (!emailUsernameEnvValue || emailUsernameEnvValue.trim() === '') { - logger.warn('Failed to find email username config. Disabling feature'); + logger.warn('Failed to find email username config. Disabling feature. To enable feature set email.auth.user in config.json or the auth.user environment variable'); disabledFeatures.email = true; } else { @@ -228,7 +228,7 @@ function configure() { if (!emailPasswordConfigValue || emailPasswordConfigValue.trim() === '') { if (!emailPasswordEnvValue || emailPasswordEnvValue.trim() === '') { - logger.warn('Failed to find email password config. Disabling feature'); + logger.warn('Failed to find email password config. Disabling feature. To enable feature set email.auth.pass in config.json or the PN_ACT_CONFIG_EMAIL_PASSWORD environment variable'); disabledFeatures.email = true; } else { @@ -243,7 +243,7 @@ function configure() { if (!emailFromConfigValue || emailFromConfigValue.trim() === '') { if (!emailFromEnvValue || emailFromEnvValue.trim() === '') { - logger.warn('Failed to find email from config. Disabling feature'); + logger.warn('Failed to find email from config. Disabling feature. To enable feature set email.from in config.json or the PN_ACT_CONFIG_EMAIL_FROM environment variable'); disabledFeatures.email = true; } else { @@ -274,7 +274,7 @@ function configure() { if (!captchaSecretConfigValue || captchaSecretConfigValue.trim() === '') { if (!captchaSecretEnvValue || captchaSecretEnvValue.trim() === '') { - logger.warn('Failed to find captcha secret config. Disabling feature'); + logger.warn('Failed to find captcha secret config. Disabling feature. To enable feature set hcaptcha.secret in config.json or the PN_ACT_CONFIG_HCAPTCHA_SECRET environment variable'); disabledFeatures.email = true; } else { @@ -289,7 +289,7 @@ function configure() { if (!s3AccessKeyConfigValue || s3AccessKeyConfigValue.trim() === '') { if (!s3AccessKeyEnvValue || s3AccessKeyEnvValue.trim() === '') { - logger.warn('Failed to find s3 access key config. Disabling feature'); + logger.warn('Failed to find s3 access key config. Disabling feature. To enable feature set aws.spaces.key in config.json or the PN_ACT_CONFIG_S3_ACCESS_KEY environment variable'); disabledFeatures.s3 = true; } else { @@ -304,7 +304,7 @@ function configure() { if (!s3SecretKeyConfigValue || s3SecretKeyConfigValue.trim() === '') { if (!s3SecretKeyEnvValue || s3SecretKeyEnvValue.trim() === '') { - logger.warn('Failed to find s3 secret key config. Disabling feature'); + logger.warn('Failed to find s3 secret key config. Disabling feature. To enable feature set aws.spaces.secret in config.json or the PN_ACT_CONFIG_S3_ACCESS_SECRET environment variable'); disabledFeatures.s3 = true; } else { From 24fd641cc6f9d1039e6f52e4cfa1f704b0c3f085 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 9 Oct 2022 15:39:26 -0400 Subject: [PATCH 18/22] Changed aws/spaces config to generic s3 config --- example.config.json | 9 ++++---- example.env | 1 + src/config-manager.js | 48 ++++++++++++++++++++++++++++--------------- src/util.js | 6 +++--- 4 files changed, 39 insertions(+), 25 deletions(-) diff --git a/example.config.json b/example.config.json index bfa68f4..9c1f457 100644 --- a/example.config.json +++ b/example.config.json @@ -24,11 +24,10 @@ }, "from": "Company Name " }, - "aws": { - "spaces": { - "key": "ACCESS_KEY", - "secret": "ACCESS_SECRET" - } + "s3": { + "endpoint": "nyc3.digitaloceanspaces.com", + "key": "ACCESS_KEY", + "secret": "ACCESS_SECRET" }, "hcaptcha": { "secret": "0x0000000000000000000000000000000000000000" diff --git a/example.env b/example.env index a5c1007..57ff506 100644 --- a/example.env +++ b/example.env @@ -11,6 +11,7 @@ PN_ACT_CONFIG_EMAIL_SECURE=false PN_ACT_CONFIG_EMAIL_USERNAME=username PN_ACT_CONFIG_EMAIL_PASSWORD=password PN_ACT_CONFIG_EMAIL_FROM=Company Name +PN_ACT_CONFIG_S3_ENDPOINT=nyc3.digitaloceanspaces.com PN_ACT_CONFIG_S3_ACCESS_KEY=ACCESS_KEY PN_ACT_CONFIG_S3_ACCESS_SECRET=ACCESS_SECRET PN_ACT_CONFIG_HCAPTCHA_SECRET=0x0000000000000000000000000000000000000000 diff --git a/src/config-manager.js b/src/config-manager.js index 2237783..b1d7547 100644 --- a/src/config-manager.js +++ b/src/config-manager.js @@ -24,10 +24,10 @@ require('dotenv').config(); * @property {object} [email.auth] Email authentication settings * @property {string} [email.auth.user] Email username * @property {string} [email.auth.pass] Email password - * @property {object} [aws] s3 client settings - * @property {object} [aws.spaces] Digital Ocean Spaces settings - * @property {string} [aws.spaces.key] s3 access key - * @property {string} [aws.spaces.secret] s3 access secret + * @property {object} [s3] s3 client settings + * @property {object} [s3.endpoint] s3 endpoint URL + * @property {string} [s3.key] s3 access key + * @property {string} [s3.secret] s3 access secret * @property {object} [hcaptcha] hCaptcha settings * @property {string} [hcaptcha.secret] hCaptcha secret * @property {string} [cdn_subdomain] Subdomain used for serving CDN contents when s3 is disabled @@ -99,11 +99,10 @@ function configure() { }, from: process.env.PN_ACT_CONFIG_EMAIL_FROM }, - aws: { - spaces: { - key: process.env.PN_ACT_CONFIG_S3_ACCESS_KEY, - secret: process.env.PN_ACT_CONFIG_S3_ACCESS_SECRET - } + s3: { + endpoint: process.env.PN_ACT_CONFIG_S3_ENDPOINT, + key: process.env.PN_ACT_CONFIG_S3_ACCESS_KEY, + secret: process.env.PN_ACT_CONFIG_S3_ACCESS_SECRET }, hcaptcha: { secret: process.env.PN_ACT_CONFIG_HCAPTCHA_SECRET @@ -284,33 +283,48 @@ function configure() { } } - const s3AccessKeyConfigValue = get(config, 'aws.spaces.key'); + const s3EndpointConfigValue = get(config, 's3.endpoint'); + const s3EndpointEnvValue = get(process.env, 'PN_ACT_CONFIG_S3_ENDPOINT'); + + if (!s3EndpointConfigValue || s3EndpointConfigValue.trim() === '') { + if (!s3EndpointEnvValue || s3EndpointEnvValue.trim() === '') { + logger.warn('Failed to find s3 endpoint config. Disabling feature. To enable feature set s3.endpoint in config.json or the PN_ACT_CONFIG_S3_ENDPOINT environment variable'); + + disabledFeatures.s3 = true; + } else { + logger.info('s3.endpoint not found in config, using environment variable PN_ACT_CONFIG_S3_ENDPOINT'); + + set(config, 's3.endpoint', s3EndpointEnvValue); + } + } + + const s3AccessKeyConfigValue = get(config, 's3.key'); const s3AccessKeyEnvValue = get(process.env, 'PN_ACT_CONFIG_S3_ACCESS_KEY'); if (!s3AccessKeyConfigValue || s3AccessKeyConfigValue.trim() === '') { if (!s3AccessKeyEnvValue || s3AccessKeyEnvValue.trim() === '') { - logger.warn('Failed to find s3 access key config. Disabling feature. To enable feature set aws.spaces.key in config.json or the PN_ACT_CONFIG_S3_ACCESS_KEY environment variable'); + logger.warn('Failed to find s3 access key config. Disabling feature. To enable feature set s3.key in config.json or the PN_ACT_CONFIG_S3_ACCESS_KEY environment variable'); disabledFeatures.s3 = true; } else { - logger.info('aws.spaces.key not found in config, using environment variable PN_ACT_CONFIG_S3_ACCESS_KEY'); + logger.info('s3.key not found in config, using environment variable PN_ACT_CONFIG_S3_ACCESS_KEY'); - set(config, 'aws.spaces.key', s3AccessKeyEnvValue); + set(config, 's3.key', s3AccessKeyEnvValue); } } - const s3SecretKeyConfigValue = get(config, 'aws.spaces.secret'); + const s3SecretKeyConfigValue = get(config, 's3.secret'); const s3SecretKeyEnvValue = get(process.env, 'PN_ACT_CONFIG_S3_ACCESS_SECRET'); if (!s3SecretKeyConfigValue || s3SecretKeyConfigValue.trim() === '') { if (!s3SecretKeyEnvValue || s3SecretKeyEnvValue.trim() === '') { - logger.warn('Failed to find s3 secret key config. Disabling feature. To enable feature set aws.spaces.secret in config.json or the PN_ACT_CONFIG_S3_ACCESS_SECRET environment variable'); + logger.warn('Failed to find s3 secret key config. Disabling feature. To enable feature set s3.secret in config.json or the PN_ACT_CONFIG_S3_ACCESS_SECRET environment variable'); disabledFeatures.s3 = true; } else { - logger.info('aws.spaces.secret not found in config, using environment variable PN_ACT_CONFIG_S3_ACCESS_SECRET'); + logger.info('s3.secret not found in config, using environment variable PN_ACT_CONFIG_S3_ACCESS_SECRET'); - set(config, 'aws.spaces.secret', s3AccessKeyEnvValue); + set(config, 's3.secret', s3AccessKeyEnvValue); } } diff --git a/src/util.js b/src/util.js index 1f298d5..25b4ba6 100644 --- a/src/util.js +++ b/src/util.js @@ -11,9 +11,9 @@ let s3; if (!disabledFeatures.s3) { s3 = new aws.S3({ - endpoint: new aws.Endpoint('nyc3.digitaloceanspaces.com'), - accessKeyId: config.aws.spaces.key, - secretAccessKey: config.aws.spaces.secret + endpoint: new aws.Endpoint(config.s3.endpoint), + accessKeyId: config.s3.key, + secretAccessKey: config.s3.secret }); } From 646916b7a885c4fb422d8beebf02e49fba32a8ad Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 9 Oct 2022 22:07:14 -0400 Subject: [PATCH 19/22] Add redis dump.rdb to gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6ca20ff..ce5d611 100644 --- a/.gitignore +++ b/.gitignore @@ -63,4 +63,5 @@ t.js p.js config.json certs -/cdn \ No newline at end of file +/cdn +dump.rdb \ No newline at end of file From 434bcfdc56bacf4974ac2ec1b8fedab538cfc192 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 9 Oct 2022 22:08:33 -0400 Subject: [PATCH 20/22] Added high memory usage warning wen both s3 and redis are disabled --- src/config-manager.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/config-manager.js b/src/config-manager.js index b1d7547..cbd0e14 100644 --- a/src/config-manager.js +++ b/src/config-manager.js @@ -343,6 +343,10 @@ function configure() { } } + if (disabledFeatures.redis) { + logger.warn('Both s3 and Redis are disabled. Large CDN files will use the in-memory cache, which may result in high memory use. Please enable s3 if you\'re running a production server.'); + } + logger.warn(`s3 file storage disabled. Using disk-based file storage. Please ensure cdn_base config or PN_ACT_CONFIG_CDN_BASE env variable is set to point to this server with the subdomain being ${config.cdn_subdomain}`); } From 4cd1665c1718e7de0034adc87ed3b73fe4fef31f Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 14 Oct 2022 06:50:11 -0400 Subject: [PATCH 21/22] Removed redundant env checks --- src/config-manager.js | 221 ++++++++++++++++++------------------------ 1 file changed, 97 insertions(+), 124 deletions(-) diff --git a/src/config-manager.js b/src/config-manager.js index cbd0e14..f5c6b85 100644 --- a/src/config-manager.js +++ b/src/config-manager.js @@ -67,8 +67,10 @@ const requiredFields = [ ]; function configure() { - if (process.env.PN_ACT_PREFER_ENV_CONFIG === 'true') { - logger.info('Loading config from env'); + const usingEnv = process.env.PN_ACT_PREFER_ENV_CONFIG === 'true'; + + if (usingEnv) { + logger.info('Loading config from environment variable'); config = { http: { @@ -147,200 +149,171 @@ function configure() { // * Check for optional settings - const redisConfigValue = get(config, 'redis.client.url'); - const redisEnvValue = get(process.env, 'PN_ACT_CONFIG_REDIS_URL'); + const redisCheck = get(config, 'redis.client.url'); - if (!redisConfigValue || redisConfigValue.trim() === '') { - if (!redisEnvValue || redisEnvValue.trim() === '') { - logger.warn('Failed to find Redis config. Disabling feature and using in-memory cache. To enable feature set redis.client.url in config.json or the PN_ACT_CONFIG_REDIS_URL environment variable'); + if (!redisCheck || redisCheck.trim() === '') { + if (usingEnv) { + logger.warn('Failed to find Redis connection url. Disabling feature and using in-memory cache. To enable feature set the PN_ACT_CONFIG_REDIS_URL environment variable'); - disabledFeatures.redis = true; } else { - logger.info('redis.client.url not found in config, using environment variable PN_ACT_CONFIG_REDIS_URL'); + logger.warn('Failed to find Redis connection url. Disabling feature and using in-memory cache. To enable feature set redis.client.url in your config.json'); - set(config, 'redis.client.url', redisEnvValue); } + + disabledFeatures.redis = true; } - const emailHostConfigValue = get(config, 'email.host'); - const emailHostEnvValue = get(process.env, 'PN_ACT_CONFIG_EMAIL_HOST'); + const emailHostCheck = get(config, 'email.host'); - if (!emailHostConfigValue || emailHostConfigValue.trim() === '') { - if (!emailHostEnvValue || emailHostEnvValue.trim() === '') { - logger.warn('Failed to find email SMTP host config. Disabling feature. To enable feature set email.host in config.json or the PN_ACT_CONFIG_EMAIL_HOST environment variable'); - - disabledFeatures.email = true; + if (!emailHostCheck || emailHostCheck.trim() === '') { + if (usingEnv) { + logger.warn('Failed to find email SMTP host. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_HOST environment variable'); } else { - logger.info('email.host not found in config, using environment variable PN_ACT_CONFIG_EMAIL_HOST'); - - set(config, 'email.host', emailHostEnvValue); + logger.warn('Failed to find email SMTP host. Disabling feature. To enable feature set email.host in your config.json'); } + + + disabledFeatures.email = true; } - const emailPortConfigValue = get(config, 'email.port'); - const emailPortEnvValue = get(process.env, 'PN_ACT_CONFIG_EMAIL_PORT'); + const emailPortCheck = get(config, 'email.port'); - if (!emailPortConfigValue) { - if (!emailPortEnvValue || emailPortEnvValue.trim() === '') { - logger.warn('Failed to find email SMTP port config. Disabling feature. To enable feature set email.port in config.json or the PN_ACT_CONFIG_EMAIL_PORT environment variable'); - - disabledFeatures.email = true; + if (!emailPortCheck) { + if (usingEnv) { + logger.warn('Failed to find email SMTP port. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_PORT environment variable'); } else { - logger.info('email.port not found in config, using environment variable PN_ACT_CONFIG_EMAIL_PORT'); - - set(config, 'email.port', Number(emailPortEnvValue)); + logger.warn('Failed to find email SMTP port. Disabling feature. To enable feature set email.port in your config.json'); } + + disabledFeatures.email = true; } - const emailSecureConfigValue = get(config, 'email.secure'); - const emailSecureEnvValue = get(process.env, 'PN_ACT_CONFIG_EMAIL_SECURE'); + const emailSecureCheck = get(config, 'email.secure'); - if (emailSecureConfigValue === undefined) { - if (!emailSecureEnvValue || emailSecureEnvValue.trim() === '') { - logger.warn('Failed to find email SMTP secure config. Disabling feature. To enable feature set email.secure in config.json or the PN_ACT_CONFIG_EMAIL_SECURE environment variable'); - - disabledFeatures.email = true; + if (emailSecureCheck === undefined) { + if (usingEnv) { + logger.warn('Failed to find email SMTP secure flag. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_SECURE environment variable'); } else { - logger.info('email.secure not found in config, using environment variable PN_ACT_CONFIG_EMAIL_SECURE'); - set(config, 'email.secure', Boolean(emailSecureEnvValue)); + logger.warn('Failed to find email SMTP secure flag. Disabling feature. To enable feature set email.secure in your config.json'); } + + disabledFeatures.email = true; } - const emailUsernameConfigValue = get(config, 'email.auth.user'); - const emailUsernameEnvValue = get(process.env, 'PN_ACT_CONFIG_EMAIL_USERNAME'); + const emailUsernameCheck = get(config, 'email.auth.user'); - if (!emailUsernameConfigValue || emailUsernameConfigValue.trim() === '') { - if (!emailUsernameEnvValue || emailUsernameEnvValue.trim() === '') { - logger.warn('Failed to find email username config. Disabling feature. To enable feature set email.auth.user in config.json or the auth.user environment variable'); - - disabledFeatures.email = true; + if (!emailUsernameCheck || emailUsernameCheck.trim() === '') { + if (usingEnv) { + logger.warn('Failed to find email account username. Disabling feature. To enable feature set the auth.user environment variable'); } else { - logger.info('email.auth.user not found in config, using environment variable PN_ACT_CONFIG_EMAIL_USERNAME'); - set(config, 'email.auth.user', emailUsernameEnvValue); + logger.warn('Failed to find email account username. Disabling feature. To enable feature set email.auth.user in your config.json'); } + + disabledFeatures.email = true; } - const emailPasswordConfigValue = get(config, 'email.auth.pass'); - const emailPasswordEnvValue = get(process.env, 'PN_ACT_CONFIG_EMAIL_PASSWORD'); + const emailPasswordCheck = get(config, 'email.auth.pass'); - if (!emailPasswordConfigValue || emailPasswordConfigValue.trim() === '') { - if (!emailPasswordEnvValue || emailPasswordEnvValue.trim() === '') { - logger.warn('Failed to find email password config. Disabling feature. To enable feature set email.auth.pass in config.json or the PN_ACT_CONFIG_EMAIL_PASSWORD environment variable'); - - disabledFeatures.email = true; + if (!emailPasswordCheck || emailPasswordCheck.trim() === '') { + if (usingEnv) { + logger.warn('Failed to find email account password. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_PASSWORD environment variable'); } else { - logger.info('email.pass not found in config, using environment variable PN_ACT_CONFIG_EMAIL_PASSWORD'); - set(config, 'email.pass', emailPasswordEnvValue); + logger.warn('Failed to find email account password. Disabling feature. To enable feature set email.auth.pass in your config.json'); } + + disabledFeatures.email = true; } - const emailFromConfigValue = get(config, 'email.from'); - const emailFromEnvValue = get(process.env, 'PN_ACT_CONFIG_EMAIL_FROM'); + const emailFromCheck = get(config, 'email.from'); - if (!emailFromConfigValue || emailFromConfigValue.trim() === '') { - if (!emailFromEnvValue || emailFromEnvValue.trim() === '') { - logger.warn('Failed to find email from config. Disabling feature. To enable feature set email.from in config.json or the PN_ACT_CONFIG_EMAIL_FROM environment variable'); - - disabledFeatures.email = true; + if (!emailFromCheck || emailFromCheck.trim() === '') { + if (usingEnv) { + logger.warn('Failed to find email from config. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_FROM environment variable'); } else { - logger.info('email.from not found in config, using environment variable PN_ACT_CONFIG_EMAIL_FROM'); - set(config, 'email.from', emailFromEnvValue); + logger.warn('Failed to find email from config. Disabling feature. To enable feature set email.from in your config.json'); } + + disabledFeatures.email = true; } if (!disabledFeatures.email) { - const websiteBaseConfigValue = get(config, 'website_base'); - const websiteBaseEnvValue = get(process.env, 'PN_ACT_CONFIG_WEBSITE_BASE'); + const websiteBaseCheck = get(config, 'website_base'); - if (!websiteBaseConfigValue || websiteBaseConfigValue.trim() === '') { - if (!websiteBaseEnvValue || websiteBaseEnvValue.trim() === '') { - logger.error('Email sending is not disabled and no website base was set. Set website_base in config.json or the PN_ACT_CONFIG_WEBSITE_BASE environment variable'); - process.exit(0); + if (!websiteBaseCheck || websiteBaseCheck.trim() === '') { + if (usingEnv) { + logger.error('Email sending is enabled and no website base was configured. Set the PN_ACT_CONFIG_WEBSITE_BASE environment variable'); } else { - logger.info('website_base not found in config, using environment variable PN_ACT_CONFIG_WEBSITE_BASE'); - - set(config, 'website_base', websiteBaseEnvValue); + logger.error('Email sending is enabled and no website base was configured. Set website_base in your config.json'); } + + process.exit(0); } } - const captchaSecretConfigValue = get(config, 'hcaptcha.secret'); - const captchaSecretEnvValue = get(process.env, 'PN_ACT_CONFIG_HCAPTCHA_SECRET'); + const captchaSecretCheck = get(config, 'hcaptcha.secret'); - if (!captchaSecretConfigValue || captchaSecretConfigValue.trim() === '') { - if (!captchaSecretEnvValue || captchaSecretEnvValue.trim() === '') { - logger.warn('Failed to find captcha secret config. Disabling feature. To enable feature set hcaptcha.secret in config.json or the PN_ACT_CONFIG_HCAPTCHA_SECRET environment variable'); - - disabledFeatures.email = true; + if (!captchaSecretCheck || captchaSecretCheck.trim() === '') { + if (usingEnv) { + logger.warn('Failed to find captcha secret config. Disabling feature. To enable feature set the PN_ACT_CONFIG_HCAPTCHA_SECRET environment variable'); } else { - logger.info('hcaptcha.secret not found in config, using environment variable PN_ACT_CONFIG_HCAPTCHA_SECRET'); - - set(config, 'hcaptcha.secret', captchaSecretEnvValue); + logger.warn('Failed to find captcha secret config. Disabling feature. To enable feature set hcaptcha.secret in your config.json'); } + + disabledFeatures.captcha = true; } - const s3EndpointConfigValue = get(config, 's3.endpoint'); - const s3EndpointEnvValue = get(process.env, 'PN_ACT_CONFIG_S3_ENDPOINT'); + const s3EndpointCheck = get(config, 's3.endpoint'); - if (!s3EndpointConfigValue || s3EndpointConfigValue.trim() === '') { - if (!s3EndpointEnvValue || s3EndpointEnvValue.trim() === '') { - logger.warn('Failed to find s3 endpoint config. Disabling feature. To enable feature set s3.endpoint in config.json or the PN_ACT_CONFIG_S3_ENDPOINT environment variable'); - - disabledFeatures.s3 = true; + if (!s3EndpointCheck || s3EndpointCheck.trim() === '') { + if (usingEnv) { + logger.warn('Failed to find s3 endpoint config. Disabling feature. To enable feature set the PN_ACT_CONFIG_S3_ENDPOINT environment variable'); } else { - logger.info('s3.endpoint not found in config, using environment variable PN_ACT_CONFIG_S3_ENDPOINT'); - - set(config, 's3.endpoint', s3EndpointEnvValue); + logger.warn('Failed to find s3 endpoint config. Disabling feature. To enable feature set s3.endpoint in your config.json'); } + + disabledFeatures.s3 = true; + } else { } - const s3AccessKeyConfigValue = get(config, 's3.key'); - const s3AccessKeyEnvValue = get(process.env, 'PN_ACT_CONFIG_S3_ACCESS_KEY'); + const s3AccessKeyCheck = get(config, 's3.key'); - if (!s3AccessKeyConfigValue || s3AccessKeyConfigValue.trim() === '') { - if (!s3AccessKeyEnvValue || s3AccessKeyEnvValue.trim() === '') { - logger.warn('Failed to find s3 access key config. Disabling feature. To enable feature set s3.key in config.json or the PN_ACT_CONFIG_S3_ACCESS_KEY environment variable'); - - disabledFeatures.s3 = true; + if (!s3AccessKeyCheck || s3AccessKeyCheck.trim() === '') { + if (usingEnv) { + logger.warn('Failed to find s3 access key config. Disabling feature. To enable feature set the PN_ACT_CONFIG_S3_ACCESS_KEY environment variable'); } else { - logger.info('s3.key not found in config, using environment variable PN_ACT_CONFIG_S3_ACCESS_KEY'); - - set(config, 's3.key', s3AccessKeyEnvValue); + logger.warn('Failed to find s3 access key config. Disabling feature. To enable feature set s3.key in your config.json'); } + + disabledFeatures.s3 = true; } - const s3SecretKeyConfigValue = get(config, 's3.secret'); - const s3SecretKeyEnvValue = get(process.env, 'PN_ACT_CONFIG_S3_ACCESS_SECRET'); + const s3SecretKeyCheck = get(config, 's3.secret'); - if (!s3SecretKeyConfigValue || s3SecretKeyConfigValue.trim() === '') { - if (!s3SecretKeyEnvValue || s3SecretKeyEnvValue.trim() === '') { - logger.warn('Failed to find s3 secret key config. Disabling feature. To enable feature set s3.secret in config.json or the PN_ACT_CONFIG_S3_ACCESS_SECRET environment variable'); - - disabledFeatures.s3 = true; + if (!s3SecretKeyCheck || s3SecretKeyCheck.trim() === '') { + if (usingEnv) { + logger.warn('Failed to find s3 secret key config. Disabling feature. To enable feature set the PN_ACT_CONFIG_S3_ACCESS_SECRET environment variable'); } else { - logger.info('s3.secret not found in config, using environment variable PN_ACT_CONFIG_S3_ACCESS_SECRET'); - - set(config, 's3.secret', s3AccessKeyEnvValue); + logger.warn('Failed to find s3 secret key config. Disabling feature. To enable feature set s3.secret in your config.json'); } + + disabledFeatures.s3 = true; } if (disabledFeatures.s3) { - const cdnSubdomainConfigValue = get(config, 'cdn_subdomain'); - const cdnSubdomainEnvValue = get(process.env, 'PN_ACT_CONFIG_CDN_SUBDOMAIN'); + const cdnSubdomainCheck = get(config, 'cdn_subdomain'); - if (!cdnSubdomainConfigValue || cdnSubdomainConfigValue.trim() === '') { - if (!cdnSubdomainEnvValue || cdnSubdomainEnvValue.trim() === '') { - logger.error('s3 file storage is disabled and no CDN subdomain was set. Set cdn_subdomain in config.json or the PN_ACT_CONFIG_CDN_SUBDOMAIN environment variable'); - process.exit(0); + if (!cdnSubdomainCheck || cdnSubdomainCheck.trim() === '') { + if (usingEnv) { + logger.error('s3 file storage is disabled and no CDN subdomain was set. Set the PN_ACT_CONFIG_CDN_SUBDOMAIN environment variable'); } else { - logger.info('cdn_subdomain not found in config, using environment variable PN_ACT_CONFIG_CDN_SUBDOMAIN'); - - set(config, 'cdn_subdomain', cdnSubdomainEnvValue); + logger.error('s3 file storage is disabled and no CDN subdomain was set. Set cdn_subdomain in your config.json'); } + + process.exit(0); } if (disabledFeatures.redis) { From 20da652b38428d94d1e8965cf591f3ff199cbcb0 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 14 Oct 2022 07:03:46 -0400 Subject: [PATCH 22/22] Made CDN disk path configurable, updated CDN config, updated examples --- example.config.json | 6 +++++- example.env | 3 ++- src/config-manager.js | 20 ++++++++++++------- src/database.js | 4 ++-- src/services/api/routes/v1/user.js | 4 ++-- src/services/local-cdn/index.js | 4 ++-- src/services/nnid/routes/miis.js | 32 +++++++++++++++--------------- src/util.js | 2 +- 8 files changed, 43 insertions(+), 32 deletions(-) diff --git a/example.config.json b/example.config.json index 9c1f457..b8a86b5 100644 --- a/example.config.json +++ b/example.config.json @@ -32,6 +32,10 @@ "hcaptcha": { "secret": "0x0000000000000000000000000000000000000000" }, - "cdn_base": "https://example.com", + "cdn": { + "base_url": "https://local-cdn.example.com", + "subdomain": "local-cdn", + "disk_path": "/home/jon/pretend-cdn" + }, "website_base": "https://example.com" } \ No newline at end of file diff --git a/example.env b/example.env index 57ff506..84a692a 100644 --- a/example.env +++ b/example.env @@ -15,6 +15,7 @@ PN_ACT_CONFIG_S3_ENDPOINT=nyc3.digitaloceanspaces.com PN_ACT_CONFIG_S3_ACCESS_KEY=ACCESS_KEY PN_ACT_CONFIG_S3_ACCESS_SECRET=ACCESS_SECRET PN_ACT_CONFIG_HCAPTCHA_SECRET=0x0000000000000000000000000000000000000000 -PN_ACT_CONFIG_CDN_BASE=https://local-cdn.example.com +PN_ACT_CONFIG_CDN_BASE_URL=https://local-cdn.example.com PN_ACT_CONFIG_CDN_SUBDOMAIN=local-cdn +PN_ACT_CONFIG_CDN_DISK_PATH=/home/jon/pretend-cdn PN_ACT_CONFIG_WEBSITE_BASE=https://example.com \ No newline at end of file diff --git a/src/config-manager.js b/src/config-manager.js index f5c6b85..3cc79e8 100644 --- a/src/config-manager.js +++ b/src/config-manager.js @@ -30,8 +30,10 @@ require('dotenv').config(); * @property {string} [s3.secret] s3 access secret * @property {object} [hcaptcha] hCaptcha settings * @property {string} [hcaptcha.secret] hCaptcha secret - * @property {string} [cdn_subdomain] Subdomain used for serving CDN contents when s3 is disabled - * @property {string} cdn_base Base URL for CDN location + * @property {object} cdn CDN config settings + * @property {object} [cdn.subdomain] Subdomain used for serving CDN contents when s3 is disabled + * @property {string} [cdn.disk_path] Fully qualified file system path for storing and reading local CDN contents + * @property {string} cdn.base_url Base URL for CDN server * @property {string} website_base Base URL for service website (used with emails) */ @@ -63,7 +65,7 @@ const requiredFields = [ ['http.port', 'PN_ACT_CONFIG_HTTP_PORT', Number], ['mongoose.uri', 'PN_ACT_CONFIG_MONGO_URI'], ['mongoose.database', 'PN_ACT_CONFIG_MONGO_DB_NAME'], - ['cdn_base', 'PN_ACT_CONFIG_CDN_BASE'] + ['cdn.base_url', 'PN_ACT_CONFIG_CDN_BASE_URL'] ]; function configure() { @@ -109,7 +111,11 @@ function configure() { hcaptcha: { secret: process.env.PN_ACT_CONFIG_HCAPTCHA_SECRET }, - cdn_base: process.env.PN_ACT_CONFIG_CDN_BASE, + cdn: { + subdomain: process.env.PN_ACT_CONFIG_CDN_BASE, + disk_path: process.env.PN_ACT_CONFIG_CDN_BASE, + base_url: process.env.PN_ACT_CONFIG_CDN_BASE + }, website_base: process.env.PN_ACT_CONFIG_WEBSITE_BASE }; } else { @@ -304,13 +310,13 @@ function configure() { } if (disabledFeatures.s3) { - const cdnSubdomainCheck = get(config, 'cdn_subdomain'); + const cdnSubdomainCheck = get(config, 'cdn.subdomain'); if (!cdnSubdomainCheck || cdnSubdomainCheck.trim() === '') { if (usingEnv) { logger.error('s3 file storage is disabled and no CDN subdomain was set. Set the PN_ACT_CONFIG_CDN_SUBDOMAIN environment variable'); } else { - logger.error('s3 file storage is disabled and no CDN subdomain was set. Set cdn_subdomain in your config.json'); + logger.error('s3 file storage is disabled and no CDN subdomain was set. Set cdn.subdomain in your config.json'); } process.exit(0); @@ -320,7 +326,7 @@ function configure() { logger.warn('Both s3 and Redis are disabled. Large CDN files will use the in-memory cache, which may result in high memory use. Please enable s3 if you\'re running a production server.'); } - logger.warn(`s3 file storage disabled. Using disk-based file storage. Please ensure cdn_base config or PN_ACT_CONFIG_CDN_BASE env variable is set to point to this server with the subdomain being ${config.cdn_subdomain}`); + logger.warn(`s3 file storage disabled. Using disk-based file storage. Please ensure cdn.base_url config or PN_ACT_CONFIG_CDN_BASE env variable is set to point to this server with the subdomain being ${config.cdn.subdomain}`); } module.exports.config = config; diff --git a/src/database.js b/src/database.js index fcb136e..860e05c 100644 --- a/src/database.js +++ b/src/database.js @@ -172,9 +172,9 @@ async function getUserProfileJSONByPID(pid) { mii_image: { // Images MUST be loaded over HTTPS or console ignores them // Bunny CDN is the only CDN which seems to support TLS 1.0/1.1 (required) - cached_url: `${config.cdn_base}/mii/${user.pid}/standard.tga`, + cached_url: `${config.cdn.base_url}/mii/${user.pid}/standard.tga`, id: user.get('mii.image_id'), - url: `${config.cdn_base}/mii/${user.pid}/standard.tga`, + url: `${config.cdn.base_url}/mii/${user.pid}/standard.tga`, type: 'standard' } }, diff --git a/src/services/api/routes/v1/user.js b/src/services/api/routes/v1/user.js index ee8ad4a..7008548 100644 --- a/src/services/api/routes/v1/user.js +++ b/src/services/api/routes/v1/user.js @@ -47,7 +47,7 @@ router.get('/', async (request, response) => { mii: { data: pnid.get('mii.data'), name: pnid.get('mii.name'), - image_url: `${config.cdn_base}/mii/${pnid.get('pid')}/normal_face.png` + image_url: `${config.cdn.base_url}/mii/${pnid.get('pid')}/normal_face.png` }, flags: { marketing: pnid.get('flags.marketing') @@ -111,7 +111,7 @@ router.post('/', async (request, response) => { mii: { data: pnid.get('mii.data'), name: pnid.get('mii.name'), - image_url: `${config.cdn_base}/mii/${pnid.get('pid')}/normal_face.png` + image_url: `${config.cdn.base_url}/mii/${pnid.get('pid')}/normal_face.png` }, flags: { marketing: pnid.get('flags.marketing') diff --git a/src/services/local-cdn/index.js b/src/services/local-cdn/index.js index 1a0a9dc..b0e3985 100644 --- a/src/services/local-cdn/index.js +++ b/src/services/local-cdn/index.js @@ -24,7 +24,7 @@ localcdn.use(routes.GET); const router = express.Router(); // Create subdomains -logger.info(`[LOCAL-CDN] Creating '${config.cdn_subdomain}' subdomain`); -router.use(subdomain(config.cdn_subdomain, localcdn)); +logger.info(`[LOCAL-CDN] Creating '${config.cdn.subdomain}' subdomain`); +router.use(subdomain(config.cdn.subdomain, localcdn)); module.exports = router; \ No newline at end of file diff --git a/src/services/nnid/routes/miis.js b/src/services/nnid/routes/miis.js index 82ff0d6..3c5603c 100644 --- a/src/services/nnid/routes/miis.js +++ b/src/services/nnid/routes/miis.js @@ -20,51 +20,51 @@ router.get('/', async (request, response) => { const miiImages = [ { - cached_url: `${config.cdn_base}/mii/${user.pid}/normal_face.png`, + cached_url: `${config.cdn.base_url}/mii/${user.pid}/normal_face.png`, id: mii.id, - url: `${config.cdn_base}/mii/${user.pid}/normal_face.png`, + url: `${config.cdn.base_url}/mii/${user.pid}/normal_face.png`, type: 'standard' }, { - cached_url: `${config.cdn_base}/mii/${user.pid}/frustrated.png`, + cached_url: `${config.cdn.base_url}/mii/${user.pid}/frustrated.png`, id: mii.id, - url: `${config.cdn_base}/mii/${user.pid}/frustrated.png`, + url: `${config.cdn.base_url}/mii/${user.pid}/frustrated.png`, type: 'frustrated_face' }, { - cached_url: `${config.cdn_base}/mii/${user.pid}/smile_open_mouth.png`, + cached_url: `${config.cdn.base_url}/mii/${user.pid}/smile_open_mouth.png`, id: mii.id, - url: `${config.cdn_base}/mii/${user.pid}/smile_open_mouth.png`, + url: `${config.cdn.base_url}/mii/${user.pid}/smile_open_mouth.png`, type: 'happy_face' }, { - cached_url: `${config.cdn_base}/mii/${user.pid}/wink_left.png`, + cached_url: `${config.cdn.base_url}/mii/${user.pid}/wink_left.png`, id: mii.id, - url: `${config.cdn_base}/mii/${user.pid}/wink_left.png`, + url: `${config.cdn.base_url}/mii/${user.pid}/wink_left.png`, type: 'like_face' }, { - cached_url: `${config.cdn_base}/mii/${user.pid}/normal_face.png`, + cached_url: `${config.cdn.base_url}/mii/${user.pid}/normal_face.png`, id: mii.id, - url: `${config.cdn_base}/mii/${user.pid}/normal_face.png`, + url: `${config.cdn.base_url}/mii/${user.pid}/normal_face.png`, type: 'normal_face' }, { - cached_url: `${config.cdn_base}/mii/${user.pid}/sorrow.png`, + cached_url: `${config.cdn.base_url}/mii/${user.pid}/sorrow.png`, id: mii.id, - url: `${config.cdn_base}/mii/${user.pid}/sorrow.png`, + url: `${config.cdn.base_url}/mii/${user.pid}/sorrow.png`, type: 'puzzled_face' }, { - cached_url: `${config.cdn_base}/mii/${user.pid}/surprised_open_mouth.png`, + cached_url: `${config.cdn.base_url}/mii/${user.pid}/surprised_open_mouth.png`, id: mii.id, - url: `${config.cdn_base}/mii/${user.pid}/surprised_open_mouth.png`, + url: `${config.cdn.base_url}/mii/${user.pid}/surprised_open_mouth.png`, type: 'surprised_face' }, { - cached_url: `${config.cdn_base}/mii/${user.pid}/body.png`, + cached_url: `${config.cdn.base_url}/mii/${user.pid}/body.png`, id: mii.id, - url: `${config.cdn_base}/mii/${user.pid}/body.png`, + url: `${config.cdn.base_url}/mii/${user.pid}/body.png`, type: 'whole_body' } ]; diff --git a/src/util.js b/src/util.js index 25b4ba6..5550a70 100644 --- a/src/util.js +++ b/src/util.js @@ -229,7 +229,7 @@ async function uploadCDNAsset(bucket, key, data, acl) { } async function writeLocalCDNFile(key, data) { - const filePath = `${__dirname}/../cdn/${key}`; + const filePath = config.cdn.disk_path; const folder = path.dirname(filePath); await fs.ensureDir(folder);