mirror of
https://github.com/SteamDeckHomebrew/decky-loader.git
synced 2026-06-17 08:47:49 +00:00
Implement CSRF protection
This commit is contained in:
+14
-1
@@ -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')
|
||||||
@@ -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
@@ -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:
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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),
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user