diff --git a/src/app.ts b/src/app.ts index d3946bbd8..b5a5671a6 100644 --- a/src/app.ts +++ b/src/app.ts @@ -20,6 +20,7 @@ import socket, { Server as SocketServer } from 'socket.io'; import createProxyMiddleware from 'http-proxy-middleware'; import morgan from 'morgan'; +import { config } from './config'; import { server as socketServer } from './sockets/sockets'; import User from './models/user'; import { emailVerified, isLoggedIn } from './routes/middleware'; @@ -38,16 +39,6 @@ import { SESSIONS_COLLECTION_NAME } from './constants'; const assetsPath = path.join(__dirname, '../assets'); -// Die if env var isn't given in -if ( - process.env.ENV !== 'local' && - process.env.ENV !== 'staging' && - process.env.ENV !== 'prod' -) { - console.error('Bad environment variable given.'); - process.exit(1); -} - const app = express(); app.use(compression()); app.use(express.static(assetsPath, { maxAge: 518400000 })); // expires in 7 days. @@ -84,7 +75,7 @@ app.use( ), ); -if (process.env.ENV === 'local') { +if (config.ENV === 'local') { console.log('Routing dist_webpack to localhost:3010.'); app.use( '/dist_webpack', @@ -92,8 +83,8 @@ if (process.env.ENV === 'local') { ); } -const port = process.env.PORT || 3000; -const dbLoc = process.env.DATABASEURL; +const port = config.PORT; +const dbLoc = config.DATABASE_URL; console.log(`Using database url: ${dbLoc}`); mongoose.connect(dbLoc, { @@ -139,7 +130,7 @@ process }); // authentication -const secretKey = process.env.MY_SECRET_KEY || 'MySecretKey'; +const secretKey = config.MY_SECRET_KEY; app.use( session({ secret: secretKey, @@ -192,7 +183,7 @@ app.use('/lobby', lobbyRoutes); app.use('/forum', forumRoutes); app.use('/profile', profileRoutes); -const IP = process.env.IP || '127.0.0.1'; +const IP = config.IP; const server = app.listen(port, () => { console.log(`Server has started on ${IP}:${port}!`); }); diff --git a/src/clients/discord/index.ts b/src/clients/discord/index.ts index 3a86f4a2a..c7da171e6 100644 --- a/src/clients/discord/index.ts +++ b/src/clients/discord/index.ts @@ -1,37 +1,39 @@ import Discord, { TextChannel } from 'discord.js'; +import { config } from '../../config'; + const client = new Discord.Client(); -if (process.env.ENV === 'prod') { - client.login(process.env.discord_bot_token); +if (config.ENV === 'prod') { + client.login(config.discord.BOT_TOKEN); } export function sendToDiscordAdmins(message: string, ping?: boolean): void { if (ping) { message = `${getAdminPing()} ${message}`; } - sendToChannel(message, process.env.discord_admin_channel_id); + sendToChannel(message, config.discord.ADMIN_CHANNEL_ID); } export function sendToDiscordMods(message: string, ping?: boolean): void { if (ping) { message = `${getModPing()} ${message}`; } - sendToChannel(message, process.env.discord_mod_channel_id); + sendToChannel(message, config.discord.MOD_CHANNEL_ID); } function sendToChannel(message: string, channelId: string): void { const channel = client.channels.cache.get(channelId) as TextChannel; - if (process.env.ENV === 'prod') { + if (config.ENV === 'prod') { channel.send(message); } } function getAdminPing(): string { - return `<@&${process.env.discord_admin_role_id}>`; + return `<@&${config.discord.ADMIN_ROLE_ID}>`; } function getModPing(): string { - return `<@&${process.env.discord_mod_role_id}>`; + return `<@&${config.discord.MOD_ROLE_ID}>`; } diff --git a/src/clients/patreon/patreonController.ts b/src/clients/patreon/patreonController.ts index 9123cfec1..27524e484 100644 --- a/src/clients/patreon/patreonController.ts +++ b/src/clients/patreon/patreonController.ts @@ -1,9 +1,11 @@ +import uuid from 'uuid'; + +import { config } from '../../config'; import { IPatreonController, PatreonUserTokens, PaidPatronFullDetails, } from './patreonAgent'; -import uuid from 'uuid'; const PATREON_URLS = { AUTHORIZATION_LINK: 'https://www.patreon.com/oauth2/authorize', @@ -12,9 +14,9 @@ const PATREON_URLS = { }; export class PatreonController implements IPatreonController { - private clientId = process.env.patreon_client_ID; - private clientSecret = process.env.patreon_client_secret; - private redirectUri = process.env.patreon_redirectURL; + private clientId = config.patreon.CLIENT_ID; + private clientSecret = config.patreon.CLIENT_SECRET; + private redirectUri = config.patreon.REDIRECT_URL; public async getPatreonUserTokens(code: string): Promise { const getPatreonUserTokensUrl = new URL(PATREON_URLS.GET_TOKENS); diff --git a/src/clients/s3/S3Controller.ts b/src/clients/s3/S3Controller.ts index c7c1de3ee..217837f57 100644 --- a/src/clients/s3/S3Controller.ts +++ b/src/clients/s3/S3Controller.ts @@ -7,6 +7,8 @@ import { PutObjectCommand, S3Client, } from '@aws-sdk/client-s3'; + +import { config } from '../../config'; import { IS3Controller } from './S3Agent'; export default class S3Controller implements IS3Controller { @@ -15,12 +17,12 @@ export default class S3Controller implements IS3Controller { private bucket: string; constructor() { - this.publicFileLinkPrefix = process.env.S3_PUBLIC_FILE_LINK_PREFIX; - this.bucket = process.env.S3_BUCKET_NAME; + this.publicFileLinkPrefix = config.s3.PUBLIC_FILE_LINK_PREFIX; + this.bucket = config.s3.BUCKET_NAME; this.client = new S3Client({ - region: process.env.S3_REGION, - endpoint: process.env.S3_ENDPOINT, + region: config.s3.REGION, + endpoint: config.s3.ENDPOINT, credentials: fromEnv(), }); } diff --git a/src/config.ts b/src/config.ts deleted file mode 100644 index 2c47f585c..000000000 --- a/src/config.ts +++ /dev/null @@ -1,22 +0,0 @@ -const VALID_ENVIRONMENTS = ['local', 'staging', 'prod']; -if (!VALID_ENVIRONMENTS.includes(process.env.ENV)) { - throw new Error(`Invalid settings: ENV=${process.env.ENV}`); -} - -if ( - process.env.ENV === 'staging' && - process.env.S3_BUCKET_NAME !== 'proavalon-staging' -) { - throw new Error( - `Invalid settings: ENV=staging S3_BUCKET_NAME=${process.env.S3_BUCKET_NAME}`, - ); -} - -if ( - process.env.ENV === 'prod' && - process.env.S3_BUCKET_NAME !== 'proavalon' -) { - throw new Error( - `Invalid settings: ENV=prod S3_BUCKET_NAME=${process.env.S3_BUCKET_NAME}`, - ); -} diff --git a/src/config/discordConfig.ts b/src/config/discordConfig.ts new file mode 100644 index 000000000..9b4455119 --- /dev/null +++ b/src/config/discordConfig.ts @@ -0,0 +1,17 @@ +import { getRequiredEnvVariable, getRequiredProdEnvVariable } from './utils'; + +export type DiscordConfigType = { + BOT_TOKEN?: string; + ADMIN_CHANNEL_ID?: string; + MOD_CHANNEL_ID?: string; + ADMIN_ROLE_ID?: string; + MOD_ROLE_ID?: string; +}; + +export const DiscordConfig: Readonly = Object.freeze({ + BOT_TOKEN: getRequiredProdEnvVariable('discord_bot_token'), + ADMIN_CHANNEL_ID: getRequiredProdEnvVariable('discord_admin_channel_id'), + MOD_CHANNEL_ID: getRequiredProdEnvVariable('discord_mod_channel_id'), + ADMIN_ROLE_ID: getRequiredProdEnvVariable('discord_admin_role_id'), + MOD_ROLE_ID: getRequiredProdEnvVariable('discord_mod_role_id'), +}); diff --git a/src/config/emailConfig.ts b/src/config/emailConfig.ts new file mode 100644 index 000000000..b6f471306 --- /dev/null +++ b/src/config/emailConfig.ts @@ -0,0 +1,17 @@ +import { getRequiredProdEnvVariable } from './utils'; + +export type EmailConfigType = { + PROAVALON_EMAIL_ADDRESS_DOMAIN?: string; + PROAVALON_EMAIL_ADDRESS?: string; + MAILGUN_API_KEY?: string; +}; + +export const EmailConfig: Readonly = Object.freeze({ + PROAVALON_EMAIL_ADDRESS_DOMAIN: getRequiredProdEnvVariable( + 'PROAVALON_EMAIL_ADDRESS_DOMAIN', + ), + PROAVALON_EMAIL_ADDRESS: getRequiredProdEnvVariable( + 'PROAVALON_EMAIL_ADDRESS', + ), + MAILGUN_API_KEY: getRequiredProdEnvVariable('MAILGUN_API_KEY'), +}); diff --git a/src/config/index.ts b/src/config/index.ts new file mode 100644 index 000000000..555d8d781 --- /dev/null +++ b/src/config/index.ts @@ -0,0 +1,72 @@ +import { PatreonConfig, PatreonConfigType } from './patreonConfig'; +import { DiscordConfig, DiscordConfigType } from './discordConfig'; +import { S3Config, S3ConfigType } from './s3Config'; +import { EmailConfig, EmailConfigType } from './emailConfig'; +import { VpnConfig, VpnConfigType } from './vpnConfig'; +import { getRequiredEnvVariable } from './utils'; + +type ConfigNew = { + ENV: string; + NODE_ENV?: string; + SERVER_DOMAIN?: string; + PORT?: number; + IP?: string; + MY_SECRET_KEY: string; + + GOOGLE_CAPTCHA_KEY?: string; + DATABASE_URL: string; + + discord: DiscordConfigType; + email: EmailConfigType; + patreon: PatreonConfigType; + s3: S3ConfigType; + vpn: VpnConfigType; +}; + +export const config: Readonly = Object.freeze({ + ENV: validateEnv(), + NODE_ENV: process.env.NODE_ENV, + SERVER_DOMAIN: process.env.SERVER_DOMAIN, + PORT: validatePort(), + IP: process.env.IP || '127.0.0.1', + MY_SECRET_KEY: process.env.MY_SECRET_KEY || 'MySecretKey', + + GOOGLE_CAPTCHA_KEY: process.env.MY_SECRET_GOOGLE_CAPTCHA_KEY, + DATABASE_URL: getRequiredEnvVariable('DATABASEURL'), // TODO-kev: Consider renaming env variable + + discord: DiscordConfig, + email: EmailConfig, + patreon: PatreonConfig, + s3: S3Config, + vpn: VpnConfig, +}); + +function validateEnv(): string { + const VALID_ENVIRONMENTS: Set = new Set(['local', 'staging', 'prod']); + const ENV = getRequiredEnvVariable('ENV'); + + if (process.env.NODE_ENV !== 'test') { + if (!VALID_ENVIRONMENTS.has(ENV)) { + console.error(`Bad environment variable given: ${ENV}`); + process.exit(1); + } + } + + return ENV; +} + +function validatePort(): number { + if (!process.env.PORT) { + return 3000; + } + + const port = parseInt(process.env.PORT, 10); + + if (isNaN(port) || port < 1 || port > 65535) { + console.error( + `Invalid port number: ${port}. Port must be between 1 and 65535.`, + ); + } + + return port; +} diff --git a/src/config/patreonConfig.ts b/src/config/patreonConfig.ts new file mode 100644 index 000000000..d4b5a6570 --- /dev/null +++ b/src/config/patreonConfig.ts @@ -0,0 +1,13 @@ +import { getRequiredProdEnvVariable } from './utils'; + +export type PatreonConfigType = { + CLIENT_ID?: string; + CLIENT_SECRET?: string; + REDIRECT_URL?: string; +}; + +export const PatreonConfig: Readonly = Object.freeze({ + CLIENT_ID: getRequiredProdEnvVariable('patreon_client_ID'), + CLIENT_SECRET: getRequiredProdEnvVariable('patreon_client_secret'), + REDIRECT_URL: getRequiredProdEnvVariable('patreon_redirectURL'), +}); diff --git a/src/config/s3Config.ts b/src/config/s3Config.ts new file mode 100644 index 000000000..225c99207 --- /dev/null +++ b/src/config/s3Config.ts @@ -0,0 +1,31 @@ +import { getRequiredEnvVariable } from './utils'; + +export type S3ConfigType = { + PUBLIC_FILE_LINK_PREFIX: string; + BUCKET_NAME: string; + REGION: string; + ENDPOINT: string; +}; + +export const S3Config: Readonly = Object.freeze({ + PUBLIC_FILE_LINK_PREFIX: getRequiredEnvVariable('S3_PUBLIC_FILE_LINK_PREFIX'), + BUCKET_NAME: validateBucketName(), + REGION: getRequiredEnvVariable('S3_REGION'), + ENDPOINT: getRequiredEnvVariable('S3_ENDPOINT'), +}); + +function validateBucketName() { + const S3_BUCKET_NAME = getRequiredEnvVariable('S3_BUCKET_NAME'); + + if ( + (process.env.ENV === 'prod' && S3_BUCKET_NAME !== `proavalon`) || + (process.env.ENV === 'staging' && S3_BUCKET_NAME !== `proavalon-staging`) + ) { + console.error( + `Invalid env variables: ENV=${process.env.ENV} S3_BUCKET_NAME=${S3_BUCKET_NAME}`, + ); + process.exit(1); + } + + return S3_BUCKET_NAME; +} diff --git a/src/config/utils.ts b/src/config/utils.ts new file mode 100644 index 000000000..05ca12729 --- /dev/null +++ b/src/config/utils.ts @@ -0,0 +1,19 @@ +export function getRequiredProdEnvVariable(variableName: string) { + return process.env.ENV === `prod` + ? getRequiredEnvVariable(variableName) + : process.env[variableName]; +} + +export function getRequiredEnvVariable(variableName: string) { + const envVariable = process.env[variableName]; + + if ( + process.env.NODE_ENV !== 'test' && + (envVariable === undefined || envVariable === '') + ) { + console.error(`Missing required environment variable: ${variableName}`); + process.exit(1); + } + + return envVariable; +} diff --git a/src/config/vpnConfig.ts b/src/config/vpnConfig.ts new file mode 100644 index 000000000..30774bbf3 --- /dev/null +++ b/src/config/vpnConfig.ts @@ -0,0 +1,13 @@ +import { getRequiredProdEnvVariable } from './utils'; + +export type VpnConfigType = { + VPN_DETECTION_TOKEN?: string; + WHITELISTED_VPN_USERNAMES?: string; +}; + +export const VpnConfig: Readonly = Object.freeze({ + VPN_DETECTION_TOKEN: getRequiredProdEnvVariable('VPN_DETECTION_TOKEN'), + WHITELISTED_VPN_USERNAMES: getRequiredProdEnvVariable( + 'WHITELISTED_VPN_USERNAMES', + ), +}); diff --git a/src/gameplay/game.ts b/src/gameplay/game.ts index 14bc4a30a..e69d8f353 100644 --- a/src/gameplay/game.ts +++ b/src/gameplay/game.ts @@ -1,6 +1,7 @@ // @ts-nocheck import _ from 'lodash'; +import { config } from '../config'; import Room, { RoomConfig } from './room'; import usernamesIndexes from '../myFunctions/usernamesIndexes'; import User from '../models/user'; @@ -128,7 +129,7 @@ class Game extends Room { constructor(gameConfig: GameConfig) { super(gameConfig.roomConfig); - // Expand config + // Expand configOld this.muteSpectators = gameConfig.muteSpectators; this.disableVoteHistory = gameConfig.disableVoteHistory; this.roomCreationType = gameConfig.roomCreationType; @@ -1561,7 +1562,7 @@ class Game extends Room { }); } - if (process.env.NODE_ENV !== 'test') { + if (config.NODE_ENV !== 'test') { this.playersInGame.forEach((player) => { User.findById(player.userId) .populate('notifications') diff --git a/src/gameplay/room.ts b/src/gameplay/room.ts index 542aea6a0..e20babb57 100644 --- a/src/gameplay/room.ts +++ b/src/gameplay/room.ts @@ -79,7 +79,7 @@ class Room { readyPrompt: ReadyPrompt; constructor(roomConfig: RoomConfig) { - // Expand config + // Expand configOld this.host = roomConfig.host; this.roomId = roomConfig.roomId; this.io = roomConfig.io; diff --git a/src/myFunctions/sendEmail.ts b/src/myFunctions/sendEmail.ts index 664c6ba2f..9c6a2aeea 100644 --- a/src/myFunctions/sendEmail.ts +++ b/src/myFunctions/sendEmail.ts @@ -1,8 +1,10 @@ import Mailgun from 'mailgun.js'; import formData from 'form-data'; -const api_key = process.env.MAILGUN_API_KEY; -const domain = process.env.PROAVALON_EMAIL_ADDRESS_DOMAIN; +import { config } from '../config'; + +const api_key = config.email.MAILGUN_API_KEY; +const domain = config.email.PROAVALON_EMAIL_ADDRESS_DOMAIN; const mailgun = new Mailgun(formData); const mg = mailgun.client({ username: 'api', key: api_key }); @@ -13,7 +15,7 @@ export const sendEmail = ( messageHtml: string, ) => { const data = { - from: 'ProAvalon <' + process.env.PROAVALON_EMAIL_ADDRESS + '>', + from: 'ProAvalon <' + config.email.PROAVALON_EMAIL_ADDRESS + '>', to: recipientEmail, subject: subject, html: messageHtml, diff --git a/src/myFunctions/sendEmailVerification.ts b/src/myFunctions/sendEmailVerification.ts index 4ffbf8b77..5a7b36b1d 100644 --- a/src/myFunctions/sendEmailVerification.ts +++ b/src/myFunctions/sendEmailVerification.ts @@ -1,10 +1,12 @@ import ejs from 'ejs'; -import emailTemplateEmailVerification from './emailTemplateEmailVerification'; import uuid from 'uuid'; + +import { config } from '../config'; +import emailTemplateEmailVerification from './emailTemplateEmailVerification'; import disposableEmails from '../util/disposableEmails.js'; import { sendEmail } from './sendEmail'; -const serverDomain = process.env.SERVER_DOMAIN; +const serverDomain = config.SERVER_DOMAIN; const uuidv4 = uuid.v4; diff --git a/src/myFunctions/sendResetPassword.ts b/src/myFunctions/sendResetPassword.ts index 639741083..f2f9063c3 100644 --- a/src/myFunctions/sendResetPassword.ts +++ b/src/myFunctions/sendResetPassword.ts @@ -1,11 +1,12 @@ import uuid from 'uuid'; import ejs from 'ejs'; + +import { config } from '../config'; import emailTemplateResetPassword from './emailTemplateResetPassword'; import { sendEmail } from './sendEmail'; const TOKEN_TIMEOUT = 60 * 60 * 1000; // 1 hour - -const serverDomain = process.env.SERVER_DOMAIN; +const serverDomain = config.SERVER_DOMAIN; export const sendResetPassword = async (user: any, email: string) => { const token = uuid.v4(); diff --git a/src/routes/forum/forumThreadCommentReplyRoutes.js b/src/routes/forum/forumThreadCommentReplyRoutes.js index 9166c329a..e60f14560 100644 --- a/src/routes/forum/forumThreadCommentReplyRoutes.js +++ b/src/routes/forum/forumThreadCommentReplyRoutes.js @@ -2,6 +2,8 @@ import { Router } from 'express'; import sanitizeHtml from 'sanitize-html'; import mongoose from 'mongoose'; import rateLimit from 'express-rate-limit'; + +import { config } from '../../config'; import forumThread from '../../models/forumThread'; import forumThreadComment from '../../models/forumThreadComment'; import forumThreadCommentReply from '../../models/forumThreadCommentReply'; @@ -38,7 +40,7 @@ const sanitizeHtmlAllowedAttributesForumThread = { }; const newReplyLimiter = - process.env.ENV === 'local' + config.ENV === 'local' ? rateLimit({ max: 0, // Disable if we are local }) diff --git a/src/routes/forum/forumThreadCommentRoutes.js b/src/routes/forum/forumThreadCommentRoutes.js index 2b7420c24..e7d95318e 100644 --- a/src/routes/forum/forumThreadCommentRoutes.js +++ b/src/routes/forum/forumThreadCommentRoutes.js @@ -2,6 +2,8 @@ import { Router } from 'express'; import sanitizeHtml from 'sanitize-html'; import mongoose from 'mongoose'; import rateLimit from 'express-rate-limit'; + +import { config } from '../../config'; import forumThread from '../../models/forumThread'; import forumThreadComment from '../../models/forumThreadComment'; import { @@ -16,7 +18,7 @@ import { userHasReward } from '../../rewards/getRewards'; const router = new Router(); const newCommentLimiter = - process.env.ENV === 'local' + config.ENV === 'local' ? rateLimit({ max: 0, // Disable if we are local }) diff --git a/src/routes/forum/forumThreadRoutes.js b/src/routes/forum/forumThreadRoutes.js index 3ff553618..557fef153 100644 --- a/src/routes/forum/forumThreadRoutes.js +++ b/src/routes/forum/forumThreadRoutes.js @@ -1,6 +1,8 @@ import { Router } from 'express'; import sanitizeHtml from 'sanitize-html'; import rateLimit from 'express-rate-limit'; + +import { config } from '../../config'; import { checkForumThreadOwnership, asyncMiddleware } from '../middleware'; import getTimeDiffInString from '../../util/getTimeDiffInString'; import lastIds from '../../models/lastIds'; @@ -128,7 +130,7 @@ lastIds.findOne({}).exec(async (err, returnedLastId) => { }); const newForumLimiter = - process.env.ENV === 'local' + config.ENV === 'local' ? rateLimit({ max: 0, // Disable if we are local }) diff --git a/src/routes/index.js b/src/routes/index.js index efd121793..8aa5a7bc0 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -4,6 +4,7 @@ import sanitizeHtml from 'sanitize-html'; import mongoose from 'mongoose'; import fs from 'fs'; import rateLimit from 'express-rate-limit'; + import User from '../models/user'; import myNotification from '../models/notification'; import gameRecord from '../models/gameRecord'; @@ -11,8 +12,9 @@ import statsCumulative from '../models/statsCumulative'; import { emailExists, validEmail } from '../routes/emailVerification'; import { sendEmailVerification } from '../myFunctions/sendEmailVerification'; +import { config } from '../config'; import { disallowVPNs } from '../util/vpnDetection'; -import Settings from '../settings'; +import { settingsSingleton } from '../settings'; import { Alliance } from '../gameplay/types'; import { resRoles, rolesToAlliances, spyRoles } from '../gameplay/roles/roles'; import { sendResetPassword } from '../myFunctions/sendResetPassword'; @@ -30,11 +32,11 @@ router.get('/', (req, res) => { // register route router.get('/register', (req, res) => { - res.render('register', { platform: process.env.ENV }); + res.render('register', { platform: config.ENV }); }); const registerLimiter = - process.env.ENV === 'local' + config.ENV === 'local' ? rateLimit({ max: 0, // Disable if we are local }) @@ -97,7 +99,7 @@ router.post( passport.authenticate('local')(req, res, () => { res.redirect('/lobby'); }); - if (process.env.ENV === 'prod') { + if (config.ENV === 'prod') { sendEmailVerification(user, req.body.emailAddress); } else { user.emailVerified = true; @@ -111,7 +113,7 @@ router.post( ); const loginLimiter = - process.env.ENV === 'local' + config.ENV === 'local' ? rateLimit({ max: 0, // Disable if we are local }) @@ -235,7 +237,7 @@ router.get('/statistics', (req, res) => { }); router.get('/resetPassword', (req, res) => { - res.render('resetPassword', { platform: process.env.ENV }); + res.render('resetPassword', { platform: config.ENV }); }); router.post( @@ -963,7 +965,7 @@ function sanitiseEmail(req, res, next) { } function disableRegistrationMiddleware(req, res, next) { - if (Settings.getDisableRegistration()) { + if (settingsSingleton.getDisableRegistration()) { req.flash( 'error', 'Registration is temporarily disabled. Please contact a moderator via discord if you would like to create an account.', diff --git a/src/routes/middleware.ts b/src/routes/middleware.ts index 1e864c9b9..2c44e1796 100644 --- a/src/routes/middleware.ts +++ b/src/routes/middleware.ts @@ -1,4 +1,7 @@ // @ts-nocheck +import { RequestHandler } from 'express'; + +import { config } from '../config'; import forumThread from '../models/forumThread'; import forumThreadComment from '../models/forumThreadComment'; import forumThreadCommentReply from '../models/forumThreadCommentReply'; @@ -7,8 +10,6 @@ import Ban from '../models/ban'; import { isMod } from '../modsadmins/mods'; import { isAdmin } from '../modsadmins/admins'; -import { RequestHandler } from 'express'; - // return a function that wraps an async middleware export const asyncMiddleware = (fn: RequestHandler): RequestHandler => @@ -221,7 +222,7 @@ export const isAdminMiddleware = (req, res, next) => { }; export const emailVerified = (req, res, next) => { - if (req.user.emailVerified === true || process.env.ENV != 'prod') { + if (req.user.emailVerified === true || config.ENV != 'prod') { next(); } else { res.redirect('/emailVerification'); diff --git a/src/routes/mod.js b/src/routes/mod.js index b7b014dcb..e026ea256 100644 --- a/src/routes/mod.js +++ b/src/routes/mod.js @@ -1,7 +1,9 @@ import React from 'react'; import { Router } from 'express'; import { renderToString } from 'react-dom/server'; +import { MongoClient } from 'mongodb'; +import { config } from '../config'; import { isModMiddleware } from './middleware'; import User from '../models/user'; import Ban from '../models/ban'; @@ -17,7 +19,6 @@ import { import ModLogComponent from '../views/components/mod/mod_log'; import ReportLog from '../views/components/mod/report'; -import { MongoClient } from 'mongodb'; import { SESSIONS_COLLECTION_NAME } from '../constants'; const router = new Router(); @@ -186,7 +187,7 @@ router.post('/ban', isModMiddleware, async (req, res) => { }); // Delete all the sessions associated with this username - const dbResult = await MongoClient.connect(process.env.DATABASEURL); + const dbResult = await MongoClient.connect(config.DATABASE_URL); const mySessions = dbResult.db().collection(SESSIONS_COLLECTION_NAME); const deleteResult = await mySessions.deleteMany({ 'session.usernameLower': banPlayerUsername.toLowerCase(), diff --git a/src/settings.ts b/src/settings.ts index 0617c4d45..a171518e7 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -3,15 +3,13 @@ class Settings { private disableRegistration = false; - getDisableRegistration(): boolean { + public getDisableRegistration(): boolean { return this.disableRegistration; } - toggleDisableRegistration() { + public toggleDisableRegistration() { this.disableRegistration = !this.disableRegistration; } } -const settingsSingleton = new Settings(); - -export default settingsSingleton; +export const settingsSingleton = new Settings(); diff --git a/src/sockets/commands/admin/acreatetestaccounts.ts b/src/sockets/commands/admin/acreatetestaccounts.ts index 33e10ea3d..105bd269f 100644 --- a/src/sockets/commands/admin/acreatetestaccounts.ts +++ b/src/sockets/commands/admin/acreatetestaccounts.ts @@ -1,3 +1,4 @@ +import { config } from '../../../config'; import { sendReplyToCommand } from '../../sockets'; import { SocketUser } from '../../types'; import User from '../../../models/user'; @@ -7,7 +8,7 @@ export const acreatetestaccounts: Command = { command: 'acreatetestaccounts', help: '/acreatetestaccounts: Creates test accounts: 1 to 10. Passwords are the username.', run: async (args: string[], socket: SocketUser) => { - if (process.env.ENV === 'prod') { + if (config.ENV === 'prod') { sendReplyToCommand(socket, 'Cannot create test accounts in prod.'); return; } diff --git a/src/sockets/commands/admin/asessions.ts b/src/sockets/commands/admin/asessions.ts index 04d6f50ba..c27f48dd7 100644 --- a/src/sockets/commands/admin/asessions.ts +++ b/src/sockets/commands/admin/asessions.ts @@ -1,7 +1,9 @@ +import { MongoClient } from 'mongodb'; + +import { config } from '../../../config'; import { sendReplyToCommand } from '../../sockets'; import { SocketUser } from '../../types'; import { Command } from '../types'; -import { MongoClient } from 'mongodb'; export const asessions: Command = { command: 'asessions', @@ -14,7 +16,7 @@ export const asessions: Command = { return; } - const dbResult = await MongoClient.connect(process.env.DATABASEURL); + const dbResult = await MongoClient.connect(config.DATABASE_URL); const mySessions = dbResult.db().collection('mySessions'); const entries = mySessions.find({ 'session.usernameLower': username }); diff --git a/src/sockets/commands/mod/mtempenableregistration.ts b/src/sockets/commands/mod/mtempenableregistration.ts index 5d37c4002..3ccf76a5a 100644 --- a/src/sockets/commands/mod/mtempenableregistration.ts +++ b/src/sockets/commands/mod/mtempenableregistration.ts @@ -1,11 +1,11 @@ import { Command } from '../types'; -import Settings from '../../../settings'; +import { settingsSingleton } from '../../../settings'; export const mtempenableregistration: Command = { command: 'mtempenableregistration', help: '/mtempenableregistration: Temporarily enables site registration for 5 minutes.', run: async (args, senderSocket) => { - const registrationStatus = Settings.getDisableRegistration(); + const registrationStatus = settingsSingleton.getDisableRegistration(); if (registrationStatus) { senderSocket.emit('messageCommandReturnStr', { @@ -17,7 +17,7 @@ export const mtempenableregistration: Command = { } // Enable registration - Settings.toggleDisableRegistration(); + settingsSingleton.toggleDisableRegistration(); senderSocket.emit('messageCommandReturnStr', { message: `Site registration was temporarily enabled for 5 minutes.`, @@ -27,10 +27,10 @@ export const mtempenableregistration: Command = { // Now disable registration after delay setTimeout(async () => { // Re-pull status in case it was already disabled separately. - const registrationStatus = Settings.getDisableRegistration(); + const registrationStatus = settingsSingleton.getDisableRegistration(); if (registrationStatus) { - Settings.toggleDisableRegistration(); + settingsSingleton.toggleDisableRegistration(); } }, 1000 * 60 * 5 /* 5 minutes */); }, diff --git a/src/sockets/commands/mod/mtoggleregistration.ts b/src/sockets/commands/mod/mtoggleregistration.ts index 6cb343565..2aa93de44 100644 --- a/src/sockets/commands/mod/mtoggleregistration.ts +++ b/src/sockets/commands/mod/mtoggleregistration.ts @@ -1,13 +1,13 @@ import { Command } from '../types'; -import Settings from '../../../settings'; +import { settingsSingleton } from '../../../settings'; export const mtoggleregistration: Command = { command: 'mtoggleregistration', help: '/mtoggleregistration: Toggles site registration.', run: async (args, senderSocket) => { - Settings.toggleDisableRegistration(); + settingsSingleton.toggleDisableRegistration(); - const descriptor = Settings.getDisableRegistration() + const descriptor = settingsSingleton.getDisableRegistration() ? 'disabled' : 'enabled'; diff --git a/src/sockets/sockets.ts b/src/sockets/sockets.ts index b64d31c70..53ea06587 100644 --- a/src/sockets/sockets.ts +++ b/src/sockets/sockets.ts @@ -2,6 +2,7 @@ import { Server as SocketServer, Socket } from 'socket.io'; import { SocketUser } from './types'; +import { config } from '../config'; import GameWrapper from '../gameplay/gameWrapper'; import savedGameObj from '../models/savedGame'; @@ -54,7 +55,7 @@ const matchmakingQueue = new MatchmakingQueue(matchFound); const joinQueueFilter = new JoinQueueFilter(() => new Date()); const readyPrompt = new ReadyPrompt(); -if (process.env.NODE_ENV !== 'test') { +if (config.NODE_ENV !== 'test') { setInterval(() => { chatSpamFilter.tick(); }, 1000); @@ -148,7 +149,7 @@ function deleteSaveGameFromDb(room) { } } -if (process.env.NODE_ENV !== 'test') { +if (config.NODE_ENV !== 'test') { setTimeout(async () => { let run = true; let i = 0; @@ -1954,7 +1955,7 @@ function joinQueue(): boolean { return false; } - if (process.env.ENV !== 'local') { + if (config.ENV !== 'local') { if (this.request.user.totalGamesPlayed < 3) { this.emit('allChatToClient', { message: 'You require 3 games to join the ranked queue.', diff --git a/src/util/captcha.ts b/src/util/captcha.ts index 3ce6303f1..6501bb076 100644 --- a/src/util/captcha.ts +++ b/src/util/captcha.ts @@ -1,8 +1,9 @@ import { RequestHandler } from 'express'; import axios from 'axios'; +import { config } from '../config'; export const captchaMiddleware: RequestHandler = async (req, res, next) => { - if (process.env.ENV !== 'prod') { + if (config.ENV !== 'prod') { return next(); } @@ -18,7 +19,7 @@ export const captchaMiddleware: RequestHandler = async (req, res, next) => { return; } - const secretKey = process.env.MY_SECRET_GOOGLE_CAPTCHA_KEY; + const secretKey = config.GOOGLE_CAPTCHA_KEY; const verifyUrl = `https://google.com/recaptcha/api/siteverify?secret=${secretKey}&response=${req.body.captcha}&remoteip=${req.connection.remoteAddress}`; const response = await axios.post(verifyUrl); diff --git a/src/util/disposableEmails.js b/src/util/disposableEmails.js index 466ec8a0d..5b519df6d 100644 --- a/src/util/disposableEmails.js +++ b/src/util/disposableEmails.js @@ -7461,7 +7461,7 @@ export default [ 'gamesportal.me', 'gamestips.ru', 'gamgling.com', - 'gamno.config.work', + 'gamno.configOld.work', 'gamora274ey.cf', 'gamora274ey.ga', 'gamora274ey.gq', diff --git a/src/util/vpnDetection.ts b/src/util/vpnDetection.ts index eb713ea68..f57ad12a6 100644 --- a/src/util/vpnDetection.ts +++ b/src/util/vpnDetection.ts @@ -1,10 +1,12 @@ import { RequestHandler } from 'express'; +import { config } from '../config'; const VPN_TIMEOUT = 1000 * 60 * 60 * 12; // 12 hours +// TODO-kev: check if this still works let whitelistedUsernames: string[] = []; -if (process.env.WHITELISTED_VPN_USERNAMES) { - whitelistedUsernames = process.env.WHITELISTED_VPN_USERNAMES.split(','); +if (config.vpn.WHITELISTED_VPN_USERNAMES) { + whitelistedUsernames = config.vpn.WHITELISTED_VPN_USERNAMES.split(','); } class VpnEntry { @@ -82,7 +84,7 @@ const isVPN = async (ip: string): Promise => { const isVpnCheck1 = async (ip: string): Promise => { const vpnResponse = await fetch( - `https://vpnapi.io/api/${ip}?key=${process.env.VPN_DETECTION_TOKEN}`, + `https://vpnapi.io/api/${ip}?key=${config.vpn.VPN_DETECTION_TOKEN}`, ); const data = await vpnResponse.json(); @@ -99,7 +101,7 @@ const isVpnCheck1 = async (ip: string): Promise => { const isVpnCheck2 = async (ip: string): Promise => { const vpnResponse = await fetch( - `https://check.getipintel.net/check.php?ip=${ip}&contact=${process.env.PROAVALON_EMAIL_ADDRESS}&flags=m`, + `https://check.getipintel.net/check.php?ip=${ip}&contact=${config.email.PROAVALON_EMAIL_ADDRESS}&flags=m`, ); const data = await vpnResponse.json(); @@ -116,7 +118,7 @@ const isVpnCheck2 = async (ip: string): Promise => { }; export const disallowVPNs: RequestHandler = (req, res, next) => { - if (process.env.ENV === 'local') { + if (config.ENV === 'local') { next(); return; }