mirror of
https://github.com/Spengreb/sync.git
synced 2026-06-10 07:12:05 +00:00
Merge pull request #14 from Spengreb/broadspectrum-code-analysis
Update to node 20 and jQuery 3
This commit is contained in:
commit
2788dae3c8
11 changed files with 166 additions and 24 deletions
|
|
@ -39,7 +39,7 @@ class Database {
|
|||
database: Config.get('mysql.database'),
|
||||
multipleStatements: true, // Legacy thing
|
||||
charset: 'utf8mb4'
|
||||
}
|
||||
};
|
||||
} else {
|
||||
connection = {
|
||||
host: Config.get('mysql.server'),
|
||||
|
|
@ -49,7 +49,7 @@ class Database {
|
|||
database: Config.get('mysql.database'),
|
||||
multipleStatements: true, // Legacy thing
|
||||
charset: 'utf8mb4'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
knexConfig = {
|
||||
|
|
|
|||
92
src/shows.js
92
src/shows.js
|
|
@ -22,15 +22,93 @@ function makeSystemProxy(name) {
|
|||
|
||||
function computeNextRunAt(show) {
|
||||
const base = Number(show.next_run_at || show.scheduled_for || Date.now());
|
||||
if (show.recurrence === 'daily') {
|
||||
return base + 24 * 60 * 60 * 1000;
|
||||
}
|
||||
|
||||
if (show.recurrence === 'weekly') {
|
||||
return base + 7 * 24 * 60 * 60 * 1000;
|
||||
}
|
||||
const recurrence = show.recurrence;
|
||||
const timezone = show.timezone || 'UTC';
|
||||
|
||||
if (recurrence !== 'daily' && recurrence !== 'weekly') {
|
||||
return base;
|
||||
}
|
||||
|
||||
const daysToAdd = recurrence === 'weekly' ? 7 : 1;
|
||||
const source = new Date(base);
|
||||
const local = toZonedParts(source, timezone);
|
||||
if (!local) {
|
||||
return base + (daysToAdd * 24 * 60 * 60 * 1000);
|
||||
}
|
||||
|
||||
const targetDate = addDaysUTC(local.year, local.month, local.day, daysToAdd);
|
||||
const zonedTarget = {
|
||||
year: targetDate.year,
|
||||
month: targetDate.month,
|
||||
day: targetDate.day,
|
||||
hour: local.hour,
|
||||
minute: local.minute,
|
||||
second: local.second
|
||||
};
|
||||
|
||||
const next = zonedDateTimeToUtc(zonedTarget, timezone);
|
||||
return next || (base + (daysToAdd * 24 * 60 * 60 * 1000));
|
||||
}
|
||||
|
||||
function toZonedParts(date, timezone) {
|
||||
try {
|
||||
const dtf = new Intl.DateTimeFormat('en-US', {
|
||||
timeZone: timezone,
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false
|
||||
});
|
||||
const parts = dtf.formatToParts(date);
|
||||
const out = {};
|
||||
for (const part of parts) {
|
||||
if (part.type === 'literal') continue;
|
||||
out[part.type] = parseInt(part.value, 10);
|
||||
}
|
||||
|
||||
return {
|
||||
year: out.year,
|
||||
month: out.month,
|
||||
day: out.day,
|
||||
hour: out.hour,
|
||||
minute: out.minute,
|
||||
second: out.second
|
||||
};
|
||||
} catch (_err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function addDaysUTC(year, month, day, days) {
|
||||
const d = new Date(Date.UTC(year, month - 1, day + days));
|
||||
return {
|
||||
year: d.getUTCFullYear(),
|
||||
month: d.getUTCMonth() + 1,
|
||||
day: d.getUTCDate()
|
||||
};
|
||||
}
|
||||
|
||||
function zonedDateTimeToUtc(local, timezone) {
|
||||
let guess = Date.UTC(local.year, local.month - 1, local.day, local.hour, local.minute, local.second);
|
||||
|
||||
// Iterate to resolve timezone offset for the target wall-clock time (handles DST shifts).
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const zoned = toZonedParts(new Date(guess), timezone);
|
||||
if (!zoned) return null;
|
||||
|
||||
const desired = Date.UTC(local.year, local.month - 1, local.day, local.hour, local.minute, local.second);
|
||||
const current = Date.UTC(zoned.year, zoned.month - 1, zoned.day, zoned.hour, zoned.minute, zoned.second);
|
||||
const delta = desired - current;
|
||||
if (delta === 0) {
|
||||
return guess;
|
||||
}
|
||||
guess += delta;
|
||||
}
|
||||
|
||||
return guess;
|
||||
}
|
||||
|
||||
function normalizePlaylist(rawPlaylist) {
|
||||
|
|
|
|||
|
|
@ -13,10 +13,13 @@ exports.init = function csrfInit (domain) {
|
|||
var secret = req.signedCookies._csrf;
|
||||
if (!secret) {
|
||||
secret = tokens.secretSync();
|
||||
const secure = req.realProtocol === 'https' || req.secure === true;
|
||||
res.cookie("_csrf", secret, {
|
||||
domain: domain,
|
||||
signed: true,
|
||||
httpOnly: true
|
||||
httpOnly: true,
|
||||
sameSite: 'lax',
|
||||
secure
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -37,7 +40,9 @@ exports.init = function csrfInit (domain) {
|
|||
|
||||
exports.verify = function csrfVerify(req) {
|
||||
var secret = req.signedCookies._csrf;
|
||||
var token = req.body._csrf || req.query._csrf;
|
||||
var token = (req.body && req.body._csrf) ||
|
||||
(req.query && req.query._csrf) ||
|
||||
req.header('x-csrf-token');
|
||||
|
||||
if (!tokens.verify(secret, token)) {
|
||||
throw new CSRFError('Invalid CSRF token');
|
||||
|
|
|
|||
|
|
@ -38,10 +38,13 @@ export function ipSessionCookieMiddleware(req, res, next) {
|
|||
}
|
||||
|
||||
if (!hasSession) {
|
||||
const secure = req.realProtocol === 'https' || req.secure === true;
|
||||
res.cookie('ip-session', createIPSessionCookie(req.realIP, firstSeen), {
|
||||
signed: true,
|
||||
httpOnly: true,
|
||||
expires: NO_EXPIRATION
|
||||
expires: NO_EXPIRATION,
|
||||
sameSite: 'lax',
|
||||
secure
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,8 +26,11 @@ async function getChannelEmotes(channelId) {
|
|||
(err, rows) => {
|
||||
if (err) return reject(new Error(err));
|
||||
if (!rows || rows.length === 0) return resolve([]);
|
||||
try { resolve(JSON.parse(rows[0].value)); }
|
||||
catch (e) { resolve([]); }
|
||||
try {
|
||||
resolve(JSON.parse(rows[0].value));
|
||||
} catch (e) {
|
||||
resolve([]);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
@ -92,7 +95,6 @@ router.put('/:name', botAuth, requireRank(4), async (req, res) => {
|
|||
return res.status(409).json({ error: 'An emote with that name already exists' });
|
||||
}
|
||||
|
||||
const old = emotes[idx];
|
||||
emotes[idx] = validated;
|
||||
await saveChannelEmotes(req.bot.channel_id, emotes);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,30 @@
|
|||
const express = require('express');
|
||||
const csrf = require('../../csrf');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
function isMutatingMethod(method) {
|
||||
return method === 'POST' || method === 'PUT' || method === 'PATCH' || method === 'DELETE';
|
||||
}
|
||||
|
||||
function hasBotBearerToken(req) {
|
||||
const authHeader = req.headers && req.headers.authorization;
|
||||
return typeof authHeader === 'string' && /^Bearer\s+cbt_/.test(authHeader);
|
||||
}
|
||||
|
||||
router.use((req, res, next) => {
|
||||
if (!isMutatingMethod(req.method) || hasBotBearerToken(req)) {
|
||||
return next();
|
||||
}
|
||||
|
||||
try {
|
||||
csrf.verify(req);
|
||||
next();
|
||||
} catch (_err) {
|
||||
return res.status(403).json({ error: 'Invalid CSRF token' });
|
||||
}
|
||||
});
|
||||
|
||||
router.use('/channels/:channel/bots', require('./bots'));
|
||||
router.use('/channels/:channel/emotes', require('./emotes'));
|
||||
router.use('/channels/:channel/playlist', require('./playlist'));
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ async function getChannelRow(channelName) {
|
|||
return new Promise((resolve, reject) => {
|
||||
db.channels.lookup(channelName, (err, row) => {
|
||||
if (err) reject(new Error(err));
|
||||
else if (!row) reject(new Error('Channel not found'));
|
||||
else resolve(row);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -138,6 +138,11 @@ async function authorizeChannel(req, res) {
|
|||
return null;
|
||||
}
|
||||
|
||||
if (bot.rank < 2) {
|
||||
res.status(403).json({ error: 'Insufficient rank' });
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
actorName: bot.name,
|
||||
rank: bot.rank,
|
||||
|
|
|
|||
|
|
@ -241,21 +241,23 @@ module.exports = {
|
|||
},
|
||||
|
||||
setAuthCookie: function setAuthCookie(req, res, expiration, auth) {
|
||||
const secure = req.realProtocol === 'https' || req.secure === true;
|
||||
const baseCookieOptions = {
|
||||
expires: expiration,
|
||||
httpOnly: true,
|
||||
signed: true,
|
||||
sameSite: 'lax',
|
||||
secure
|
||||
};
|
||||
|
||||
if (req.hostname.indexOf(Config.get("http.root-domain")) >= 0) {
|
||||
// Prevent non-root cookie from screwing things up
|
||||
res.clearCookie("auth");
|
||||
res.cookie("auth", auth, {
|
||||
res.cookie("auth", auth, Object.assign({}, baseCookieOptions, {
|
||||
domain: Config.get("http.root-domain-dotted"),
|
||||
expires: expiration,
|
||||
httpOnly: true,
|
||||
signed: true
|
||||
});
|
||||
}));
|
||||
} else {
|
||||
res.cookie("auth", auth, {
|
||||
expires: expiration,
|
||||
httpOnly: true,
|
||||
signed: true
|
||||
});
|
||||
res.cookie("auth", auth, baseCookieOptions);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -15,10 +15,12 @@ mixin head()
|
|||
var DEFAULT_THEME = '#{DEFAULT_THEME}';
|
||||
var CHANNELPATH = '#{channelPath}';
|
||||
var CHANNELNAME = '#{channelName}';
|
||||
var CSRF_TOKEN = '#{csrfToken}';
|
||||
else
|
||||
script(type="text/javascript").
|
||||
var DEFAULT_THEME = '#{DEFAULT_THEME}';
|
||||
var CHANNELPATH = '#{channelPath}';
|
||||
var CSRF_TOKEN = '#{csrfToken}';
|
||||
|
||||
script(src="/js/theme.js")
|
||||
//[if lt IE 9]
|
||||
|
|
|
|||
21
www/js/ui.js
21
www/js/ui.js
|
|
@ -1257,6 +1257,27 @@ $("#resize-video-smaller").on('click', function () {
|
|||
}
|
||||
});
|
||||
|
||||
$.ajaxPrefilter(function (options, _originalOptions, _jqXHR) {
|
||||
var url = String(options.url || '');
|
||||
if (!/\/api\/v1\//.test(url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var method = String(options.type || options.method || 'GET').toUpperCase();
|
||||
if (method === 'GET' || method === 'HEAD' || method === 'OPTIONS') {
|
||||
return;
|
||||
}
|
||||
|
||||
options.headers = options.headers || {};
|
||||
if (options.headers.Authorization || options.headers.authorization) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof CSRF_TOKEN === 'string' && CSRF_TOKEN.length > 0) {
|
||||
options.headers['X-CSRF-Token'] = CSRF_TOKEN;
|
||||
}
|
||||
});
|
||||
|
||||
var CSTBots = (function () {
|
||||
function apiBase() {
|
||||
return '/api/v1/channels/' + CHANNEL.name;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue