diff --git a/README.md b/README.md index 1978647..4361fae 100644 --- a/README.md +++ b/README.md @@ -65,8 +65,6 @@ Configuration takes place in the environment variables that are defined in ecosy | Variable | Description | Example | |----------------|:-------------------------|------------------------:| -| HOSTURL | URL of the canvas | "http://localhost" | -| ASSET_SERVER | URL for assets | "http://localhost" | | PORT | Port | 80 | | REDIS_URL | URL:PORT of redis server | "http://localhost:6379" | | MYSQL_HOST | MySql Host | "localhost" | @@ -76,16 +74,18 @@ Configuration takes place in the environment variables that are defined in ecosy #### Optional Configuration -| Variable | Description | Example | -|-------------------|:--------------------------------------|-------------| -| USE_PROXYCHECK | Check users for Proxies | 0 | -| APISOCKET_KEY | Key for API Socket for SpecialAccess™ | "SDfasife3" | -| ADMIN_IDS | Ids of users with Admin rights | "1,12,3" | -| RECAPTCHA_SECRET | reCaptcha secret key | "asdieewff" | -| RECAPTCHA_SITEKEY | reCaptcha site key | "23ksdfssd" | -| RECAPTCHA_TIME | time in minutes between captchas | 30 | -| SESSION_SECRET | random sting for expression sessions | "ayylmao" | -| LOG_MYSQL | if sql queries should get logged | 0 | +| Variable | Description | Example | +|-------------------|:--------------------------------------|--------------------| +| ASSET_SERVER | URL for assets | "http://localhost" | +| USE_PROXYCHECK | Check users for Proxies | 0 | +| APISOCKET_KEY | Key for API Socket for SpecialAccess™ | "SDfasife3" | +| ADMIN_IDS | Ids of users with Admin rights | "1,12,3" | +| RECAPTCHA_SECRET | reCaptcha secret key | "asdieewff" | +| RECAPTCHA_SITEKEY | reCaptcha site key | "23ksdfssd" | +| RECAPTCHA_TIME | time in minutes between captchas | 30 | +| SESSION_SECRET | random sting for expression sessions | "ayylmao" | +| LOG_MYSQL | if sql queries should get logged | 0 | +| USE_XREALIP | see cloudflare section | 1 | Notes: @@ -156,10 +156,12 @@ pm2 log flush pm2 stop web ``` -### If using Cloudflare +### If using Cloudflare / Reverse Proxy In order to get the real IP and not use the cloudflare Proxy IP for placing pixels, we filter those out. The cloudflare IPs are in src/utils/cloudflareip.js and used in src/utils/ip.js. If for some reason cloudflare ads more IPs to it, you can see them at https://www.cloudflare.com/ips/ and add them. If you use any other Reverse Proxy, you can define it's IPs there too. +If USE\_XREALIP is set, we take the IP from the X-Real-Ip header without checking for cloudflare IPs. Use this if you have pixelplanet running behind nginx use the nginx set\_realip module to give us the client ip on the X-Real-Ip header. And be sure to also forward X-Forwarded-Port and set X-Forwarded-Proto. + ### Auto-Start To have the canvas with all it's components autostart at systemstart, enable mysql, redis (and probably nginx if you use it) according to your system (`systemctl enable ...`) diff --git a/src/components/RedirectionPage.jsx b/src/components/RedirectionPage.jsx index acc2881..e5592fa 100644 --- a/src/components/RedirectionPage.jsx +++ b/src/components/RedirectionPage.jsx @@ -7,20 +7,20 @@ import React from 'react'; import ReactDOM from 'react-dom/server'; import Html from './Html'; -const RedirectionPage = ({ text }) => ( +const RedirectionPage = ({ text, host }) => (

{text}

You will be automatically redirected after 5s

-

Or Click here to go back to pixelplanet

+

Or Click here to go back to pixelplanet

); -export function getHtml(description, text) { +export function getHtml(description, text, host) { const data = { title: 'PixelPlanet.fun Accounts', description, - body: , - code: 'window.setTimeout(function(){window.location.href="https://pixelplanet.fun";},4000)', + body: , + code: `window.setTimeout(function(){window.location.href="${host}";},4000)`, }; const index = `${ReactDOM.renderToStaticMarkup()}`; return index; diff --git a/src/core/config.js b/src/core/config.js index 753aab7..7dfaef9 100644 --- a/src/core/config.js +++ b/src/core/config.js @@ -7,8 +7,6 @@ if (process.env.BROWSER) { throw new Error('Do not import `config.js` from inside the client-side code.'); } -export const HOSTURL = process.env.HOSTURL || 'https://pixelplanet.fun'; - export const PORT = process.env.PORT || 80; const TILE_FOLDER_REL = process.env.TILE_FOLDER || 'tiles'; @@ -16,6 +14,8 @@ export const TILE_FOLDER = path.join(__dirname, `./${TILE_FOLDER_REL}`); export const ASSET_SERVER = process.env.ASSET_SERVER || '.'; +export const USE_XREALIP = process.env.USE_XREALIP || false; + // Proxycheck export const USE_PROXYCHECK = parseInt(process.env.USE_PROXYCHECK, 10) || false; diff --git a/src/core/mail.js b/src/core/mail.js index f5cc79f..d530018 100644 --- a/src/core/mail.js +++ b/src/core/mail.js @@ -8,7 +8,6 @@ import nodemailer from 'nodemailer'; import logger from './logger'; import { HOUR, MINUTE } from './constants'; -import { HOSTURL } from './config'; import { DailyCron, HourlyCron } from '../utils/cron'; import RegUser from '../data/models/RegUser'; @@ -37,7 +36,7 @@ class MailProvider { DailyCron.hook(MailProvider.cleanUsers); } - sendVerifyMail(to, name) { + sendVerifyMail(to, name, host) { const pastMail = this.verifyCodes[to]; if (pastMail) { const minLeft = Math.floor( @@ -53,7 +52,7 @@ class MailProvider { } logger.info(`Sending verification mail to ${to} / ${name}`); const code = this.setCode(to); - const verifyUrl = `${HOSTURL}/api/auth/verify?token=${code}`; + const verifyUrl = `${host}/api/auth/verify?token=${code}`; transporter.sendMail({ from: 'donotreply@pixelplanet.fun', to, @@ -70,7 +69,7 @@ class MailProvider { return null; } - async sendPasswdResetMail(to, ip) { + async sendPasswdResetMail(to, ip, host) { const pastMail = this.verifyCodes[to]; if (pastMail) { if (Date.now() < pastMail.timestamp + 15 * MINUTE) { @@ -100,7 +99,7 @@ class MailProvider { logger.info(`Sending Password reset mail to ${to}`); const code = this.setCode(to); - const restoreUrl = `${HOSTURL}/reset_password?token=${code}`; + const restoreUrl = `${host}/reset_password?token=${code}`; transporter.sendMail({ from: 'donotreply@pixelplanet.fun', to, diff --git a/src/core/passport.js b/src/core/passport.js index 4075a71..37cafaf 100644 --- a/src/core/passport.js +++ b/src/core/passport.js @@ -16,7 +16,7 @@ import { sanitizeName } from '../utils/validation'; import logger from './logger'; import { User, RegUser } from '../data/models'; -import { auth, HOSTURL } from './config'; +import { auth } from './config'; import { compareToHash } from '../utils/hash'; @@ -105,7 +105,8 @@ async function oauth_login(email, name, discordid = null) { */ passport.use(new FacebookStrategy({ ...auth.facebook, - callbackURL: `${HOSTURL}/api/auth/facebook/return`, + callbackURL: `/api/auth/facebook/return`, + proxy: true, profileFields: ['displayName', 'email'], }, async (req, accessToken, refreshToken, profile, done) => { const { displayName: name, emails } = profile; @@ -119,7 +120,8 @@ passport.use(new FacebookStrategy({ */ passport.use(new DiscordStrategy({ ...auth.discord, - callbackURL: `${HOSTURL}/api/auth/discord/return`, + callbackURL: `/api/auth/discord/return`, + proxy: true, }, async (accessToken, refreshToken, profile, done) => { // TODO get discord id console.log({ profile, refreshToken, accessToken }); @@ -133,7 +135,8 @@ passport.use(new DiscordStrategy({ */ passport.use(new GoogleStrategy({ ...auth.google, - callbackURL: `${HOSTURL}/api/auth/google/return`, + callbackURL: `/api/auth/google/return`, + proxy: true, }, async (accessToken, refreshToken, profile, done) => { const { displayName: name, emails } = profile; const email = emails[0].value; @@ -146,7 +149,8 @@ passport.use(new GoogleStrategy({ */ passport.use(new RedditStrategy({ ...auth.reddit, - callbackURL: `${HOSTURL}/api/auth/reddit/return`, + callbackURL: `/api/auth/reddit/return`, + proxy: true, }, async (accessToken, refreshToken, profile, done) => { console.log({ profile, refreshToken, accessToken }); const redditid = profile.id; @@ -177,7 +181,8 @@ passport.use(new RedditStrategy({ */ passport.use(new VkontakteStrategy({ ...auth.vk, - callbackURL: `${HOSTURL}/api/auth/vk/return`, + callbackURL: `/api/auth/vk/return`, + proxy: true, scope: ['email'], profileFields: ['displayName', 'email'], }, async (accessToken, refreshToken, params, profile, done) => { diff --git a/src/routes/api/auth/change_mail.js b/src/routes/api/auth/change_mail.js index 5cbf3c9..ab7b30e 100644 --- a/src/routes/api/auth/change_mail.js +++ b/src/routes/api/auth/change_mail.js @@ -8,6 +8,7 @@ import type { Request, Response } from 'express'; import mailProvider from '../../../core/mail'; import { validatePassword, validateEMail } from '../../../utils/validation'; +import { getHostFromRequest } from '../../../utils/ip'; import { compareToHash } from '../../../utils/hash'; function validate(email, password) { @@ -55,7 +56,8 @@ export default async (req: Request, res: Response) => { mailVerified: false, }); - mailProvider.sendVerifyMail(email, user.regUser.name); + const host = getHostFromRequest(req); + mailProvider.sendVerifyMail(email, user.regUser.name, host); res.json({ success: true, diff --git a/src/routes/api/auth/index.js b/src/routes/api/auth/index.js index 4e5b82d..fee2e62 100644 --- a/src/routes/api/auth/index.js +++ b/src/routes/api/auth/index.js @@ -6,6 +6,7 @@ import express from 'express'; import logger from '../../../core/logger'; +import { getHostFromRequest } from '../../../utils/ip'; import register from './register'; import verify from './verify'; @@ -67,7 +68,8 @@ export default (passport) => { res.set({ 'Content-Type': 'text/html', }); - const index = getHtml('OAuth Authentification', 'LogIn failed :(, please try again later or register a new account with Mail.'); + const host = getHostFromRequest(req); + const index = getHtml('OAuth Authentification', 'LogIn failed :(, please try again later or register a new account with Mail.', host); res.status(200).send(index); }); diff --git a/src/routes/api/auth/register.js b/src/routes/api/auth/register.js index a7b7b90..3c5b566 100644 --- a/src/routes/api/auth/register.js +++ b/src/routes/api/auth/register.js @@ -10,6 +10,7 @@ import Sequelize from 'sequelize'; import { RegUser } from '../../../data/models'; import mailProvider from '../../../core/mail'; import getMe from '../../../core/me'; +import { getHostFromRequest } from '../../../utils/ip'; import { validateEMail, validateName, @@ -73,7 +74,8 @@ export default async (req: Request, res: Response) => { }); return; } - mailProvider.sendVerifyMail(email, name); + const host = getHostFromRequest(req); + mailProvider.sendVerifyMail(email, name, host); res.status(200); res.json({ success: true, diff --git a/src/routes/api/auth/resend_verify.js b/src/routes/api/auth/resend_verify.js index 35d4bad..daec475 100644 --- a/src/routes/api/auth/resend_verify.js +++ b/src/routes/api/auth/resend_verify.js @@ -7,6 +7,7 @@ import type { Request, Response } from 'express'; import mailProvider from '../../../core/mail'; +import { getHostFromRequest } from '../../../utils/ip'; export default async (req: Request, res: Response) => { const { user } = req; @@ -27,7 +28,9 @@ export default async (req: Request, res: Response) => { return; } - const error = mailProvider.sendVerifyMail(email, name); + const host = getHostFromRequest(req); + + const error = mailProvider.sendVerifyMail(email, name, host); if (error) { res.status(400); res.json({ diff --git a/src/routes/api/auth/restore_password.js b/src/routes/api/auth/restore_password.js index 7bf185d..35475a0 100644 --- a/src/routes/api/auth/restore_password.js +++ b/src/routes/api/auth/restore_password.js @@ -8,6 +8,7 @@ import type { Request, Response } from 'express'; import mailProvider from '../../../core/mail'; import { validateEMail } from '../../../utils/validation'; +import { getHostFromRequest } from '../../../utils/ip'; async function validate(email) { const errors = []; @@ -29,7 +30,8 @@ export default async (req: Request, res: Response) => { }); return; } - const error = await mailProvider.sendPasswdResetMail(email, ip); + const host = getHostFromRequest(req); + const error = await mailProvider.sendPasswdResetMail(email, ip, host); if (error) { res.status(400); res.json({ diff --git a/src/routes/api/auth/verify.js b/src/routes/api/auth/verify.js index a5b68f8..8878daf 100644 --- a/src/routes/api/auth/verify.js +++ b/src/routes/api/auth/verify.js @@ -6,17 +6,19 @@ import type { Request, Response } from 'express'; import { getHtml } from '../../../components/RedirectionPage'; +import { getHostFromRequest } from '../../../utils/ip'; import mailProvider from '../../../core/mail'; export default async (req: Request, res: Response) => { const { token } = req.query; const success = await mailProvider.verify(token); + const host = getHostFromRequest(req); if (success) { - const index = getHtml('Mail verification', 'You are now verified :)'); + const index = getHtml('Mail verification', 'You are now verified :)', host); res.status(200).send(index); } else { // eslint-disable-next-line max-len - const index = getHtml('Mail verification', 'Your mail verification code is invalid or already expired :(, please request a new one.'); + const index = getHtml('Mail verification', 'Your mail verification code is invalid or already expired :(, please request a new one.', host); res.status(400).send(index); } }; diff --git a/src/utils/ip.js b/src/utils/ip.js index 7878460..a3f46d7 100644 --- a/src/utils/ip.js +++ b/src/utils/ip.js @@ -7,6 +7,8 @@ import isCloudflareIp from './cloudflareip'; import logger from '../core/logger'; +import { USE_XREALIP } from '../core/config'; + function isTrustedProxy(ip: string): boolean { if (ip === '::ffff:127.0.0.1' || ip === '127.0.0.1' || isCloudflareIp(ip)) { @@ -15,15 +17,24 @@ function isTrustedProxy(ip: string): boolean { return false; } -/** - * Note: nginx should handle that, - * it's not neccessary to do that ourself - */ +export function getHostFromRequest(req): ?string { + const { headers } = req; + const host = headers['x-forwarded-host'] || headers['host']; + const proto = headers['x-forwarded-proto'] || 'http'; + + return `${proto}://${host}`; +} + export async function getIPFromRequest(req): ?string { const { socket, connection, headers } = req; const conip = (connection ? connection.remoteAddress : socket.remoteAddress); + if (USE_XREALIP) { + const ip = headers['x-real-ip']; + return ip || conip; + } + if (!headers['x-forwarded-for'] || !isTrustedProxy(conip)) { // eslint-disable-next-line max-len logger.warn(`Connection not going through nginx and cloudflare! IP: ${conip}`, headers);