diff --git a/AppImage/scripts/flask_server.py b/AppImage/scripts/flask_server.py index 2090fb4..919c328 100644 --- a/AppImage/scripts/flask_server.py +++ b/AppImage/scripts/flask_server.py @@ -88,6 +88,210 @@ def require_auth(f): return decorated_function +# Authentication endpoints +@app.route('/api/auth/status', methods=['GET']) +def auth_status(): + """Check if authentication is enabled and if current session is valid""" + try: + auth_config = load_auth_config() + is_enabled = auth_config.get("auth_enabled", False) + + # Check if user has valid token + is_authenticated = False + if is_enabled: + auth_header = request.headers.get('Authorization') + if auth_header and auth_header.startswith('Bearer '): + token = auth_header.split(' ')[1] + try: + payload = jwt.decode(token, JWT_SECRET, algorithms=['HS256']) + if time.time() <= payload.get('exp', 0): + is_authenticated = True + except: + pass + + return jsonify({ + "auth_enabled": is_enabled, + "authenticated": is_authenticated or not is_enabled + }) + except Exception as e: + return jsonify({"error": str(e)}), 500 + +@app.route('/api/auth/setup', methods=['POST']) +def auth_setup(): + """Setup authentication for the first time""" + try: + data = request.get_json() + username = data.get('username', '').strip() + password = data.get('password', '').strip() + + if not username or not password: + return jsonify({"error": "Username and password are required"}), 400 + + if len(password) < 6: + return jsonify({"error": "Password must be at least 6 characters"}), 400 + + # Hash password and save config + password_hash = hash_password(password) + + auth_config = { + "auth_enabled": True, + "username": username, + "password_hash": password_hash, + "created_at": datetime.now().isoformat() + } + + save_auth_config(auth_config) + + # Generate JWT token + token = jwt.encode({ + 'username': username, + 'exp': time.time() + SESSION_TIMEOUT + }, JWT_SECRET, algorithm='HS256') + + return jsonify({ + "success": True, + "token": token + }) + except Exception as e: + return jsonify({"error": str(e)}), 500 + +@app.route('/api/auth/skip', methods=['POST']) +def auth_skip(): + """Skip authentication setup""" + try: + auth_config = { + "auth_enabled": False, + "skipped": True, + "skipped_at": datetime.now().isoformat() + } + + save_auth_config(auth_config) + + return jsonify({"success": True}) + except Exception as e: + return jsonify({"error": str(e)}), 500 + +@app.route('/api/auth/login', methods=['POST']) +def auth_login(): + """Login with username and password""" + try: + data = request.get_json() + username = data.get('username', '').strip() + password = data.get('password', '').strip() + + if not username or not password: + return jsonify({"error": "Username and password are required"}), 400 + + # Load auth config + auth_config = load_auth_config() + + if not auth_config.get("auth_enabled", False): + return jsonify({"error": "Authentication is not enabled"}), 400 + + # Verify credentials + stored_username = auth_config.get("username", "") + stored_password_hash = auth_config.get("password_hash", "") + + if username != stored_username or hash_password(password) != stored_password_hash: + return jsonify({"error": "Invalid username or password"}), 401 + + # Generate JWT token + token = jwt.encode({ + 'username': username, + 'exp': time.time() + SESSION_TIMEOUT + }, JWT_SECRET, algorithm='HS256') + + return jsonify({ + "success": True, + "token": token + }) + except Exception as e: + return jsonify({"error": str(e)}), 500 + +@app.route('/api/auth/refresh', methods=['POST']) +def auth_refresh(): + """Refresh JWT token""" + try: + auth_header = request.headers.get('Authorization') + if not auth_header or not auth_header.startswith('Bearer '): + return jsonify({"error": "No token provided"}), 401 + + token = auth_header.split(' ')[1] + + try: + # Verify current token + payload = jwt.decode(token, JWT_SECRET, algorithms=['HS256']) + username = payload.get('username') + + # Generate new token + new_token = jwt.encode({ + 'username': username, + 'exp': time.time() + SESSION_TIMEOUT + }, JWT_SECRET, algorithm='HS256') + + return jsonify({ + "success": True, + "token": new_token + }) + except jwt.InvalidTokenError: + return jsonify({"error": "Invalid token"}), 401 + except Exception as e: + return jsonify({"error": str(e)}), 500 + +@app.route('/api/auth/logout', methods=['POST']) +@require_auth +def auth_logout(): + """Logout (client should delete token)""" + return jsonify({"success": True}) + +@app.route('/api/auth/disable', methods=['POST']) +@require_auth +def auth_disable(): + """Disable authentication""" + try: + auth_config = { + "auth_enabled": False, + "disabled_at": datetime.now().isoformat() + } + + save_auth_config(auth_config) + + return jsonify({"success": True}) + except Exception as e: + return jsonify({"error": str(e)}), 500 + +@app.route('/api/auth/change-password', methods=['POST']) +@require_auth +def auth_change_password(): + """Change password""" + try: + data = request.get_json() + current_password = data.get('current_password', '').strip() + new_password = data.get('new_password', '').strip() + + if not current_password or not new_password: + return jsonify({"error": "Current and new password are required"}), 400 + + if len(new_password) < 6: + return jsonify({"error": "New password must be at least 6 characters"}), 400 + + # Load auth config + auth_config = load_auth_config() + + # Verify current password + stored_password_hash = auth_config.get("password_hash", "") + if hash_password(current_password) != stored_password_hash: + return jsonify({"error": "Current password is incorrect"}), 401 + + # Update password + auth_config["password_hash"] = hash_password(new_password) + auth_config["updated_at"] = datetime.now().isoformat() + + save_auth_config(auth_config) + + return jsonify({"success": True}) + except Exception as e: + return jsonify({"error": str(e)}), 500 app = Flask(__name__) CORS(app) # Enable CORS for Next.js frontend