From 1f00bacb6fd6fe28e33a2a985dceb9d8c4e762f4 Mon Sep 17 00:00:00 2001 From: rainbow napkin Date: Sun, 4 May 2025 17:52:55 -0400 Subject: [PATCH] Improved CSRF handling --- .../api/refreshCSRFTokenController.js | 31 ++++++++++++++++++ src/routers/apiRouter.js | 7 +++- www/js/channel/channel.js | 12 +++++-- www/js/utils.js | 32 ++++++++++++++++--- 4 files changed, 74 insertions(+), 8 deletions(-) create mode 100644 src/controllers/api/refreshCSRFTokenController.js diff --git a/src/controllers/api/refreshCSRFTokenController.js b/src/controllers/api/refreshCSRFTokenController.js new file mode 100644 index 0000000..3f16a83 --- /dev/null +++ b/src/controllers/api/refreshCSRFTokenController.js @@ -0,0 +1,31 @@ +/*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 .*/ + +//local imports +const {exceptionHandler, errorHandler} = require('../../utils/loggerUtils'); +const csrfUtils = require('../../utils/csrfUtils'); + +//api account functions +module.exports.get = async function(req, res){ + try{ + //Set status to 200 + res.status(200); + //Generate and send token based on the request + res.send({token: csrfUtils.generateToken(req)}); + }catch(err){ + return exceptionHandler(res, err); + } +} \ No newline at end of file diff --git a/src/routers/apiRouter.js b/src/routers/apiRouter.js index 0512102..8e7be47 100644 --- a/src/routers/apiRouter.js +++ b/src/routers/apiRouter.js @@ -18,14 +18,19 @@ along with this program. If not, see .*/ const { Router } = require('express'); //local imports +const csrfUtil = require('../utils/csrfUtils'); const accountRouter = require("./api/accountRouter"); const channelRouter = require("./api/channelRouter"); const adminRouter = require("./api/adminRouter"); -const csrfUtil = require('../utils/csrfUtils'); +const refreshCSRFTokenController = require("../controllers/api/refreshCSRFTokenController"); //globals const router = Router(); + +//CSRF token request controller +router.get('/refreshToken', refreshCSRFTokenController.get); + //Apply Cross-Site Request Forgery protection to API calls router.use(csrfUtil.csrfSynchronisedProtection); diff --git a/www/js/channel/channel.js b/www/js/channel/channel.js index 9ce7f9a..e4e5eda 100644 --- a/www/js/channel/channel.js +++ b/www/js/channel/channel.js @@ -48,8 +48,16 @@ class channel{ document.title = `${this.channelName} - Connected` }); - this.socket.on("kick", (data) => { - new canopyUXUtils.popup(`You have been ${data.type} from the channel for the following reason:
${data.reason}`); + this.socket.on("kick", async (data) => { + if(data.reason == "Invalid CSRF Token!"){ + //Reload the CSRF token + await utils.ajax.reloadCSRFToken(); + + //Retry the connection + this.connect(); + }else{ + new canopyUXUtils.popup(`You have been ${data.type} from the channel for the following reason:
${data.reason}`); + } }); this.socket.on("clientMetadata", this.handleClientInfo.bind(this)); diff --git a/www/js/utils.js b/www/js/utils.js index f93e2b7..d724823 100644 --- a/www/js/utils.js +++ b/www/js/utils.js @@ -105,11 +105,18 @@ class canopyUXUtils{ try{ const errors = body.errors; errors.forEach((err)=>{ - //Capitalize the first letter of the type - const type = `${err.type[0].toUpperCase()}${err.type.slice(1)}` + if(err.msg == "invalid csrf token"){ + //reload CSRF token + utils.ajax.reloadCSRFToken(); - //Display our error - new canopyUXUtils.popup(`

${type} Error:


${err.msg}

`); + new canopyUXUtils.popup(`

CSRF Error:


Bad CSRF token, try again!

`); + }else{ + //Capitalize the first letter of the type + const type = `${err.type[0].toUpperCase()}${err.type.slice(1)}` + + //Display our error + new canopyUXUtils.popup(`

${type} Error:


${err.msg}

`); + } }); }catch(err){ console.error("Display Error Body:"); @@ -1056,12 +1063,27 @@ class canopyAjaxUtils{ } } - //Syntatic sugar getCSRFToken(){ return document.querySelector("[name='csrf-token']").content; } + async reloadCSRFToken(){ + //Fetch a new token + var response = await fetch('/api/refreshToken',{ + method: "GET" + }); + + if(response.ok){ + //Get data from fetch + let data = await response.json() + //Inject new token into the page + document.querySelector("[name='csrf-token']").content = data.token; + }else{ + utils.ux.displayResponseError(await response.json()); + } + } + } const utils = new canopyUtils() \ No newline at end of file