Compare commits

..

7 commits

Author SHA1 Message Date
Calvin Montgomery c3509b9639 Update NEWS 2022-09-21 22:55:52 -07:00
Calvin Montgomery 7cf1593c7c Fix todo 2022-09-21 22:50:58 -07:00
Calvin Montgomery c686100008 Work around flaky test 2022-09-21 22:50:58 -07:00
Calvin Montgomery 5985c8d280 Add cache, test 2022-09-21 22:50:58 -07:00
Calvin Montgomery d61af3f9d5 Continue working on banned channels 2022-09-21 22:50:58 -07:00
Calvin Montgomery 387faf6d75 Continue working on banned channels 2022-09-21 22:50:58 -07:00
Calvin Montgomery d4727533b1 Work on banned channels feature 2022-09-21 22:50:57 -07:00
15 changed files with 8423 additions and 2773 deletions

View file

@ -1,6 +0,0 @@
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_size = 4
indent_style = space

View file

@ -128,8 +128,6 @@ max-channels-per-user: 5
max-accounts-per-ip: 5 max-accounts-per-ip: 5
# Minimum number of seconds between guest logins from the same IP # Minimum number of seconds between guest logins from the same IP
guest-login-delay: 60 guest-login-delay: 60
# Maximum character length of a chat message, capped at 1000 characters
max-chat-message-length: 320
# Allows you to customize the path divider. The /r/ in http://localhost/r/yourchannel # Allows you to customize the path divider. The /r/ in http://localhost/r/yourchannel
# Acceptable characters are a-z A-Z 0-9 _ and - # Acceptable characters are a-z A-Z 0-9 _ and -

View file

@ -110,25 +110,6 @@ describe('KickbanModule', () => {
); );
}); });
it('rejects if the username is invalid', done => {
mockUser.socket.emit = (frame, obj) => {
if (frame === 'errorMsg') {
assert.strictEqual(
obj.msg,
'Invalid username'
);
done();
}
};
kickban.handleCmdBan(
mockUser,
'/ban test_user<>%$# because reasons',
{}
);
});
it('rejects if the user does not have ban permission', done => { it('rejects if the user does not have ban permission', done => {
mockUser.socket.emit = (frame, obj) => { mockUser.socket.emit = (frame, obj) => {
if (frame === 'errorMsg') { if (frame === 'errorMsg') {

11014
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -2,17 +2,17 @@
"author": "Calvin Montgomery", "author": "Calvin Montgomery",
"name": "CyTube", "name": "CyTube",
"description": "Online media synchronizer and chat", "description": "Online media synchronizer and chat",
"version": "3.86.0", "version": "3.84.0",
"repository": { "repository": {
"url": "http://github.com/calzoneman/sync" "url": "http://github.com/calzoneman/sync"
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@calzoneman/jsli": "^2.0.1", "@calzoneman/jsli": "^2.0.1",
"@cytube/mediaquery": "github:CyTube/mediaquery#564d0c4615e80f72722b0f68ac81f837a4c5fc81", "@cytube/mediaquery": "github:CyTube/mediaquery#c1dcf792cd6e9977c04c1e96f23315dad5e3294d",
"bcrypt": "^5.0.1", "bcrypt": "^5.0.1",
"bluebird": "^3.7.2", "bluebird": "^3.7.2",
"body-parser": "^1.20.1", "body-parser": "^1.19.0",
"cheerio": "^1.0.0-rc.10", "cheerio": "^1.0.0-rc.10",
"clone": "^2.1.2", "clone": "^2.1.2",
"compression": "^1.7.4", "compression": "^1.7.4",
@ -20,22 +20,21 @@
"create-error": "^0.3.1", "create-error": "^0.3.1",
"csrf": "^3.1.0", "csrf": "^3.1.0",
"cytubefilters": "github:calzoneman/cytubefilters#c67b2dab2dc5cc5ed11018819f71273d0f8a1bf5", "cytubefilters": "github:calzoneman/cytubefilters#c67b2dab2dc5cc5ed11018819f71273d0f8a1bf5",
"express": "^4.18.2", "express": "^4.17.1",
"express-minify": "^1.0.0", "express-minify": "^1.0.0",
"json-typecheck": "^0.1.3", "json-typecheck": "^0.1.3",
"knex": "^2.4.0", "knex": "^0.95.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"mysql": "^2.18.1",
"nodemailer": "^6.6.1", "nodemailer": "^6.6.1",
"pg": "^8.11.3",
"pg-native": "^3.0.1",
"prom-client": "^13.1.0", "prom-client": "^13.1.0",
"proxy-addr": "^2.0.6", "proxy-addr": "^2.0.6",
"pug": "^3.0.2", "pug": "^3.0.2",
"redis": "^3.1.1", "redis": "^3.1.1",
"sanitize-html": "^2.7.0", "sanitize-html": "^2.7.0",
"serve-static": "^1.15.0", "serve-static": "^1.14.1",
"socket.io": "^4.5.4", "socket.io": "^4.5.0",
"source-map-support": "^0.5.19", "source-map-support": "^0.5.19",
"toml": "^3.0.0", "toml": "^3.0.0",
"uuid": "^8.3.2", "uuid": "^8.3.2",

View file

@ -15,19 +15,8 @@ window.CustomEmbedPlayer = class CustomEmbedPlayer extends EmbedPlayer
return return
embedSrc = data.meta.embed.src embedSrc = data.meta.embed.src
link = "<a href=\"#{embedSrc}\" target=\"_blank\"><strong>#{embedSrc}</strong></a>"
link = document.createElement('a') alert = makeAlert('Untrusted Content', CUSTOM_EMBED_WARNING.replace('%link%', link),
link.href = embedSrc
link.target = '_blank'
link.rel = 'noopener noreferer'
strong = document.createElement('strong')
strong.textContent = embedSrc
link.appendChild(strong)
# TODO: Ideally makeAlert() would allow optionally providing a DOM
# element instead of requiring HTML text
alert = makeAlert('Untrusted Content', CUSTOM_EMBED_WARNING.replace('%link%', link.outerHTML),
'alert-warning') 'alert-warning')
.removeClass('col-md-12') .removeClass('col-md-12')
$('<button/>').addClass('btn btn-default') $('<button/>').addClass('btn btn-default')

View file

@ -162,7 +162,7 @@ ChatModule.prototype.handleChatMsg = function (user, data) {
return; return;
} }
data.msg = data.msg.substring(0, Config.get("max-chat-message-length")); data.msg = data.msg.substring(0, 320);
// Restrict new accounts/IPs from chatting and posting links // Restrict new accounts/IPs from chatting and posting links
if (this.restrictNewAccount(user, data)) { if (this.restrictNewAccount(user, data)) {
@ -248,7 +248,7 @@ ChatModule.prototype.handlePm = function (user, data) {
} }
data.msg = data.msg.substring(0, Config.get("max-chat-message-length")); data.msg = data.msg.substring(0, 320);
var to = null; var to = null;
for (var i = 0; i < this.channel.users.length; i++) { for (var i = 0; i < this.channel.users.length; i++) {
if (this.channel.users[i].getLowerName() === data.to) { if (this.channel.users[i].getLowerName() === data.to) {

View file

@ -4,7 +4,6 @@ var Flags = require("../flags");
var util = require("../utilities"); var util = require("../utilities");
var Account = require("../account"); var Account = require("../account");
import Promise from 'bluebird'; import Promise from 'bluebird';
const XSS = require("../xss");
const dbIsNameBanned = Promise.promisify(db.channels.isNameBanned); const dbIsNameBanned = Promise.promisify(db.channels.isNameBanned);
const dbIsIPBanned = Promise.promisify(db.channels.isIPBanned); const dbIsIPBanned = Promise.promisify(db.channels.isIPBanned);
@ -262,6 +261,7 @@ KickBanModule.prototype.handleCmdIPBan = function (user, msg, _meta) {
chan.refCounter.ref("KickBanModule::handleCmdIPBan"); chan.refCounter.ref("KickBanModule::handleCmdIPBan");
this.banAll(user, name, range, reason).catch(error => { this.banAll(user, name, range, reason).catch(error => {
//console.log('!!!', error.stack);
const message = error.message || error; const message = error.message || error;
user.socket.emit("errorMsg", { msg: message }); user.socket.emit("errorMsg", { msg: message });
}).then(() => { }).then(() => {
@ -276,10 +276,6 @@ KickBanModule.prototype.checkChannelAlive = function checkChannelAlive() {
}; };
KickBanModule.prototype.banName = async function banName(actor, name, reason) { KickBanModule.prototype.banName = async function banName(actor, name, reason) {
if (!util.isValidUserName(name)) {
throw new Error("Invalid username");
}
reason = reason.substring(0, 255); reason = reason.substring(0, 255);
var chan = this.channel; var chan = this.channel;
@ -327,9 +323,6 @@ KickBanModule.prototype.banName = async function banName(actor, name, reason) {
}; };
KickBanModule.prototype.banIP = async function banIP(actor, ip, name, reason) { KickBanModule.prototype.banIP = async function banIP(actor, ip, name, reason) {
if (!util.isValidUserName(name)) {
throw new Error("Invalid username");
}
reason = reason.substring(0, 255); reason = reason.substring(0, 255);
var masked = util.cloakIP(ip); var masked = util.cloakIP(ip);
@ -452,9 +445,8 @@ KickBanModule.prototype.handleUnban = function (user, data) {
self.channel.logger.log("[mod] " + user.getName() + " unbanned " + data.name); self.channel.logger.log("[mod] " + user.getName() + " unbanned " + data.name);
if (self.channel.modules.chat) { if (self.channel.modules.chat) {
var banperm = self.channel.modules.permissions.permissions.ban; var banperm = self.channel.modules.permissions.permissions.ban;
// TODO: quick fix, shouldn't trust name from unban frame.
self.channel.modules.chat.sendModMessage( self.channel.modules.chat.sendModMessage(
user.getName() + " unbanned " + XSS.sanitizeText(data.name), user.getName() + " unbanned " + data.name,
banperm banperm
); );
} }

View file

@ -11,8 +11,7 @@ import { CaptchaConfig } from './configuration/captchaconfig';
const LOGGER = require('@calzoneman/jsli')('config'); const LOGGER = require('@calzoneman/jsli')('config');
var defaults = { var defaults = {
database: { mysql: {
client: "mysql",
server: "localhost", server: "localhost",
port: 3306, port: 3306,
database: "cytube3", database: "cytube3",
@ -72,7 +71,6 @@ var defaults = {
"max-channels-per-user": 5, "max-channels-per-user": 5,
"max-accounts-per-ip": 5, "max-accounts-per-ip": 5,
"guest-login-delay": 60, "guest-login-delay": 60,
"max-chat-message-length": 320,
aliases: { aliases: {
"purge-interval": 3600000, "purge-interval": 3600000,
"max-age": 2592000000 "max-age": 2592000000
@ -429,11 +427,6 @@ function preprocessConfig(cfg) {
cfg['channel-storage'] = { type: undefined }; cfg['channel-storage'] = { type: undefined };
} }
if (cfg["max-chat-message-length"] > 1000) {
LOGGER.warn("Max chat message length was greater than 1000. Setting to 1000.");
cfg["max-chat-message-length"] = 1000;
}
return cfg; return cfg;
} }

View file

@ -169,7 +169,7 @@ export function validate(data) {
validateURL(data.thumbnail); validateURL(data.thumbnail);
} }
validateSources(data.sources); validateSources(data.sources, data);
validateAudioTracks(data.audioTracks); validateAudioTracks(data.audioTracks);
validateTextTracks(data.textTracks); validateTextTracks(data.textTracks);
/* /*
@ -182,7 +182,7 @@ export function validate(data) {
validateFonts(data.fonts); validateFonts(data.fonts);
} }
function validateSources(sources) { function validateSources(sources, data) {
if (!Array.isArray(sources)) if (!Array.isArray(sources))
throw new ValidationError('sources must be a list'); throw new ValidationError('sources must be a list');
if (sources.length === 0) if (sources.length === 0)

View file

@ -30,19 +30,19 @@ class Database {
constructor(knexConfig = null) { constructor(knexConfig = null) {
if (knexConfig === null) { if (knexConfig === null) {
knexConfig = { knexConfig = {
client: Config.get('database.client'), client: 'mysql',
connection: { connection: {
host: Config.get('database.server'), host: Config.get('mysql.server'),
port: Config.get('database.port'), port: Config.get('mysql.port'),
user: Config.get('database.user'), user: Config.get('mysql.user'),
password: Config.get('database.password'), password: Config.get('mysql.password'),
database: Config.get('database.database'), database: Config.get('mysql.database'),
multipleStatements: true, // Legacy thing multipleStatements: true, // Legacy thing
charset: 'utf8mb4' charset: 'utf8mb4'
}, },
pool: { pool: {
min: Config.get('database.pool-size'), min: Config.get('mysql.pool-size'),
max: Config.get('database.pool-size') max: Config.get('mysql.pool-size')
}, },
debug: !!process.env.KNEX_DEBUG debug: !!process.env.KNEX_DEBUG
}; };
@ -73,8 +73,6 @@ module.exports.init = function (newDB) {
} else { } else {
db = new Database(); db = new Database();
} }
// FIXME Initial database connection failed: error: select 1 from dual
// relation "dual" does not exist
db.knex.raw('select 1 from dual') db.knex.raw('select 1 from dual')
.catch(error => { .catch(error => {
LOGGER.error('Initial database connection failed: %s', error.stack); LOGGER.error('Initial database connection failed: %s', error.stack);

View file

@ -1,5 +1,4 @@
import CyTubeUtil from '../../utilities'; import CyTubeUtil from '../../utilities';
import Config from '../../config';
import { sanitizeText } from '../../xss'; import { sanitizeText } from '../../xss';
import { sendPug } from '../pug'; import { sendPug } from '../pug';
import * as HTTPStatus from '../httpstatus'; import * as HTTPStatus from '../httpstatus';
@ -28,8 +27,7 @@ export default function initialize(app, ioConfig, chanPath, getBannedChannel) {
sendPug(res, 'channel', { sendPug(res, 'channel', {
channelName: req.params.channel, channelName: req.params.channel,
sioSource: `${socketBaseURL}/socket.io/socket.io.js`, sioSource: `${socketBaseURL}/socket.io/socket.io.js`
maxMsgLen: Config.get("max-chat-message-length")
}); });
}); });
} }

View file

@ -46,7 +46,7 @@ html(lang="en")
#userlist #userlist
#messagebuffer.linewrap #messagebuffer.linewrap
form(action="javascript:void(0)") form(action="javascript:void(0)")
input#chatline.form-control(type="text", maxlength=maxMsgLen, style="display: none") input#chatline.form-control(type="text", maxlength="320", style="display: none")
#guestlogin.input-group #guestlogin.input-group
span.input-group-addon Guest login span.input-group-addon Guest login
input#guestname.form-control(type="text", placeholder="Name") input#guestname.form-control(type="text", placeholder="Name")

View file

@ -1,55 +1,4 @@
/*eslint no-unused-vars: "off"*/ /*eslint no-unused-vars: "off"*/
(function() {
/**
* Test whether the browser supports nullish-coalescing operator.
*
* Users with old browsers will probably fail to load the client correctly
* because parsing this operator in older browsers results in a SyntaxError
* that aborts compilation of the entire script (not just an exception where
* it is used). In particular, as of 2023-01-28, Utherverse ships with
* a rather old browser version (Chrome 76) and several users have reported
* it not working.
*/
try {
try {
new Function('x?.y');
} catch (e) {
if (e.name === 'SyntaxError') {
/**
* If we're at this point, we can't be sure what scripts have
* actually loaded, so construct the error alert the old
* fashioned way.
*/
var wrap = document.createElement('div');
wrap.className = 'col-md-12';
var al = document.createElement('div');
al.className = 'alert alert-danger';
var title = document.createElement('strong');
title.textContent = 'Unsupported Browser';
var msg = document.createElement('p');
msg.textContent = 'It looks like your browser does not support ' +
'the required JavaScript features to run ' +
'CyTube. This is usually caused by ' +
'using an outdated browser version. Please '+
'check if an update is available. Your ' +
'browser version is reported as:';
var version = document.createElement('tt');
version.textContent = navigator.userAgent;
wrap.appendChild(al);
al.appendChild(title);
al.appendChild(msg);
al.appendChild(document.createElement('br'));
al.appendChild(version);
document.getElementById('motdrow').appendChild(wrap);
}
}
} catch (e) {
console.error('Error probing for feature support:', e.stack);
}
})();
var CL_VERSION = 3.0; var CL_VERSION = 3.0;
var GS_VERSION = 1.7; // Google Drive Userscript var GS_VERSION = 1.7; // Google Drive Userscript

View file

@ -1287,11 +1287,6 @@ function playlistMove(from, after, cb) {
} }
} }
function checkYP(id) {
if (!/^(PL[a-zA-Z0-9_-]{32}|PL[A-F0-9]{16}|OLA[a-zA-Z0-9_-]{38})$/.test(id)) {
throw new Error('Invalid YouTube Playlist ID. Note that only regular user-created playlists are supported.');
}
}
function parseMediaLink(url) { function parseMediaLink(url) {
function parseShortCode(url){ function parseShortCode(url){
@ -1306,9 +1301,6 @@ function parseMediaLink(url) {
case 'fi': case 'fi':
case 'cm': case 'cm':
return { type, id }; return { type, id };
case 'yp':
checkYP(id);
return { type, id };
// Generic for the rest. // Generic for the rest.
default: default:
return { type, id: id.match(/([^\?&#]+)/)[1] }; return { type, id: id.match(/([^\?&#]+)/)[1] };
@ -1364,7 +1356,6 @@ function parseMediaLink(url) {
return { type: 'yt', id: data.pathname.slice(8,19) } return { type: 'yt', id: data.pathname.slice(8,19) }
} }
if(data.pathname == '/playlist'){ if(data.pathname == '/playlist'){
checkYP(data.searchParams.get('list'));
return { type: 'yp', id: data.searchParams.get('list') } return { type: 'yp', id: data.searchParams.get('list') }
} }
case 'youtu.be': case 'youtu.be':