diff --git a/app/app.py b/app/app.py index a2d3b13..31f6fc7 100644 --- a/app/app.py +++ b/app/app.py @@ -117,7 +117,25 @@ def index(): @tinfoil_access def access_tinfoil_shop(): - shop = gen_shop(db, app_settings) + shop = {} + # Host verification to prevent hotlinking + request_host = request.host + host_verification = request.is_secure or request.headers.get("X-Forwarded-Proto") == "https" + if host_verification: + logger.info(f"Secure access with remote host {request_host}, proceeding with host verification") + shop_host = app_settings["shop"].get("url") + if not shop_host: + logger.error("Missing shop URL configuration, remote access is disabled.") + return tinfoil_error(f"You are trying to access this shop with the `{request_host}` URL, but the shop URL is missing in Ownfoil configuration, remote access is disabled.\nPlease configure the shop URL to enable remote access and prevent someone else from stealing your shop.") + + if request_host != shop_host: + logger.warning(f"Incorrect URL referrer detected: {request_host}.") + return tinfoil_error(f"Incorrect URL `{request_host}`.\nSomeone is trying to steal from the shop with original URL `{shop_host}`.") + + # enforce client side host verification + shop["referrer"] = f"https://{shop_host}" + + shop.update(gen_shop(db, app_settings)) return jsonify(shop) if all(header in request.headers for header in TINFOIL_HEADERS): @@ -135,7 +153,7 @@ def settings_page(): with open(os.path.join(TITLEDB_DIR, 'languages.json')) as f: languages = json.load(f) languages = dict(sorted(languages.items())) - return render_template('settings.html', title='Settings', languages_from_titledb=languages, admin_account_created=admin_account_created(), valid_keys=app_settings['titles']['valid_keys']) + return render_template('settings.html', title='Settings', languages_from_titledb=languages, admin_account_created=admin_account_created(), valid_keys=app_settings['titles']['valid_keys'], url_set=bool(app_settings['shop']['url'])) @app.get('/api/settings') @access_required('admin') diff --git a/app/constants.py b/app/constants.py index 2e81480..b8801b3 100644 --- a/app/constants.py +++ b/app/constants.py @@ -31,7 +31,8 @@ "public": False, "encrypt": False, "clientCertPub": "-----BEGIN PUBLIC KEY-----", - "clientCertKey": "-----BEGIN PRIVATE KEY-----" + "clientCertKey": "-----BEGIN PRIVATE KEY-----", + "url": "", } } diff --git a/app/settings.py b/app/settings.py index 477e671..ada7e04 100644 --- a/app/settings.py +++ b/app/settings.py @@ -109,6 +109,9 @@ def set_titles_settings(region, language): def set_shop_settings(data): settings = load_settings() + shop_url = data['url'] + if '://' in shop_url: + data['url'] = shop_url.split('://')[-1] settings['shop'] = data with open(CONFIG_FILE, 'w') as yaml_file: yaml.dump(settings, yaml_file) diff --git a/app/templates/settings.html b/app/templates/settings.html index b4646ab..5e5be31 100644 --- a/app/templates/settings.html +++ b/app/templates/settings.html @@ -16,7 +16,8 @@

Missing admin account!

created, authentication is disabled, anyone can access and change the configuration of your shop!
- Add an admin account here under Authentication. + Add an admin account here under + Authentication.

{% endif %} @@ -38,6 +39,16 @@

Missing console keys!

{% endif %} + {% if url_set == false %} + + {% endif %} +

Authentication

@@ -145,8 +156,8 @@

Library

- +
@@ -166,7 +177,8 @@

Library

-
--'); } else { @@ -474,11 +498,11 @@

Shop

$('#checkboxNewUserAdminAccess').change(function () { if (this.checked != false) { $('#checkboxNewUserShopAccess').prop('checked', true).attr("disabled", true); - $('#checkboxNewUserBackupAccess').prop('checked', true).attr("disabled", true) ; + $('#checkboxNewUserBackupAccess').prop('checked', true).attr("disabled", true); } else { $('#checkboxNewUserShopAccess').attr("disabled", false); - $('#checkboxNewUserBackupAccess').attr("disabled", false) ; + $('#checkboxNewUserBackupAccess').attr("disabled", false); } }); @@ -564,7 +588,7 @@

Shop

} else { console.log('Error adding path ' + path); } - $('#submitNewLibraryPathBtn').prop('disabled', false); + $('#submitNewLibraryPathBtn').prop('disabled', false); } }); } @@ -627,6 +651,7 @@

Shop

function submitShopSettings() { $('#submitShopSettingsBtn').prop('disabled', true); data = { + url: $('#shopUrlInput').val(), public: getCheckboxStatus("publicShopCheck"), encrypt: getCheckboxStatus("encryptShopCheck"), motd: $('#motdTextArea').val(), @@ -650,7 +675,7 @@

Shop

$("#" + formTextElement).text(error['error']); }); } - $('#submitShopSettingsBtn').prop('disabled', false); + $('#submitShopSettingsBtn').prop('disabled', false); } }); } @@ -686,6 +711,7 @@

Shop

// Shop settings shopSettings = result['shop']; + setInputVal("shopUrlInput", shopSettings['url']); setInputVal("motdTextArea", shopSettings['motd']); $("#publicShopCheck").prop("checked", shopSettings['public']); $("#encryptShopCheck").prop("checked", shopSettings['encrypt']);