diff --git a/app/app.py b/app/app.py index 65e5bea..5e68871 100644 --- a/app/app.py +++ b/app/app.py @@ -96,11 +96,57 @@ def tinfoil_error(error): def tinfoil_access(f): @wraps(f) def _tinfoil_access(*args, **kwargs): + reload_conf() + hauth_success = None + auth_success = None + request.verified_host = None + # Host verification to prevent hotlinking + host_verification = request.is_secure or request.headers.get("X-Forwarded-Proto") == "https" + if host_verification: + request_host = request.host + request_hauth = request.headers.get('Hauth') + logger.info(f"Secure Tinfoil request from remote host {request_host}, proceeding with host verification.") + shop_host = app_settings["shop"].get("host") + shop_hauth = app_settings["shop"].get("hauth") + if not shop_host: + logger.error("Missing shop host configuration, Host verification is disabled.") + + elif request_host != shop_host: + logger.warning(f"Incorrect URL referrer detected: {request_host}.") + error = f"Incorrect URL `{request_host}`." + hauth_success = False + + elif not shop_hauth: + # Try authentication, if an admin user is logging in then set the hauth + auth_success, auth_error, auth_is_admin = basic_auth(request) + if auth_success and auth_is_admin: + shop_settings = app_settings['shop'] + shop_settings['hauth'] = request_hauth + set_shop_settings(shop_settings) + logger.info(f"Successfully set Hauth value for host {request_host}.") + hauth_success = True + else: + logger.warning(f"Hauth value not set for host {request_host}, Host verification is disabled. Connect to the shop from Tinfoil with an admin account to set it.") + + elif request_hauth != shop_hauth: + logger.warning(f"Incorrect Hauth detected for host: {request_host}.") + error = f"Incorrect Hauth for URL `{request_host}`." + hauth_success = False + + else: + hauth_success = True + request.verified_host = shop_host + + if hauth_success is False: + return tinfoil_error(error) + + # Now checking auth if shop is private if not app_settings['shop']['public']: # Shop is private - success, error = basic_auth(request) - if not success: - return tinfoil_error(error) + if auth_success is None: + auth_success, auth_error, _ = basic_auth(request) + if not auth_success: + return tinfoil_error(auth_error) # Auth success return f(*args, **kwargs) return _tinfoil_access @@ -120,22 +166,10 @@ def access_tinfoil_shop(): shop = { "success": app_settings['shop']['motd'] } - # 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, Host verification is disabled.") - shop["error"] = f"You are trying to access this shop with the `{request_host}` URL, but the shop URL is missing in Ownfoil configuration.\nPlease configure the shop URL to secure remote access and prevent someone else from stealing your shop." - - elif 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}`.") - else: - # enforce client side host verification - shop["referrer"] = f"https://{shop_host}" + + if request.verified_host is not None: + # enforce client side host verification + shop["referrer"] = f"https://{request.verified_host}" shop["files"] = gen_shop_files(db) return jsonify(shop) @@ -155,13 +189,23 @@ 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'], url_set=bool(app_settings['shop']['url'])) + return render_template( + 'settings.html', + title='Settings', + languages_from_titledb=languages, + admin_account_created=admin_account_created(), + valid_keys=app_settings['titles']['valid_keys']) @app.get('/api/settings') @access_required('admin') def get_settings_api(): reload_conf() - return jsonify(app_settings) + settings = app_settings + if settings['shop'].get('hauth'): + settings['shop']['hauth'] = True + else: + settings['shop']['hauth'] = False + return jsonify(settings) @app.post('/api/settings/titles') @access_required('admin') @@ -401,31 +445,6 @@ def on_library_change(events): remove_missing_files() titles_library = generate_library() -@app.before_request -def before_request(): - # Code to run before each request - print("Before request") - # print(f"access_route: {request.access_route}") - # print(f"args: {request.args}") - # print(f"base_url: {request.base_url}") - # print(f"data: {request.data}") - # print(f"full_path: {request.full_path}") - # print(f"host: {request.host}") - # print(f"host_url: {request.host_url}") - # print(f"is_json: {request.is_json}") - # print(f"is_secure: {request.is_secure}") - # # print(f"json: {request.json}") - # print(f"method: {request.method}") - # print(f"path: {request.path}") - # print(f"query_string: {request.query_string}") - # print(f"referrer: {request.referrer}") - # print(f"remote_addr: {request.remote_addr}") - # print(f"scheme: {request.scheme}") - # print(f"url_root: {request.url_root}") - # print(f"url: {request.url}") - # print(f"user_agent: {request.user_agent}") - # print(f"trusted_hosts: {request.trusted_hosts}") - # print(request.headers) if __name__ == '__main__': logger.info('Starting initialization of Ownfoil...') diff --git a/app/auth.py b/app/auth.py index 6aba31b..085cc69 100644 --- a/app/auth.py +++ b/app/auth.py @@ -62,6 +62,7 @@ def decorated_view(*args, **kwargs): def basic_auth(request): success = True error = '' + is_admin = False auth = request.authorization if auth is None: @@ -84,7 +85,9 @@ def basic_auth(request): success = False error = f'User "{username}" does not have access to the shop.' - return success, error + else: + is_admin = user.has_admin_access() + return success, error, is_admin auth_blueprint = Blueprint('auth', __name__) diff --git a/app/constants.py b/app/constants.py index b8801b3..d4ca67b 100644 --- a/app/constants.py +++ b/app/constants.py @@ -32,7 +32,8 @@ "encrypt": False, "clientCertPub": "-----BEGIN PUBLIC KEY-----", "clientCertKey": "-----BEGIN PRIVATE KEY-----", - "url": "", + "host": "", + "hauth": "", } } diff --git a/app/settings.py b/app/settings.py index ada7e04..04017e1 100644 --- a/app/settings.py +++ b/app/settings.py @@ -109,9 +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 + shop_host = data['host'] + if '://' in shop_host: + data['host'] = shop_host.split('://')[-1] + settings['shop'].update(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 5e5be31..cd240b7 100644 --- a/app/templates/settings.html +++ b/app/templates/settings.html @@ -39,15 +39,22 @@

Missing console keys!

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