From 2637cdba61f0109f51d0d3d3ff0295965174f7f6 Mon Sep 17 00:00:00 2001 From: "Troll (Hermes Agent)" Date: Wed, 5 Aug 2026 14:41:39 +0000 Subject: [PATCH] Add /api/key-test diagnostic endpoint for API key verification Provides a simple GET endpoint that validates the Authorization Bearer token against HERMES_API_KEY and returns JSON: {"ok": true, "reason": "valid"} or {"ok": false, "reason": "key_mismatch" / "missing_bearer" / "not_configured"} Rate limited to 4 per minute to prevent brute-force guessing. Bump version 0.4.3 -> 0.4.4. --- README.md | 2 +- VERSION | 2 +- app.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8a3171f..13b12e9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Theme Song Booth -**Version:** `v0.4.3` +**Version:** `v0.4.4` A Flask web app for a convention booth where visitors request a custom AI-generated theme song, the operator manages the queue, and the final MP3(s) are delivered by email after payment. diff --git a/VERSION b/VERSION index 17b2ccd..6f2743d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.4.3 +0.4.4 diff --git a/app.py b/app.py index 5d8452a..f200833 100644 --- a/app.py +++ b/app.py @@ -516,6 +516,34 @@ def request_form(): return render_template('request.html', form=None) +@app.route('/api/ping', methods=['GET']) +@app.route('/api/key-test', methods=['GET']) +@limiter.limit('4 per minute') +def api_key_test(): + """ + Diagnostic endpoint for verifying the Hermes API key configuration. + + Accepts a Bearer token in the Authorization header and compares it against + the configured HERMES_API_KEY. Returns plain JSON so callers can distinguish + key mismatch from networking / signed-token issues. + + Rate limited to 4 per minute to prevent brute-force guessing. + """ + expected_key = get_hermes_api_key() + if not expected_key: + return jsonify({'ok': False, 'reason': 'not_configured'}), 500 + + auth_header = request.headers.get('Authorization', '').strip() + if not auth_header.startswith('Bearer '): + return jsonify({'ok': False, 'reason': 'missing_bearer'}), 401 + + provided_key = auth_header[7:].strip() + if not hmac.compare_digest(expected_key, provided_key): + return jsonify({'ok': False, 'reason': 'key_mismatch'}), 401 + + return jsonify({'ok': True, 'reason': 'valid'}), 200 + + @app.route('/api/prompt/', methods=['POST']) def api_update_prompt(rid): """