Implement CSRF protection

This commit is contained in:
AAGaming
2022-08-05 21:16:29 -04:00
parent ab6ec98160
commit f21d34506d
7 changed files with 95 additions and 21 deletions
+14 -1
View File
@@ -1,7 +1,20 @@
from aiohttp.web import middleware, Response
import ssl import ssl
import certifi import certifi
import uuid
ssl_ctx = ssl.create_default_context(cafile=certifi.where()) ssl_ctx = ssl.create_default_context(cafile=certifi.where())
csrf_token = str(uuid.uuid4())
def get_ssl_context(): def get_ssl_context():
return ssl_ctx return ssl_ctx
def get_csrf_token():
return csrf_token
@middleware
async def csrf_middleware(request, handler):
if str(request.method) == "OPTIONS" or request.headers.get('Authentication') == csrf_token or str(request.rel_url) == "/auth/token" or str(request.rel_url).startswith("/plugins/load_main/") or str(request.rel_url).startswith("/static/") or str(request.rel_url).startswith("/legacy/") or str(request.rel_url).startswith("/steam_resource/"):
return await handler(request)
return Response(text='Forbidden', status='403')
+7 -1
View File
@@ -8,10 +8,13 @@ window.addEventListener("message", function(evt) {
}, false); }, false);
async function call_server_method(method_name, arg_object={}) { async function call_server_method(method_name, arg_object={}) {
const token = await fetch("http://127.0.0.1:1337/auth/token").then(r => r.text());
const response = await fetch(`http://127.0.0.1:1337/methods/${method_name}`, { const response = await fetch(`http://127.0.0.1:1337/methods/${method_name}`, {
method: 'POST', method: 'POST',
credentials: "include",
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Authentication: token
}, },
body: JSON.stringify(arg_object), body: JSON.stringify(arg_object),
}); });
@@ -40,10 +43,13 @@ async function fetch_nocors(url, request={}) {
async function call_plugin_method(method_name, arg_object={}) { async function call_plugin_method(method_name, arg_object={}) {
if (plugin_name == undefined) if (plugin_name == undefined)
throw new Error("Plugin methods can only be called from inside plugins (duh)"); throw new Error("Plugin methods can only be called from inside plugins (duh)");
const token = await fetch("http://127.0.0.1:1337/auth/token").then(r => r.text());
const response = await fetch(`http://127.0.0.1:1337/plugins/${plugin_name}/methods/${method_name}`, { const response = await fetch(`http://127.0.0.1:1337/plugins/${plugin_name}/methods/${method_name}`, {
method: 'POST', method: 'POST',
credentials: "include",
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Authentication: token
}, },
body: JSON.stringify({ body: JSON.stringify({
args: arg_object, args: arg_object,
+9 -2
View File
@@ -20,12 +20,13 @@ from os import path
from subprocess import call from subprocess import call
import aiohttp_cors import aiohttp_cors
from aiohttp.web import Application, run_app, static from aiohttp.web import Application, run_app, static, get, Response
from aiohttp_jinja2 import setup as jinja_setup from aiohttp_jinja2 import setup as jinja_setup
from browser import PluginBrowser from browser import PluginBrowser
from injector import inject_to_tab, tab_has_global_var from injector import inject_to_tab, tab_has_global_var
from loader import Loader from loader import Loader
from helpers import csrf_middleware, get_csrf_token
from utilities import Utilities from utilities import Utilities
from updater import Updater from updater import Updater
@@ -41,9 +42,10 @@ class PluginManager:
def __init__(self) -> None: def __init__(self) -> None:
self.loop = get_event_loop() self.loop = get_event_loop()
self.web_app = Application() self.web_app = Application()
self.web_app.middlewares.append(csrf_middleware)
self.cors = aiohttp_cors.setup(self.web_app, defaults={ self.cors = aiohttp_cors.setup(self.web_app, defaults={
"https://steamloopback.host": aiohttp_cors.ResourceOptions(expose_headers="*", "https://steamloopback.host": aiohttp_cors.ResourceOptions(expose_headers="*",
allow_headers="*") allow_headers="*", allow_credentials=True)
}) })
self.plugin_loader = Loader(self.web_app, CONFIG["plugin_path"], self.loop, CONFIG["live_reload"]) self.plugin_loader = Loader(self.web_app, CONFIG["plugin_path"], self.loop, CONFIG["live_reload"])
self.plugin_browser = PluginBrowser(CONFIG["plugin_path"], self.web_app, self.plugin_loader.plugins) self.plugin_browser = PluginBrowser(CONFIG["plugin_path"], self.web_app, self.plugin_loader.plugins)
@@ -57,6 +59,8 @@ class PluginManager:
self.loop.create_task(self.loader_reinjector()) self.loop.create_task(self.loader_reinjector())
self.loop.create_task(self.load_plugins()) self.loop.create_task(self.load_plugins())
self.loop.set_exception_handler(self.exception_handler) self.loop.set_exception_handler(self.exception_handler)
self.web_app.add_routes([get("/auth/token", self.get_auth_token)])
for route in list(self.web_app.router.routes()): for route in list(self.web_app.router.routes()):
self.cors.add(route) self.cors.add(route)
self.web_app.add_routes([static("/static", path.join(path.dirname(__file__), 'static'))]) self.web_app.add_routes([static("/static", path.join(path.dirname(__file__), 'static'))])
@@ -67,6 +71,9 @@ class PluginManager:
return return
loop.default_exception_handler(context) loop.default_exception_handler(context)
async def get_auth_token(self, request):
return Response(text=get_csrf_token())
async def wait_for_server(self): async def wait_for_server(self):
async with ClientSession() as web: async with ClientSession() as web:
while True: while True:
+26 -2
View File
@@ -35,6 +35,10 @@ export async function installFromURL(url: string) {
await fetch('http://localhost:1337/browser/install_plugin', { await fetch('http://localhost:1337/browser/install_plugin', {
method: 'POST', method: 'POST',
body: formData, body: formData,
credentials: 'include',
headers: {
Authentication: window.deckyAuthToken,
},
}); });
} }
@@ -50,6 +54,10 @@ export function requestLegacyPluginInstall(plugin: LegacyStorePlugin, selectedVe
fetch('http://localhost:1337/browser/install_plugin', { fetch('http://localhost:1337/browser/install_plugin', {
method: 'POST', method: 'POST',
body: formData, body: formData,
credentials: 'include',
headers: {
Authentication: window.deckyAuthToken,
},
}); });
}} }}
onCancel={() => { onCancel={() => {
@@ -75,6 +83,10 @@ export async function requestPluginInstall(plugin: StorePlugin, selectedVer: Sto
await fetch('http://localhost:1337/browser/install_plugin', { await fetch('http://localhost:1337/browser/install_plugin', {
method: 'POST', method: 'POST',
body: formData, body: formData,
credentials: 'include',
headers: {
Authentication: window.deckyAuthToken,
},
}); });
} }
@@ -84,12 +96,24 @@ const StorePage: FC<{}> = () => {
useEffect(() => { useEffect(() => {
(async () => { (async () => {
const res = await fetch('https://beta.deckbrew.xyz/plugins', { method: 'GET' }).then((r) => r.json()); const res = await fetch('https://beta.deckbrew.xyz/plugins', {
method: 'GET',
credentials: 'include',
headers: {
Authentication: window.deckyAuthToken,
},
}).then((r) => r.json());
console.log(res); console.log(res);
setData(res.filter((x: StorePlugin) => x.name !== 'Example Plugin')); setData(res.filter((x: StorePlugin) => x.name !== 'Example Plugin'));
})(); })();
(async () => { (async () => {
const res = await fetch('https://plugins.deckbrew.xyz/get_plugins', { method: 'GET' }).then((r) => r.json()); const res = await fetch('https://plugins.deckbrew.xyz/get_plugins', {
method: 'GET',
credentials: 'include',
headers: {
Authentication: window.deckyAuthToken,
},
}).then((r) => r.json());
console.log(res); console.log(res);
setLegacyData(res); setLegacyData(res);
})(); })();
+23 -14
View File
@@ -8,24 +8,33 @@ declare global {
importDeckyPlugin: Function; importDeckyPlugin: Function;
syncDeckyPlugins: Function; syncDeckyPlugins: Function;
deckyHasLoaded: boolean; deckyHasLoaded: boolean;
deckyAuthToken: string;
} }
} }
(async () => {
window.deckyAuthToken = await fetch('http://127.0.0.1:1337/auth/token').then((r) => r.text());
window.DeckyPluginLoader?.dismountAll(); window.DeckyPluginLoader?.dismountAll();
window.DeckyPluginLoader?.deinit(); window.DeckyPluginLoader?.deinit();
window.DeckyPluginLoader = new PluginLoader(); window.DeckyPluginLoader = new PluginLoader();
window.importDeckyPlugin = function (name: string) { window.importDeckyPlugin = function (name: string) {
window.DeckyPluginLoader?.importPlugin(name); window.DeckyPluginLoader?.importPlugin(name);
}; };
window.syncDeckyPlugins = async function () { window.syncDeckyPlugins = async function () {
const plugins = await (await fetch('http://127.0.0.1:1337/plugins')).json(); const plugins = await (
for (const plugin of plugins) { await fetch('http://127.0.0.1:1337/plugins', {
if (!window.DeckyPluginLoader.hasPlugin(plugin)) window.DeckyPluginLoader?.importPlugin(plugin); credentials: 'include',
} headers: { Authentication: window.deckyAuthToken },
}; })
).json();
for (const plugin of plugins) {
if (!window.DeckyPluginLoader.hasPlugin(plugin)) window.DeckyPluginLoader?.importPlugin(plugin);
}
};
setTimeout(() => window.syncDeckyPlugins(), 5000); setTimeout(() => window.syncDeckyPlugins(), 5000);
window.deckyHasLoaded = true; window.deckyHasLoaded = true;
})();
+14 -1
View File
@@ -75,6 +75,10 @@ class PluginLoader extends Logger {
await fetch('http://localhost:1337/browser/uninstall_plugin', { await fetch('http://localhost:1337/browser/uninstall_plugin', {
method: 'POST', method: 'POST',
body: formData, body: formData,
credentials: 'include',
headers: {
Authentication: window.deckyAuthToken,
},
}); });
}} }}
onCancel={() => { onCancel={() => {
@@ -144,7 +148,12 @@ class PluginLoader extends Logger {
} }
private async importReactPlugin(name: string) { private async importReactPlugin(name: string) {
let res = await fetch(`http://127.0.0.1:1337/plugins/${name}/frontend_bundle`); let res = await fetch(`http://127.0.0.1:1337/plugins/${name}/frontend_bundle`, {
credentials: 'include',
headers: {
Authentication: window.deckyAuthToken,
},
});
if (res.ok) { if (res.ok) {
let plugin = await eval(await res.text())(this.createPluginAPI(name)); let plugin = await eval(await res.text())(this.createPluginAPI(name));
this.plugins.push({ this.plugins.push({
@@ -166,8 +175,10 @@ class PluginLoader extends Logger {
async callServerMethod(methodName: string, args = {}) { async callServerMethod(methodName: string, args = {}) {
const response = await fetch(`http://127.0.0.1:1337/methods/${methodName}`, { const response = await fetch(`http://127.0.0.1:1337/methods/${methodName}`, {
method: 'POST', method: 'POST',
credentials: 'include',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Authentication: window.deckyAuthToken,
}, },
body: JSON.stringify(args), body: JSON.stringify(args),
}); });
@@ -182,8 +193,10 @@ class PluginLoader extends Logger {
async callPluginMethod(methodName: string, args = {}) { async callPluginMethod(methodName: string, args = {}) {
const response = await fetch(`http://127.0.0.1:1337/plugins/${pluginName}/methods/${methodName}`, { const response = await fetch(`http://127.0.0.1:1337/plugins/${pluginName}/methods/${methodName}`, {
method: 'POST', method: 'POST',
credentials: 'include',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Authentication: window.deckyAuthToken,
}, },
body: JSON.stringify({ body: JSON.stringify({
args, args,
+2
View File
@@ -14,8 +14,10 @@ export interface DeckyUpdater {
export async function callUpdaterMethod(methodName: string, args = {}) { export async function callUpdaterMethod(methodName: string, args = {}) {
const response = await fetch(`http://127.0.0.1:1337/updater/${methodName}`, { const response = await fetch(`http://127.0.0.1:1337/updater/${methodName}`, {
method: 'POST', method: 'POST',
credentials: 'include',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Authentication: window.deckyAuthToken,
}, },
body: JSON.stringify(args), body: JSON.stringify(args),
}); });