Added CSRF protection to all API calls. /api/account AJAX calls updated.
This commit is contained in:
parent
7e0c8e72c5
commit
106b0fcddb
30
src/controllers/404Controller.js
Normal file
30
src/controllers/404Controller.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2025 Rainbownapkin and the TTN Community
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
||||
|
||||
//Config
|
||||
const config = require('../../config.json');
|
||||
|
||||
//Local Imports
|
||||
const csrfUtils = require('../utils/csrfUtils');
|
||||
|
||||
//register page functions
|
||||
module.exports = async function(req, res, next){
|
||||
//set status
|
||||
res.status(404);
|
||||
|
||||
//Render page
|
||||
return res.render('404', {instance: config.instanceName, user: req.session.user, csrfToken: csrfUtils.generateToken(req)});
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
|||
const accountUtils = require('../../../utils/sessionUtils');
|
||||
const {exceptionHandler, errorHandler} = require('../../../utils/loggerUtils');
|
||||
|
||||
module.exports.get = async function(req, res){
|
||||
module.exports.post = async function(req, res){
|
||||
if(req.session.user){
|
||||
try{
|
||||
accountUtils.killSession(req.session);
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ const router = Router();
|
|||
//login
|
||||
router.post('/login', accountValidator.user(), accountValidator.pass(), loginController.post);
|
||||
//logout
|
||||
router.get('/logout', logoutController.get);
|
||||
router.post('/logout', logoutController.post);
|
||||
//register
|
||||
router.post('/register', accountValidator.user(),
|
||||
accountValidator.securePass(),
|
||||
|
|
|
|||
|
|
@ -21,10 +21,14 @@ const { Router } = require('express');
|
|||
const accountRouter = require("./api/accountRouter");
|
||||
const channelRouter = require("./api/channelRouter");
|
||||
const adminRouter = require("./api/adminRouter");
|
||||
const csrfUtil = require('../utils/csrfUtils');
|
||||
|
||||
//globals
|
||||
const router = Router();
|
||||
|
||||
//Apply Cross-Site Request Forgery protection to API calls
|
||||
router.use(csrfUtil.csrfSynchronisedProtection);
|
||||
|
||||
//routing functions
|
||||
router.use('/account', accountRouter);
|
||||
router.use('/channel', channelRouter);
|
||||
|
|
|
|||
|
|
@ -409,10 +409,13 @@ userSchema.methods.getAuthenticatedSessions = async function(){
|
|||
|
||||
//crawl through active sessions
|
||||
sessions.forEach((session) => {
|
||||
//if a session matches the current user
|
||||
if(session.user.id == this.id){
|
||||
//we return it
|
||||
returnArr.push(session);
|
||||
//Skip un-authed sessions
|
||||
if(session.user != null){
|
||||
//if a session matches the current user
|
||||
if(session.user.id == this.id){
|
||||
//we return it
|
||||
returnArr.push(session);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -32,11 +32,14 @@ const channelManager = require('./app/channel/channelManager');
|
|||
//Util
|
||||
const configCheck = require('./utils/configCheck');
|
||||
const scheduler = require('./utils/scheduler');
|
||||
const {errorMiddleware} = require('./utils/loggerUtils');
|
||||
//DB Model
|
||||
const statModel = require('./schemas/statSchema');
|
||||
const flairModel = require('./schemas/flairSchema');
|
||||
const emoteModel = require('./schemas/emoteSchema');
|
||||
const tokeCommandModel = require('./schemas/tokebot/tokeCommandSchema');
|
||||
//Controller
|
||||
const fileNotFoundController = require('./controllers/404Controller');
|
||||
//Router
|
||||
//Humie-Friendly
|
||||
const indexRouter = require('./routers/indexRouter');
|
||||
|
|
@ -132,6 +135,14 @@ app.use('/lib/altcha',express.static(path.join(__dirname, '../node_modules/altch
|
|||
//Server public 'www' folder
|
||||
app.use(express.static(path.join(__dirname, '../www')));
|
||||
|
||||
//Handle error checking
|
||||
app.use(errorMiddleware);
|
||||
|
||||
//Basic 404 handler
|
||||
app.use(fileNotFoundController);
|
||||
|
||||
|
||||
|
||||
//Increment launch counter
|
||||
statModel.incrementLaunchCount();
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
|||
//NPM Imports
|
||||
const { csrfSync } = require('csrf-sync');
|
||||
|
||||
//Local Imports
|
||||
const {errorHandler} = require('./loggerUtils');
|
||||
|
||||
//Pull needed methods from csrfSync
|
||||
const {generateToken, revokeToken, csrfSynchronisedProtection,} = csrfSync();
|
||||
const {generateToken, revokeToken, csrfSynchronisedProtection} = csrfSync();
|
||||
|
||||
//Export them per csrfSync documentation
|
||||
module.exports.generateToken = generateToken;
|
||||
|
|
|
|||
|
|
@ -38,4 +38,17 @@ module.exports.socketCriticalExceptionHandler = function(socket, err){
|
|||
|
||||
module.exports.consoleWarn = function(string){
|
||||
console.warn('\x1b[31m\x1b[4m%s\x1b[0m',string);
|
||||
}
|
||||
|
||||
//Basic error-handling middleware to ensure we're not dumping stack traces
|
||||
module.exports.errorMiddleware = function(err, req, res, next){
|
||||
//Set generic error
|
||||
var reason = "Server Error";
|
||||
|
||||
//If it's un-authorized
|
||||
if(err.status == 403){
|
||||
reason = "Unauthorized"
|
||||
}
|
||||
|
||||
module.exports.errorHandler(res, err.message, reason, err.status);
|
||||
}
|
||||
33
src/views/404.ejs
Normal file
33
src/views/404.ejs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<%# Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2025 Rainbownapkin and the TTN Community
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. %>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<%- include('partial/styles', {instance, user}); %>
|
||||
<%- include('partial/csrfToken', {csrfToken}); %>
|
||||
<link rel="stylesheet" type="text/css" href="css/error.css">
|
||||
<title><%= instance %></title>
|
||||
</head>
|
||||
<body>
|
||||
<%- include('partial/navbar', {user}); %>
|
||||
<h1>404</h1>
|
||||
<h3>Congratulations, you've found a dead link!</h3>
|
||||
<img src="https://web.archive.org/web/20091027105418/http://geocities.com/cd_franks_elementary/graphics/cdcongratulationsspinning.gif">
|
||||
</body>
|
||||
<footer>
|
||||
<%- include('partial/scripts', {user}); %>
|
||||
</footer>
|
||||
</html>
|
||||
23
www/css/error.css
Normal file
23
www/css/error.css
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2025 Rainbownapkin and the TTN Community
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
||||
h1, h3{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
img{
|
||||
width: 50%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
|
@ -395,7 +395,9 @@ class canopyAjaxUtils{
|
|||
var response = await fetch(`/api/account/register`,{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
"Content-Type": "application/json",
|
||||
//It's either this or find and bind all event listeners :P
|
||||
"x-csrf-token": utils.ajax.getCSRFToken()
|
||||
},
|
||||
body: JSON.stringify(email ? {user, pass, passConfirm, email, verification} : {user, pass, passConfirm, verification})
|
||||
});
|
||||
|
|
@ -411,7 +413,8 @@ class canopyAjaxUtils{
|
|||
var response = await fetch(`/api/account/login`,{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
"Content-Type": "application/json",
|
||||
"x-csrf-token": utils.ajax.getCSRFToken()
|
||||
},
|
||||
body: JSON.stringify(verification ? {user, pass, verification} : {user, pass})
|
||||
});
|
||||
|
|
@ -427,7 +430,10 @@ class canopyAjaxUtils{
|
|||
|
||||
async logout(){
|
||||
var response = await fetch(`/api/account/logout`,{
|
||||
method: "GET",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"x-csrf-token": utils.ajax.getCSRFToken()
|
||||
}
|
||||
});
|
||||
|
||||
if(response.status == 200){
|
||||
|
|
@ -441,7 +447,8 @@ class canopyAjaxUtils{
|
|||
const response = await fetch(`/api/account/update`,{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
"Content-Type": "application/json",
|
||||
"x-csrf-token": utils.ajax.getCSRFToken()
|
||||
},
|
||||
body: JSON.stringify(update)
|
||||
});
|
||||
|
|
@ -469,7 +476,8 @@ class canopyAjaxUtils{
|
|||
const response = await fetch(`/api/account/delete`,{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
"Content-Type": "application/json",
|
||||
"x-csrf-token": utils.ajax.getCSRFToken()
|
||||
},
|
||||
body: JSON.stringify({pass})
|
||||
});
|
||||
|
|
@ -485,7 +493,8 @@ class canopyAjaxUtils{
|
|||
const response = await fetch(`/api/account/passwordResetRequest`,{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
"Content-Type": "application/json",
|
||||
"x-csrf-token": utils.ajax.getCSRFToken()
|
||||
},
|
||||
body: JSON.stringify({user, verification})
|
||||
});
|
||||
|
|
@ -506,7 +515,8 @@ class canopyAjaxUtils{
|
|||
const response = await fetch(`/api/account/passwordReset`,{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
"Content-Type": "application/json",
|
||||
"x-csrf-token": utils.ajax.getCSRFToken()
|
||||
},
|
||||
body: JSON.stringify({token, pass, confirmPass, verification})
|
||||
});
|
||||
|
|
@ -782,6 +792,11 @@ class canopyAjaxUtils{
|
|||
}
|
||||
}
|
||||
|
||||
//Syntatic sugar
|
||||
getCSRFToken(){
|
||||
return document.querySelector("[name='csrf-token']").content;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const utils = new canopyUtils()
|
||||
Loading…
Reference in a new issue