Merge branch '3.0' into uws

This commit is contained in:
Calvin Montgomery 2018-07-26 21:02:01 -07:00
commit 17cf6023db
7 changed files with 181 additions and 109 deletions

View file

@ -2,7 +2,7 @@
"author": "Calvin Montgomery", "author": "Calvin Montgomery",
"name": "CyTube", "name": "CyTube",
"description": "Online media synchronizer and chat", "description": "Online media synchronizer and chat",
"version": "3.56.3", "version": "3.56.5",
"repository": { "repository": {
"url": "http://github.com/calzoneman/sync" "url": "http://github.com/calzoneman/sync"
}, },

View file

@ -79,7 +79,9 @@ PollModule.prototype.onUserPostJoin = function (user) {
this.addUserToPollRoom(user); this.addUserToPollRoom(user);
const self = this; const self = this;
user.on("effectiveRankChange", () => { user.on("effectiveRankChange", () => {
if (self.channel && !self.channel.dead) {
self.addUserToPollRoom(user); self.addUserToPollRoom(user);
}
}); });
}; };

View file

@ -386,6 +386,12 @@ function preprocessConfig(cfg) {
return contact.name !== 'calzoneman'; return contact.name !== 'calzoneman';
}); });
if (!cfg.io.throttle) {
cfg.io.throttle = {
'in-rate-limit': Infinity
};
}
return cfg; return cfg;
} }

View file

@ -103,8 +103,14 @@ function translateStatusCode(statusCode) {
"the file to be downloaded."; "the file to be downloaded.";
case 404: case 404:
return "The requested link could not be found (404)."; return "The requested link could not be found (404).";
case 405:
return "The website hosting the link does not support HEAD requests, " +
"so the link could not be retrieved.";
case 410: case 410:
return "The requested link does not exist (410 Gone)."; return "The requested link does not exist (410 Gone).";
case 501:
return "The requested link could not be retrieved because the server " +
"hosting it does not support CyTube's request.";
case 500: case 500:
case 503: case 503:
return "The website hosting the audio/video link encountered an error " + return "The website hosting the audio/video link encountered an error " +
@ -143,6 +149,8 @@ function testUrl(url, cb, params = { redirCount: 0, cookie: '' }) {
if (cookie) { if (cookie) {
data.headers = { 'Cookie': cookie }; data.headers = { 'Cookie': cookie };
} }
try {
var req = transport.request(data, function (res) { var req = transport.request(data, function (res) {
req.abort(); req.abort();
@ -205,6 +213,12 @@ function testUrl(url, cb, params = { redirCount: 0, cookie: '' }) {
}); });
req.end(); req.end();
} catch (error) {
LOGGER.error('Unable to make raw file probe request: %s', error.stack);
cb("An unexpected error occurred while trying to process the link. " +
"Try again, and contact support for further troubleshooting if the " +
"problem continues.");
}
} }
function readOldFormat(buf) { function readOldFormat(buf) {

View file

@ -233,6 +233,8 @@ class IOServer {
return; return;
} }
this.setRateLimiter(socket);
emitMetrics(socket); emitMetrics(socket);
LOGGER.info('Accepted socket from %s', socket.context.ipAddress); LOGGER.info('Accepted socket from %s', socket.context.ipAddress);
@ -250,6 +252,25 @@ class IOServer {
} }
} }
setRateLimiter(socket) {
const thunk = () => Config.get('io.throttle.in-rate-limit');
socket._inRateLimit = new TokenBucket(thunk, thunk);
socket.on('cytube:count-event', () => {
if (socket._inRateLimit.throttle()) {
LOGGER.warn(
'Kicking client %s: exceeded in-rate-limit of %d',
socket.context.ipAddress,
thunk()
);
socket.emit('kick', { reason: 'Rate limit exceeded' });
socket.disconnect();
}
});
}
initSocketIO() { initSocketIO() {
patchSocketMetrics(); patchSocketMetrics();
patchTypecheckedFunctions(); patchTypecheckedFunctions();
@ -306,10 +327,12 @@ const outgoingPacketCount = new Counter({
function patchSocketMetrics() { function patchSocketMetrics() {
const onevent = Socket.prototype.onevent; const onevent = Socket.prototype.onevent;
const packet = Socket.prototype.packet; const packet = Socket.prototype.packet;
const emit = require('events').EventEmitter.prototype.emit;
Socket.prototype.onevent = function patchedOnevent() { Socket.prototype.onevent = function patchedOnevent() {
onevent.apply(this, arguments); onevent.apply(this, arguments);
incomingEventCount.inc(1); incomingEventCount.inc(1);
emit.call(this, 'cytube:count-event');
}; };
Socket.prototype.packet = function patchedPacket() { Socket.prototype.packet = function patchedPacket() {

View file

@ -1,16 +1,27 @@
class TokenBucket { class TokenBucket {
constructor(capacity, refillRate) { constructor(capacity, refillRate) {
if (typeof refillRate !== 'function') {
const _refillRate = refillRate;
refillRate = () => _refillRate;
}
if (typeof capacity !== 'function') {
const _capacity = capacity;
capacity = () => _capacity;
}
this.capacity = capacity; this.capacity = capacity;
this.refillRate = refillRate; this.refillRate = refillRate;
this.count = capacity; this.count = capacity();
this.lastRefill = Date.now(); this.lastRefill = Date.now();
} }
throttle() { throttle() {
const now = Date.now(); const now = Date.now();
const delta = Math.floor((now - this.lastRefill) / 1000 * this.refillRate); const delta = Math.floor(
(now - this.lastRefill) / 1000 * this.refillRate()
);
if (delta > 0) { if (delta > 0) {
this.count = Math.min(this.capacity, this.count + delta); this.count = Math.min(this.capacity(), this.count + delta);
this.lastRefill = now; this.lastRefill = now;
} }

View file

@ -13,6 +13,7 @@ var Config = require("../config");
var session = require("../session"); var session = require("../session");
var csrf = require("./csrf"); var csrf = require("./csrf");
const url = require("url"); const url = require("url");
import crypto from 'crypto';
const LOGGER = require('@calzoneman/jsli')('web/accounts'); const LOGGER = require('@calzoneman/jsli')('web/accounts');
@ -536,24 +537,38 @@ function handlePasswordReset(req, res) {
return; return;
} }
if (actualEmail !== email.trim()) { if (actualEmail === '') {
sendPug(res, "account-passwordreset", {
reset: false,
resetEmail: "",
resetErr: `Username ${name} cannot be recovered because it ` +
"doesn't have an email address associated with it."
});
return;
} else if (actualEmail.toLowerCase() !== email.trim().toLowerCase()) {
sendPug(res, "account-passwordreset", { sendPug(res, "account-passwordreset", {
reset: false, reset: false,
resetEmail: "", resetEmail: "",
resetErr: "Provided email does not match the email address on record for " + name resetErr: "Provided email does not match the email address on record for " + name
}); });
return; return;
} else if (actualEmail === "") { }
crypto.randomBytes(20, (err, bytes) => {
if (err) {
LOGGER.error(
'Could not generate random bytes for password reset: %s',
err.stack
);
sendPug(res, "account-passwordreset", { sendPug(res, "account-passwordreset", {
reset: false, reset: false,
resetEmail: "", resetEmail: email,
resetErr: name + " doesn't have an email address on record. Please contact an " + resetErr: "Internal error when generating password reset"
"administrator to manually reset your password."
}); });
return; return;
} }
var hash = $util.sha1($util.randomSalt(64)); var hash = bytes.toString('hex');
// 24-hour expiration // 24-hour expiration
var expire = Date.now() + 86400000; var expire = Date.now() + 86400000;
var ip = req.realIP; var ip = req.realIP;
@ -561,7 +576,7 @@ function handlePasswordReset(req, res) {
db.addPasswordReset({ db.addPasswordReset({
ip: ip, ip: ip,
name: name, name: name,
email: email, email: actualEmail,
hash: hash, hash: hash,
expire: expire expire: expire
}, function (err, _dbres) { }, function (err, _dbres) {
@ -610,6 +625,7 @@ function handlePasswordReset(req, res) {
}); });
}); });
}); });
});
} }
/** /**