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