mirror of https://github.com/calzoneman/sync.git
Merge branch 'web-refactoring' into 3.0
This commit is contained in:
commit
de9b963d38
1
index.js
1
index.js
|
@ -3,6 +3,7 @@ try {
|
|||
} catch (err) {
|
||||
console.error('FATAL: Failed to require() lib/server.js');
|
||||
console.error('Have you run `npm run build-server` yet to generate it?');
|
||||
console.error(err.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
var Config = require("./lib/config");
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"bluebird": "^2.10.1",
|
||||
"body-parser": "^1.14.0",
|
||||
"cheerio": "^0.19.0",
|
||||
"clone": "^1.0.2",
|
||||
"compression": "^1.5.2",
|
||||
"cookie-parser": "^1.4.0",
|
||||
"create-error": "^0.3.1",
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
import clone from 'clone';
|
||||
|
||||
const DEFAULT_TRUSTED_PROXIES = Object.freeze([
|
||||
'127.0.0.1',
|
||||
'::1'
|
||||
]);
|
||||
|
||||
export default class WebConfiguration {
|
||||
constructor(config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
getEmailContacts() {
|
||||
return clone(this.config.contacts);
|
||||
}
|
||||
|
||||
getTrustedProxies() {
|
||||
return DEFAULT_TRUSTED_PROXIES;
|
||||
}
|
||||
|
||||
getCookieSecret() {
|
||||
return this.config.authCookie.cookieSecret;
|
||||
}
|
||||
|
||||
getCookieDomain() {
|
||||
return this.config.authCookie.cookieDomain;
|
||||
}
|
||||
|
||||
getEnableGzip() {
|
||||
return this.config.gzip.enabled;
|
||||
}
|
||||
|
||||
getGzipThreshold() {
|
||||
return this.config.gzip.threshold;
|
||||
}
|
||||
|
||||
getEnableMinification() {
|
||||
return this.config.enableMinification;
|
||||
}
|
||||
|
||||
getCacheTTL() {
|
||||
return this.config.cacheTTL;
|
||||
}
|
||||
}
|
||||
|
||||
WebConfiguration.fromOldConfig = function (oldConfig) {
|
||||
const config = {
|
||||
contacts: []
|
||||
};
|
||||
|
||||
oldConfig.get('contacts').forEach(contact => {
|
||||
config.contacts.push({
|
||||
name: contact.name,
|
||||
email: contact.email,
|
||||
title: contact.title
|
||||
});
|
||||
});
|
||||
|
||||
config.gzip = {
|
||||
enabled: oldConfig.get('http.gzip'),
|
||||
threshold: oldConfig.get('http.gzip-threshold')
|
||||
};
|
||||
|
||||
config.authCookie = {
|
||||
cookieSecret: oldConfig.get('http.cookie-secret'),
|
||||
cookieDomain: oldConfig.get('http.root-domain-dotted')
|
||||
};
|
||||
|
||||
config.enableMinification = oldConfig.get('http.minify');
|
||||
|
||||
config.cacheTTL = oldConfig.get('http.max-age');
|
||||
|
||||
return new WebConfiguration(config);
|
||||
};
|
|
@ -1,4 +1,9 @@
|
|||
import createError from 'create-error';
|
||||
import * as HTTPStatus from './web/httpstatus';
|
||||
|
||||
export const ChannelStateSizeError = createError('ChannelStateSizeError');
|
||||
export const ChannelNotFoundError = createError('ChannelNotFoundError');
|
||||
export const CSRFError = createError('CSRFError');
|
||||
export const HTTPError = createError('HTTPError', {
|
||||
status: HTTPStatus.INTERNAL_SERVER_ERROR
|
||||
});
|
||||
|
|
|
@ -42,6 +42,11 @@ var $util = require("./utilities");
|
|||
var db = require("./database");
|
||||
var Flags = require("./flags");
|
||||
var sio = require("socket.io");
|
||||
import LocalChannelIndex from './web/localchannelindex';
|
||||
import IOConfiguration from './configuration/ioconfig';
|
||||
import WebConfiguration from './configuration/webconfig';
|
||||
import NullClusterClient from './io/cluster/nullclusterclient';
|
||||
import session from './session';
|
||||
|
||||
var Server = function () {
|
||||
var self = this;
|
||||
|
@ -60,8 +65,17 @@ var Server = function () {
|
|||
ChannelStore.init();
|
||||
|
||||
// webserver init -----------------------------------------------------
|
||||
const ioConfig = IOConfiguration.fromOldConfig(Config);
|
||||
const webConfig = WebConfiguration.fromOldConfig(Config);
|
||||
const clusterClient = new NullClusterClient(ioConfig);
|
||||
const channelIndex = new LocalChannelIndex();
|
||||
self.express = express();
|
||||
require("./web/webserver").init(self.express);
|
||||
require("./web/webserver").init(self.express,
|
||||
webConfig,
|
||||
ioConfig,
|
||||
clusterClient,
|
||||
channelIndex,
|
||||
session);
|
||||
|
||||
// http/https/sio server init -----------------------------------------
|
||||
var key = "", cert = "", ca = undefined;
|
||||
|
@ -241,4 +255,3 @@ Server.prototype.shutdown = function () {
|
|||
process.exit(1);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -92,7 +92,7 @@ function handleChangePassword(req, res) {
|
|||
return;
|
||||
}
|
||||
|
||||
Logger.eventlog.log("[account] " + webserver.ipForRequest(req) +
|
||||
Logger.eventlog.log("[account] " + req.realIP +
|
||||
" changed password for " + name);
|
||||
|
||||
db.users.getUser(name, function (err, user) {
|
||||
|
@ -172,7 +172,7 @@ function handleChangeEmail(req, res) {
|
|||
});
|
||||
return;
|
||||
}
|
||||
Logger.eventlog.log("[account] " + webserver.ipForRequest(req) +
|
||||
Logger.eventlog.log("[account] " + req.realIP +
|
||||
" changed email for " + name +
|
||||
" to " + email);
|
||||
sendJade(res, "account-edit", {
|
||||
|
@ -269,7 +269,7 @@ function handleNewChannel(req, res) {
|
|||
db.channels.register(name, req.user.name, function (err, channel) {
|
||||
if (!err) {
|
||||
Logger.eventlog.log("[channel] " + req.user.name + "@" +
|
||||
webserver.ipForRequest(req) +
|
||||
req.realIP +
|
||||
" registered channel " + name);
|
||||
var sv = Server.getServer();
|
||||
if (sv.isChannelLoaded(name)) {
|
||||
|
@ -336,7 +336,7 @@ function handleDeleteChannel(req, res) {
|
|||
db.channels.drop(name, function (err) {
|
||||
if (!err) {
|
||||
Logger.eventlog.log("[channel] " + req.user.name + "@" +
|
||||
webserver.ipForRequest(req) + " deleted channel " +
|
||||
req.realIP + " deleted channel " +
|
||||
name);
|
||||
}
|
||||
var sv = Server.getServer();
|
||||
|
@ -498,7 +498,7 @@ function handlePasswordReset(req, res) {
|
|||
var hash = $util.sha1($util.randomSalt(64));
|
||||
// 24-hour expiration
|
||||
var expire = Date.now() + 86400000;
|
||||
var ip = webserver.ipForRequest(req);
|
||||
var ip = req.realIP;
|
||||
|
||||
db.addPasswordReset({
|
||||
ip: ip,
|
||||
|
@ -575,7 +575,7 @@ function handlePasswordRecover(req, res) {
|
|||
return;
|
||||
}
|
||||
|
||||
var ip = webserver.ipForRequest(req);
|
||||
var ip = req.realIP;
|
||||
|
||||
db.lookupPasswordReset(hash, function (err, row) {
|
||||
if (err) {
|
||||
|
|
|
@ -15,7 +15,7 @@ function checkAdmin(cb) {
|
|||
if (req.user.global_rank < 255) {
|
||||
res.send(403);
|
||||
Logger.eventlog.log("[acp] Attempted GET "+req.path+" from non-admin " +
|
||||
user.name + "@" + webserver.ipForRequest(req));
|
||||
user.name + "@" + req.realIP);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ function handleLogin(req, res) {
|
|||
if (err) {
|
||||
if (err === "Invalid username/password combination") {
|
||||
Logger.eventlog.log("[loginfail] Login failed (bad password): " + name
|
||||
+ "@" + webserver.ipForRequest(req));
|
||||
+ "@" + req.realIP);
|
||||
}
|
||||
sendJade(res, "login", {
|
||||
loggedIn: false,
|
||||
|
@ -127,7 +127,7 @@ function handleLogout(req, res) {
|
|||
res.clearCookie("auth");
|
||||
req.user = res.user = null;
|
||||
// Try to find an appropriate redirect
|
||||
var dest = req.query.dest || req.header("referer");
|
||||
var dest = req.body.dest || req.header("referer");
|
||||
dest = dest && dest.match(/login|logout|account/) ? null : dest;
|
||||
|
||||
var host = req.hostname;
|
||||
|
@ -173,7 +173,7 @@ function handleRegister(req, res) {
|
|||
if (typeof email !== "string") {
|
||||
email = "";
|
||||
}
|
||||
var ip = webserver.ipForRequest(req);
|
||||
var ip = req.realIP;
|
||||
|
||||
if (typeof name !== "string" || typeof password !== "string") {
|
||||
res.sendStatus(400);
|
||||
|
@ -234,7 +234,7 @@ module.exports = {
|
|||
init: function (app) {
|
||||
app.get("/login", handleLoginPage);
|
||||
app.post("/login", handleLogin);
|
||||
app.get("/logout", handleLogout);
|
||||
app.post("/logout", handleLogout);
|
||||
app.get("/register", handleRegisterPage);
|
||||
app.post("/register", handleRegister);
|
||||
}
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
* Adapted from https://github.com/expressjs/csurf
|
||||
*/
|
||||
|
||||
import { CSRFError } from '../errors';
|
||||
|
||||
var csrf = require("csrf");
|
||||
var createError = require("http-errors");
|
||||
|
||||
var tokens = csrf();
|
||||
|
||||
|
@ -39,8 +40,6 @@ exports.verify = function csrfVerify(req) {
|
|||
var token = req.body._csrf || req.query._csrf;
|
||||
|
||||
if (!tokens.verify(secret, token)) {
|
||||
throw createError(403, 'invalid csrf token', {
|
||||
code: 'EBADCSRFTOKEN'
|
||||
});
|
||||
throw new CSRFError('Invalid CSRF token');
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
export const BAD_REQUEST = 400;
|
||||
export const FORBIDDEN = 403;
|
||||
export const NOT_FOUND = 404;
|
||||
export const INTERNAL_SERVER_ERROR = 500;
|
|
@ -0,0 +1,14 @@
|
|||
import Promise from 'bluebird';
|
||||
import Server from '../server';
|
||||
|
||||
var SERVER = null;
|
||||
|
||||
export default class LocalChannelIndex {
|
||||
listPublicChannels() {
|
||||
if (SERVER === null) {
|
||||
SERVER = require('../server').getServer();
|
||||
}
|
||||
|
||||
return Promise.resolve(SERVER.packChannelList(true));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
const STATIC_RESOURCE = /\..+$/;
|
||||
|
||||
export default function initialize(app, session) {
|
||||
app.use((req, res, next) => {
|
||||
if (STATIC_RESOURCE.test(req.path)) {
|
||||
return next();
|
||||
} else if (!req.signedCookies || !req.signedCookies.auth) {
|
||||
return nuext();
|
||||
} else {
|
||||
session.verifySession(req.signedCookies.auth, (err, account) => {
|
||||
if (!err) {
|
||||
req.user = res.user = account;
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import net from 'net';
|
||||
|
||||
export default function initialize(app, webConfig) {
|
||||
function isTrustedProxy(ip) {
|
||||
return webConfig.getTrustedProxies().indexOf(ip) >= 0;
|
||||
}
|
||||
|
||||
function getForwardedIP(req) {
|
||||
const xForwardedFor = req.header('x-forwarded-for');
|
||||
if (!xForwardedFor) {
|
||||
return req.ip;
|
||||
}
|
||||
|
||||
const ipList = xForwardedFor.split(',');
|
||||
for (let i = 0; i < ipList.length; i++) {
|
||||
const ip = ipList[i].trim();
|
||||
if (net.isIP(ip)) {
|
||||
return ip;
|
||||
}
|
||||
}
|
||||
|
||||
return req.ip;
|
||||
}
|
||||
|
||||
app.use((req, res, next) => {
|
||||
if (isTrustedProxy(req.ip)) {
|
||||
req.realIP = getForwardedIP(req);
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import CyTubeUtil from '../../utilities';
|
||||
import { sanitizeText } from '../../xss';
|
||||
import { sendJade } from '../jade';
|
||||
import * as HTTPStatus from '../httpstatus';
|
||||
import { HTTPError } from '../../errors';
|
||||
|
||||
export default function initialize(app, ioConfig) {
|
||||
app.get('/r/:channel', (req, res) => {
|
||||
if (!req.params.channel || !CyTubeUtil.isValidChannelName(req.params.channel)) {
|
||||
throw new HTTPError(`"${sanitizeText(req.params.channel)}" is not a valid ` +
|
||||
'channel name.', { status: HTTPStatus.NOT_FOUND });
|
||||
}
|
||||
|
||||
const endpoints = ioConfig.getSocketEndpoints();
|
||||
if (endpoints.length === 0) {
|
||||
throw new HTTPError('No socket.io endpoints configured');
|
||||
}
|
||||
const socketBaseURL = endpoints[0].url;
|
||||
|
||||
sendJade(res, 'channel', {
|
||||
channelName: req.params.channel,
|
||||
sioSource: `${socketBaseURL}/socket.io/socket.io.js`
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import CyTubeUtil from '../../utilities';
|
||||
import { sendJade } from '../jade';
|
||||
|
||||
export default function initialize(app, webConfig) {
|
||||
app.get('/contact', (req, res) => {
|
||||
// Basic obfuscation of email addresses to prevent spambots
|
||||
// from picking them up. Not real encryption.
|
||||
// Deobfuscated by clientside JS.
|
||||
const contacts = webConfig.getEmailContacts().map(contact => {
|
||||
const emkey = CyTubeUtil.randomSalt(16);
|
||||
let email = new Array(contact.email.length);
|
||||
for (let i = 0; i < contact.email.length; i++) {
|
||||
email[i] = String.fromCharCode(
|
||||
contact.email.charCodeAt(i) ^ emkey.charCodeAt(i % emkey.length)
|
||||
);
|
||||
}
|
||||
contact.email = escape(email.join(""));
|
||||
contact.emkey = escape(emkey);
|
||||
return contact;
|
||||
});
|
||||
|
||||
return sendJade(res, 'contact', {
|
||||
contacts: contacts
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import { sendJade } from '../jade';
|
||||
|
||||
export default function initialize(app, channelIndex) {
|
||||
app.get('/', (req, res) => {
|
||||
channelIndex.listPublicChannels().then((channels) => {
|
||||
channels.sort((a, b) => {
|
||||
if (a.usercount === b.usercount) {
|
||||
return a.uniqueName > b.uniqueName ? -1 : 1;
|
||||
}
|
||||
|
||||
return b.usercount - a.usercount;
|
||||
});
|
||||
|
||||
sendJade(res, 'index', {
|
||||
channels: channels
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,16 +1,12 @@
|
|||
import IOConfiguration from '../../configuration/ioconfig';
|
||||
import NullClusterClient from '../../io/cluster/nullclusterclient';
|
||||
import Config from '../../config';
|
||||
import CyTubeUtil from '../../utilities';
|
||||
import Logger from '../../logger';
|
||||
import * as HTTPStatus from '../httpstatus';
|
||||
|
||||
export default function initialize(app) {
|
||||
const ioConfig = IOConfiguration.fromOldConfig(Config);
|
||||
const clusterClient = new NullClusterClient(ioConfig);
|
||||
|
||||
export default function initialize(app, clusterClient) {
|
||||
app.get('/socketconfig/:channel.json', (req, res) => {
|
||||
if (!req.params.channel || !CyTubeUtil.isValidChannelName(req.params.channel)) {
|
||||
return res.status(404).json({
|
||||
return res.status(HTTPStatus.NOT_FOUND).json({
|
||||
error: `Channel "${req.params.channel}" does not exist.`
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,55 +1,38 @@
|
|||
var path = require("path");
|
||||
var fs = require("fs");
|
||||
var net = require("net");
|
||||
var express = require("express");
|
||||
var webroot = path.join(__dirname, "..", "www");
|
||||
var sendJade = require("./jade").sendJade;
|
||||
var Server = require("../server");
|
||||
var $util = require("../utilities");
|
||||
var Logger = require("../logger");
|
||||
var Config = require("../config");
|
||||
var db = require("../database");
|
||||
var bodyParser = require("body-parser");
|
||||
var cookieParser = require("cookie-parser");
|
||||
var serveStatic = require("serve-static");
|
||||
var morgan = require("morgan");
|
||||
var session = require("../session");
|
||||
var csrf = require("./csrf");
|
||||
var XSS = require("../xss");
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import net from 'net';
|
||||
import express from 'express';
|
||||
import { sendJade } from './jade';
|
||||
import Logger from '../logger';
|
||||
import Config from '../config';
|
||||
import bodyParser from 'body-parser';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import serveStatic from 'serve-static';
|
||||
import morgan from 'morgan';
|
||||
import csrf from './csrf';
|
||||
import * as HTTPStatus from './httpstatus';
|
||||
import { CSRFError, HTTPError } from '../errors';
|
||||
import counters from "../counters";
|
||||
|
||||
const LOG_FORMAT = ':real-address - :remote-user [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"';
|
||||
morgan.token('real-address', function (req) { return req._ip; });
|
||||
|
||||
/**
|
||||
* Extracts an IP address from a request. Uses X-Forwarded-For if the IP is localhost
|
||||
*/
|
||||
function ipForRequest(req) {
|
||||
var ip = req.ip;
|
||||
if (ip === "127.0.0.1" || ip === "::1") {
|
||||
var xforward = req.header("x-forwarded-for");
|
||||
if (typeof xforward !== "string") {
|
||||
xforward = [];
|
||||
} else {
|
||||
xforward = xforward.split(",");
|
||||
}
|
||||
|
||||
for (var i = 0; i < xforward.length; i++) {
|
||||
if (net.isIP(xforward[i])) {
|
||||
return xforward[i];
|
||||
}
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
return ip;
|
||||
function initializeLog(app) {
|
||||
const logFormat = ':real-address - :remote-user [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"';
|
||||
const logPath = path.join(__dirname, '..', '..', 'http.log');
|
||||
const outputStream = fs.createWriteStream(logPath, {
|
||||
flags: 'a', // append to existing file
|
||||
encoding: 'utf8'
|
||||
});
|
||||
morgan.token('real-address', req => req.realIP);
|
||||
app.use(morgan(logFormat, {
|
||||
stream: outputStream
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects a request to HTTPS if the server supports it
|
||||
*/
|
||||
function redirectHttps(req, res) {
|
||||
if (!req.secure && Config.get("https.enabled") && Config.get("https.redirect")) {
|
||||
var ssldomain = Config.get("https.full-address");
|
||||
if (!req.secure && Config.get('https.enabled') && Config.get('https.redirect')) {
|
||||
var ssldomain = Config.get('https.full-address');
|
||||
if (ssldomain.indexOf(req.hostname) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
@ -65,120 +48,92 @@ function redirectHttps(req, res) {
|
|||
*/
|
||||
function redirectHttp(req, res) {
|
||||
if (req.secure) {
|
||||
var domain = Config.get("http.full-address");
|
||||
var domain = Config.get('http.full-address');
|
||||
res.redirect(domain + req.path);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a GET request for /r/:channel - serves channel.html
|
||||
*/
|
||||
function handleChannel(req, res) {
|
||||
if (!$util.isValidChannelName(req.params.channel)) {
|
||||
res.status(404);
|
||||
res.send("Invalid channel name '" + XSS.sanitizeText(req.params.channel) + "'");
|
||||
return;
|
||||
}
|
||||
|
||||
var sio;
|
||||
if (net.isIPv6(ipForRequest(req))) {
|
||||
sio = Config.get("io.ipv6-default");
|
||||
}
|
||||
|
||||
if (!sio) {
|
||||
sio = Config.get("io.ipv4-default");
|
||||
}
|
||||
|
||||
sio += "/socket.io/socket.io.js";
|
||||
|
||||
sendJade(res, "channel", {
|
||||
channelName: req.params.channel,
|
||||
sioSource: sio
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a request for the index page
|
||||
*/
|
||||
function handleIndex(req, res) {
|
||||
var channels = Server.getServer().packChannelList(true);
|
||||
channels.sort(function (a, b) {
|
||||
if (a.usercount === b.usercount) {
|
||||
return a.uniqueName > b.uniqueName ? -1 : 1;
|
||||
}
|
||||
|
||||
return b.usercount - a.usercount;
|
||||
});
|
||||
|
||||
sendJade(res, "index", {
|
||||
channels: channels
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy socket.io configuration endpoint. This is being migrated to
|
||||
* /socketconfig/<channel name>.json (see ./routes/socketconfig.js)
|
||||
*/
|
||||
function handleSocketConfig(req, res) {
|
||||
function handleLegacySocketConfig(req, res) {
|
||||
if (/\.json$/.test(req.path)) {
|
||||
res.json(Config.get("sioconfigjson"));
|
||||
res.json(Config.get('sioconfigjson'));
|
||||
return;
|
||||
}
|
||||
|
||||
res.type("application/javascript");
|
||||
res.type('application/javascript');
|
||||
|
||||
var sioconfig = Config.get("sioconfig");
|
||||
var sioconfig = Config.get('sioconfig');
|
||||
var iourl;
|
||||
var ip = ipForRequest(req);
|
||||
var ip = req.realIP;
|
||||
var ipv6 = false;
|
||||
|
||||
if (net.isIPv6(ip)) {
|
||||
iourl = Config.get("io.ipv6-default");
|
||||
iourl = Config.get('io.ipv6-default');
|
||||
ipv6 = true;
|
||||
}
|
||||
|
||||
if (!iourl) {
|
||||
iourl = Config.get("io.ipv4-default");
|
||||
iourl = Config.get('io.ipv4-default');
|
||||
}
|
||||
|
||||
sioconfig += "var IO_URL='" + iourl + "';";
|
||||
sioconfig += "var IO_V6=" + ipv6 + ";";
|
||||
sioconfig += 'var IO_URL=\'' + iourl + '\';';
|
||||
sioconfig += 'var IO_V6=' + ipv6 + ';';
|
||||
res.send(sioconfig);
|
||||
}
|
||||
|
||||
function handleUserAgreement(req, res) {
|
||||
sendJade(res, "tos", {
|
||||
domain: Config.get("http.domain")
|
||||
sendJade(res, 'tos', {
|
||||
domain: Config.get('http.domain')
|
||||
});
|
||||
}
|
||||
|
||||
function handleContactPage(req, res) {
|
||||
// Make a copy to prevent messing with the original
|
||||
var contacts = Config.get("contacts").map(function (c) {
|
||||
return {
|
||||
name: c.name,
|
||||
email: c.email,
|
||||
title: c.title
|
||||
};
|
||||
function initializeErrorHandlers(app) {
|
||||
app.use((req, res, next) => {
|
||||
return next(new HTTPError(`No route for ${req.path}`, {
|
||||
status: HTTPStatus.NOT_FOUND
|
||||
}));
|
||||
});
|
||||
|
||||
// Rudimentary hiding of email addresses to prevent spambots
|
||||
contacts.forEach(function (c) {
|
||||
c.emkey = $util.randomSalt(16)
|
||||
var email = new Array(c.email.length);
|
||||
for (var i = 0; i < c.email.length; i++) {
|
||||
email[i] = String.fromCharCode(
|
||||
c.email.charCodeAt(i) ^ c.emkey.charCodeAt(i % c.emkey.length)
|
||||
);
|
||||
app.use((err, req, res, next) => {
|
||||
if (err) {
|
||||
if (err instanceof CSRFError) {
|
||||
res.status(HTTPStatus.FORBIDDEN);
|
||||
return sendJade(res, 'csrferror', {
|
||||
path: req.path,
|
||||
referer: req.header('referer')
|
||||
});
|
||||
}
|
||||
|
||||
let { message, status } = err;
|
||||
if (!status) {
|
||||
status = HTTPStatus.INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
if (!message) {
|
||||
message = 'An unknown error occurred.';
|
||||
} else if (/\.(jade|js)/.test(message)) {
|
||||
// Prevent leakage of stack traces
|
||||
message = 'An internal error occurred.';
|
||||
}
|
||||
|
||||
// Log 5xx (server) errors
|
||||
if (Math.floor(status / 100) === 5) {
|
||||
Logger.errlog.log(err.stack);
|
||||
}
|
||||
|
||||
res.status(status);
|
||||
return sendJade(res, 'httperror', {
|
||||
path: req.path,
|
||||
status: status,
|
||||
message: message
|
||||
});
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
c.email = escape(email.join(""));
|
||||
c.emkey = escape(c.emkey);
|
||||
});
|
||||
|
||||
sendJade(res, "contact", {
|
||||
contacts: contacts
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -186,100 +141,60 @@ module.exports = {
|
|||
/**
|
||||
* Initializes webserver callbacks
|
||||
*/
|
||||
init: function (app) {
|
||||
app.use(function (req, res, next) {
|
||||
init: function (app, webConfig, ioConfig, clusterClient, channelIndex, session) {
|
||||
app.use((req, res, next) => {
|
||||
counters.add("http:request", 1);
|
||||
req._ip = ipForRequest(req);
|
||||
next();
|
||||
});
|
||||
require('./middleware/x-forwarded-for')(app, webConfig);
|
||||
app.use(bodyParser.urlencoded({
|
||||
extended: false,
|
||||
limit: '1kb' // No POST data should ever exceed this size under normal usage
|
||||
}));
|
||||
if (Config.get("http.cookie-secret") === "change-me") {
|
||||
Logger.errlog.log("YOU SHOULD CHANGE THE VALUE OF cookie-secret IN config.yaml");
|
||||
if (webConfig.getCookieSecret() === 'change-me') {
|
||||
Logger.errlog.log('WARNING: The configured cookie secret was left as the ' +
|
||||
'default of "change-me".');
|
||||
}
|
||||
app.use(cookieParser(Config.get("http.cookie-secret")));
|
||||
app.use(csrf.init(Config.get("http.root-domain-dotted")));
|
||||
app.use(morgan(LOG_FORMAT, {
|
||||
stream: require("fs").createWriteStream(path.join(__dirname, "..", "..",
|
||||
"http.log"), {
|
||||
flags: "a",
|
||||
encoding: "utf-8"
|
||||
})
|
||||
}));
|
||||
app.use(cookieParser(webConfig.getCookieSecret()));
|
||||
app.use(csrf.init(webConfig.getCookieDomain()));
|
||||
initializeLog(app);
|
||||
require('./middleware/authorize')(app, session);
|
||||
|
||||
app.use(function (req, res, next) {
|
||||
if (req.path.match(/^\/(css|js|img|boop).*$/)) {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (!req.signedCookies || !req.signedCookies.auth) {
|
||||
return next();
|
||||
}
|
||||
|
||||
session.verifySession(req.signedCookies.auth, function (err, account) {
|
||||
if (!err) {
|
||||
req.user = res.user = account;
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
if (Config.get("http.gzip")) {
|
||||
app.use(require("compression")({ threshold: Config.get("http.gzip-threshold") }));
|
||||
Logger.syslog.log("Enabled gzip compression");
|
||||
if (webConfig.getEnableGzip()) {
|
||||
app.use(require('compression')({
|
||||
threshold: webConfig.getGzipThreshold()
|
||||
}));
|
||||
Logger.syslog.log('Enabled gzip compression');
|
||||
}
|
||||
|
||||
if (Config.get("http.minify")) {
|
||||
var cache = path.join(__dirname, "..", "..", "www", "cache")
|
||||
if (webConfig.getEnableMinification()) {
|
||||
const cacheDir = path.join(__dirname, '..', '..', 'www', 'cache');
|
||||
if (!fs.existsSync(cache)) {
|
||||
fs.mkdirSync(cache);
|
||||
}
|
||||
app.use(require("express-minify")({
|
||||
cache: cache
|
||||
app.use(require('express-minify')({
|
||||
cache: cacheDir
|
||||
}));
|
||||
Logger.syslog.log("Enabled express-minify for CSS and JS");
|
||||
Logger.syslog.log('Enabled express-minify for CSS and JS');
|
||||
}
|
||||
|
||||
app.get("/r/:channel", handleChannel);
|
||||
app.get("/", handleIndex);
|
||||
app.get("/sioconfig(.json)?", handleSocketConfig);
|
||||
require("./routes/socketconfig")(app);
|
||||
app.get("/useragreement", handleUserAgreement);
|
||||
app.get("/contact", handleContactPage);
|
||||
require("./auth").init(app);
|
||||
require("./account").init(app);
|
||||
require("./acp").init(app);
|
||||
require("../google2vtt").attach(app);
|
||||
app.use(serveStatic(path.join(__dirname, "..", "..", "www"), {
|
||||
maxAge: Config.get("http.max-age") || Config.get("http.cache-ttl")
|
||||
require('./routes/channel')(app, ioConfig);
|
||||
require('./routes/index')(app, channelIndex);
|
||||
app.get('/sioconfig(.json)?', handleLegacySocketConfig);
|
||||
require('./routes/socketconfig')(app, clusterClient);
|
||||
app.get('/useragreement', handleUserAgreement);
|
||||
require('./routes/contact')(app, webConfig);
|
||||
require('./auth').init(app);
|
||||
require('./account').init(app);
|
||||
require('./acp').init(app);
|
||||
require('../google2vtt').attach(app);
|
||||
app.use(serveStatic(path.join(__dirname, '..', '..', 'www'), {
|
||||
maxAge: webConfig.getCacheTTL()
|
||||
}));
|
||||
app.use(function (err, req, res, next) {
|
||||
if (err) {
|
||||
if (err.message && err.message.match(/failed to decode param/i)) {
|
||||
return res.status(400).send("Malformed path: " + req.path);
|
||||
} else if (err.message && err.message.match(/range not satisfiable/i)) {
|
||||
return res.status(416).end();
|
||||
} else if (err.message && err.message.match(/request entity too large/i)) {
|
||||
return res.status(413).end();
|
||||
} else if (err.message && err.message.match(/bad request/i)) {
|
||||
return res.status(400).end("Bad Request");
|
||||
} else if (err.message && err.message.match(/invalid csrf token/i)) {
|
||||
res.status(403);
|
||||
sendJade(res, 'csrferror', { path: req.path });
|
||||
return;
|
||||
}
|
||||
Logger.errlog.log(err.stack);
|
||||
res.status(500).end();
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
ipForRequest: ipForRequest,
|
||||
initializeErrorHandlers(app);
|
||||
},
|
||||
|
||||
redirectHttps: redirectHttps,
|
||||
|
||||
|
|
|
@ -24,7 +24,8 @@ html(lang="en")
|
|||
li A malicious user has attempted to tamper with your session
|
||||
li Your browser does not support cookies, or they are not enabled
|
||||
| If the problem persists, please contact an administrator.
|
||||
a(href=path) Return to previous page
|
||||
if referer
|
||||
a(href=referer) Return to previous page
|
||||
|
||||
include footer
|
||||
mixin footer()
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
mixin notfound()
|
||||
h1 Not Found
|
||||
p The page you were looking for doesn't seem to exist. Please check that you typed the URL correctly.
|
||||
if message
|
||||
p Reason: #{message}
|
||||
mixin forbidden()
|
||||
h1 Forbidden
|
||||
p You don't have permission to access <code>#{path}</code>
|
||||
mixin genericerror()
|
||||
h1 Oops
|
||||
p Your request could not be processed. Status code: <code>#{status}</code>, message: <code>#{message}</code>
|
||||
doctype html
|
||||
html(lang="en")
|
||||
head
|
||||
include head
|
||||
mixin head()
|
||||
body
|
||||
#wrap
|
||||
nav.navbar.navbar-inverse.navbar-fixed-top(role="navigation")
|
||||
include nav
|
||||
mixin navheader()
|
||||
#nav-collapsible.collapse.navbar-collapse
|
||||
ul.nav.navbar-nav
|
||||
mixin navdefaultlinks(path)
|
||||
mixin navloginlogout(path)
|
||||
|
||||
section#mainpage.container
|
||||
.col-md-12
|
||||
.alert.alert-danger
|
||||
if status == 404
|
||||
mixin notfound()
|
||||
else if status == 403
|
||||
mixin forbidden()
|
||||
else
|
||||
mixin genericerror()
|
||||
|
||||
include footer
|
||||
mixin footer()
|
|
@ -67,8 +67,10 @@ mixin navloginform(redirect)
|
|||
|
||||
|
||||
mixin navlogoutform(redirect)
|
||||
p#logoutform.navbar-text.pull-right
|
||||
form#logoutform.navbar-text.pull-right(action="/logout", method="post")
|
||||
input(type="hidden", name="dest", value=baseUrl + redirect)
|
||||
input(type="hidden", name="_csrf", value=csrfToken)
|
||||
span#welcome Welcome, #{loginName}
|
||||
span ·
|
||||
a#logout.navbar-link(href="/logout?dest=#{encodeURIComponent(baseUrl + redirect)}&_csrf=#{csrfToken}") Logout
|
||||
input#logout.navbar-link(type="submit", value="Logout")
|
||||
|
||||
|
|
|
@ -639,3 +639,13 @@ li.vjs-menu-item.vjs-selected {
|
|||
.video-js video::-webkit-media-text-track-container {
|
||||
bottom: 50px;
|
||||
}
|
||||
|
||||
input#logout[type="submit"] {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
input#logout[type="submit"]:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue