diff --git a/package.json b/package.json index ec12dfb62..0a2677e83 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,10 @@ "png-chunks-encode": "^1.0.0", "png-chunks-extract": "^1.0.0", "rimraf": "^3.0.2", - "sharp": "^0.31.3" + "sharp": "^0.31.3", + "csrf-csrf": "^2.2.3", + "cookie-parser": "^1.4.6", + "cors": "^2.8.5" }, "name": "TavernAI", "version": "1.2.7", diff --git a/public/index.html b/public/index.html index e19a7131c..cc638ce37 100644 --- a/public/index.html +++ b/public/index.html @@ -3,6 +3,7 @@ + @@ -158,15 +159,23 @@ }, 500); ///////////// - getSettings("def"); - getLastVersion(); - //var interval_getSettings = setInterval(getSettings, 1000); - getCharacters(); + var token; + $.ajaxPrefilter((options, originalOptions, xhr) => { + xhr.setRequestHeader("X-CSRF-Token", token); + }); + + $.get("/csrf-token") + .then(data => { + token = data.token; + getSettings("def"); + getLastVersion(); + getCharacters(); - printMessages(); - getBackgrounds(); - getUserAvatars(); - // + printMessages(); + getBackgrounds(); + getUserAvatars(); + }); + $('#characloud_url').click(function(){ window.open('https://boosty.to/tavernai', '_blank'); }); @@ -306,7 +315,10 @@ const response = await fetch("/getcharacters", { method: "POST", - headers: { "Content-Type": "application/json" }, + headers: { + "Content-Type": "application/json", + "X-CSRF-Token": token, + }, body: JSON.stringify({                      "": ""                  }) @@ -340,7 +352,10 @@ const response = await fetch("/getbackgrounds", { method: "POST", - headers: { "Content-Type": "application/json" }, + headers: { + "Content-Type": "application/json", + "X-CSRF-Token": token + }, body: JSON.stringify({                      "": ""                  }) @@ -364,7 +379,10 @@ is_checked_colab = true; const response = await fetch("/iscolab", { method: "POST", - headers: { "Content-Type": "application/json" }, + headers: { + "Content-Type": "application/json", + "X-CSRF-Token": token + }, body: JSON.stringify({                      "": ""                  }) @@ -430,7 +448,10 @@ async function delBackground(bg) { const response = await fetch("/delbackground", { method: "POST", - headers: { "Content-Type": "application/json" }, + headers: { + "Content-Type": "application/json", + "X-CSRF-Token": token + }, body: JSON.stringify({                      "bg": bg                  }) @@ -2017,7 +2038,10 @@ async function getUserAvatars(){ const response = await fetch("/getuseravatars", { method: "POST", - headers: { "Content-Type": "application/json" }, + headers: { + "Content-Type": "application/json", + "X-CSRF-Token": token + }, body: JSON.stringify({                      "": ""                  }) diff --git a/public/scripts/jquery-cookie-1.4.1.min.js b/public/scripts/jquery-cookie-1.4.1.min.js new file mode 100644 index 000000000..c0f19d8a3 --- /dev/null +++ b/public/scripts/jquery-cookie-1.4.1.min.js @@ -0,0 +1,2 @@ +/*! jquery.cookie v1.4.1 | MIT */ +!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof exports?a(require("jquery")):a(jQuery)}(function(a){function b(a){return h.raw?a:encodeURIComponent(a)}function c(a){return h.raw?a:decodeURIComponent(a)}function d(a){return b(h.json?JSON.stringify(a):String(a))}function e(a){0===a.indexOf('"')&&(a=a.slice(1,-1).replace(/\\"/g,'"').replace(/\\\\/g,"\\"));try{return a=decodeURIComponent(a.replace(g," ")),h.json?JSON.parse(a):a}catch(b){}}function f(b,c){var d=h.raw?b:e(b);return a.isFunction(c)?c(d):d}var g=/\+/g,h=a.cookie=function(e,g,i){if(void 0!==g&&!a.isFunction(g)){if(i=a.extend({},h.defaults,i),"number"==typeof i.expires){var j=i.expires,k=i.expires=new Date;k.setTime(+k+864e5*j)}return document.cookie=[b(e),"=",d(g),i.expires?"; expires="+i.expires.toUTCString():"",i.path?"; path="+i.path:"",i.domain?"; domain="+i.domain:"",i.secure?"; secure":""].join("")}for(var l=e?void 0:{},m=document.cookie?document.cookie.split("; "):[],n=0,o=m.length;o>n;n++){var p=m[n].split("="),q=c(p.shift()),r=p.join("=");if(e&&e===q){l=f(r,g);break}e||void 0===(r=f(r))||(l[q]=r)}return l};h.defaults={},a.removeCookie=function(b,c){return void 0===a.cookie(b)?!1:(a.cookie(b,"",a.extend({},c,{expires:-1})),!a.cookie(b))}}); \ No newline at end of file diff --git a/server.js b/server.js index c11fd6824..7ab5120d9 100644 --- a/server.js +++ b/server.js @@ -16,6 +16,10 @@ const sharp = require('sharp'); sharp.cache(false); const path = require('path'); +const cookieParser = require('cookie-parser'); +const crypto = require('crypto'); + + const config = require('./config.json'); const server_port = config.port; const whitelist = config.whitelist; @@ -54,6 +58,40 @@ if (is_colab && process.env.googledrive == 2){ const jsonParser = express.json({limit: '100mb'}); const urlencodedParser = express.urlencoded({extended: true, limit: '100mb'}); +// CSRF Protection // +const doubleCsrf = require('csrf-csrf').doubleCsrf; + +const CSRF_SECRET = crypto.randomBytes(8).toString('hex'); +const COOKIES_SECRET = crypto.randomBytes(8).toString('hex'); + +const { invalidCsrfTokenError, generateToken, doubleCsrfProtection } = doubleCsrf({ + getSecret: () => CSRF_SECRET, + cookieName: "X-CSRF-Token", + cookieOptions: { + httpOnly: true, + sameSite: "strict", + }, + size: 64, + getTokenFromRequest: (req) => req.headers["x-csrf-token"] +}); + +app.get("/csrf-token", (req, res) => { + res.json({ + "token": generateToken(res) + }); +}); + +app.use(cookieParser(COOKIES_SECRET)); +app.use(doubleCsrfProtection); + +// CORS Settings // +const cors = require('cors'); +const CORS = cors({ + origin: 'null', + methods: ['OPTIONS'] +}) + +app.use(CORS); app.use(function (req, res, next) { //Security const clientIp = req.connection.remoteAddress.split(':').pop(); @@ -909,14 +947,14 @@ app.post("/importcharacter", urlencodedParser, function(request, response){ } const jsonData = JSON.parse(data); - if(jsonData.char_name !== undefined){//json Pygmalion notepad - png_name = getPngName(jsonData.char_name); - var char = {"name": jsonData.char_name, "description": jsonData.char_persona, "personality": '', "first_mes": jsonData.char_greeting, "avatar": 'none', "chat": Date.now(), "mes_example": jsonData.example_dialogue, "scenario": jsonData.world_scenario, "create_date": Date.now()}; + if(jsonData.name !== undefined){ + png_name = getPngName(jsonData.name); + var char = {"name": jsonData.name, "description": jsonData.description ?? '', "personality": jsonData.personality ?? '', "first_mes": jsonData.first_mes ?? '', "avatar": 'none', "chat": Date.now(), "mes_example": jsonData.mes_example ?? '', "scenario": jsonData.scenario ?? '', "create_date": Date.now()}; char = JSON.stringify(char); charaWrite('./public/img/fluffy.png', char, png_name, response, {file_name: png_name}); - }else if(jsonData.name !== undefined){ - png_name = getPngName(jsonData.name); - var char = {"name": jsonData.name, "description": jsonData.description, "personality": jsonData.personality, "first_mes": jsonData.first_mes, "avatar": 'none', "chat": Date.now(), "mes_example": '', "scenario": '', "create_date": Date.now()}; + }else if(jsonData.char_name !== undefined){//json Pygmalion notepad + png_name = getPngName(jsonData.char_name); + var char = {"name": jsonData.char_name, "description": jsonData.char_persona ?? '', "personality": '', "first_mes": jsonData.char_greeting ?? '', "avatar": 'none', "chat": Date.now(), "mes_example": jsonData.example_dialogue ?? '', "scenario": jsonData.world_scenario ?? '', "create_date": Date.now()}; char = JSON.stringify(char); charaWrite('./public/img/fluffy.png', char, png_name, response, {file_name: png_name}); }else{