import jwt import json import openai from flask import Flask, request, jsonify from functools import wraps import re import requests import os from dotenv import load_dotenv from importlib import resources from .fabric_web_interface import main as web_interface app = Flask(__name__) @app.errorhandler(404) def not_found(e): return jsonify({"error": "The requested resource was not found."}), 404 @app.errorhandler(500) def server_error(e): return jsonify({"error": "An internal server error occurred."}), 500 ################################################## ################################################## # # ⚠️ CAUTION: This is an HTTP-only server! # # If you don't know what you're doing, don't run # ################################################## ################################################## ## Setup ## Did I mention this is HTTP only? Don't run this on the public internet. ## Set authentication on your APIs ## Let's at least have some kind of auth load_dotenv() openai.api_key = os.getenv("OPENAI_API_KEY") ## Define our own client client = openai.OpenAI(api_key = openai.api_key) # Read API tokens from the apikeys.json file api_keys = resources.read_text("fabric_server", "fabric_api_keys.json") valid_tokens = json.loads(api_keys) # Read users from the users.json file users = resources.read_text("fabric_server", "users.json") users = json.loads(users) # The function to check if the token is valid def auth_required(f): """ Decorator function to check if the token is valid. Args: f: The function to be decorated Returns: The decorated function """ @wraps(f) def decorated_function(*args, **kwargs): """ Decorated function to handle authentication token and API endpoint. Args: *args: Variable length argument list. **kwargs: Arbitrary keyword arguments. Returns: Result of the decorated function. Raises: KeyError: If 'Authorization' header is not found in the request. TypeError: If 'Authorization' header value is not a string. ValueError: If the authentication token is invalid or expired. """ # Get the authentication token from request header auth_token = request.headers.get("Authorization", "") # Remove any bearer token prefix if present if auth_token.lower().startswith("bearer "): auth_token = auth_token[7:] # Get API endpoint from request endpoint = request.path # Check if token is valid user = check_auth_token(auth_token, endpoint) if user == "Unauthorized: You are not authorized for this API": return jsonify({"error": user}), 401 return f(*args, **kwargs) return decorated_function # Check for a valid token/user for the given route def check_auth_token(token, route): """ Check if the provided token is valid for the given route and return the corresponding user. Args: token (str): The token to be checked for validity. route (str): The route for which the token validity is to be checked. Returns: str: The user corresponding to the provided token and route if valid, otherwise returns "Unauthorized: You are not authorized for this API". """ # Check if token is valid for the given route and return corresponding user if route in valid_tokens and token in valid_tokens[route]: return users[valid_tokens[route][token]] else: return "Unauthorized: You are not authorized for this API" # Define the allowlist of characters ALLOWLIST_PATTERN = re.compile(r"^[a-zA-Z0-9\s.,;:!?\-]+$") # Sanitize the content, sort of. Prompt injection is the main threat so this isn't a huge deal def sanitize_content(content): """ Sanitize the content by removing characters that do not match the ALLOWLIST_PATTERN. Args: content (str): The content to be sanitized. Returns: str: The sanitized content. """ return "".join(char for char in content if ALLOWLIST_PATTERN.match(char)) # Pull the URL content's from the GitHub repo def fetch_content_from_url(url): """ Fetches content from the given URL. Args: url (str): The URL from which to fetch content. Returns: str: The sanitized content fetched from the URL. Raises: requests.RequestException: If an error occurs while making the request to the URL. """ try: response = requests.get(url) response.raise_for_status() sanitized_content = sanitize_content(response.text) return sanitized_content except requests.RequestException as e: return str(e) ## APIs # Make path mapping flexible and scalable pattern_path_mappings = { "extwis": {"system_url": "https://raw.githubusercontent.com/danielmiessler/fabric/main/patterns/extract_wisdom/system.md", "user_url": "https://raw.githubusercontent.com/danielmiessler/fabric/main/patterns/extract_wisdom/user.md"}, "summarize": {"system_url": "https://raw.githubusercontent.com/danielmiessler/fabric/main/patterns/summarize/system.md", "user_url": "https://raw.githubusercontent.com/danielmiessler/fabric/main/patterns/summarize/user.md"} } # Add more pattern with your desire path as a key in this dictionary # / @app.route("/", methods=["POST"]) @auth_required # Require authentication def milling(pattern): """ Combine fabric pattern with input from user and send to OpenAI's GPT-4 model. Returns: JSON: A JSON response containing the generated response or an error message. Raises: Exception: If there is an error during the API call. """ data = request.get_json() # Warn if there's no input if "input" not in data: return jsonify({"error": "Missing input parameter"}), 400 # Get data from client input_data = data["input"] # Set the system and user URLs urls = pattern_path_mappings[pattern] system_url, user_url = urls["system_url"], urls["user_url"] # Fetch the prompt content system_content = fetch_content_from_url(system_url) user_file_content = fetch_content_from_url(user_url) # Build the API call system_message = {"role": "system", "content": system_content} user_message = {"role": "user", "content": user_file_content + "\n" + input_data} messages = [system_message, user_message] try: response = openai.chat.completions.create( model="gpt-4-1106-preview", messages=messages, temperature=0.0, top_p=1, frequency_penalty=0.1, presence_penalty=0.1, ) assistant_message = response.choices[0].message.content return jsonify({"response": assistant_message}) except Exception as e: app.logger.error(f"Error occurred: {str(e)}") return jsonify({"error": "An error occurred while processing the request."}), 500 @app.route("/register", methods=["POST"]) def register(): data = request.get_json() username = data["username"] password = data["password"] if username in users: return jsonify({"error": "Username already exists"}), 400 new_user = { "username": username, "password": password } users[username] = new_user token = jwt.encode({"username": username}, os.getenv("JWT_SECRET"), algorithm="HS256") return jsonify({"token": token.decode("utf-8")}) @app.route("/login", methods=["POST"]) def login(): data = request.get_json() username = data["username"] password = data["password"] if username in users and users[username]["password"] == password: # Generate a JWT token token = jwt.encode({"username": username}, os.getenv("JWT_SECRET"), algorithm="HS256") return jsonify({"token": token.decode("utf-8")}) return jsonify({"error": "Invalid username or password"}), 401 def main(): """Runs the main fabric API backend server""" app.run(host="127.0.0.1", port=13337, debug=True) def run_web_interface(): """Runs the web insterface""" web_interface() if __name__ == "__main__": main()