Skip to content

Commit e5e4e23

Browse files
v1.0
1 parent 0831470 commit e5e4e23

File tree

13 files changed

+516
-280
lines changed

13 files changed

+516
-280
lines changed

.keys

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ffdf4d00-56f2-440c-881e-93459a9c71ba

app.py

Lines changed: 192 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,156 @@
1-
from flask import Flask, render_template, jsonify, request, send_from_directory
1+
from flask import (
2+
Flask,
3+
render_template,
4+
jsonify,
5+
request,
6+
send_from_directory,
7+
redirect,
8+
make_response,
9+
)
210
import requests
311
import threading
412
import queue
513
from ollamaClient import OllamaClient
614
from dotenv import load_dotenv
7-
15+
import os
816
import hashlib
17+
from uuid import uuid4
18+
from base64 import b64encode
19+
from itsdangerous import URLSafeSerializer
20+
import markdown
921

10-
# Load environment variables from .env file
22+
# ===================== INITIALIZATION =====================
23+
# Initialize Flask app and Ollama client
24+
app = Flask(__name__)
25+
ai = OllamaClient()
1126

12-
load_dotenv("./config.env")
27+
# Queue for handling asynchronous chat requests
28+
request_queue: queue.Queue = queue.Queue()
29+
auth_portal_api_url = ""
30+
no_of_auth_reqs = 0
31+
no_of_reqs = 0
1332

33+
# Load environment variables from .env file
34+
load_dotenv("./config.env")
1435

15-
# Helper function to get configuration values
1636

37+
# ===================== HELPER FUNCTIONS =====================
1738
def get_config(key, default=None):
18-
39+
"""
40+
Retrieve a configuration value from environment variables.
41+
Exits the program if the key is not found and no default is provided.
42+
"""
1943
value = os.getenv(key, default)
20-
2144
if value is None:
22-
2345
print(f"Error: The {key} is not set in the environment.")
24-
2546
exit(1) # Exit with non-zero status to indicate an error occurred
26-
2747
return value
2848

2949

30-
# Configuration
31-
50+
# ===================== CONFIGURATION =====================
51+
# Load configuration from environment variables
3252
config = {
33-
34-
"host": get_config("SIMPLE_AI_UI_SERVER_HOST", "0.0.0.0"),
35-
36-
"port": int(get_config("SIMPLE_AI_UI_SERVER_PORT", "5000")),
37-
53+
"host": get_config("SIMPLE_AI_UI_SERVER_HOST"),
54+
"port": int(get_config("SIMPLE_AI_UI_SERVER_PORT")),
3855
"users": {
39-
4056
pair.split(":")[0]: pair.split(":")[1]
41-
42-
for pair in get_config("SIMPLE_AI_UI_AUTH_USERS", "joe:1234,apple:1234").split(",")
43-
57+
for pair in get_config("SIMPLE_AI_UI_AUTH_USERS").split(",")
4458
if ":" in pair
45-
4659
},
60+
"limit_of_auth_requests": int(get_config("SIMPLE_AI_UI_AUTH_NO_OF_USERS_PER_DAY")),
61+
"limit_of_requests": int(get_config("SIMPLE_AI_UI_API_LIMIT_OF_REQS_PER_DAY")),
62+
}
4763

48-
"open_stats": get_config("SIMPLE_AI_UI_AUTH_SHOW_USER_STATS", "true").lower() == "true",
4964

50-
"limit_of_requests": int(get_config("SIMPLE_AI_UI_API_LIMIT_OF_REQS_PER_DAY", "1000")),
65+
# ===================== AUTHENTICATION AND SECURITY =====================
66+
def auth(username, password):
67+
"""
68+
Authenticate a user by checking their username and password.
69+
Returns "OK-12" if authentication is successful, otherwise an error code.
70+
"""
71+
if username in config["users"]:
72+
if hashing(config["users"][username]) == hashing(password):
73+
return "OK-12"
74+
else:
75+
return "BAD-2"
76+
return "BAD-12"
5177

52-
"limit_of_devices": int(get_config("SIMPLE_AI_UI_API_NO_OF_DEVICES", "1000")),
5378

54-
}
55-
# Initialize Flask app and Ollama client
56-
app = Flask(__name__)
57-
ai = OllamaClient()
79+
def hashing(text):
80+
"""
81+
Hash a given text using SHA-256.
82+
"""
83+
hash_object = hashlib.sha256(text.encode("utf-8"))
84+
return hash_object.hexdigest()
85+
86+
87+
def middleware_check(type):
88+
"""
89+
Middleware to check if the user is authenticated.
90+
Redirects to the login page or returns an error if not authenticated.
91+
"""
92+
global no_of_auth_reqs
93+
try:
94+
auth_cookies = request.cookies.get("auth", "")
95+
except ValueError:
96+
auth_cookies = ""
97+
98+
if auth_cookies == "":
99+
if type == "api":
100+
return jsonify(
101+
{
102+
"error": "Auth error, You are not logged in!\n Login via <a href='/login'>LOGIN PAGE</a>"
103+
}
104+
)
105+
else:
106+
return redirect("/login")
107+
else:
108+
no_of_auth_reqs += 1
109+
return None
110+
111+
112+
def middleware_init(username):
113+
"""
114+
Initialize middleware by generating a token for the authenticated user.
115+
"""
116+
global no_of_auth_reqs
117+
data = {
118+
"device": request.headers.get("User-Agent"),
119+
"user": username,
120+
"ID": str(uuid4()),
121+
"number": no_of_auth_reqs,
122+
}
123+
token, _ = server_encode_tokenizer(data)
124+
return token
125+
126+
127+
def server_encode_tokenizer(data):
128+
"""
129+
Encode data into a token using a URL-safe serializer.
130+
"""
131+
key = str(uuid4())
132+
with open(".keys", "a") as f:
133+
f.write(key + "\n")
134+
auth_s = URLSafeSerializer(
135+
key + "-abujuni.dev", "Hi I am Junaid, Support my project"
136+
)
137+
token = auth_s.dumps(data)
138+
return token, auth_s
58139

59-
# Queue for handling asynchronous chat requests
60-
request_queue: queue.Queue = queue.Queue()
61140

141+
def server_decode_tokenizer(token, auth_s):
142+
"""
143+
Decode a token back into data using a URL-safe serializer.
144+
"""
145+
data = auth_s.loads(token)
146+
return data
62147

148+
149+
# ===================== BACKGROUND WORKER THREAD =====================
63150
def process_requests() -> None:
64-
"""Worker thread function to process queued chat requests."""
151+
"""
152+
Worker thread function to process queued chat requests.
153+
"""
65154
while True:
66155
task = request_queue.get()
67156
if task is None:
@@ -81,60 +170,67 @@ def process_requests() -> None:
81170
# Start background worker thread
82171
threading.Thread(target=process_requests, daemon=True).start()
83172

84-
def auth(username, password):
85-
for user in config.get("users").keys():
86-
if user == username:
87-
if config.get("users").get(user) == password:
88-
return "OK-12"
89-
else:
90-
return "BAD-2"
91-
else:
92-
return "BAD-12"
93173

94-
def hashing(text):
95-
content = str(uuid4)+text+str(uuid4)
96-
hash_object = hashlib.sha256(content.encode("utf-8"))
97-
hex_dig = hash_object.hexdigest()
98-
return hex_dig
99-
100-
def middlewares(username,password):
101-
auth_status=auth(username, password)
102-
if auth_status == "OK-12":
103-
hash_obj=hashing(username+password)
104-
request.cookies.add("auth":hash_obj)
105-
ALLOW_HASHS.append(hash_obj)
106-
return True
107-
else:
108-
return render_template("error.html", error="YOU ARE NOT ALLOWED - AUTH ERROR")
109-
110-
def api_chat_func():
111-
data = request.json
112-
response_queue: queue.Queue = queue.Queue()
113-
request_queue.put((data, response_queue))
114-
response = response_queue.get() # Blocking wait for response
115-
status = 200 if "response" in response else 400
116-
return jsonify(response), status
117-
118-
# Routes
174+
# ===================== ROUTES =====================
175+
# ============= FRONTEND ROUTES =============
119176
@app.route("/")
120177
def index():
178+
"""
179+
Render the index page after checking authentication.
180+
"""
181+
middleware_response = middleware_check(type="html")
182+
if middleware_response:
183+
return middleware_response
121184
return render_template("index.html"), 200
122185

123186

124187
@app.route("/static/<path:filename>")
125188
def serve_static(filename: str):
189+
"""
190+
Serve static files from the 'static' directory.
191+
"""
126192
return send_from_directory("static", filename), 200
127193

128194

129195
@app.errorhandler(Exception)
130196
def handle_error(error: Exception):
197+
"""
198+
Handle errors and render appropriate error pages.
199+
"""
131200
if hasattr(error, "code") and error.code == 404:
132201
return render_template("404.html"), 404
133202
return render_template("error.html", error=str(error)), 500
134203

135204

205+
# ============= SECURITY ROUTES =============
206+
@app.route("/login", methods=["GET", "POST"])
207+
def api_auth():
208+
"""
209+
Handle user login. Authenticates the user and sets an auth cookie if successful.
210+
"""
211+
if request.method == "POST":
212+
username = request.form.get("username")
213+
password = request.form.get("password")
214+
auth_result = auth(username, password)
215+
if auth_result == "OK-12":
216+
token = middleware_init(username)
217+
response = make_response(redirect("/"))
218+
response.set_cookie("auth", token)
219+
return response
220+
else:
221+
return render_template("login.html", error="Invalid username or password")
222+
return render_template("login.html")
223+
224+
225+
# ============= API ROUTES =============
136226
@app.route("/api/connection")
137227
def api_connection():
228+
"""
229+
Check the connection to the Ollama server.
230+
"""
231+
middleware_response = middleware_check(type="api")
232+
if middleware_response:
233+
return middleware_response
138234
try:
139235
res = requests.get("http://localhost:11434", timeout=5)
140236
status_code = res.status_code
@@ -156,6 +252,12 @@ def api_connection():
156252

157253
@app.route("/api/connection/stats")
158254
def api_connection_stats():
255+
"""
256+
Retrieve statistics about available and active models from the Ollama client.
257+
"""
258+
middleware_response = middleware_check(type="api")
259+
if middleware_response:
260+
return middleware_response
159261
models = ai.list_models()
160262
active_models = ai.list_active_models()
161263

@@ -167,8 +269,30 @@ def api_connection_stats():
167269

168270
@app.route("/api/chat", methods=["POST"])
169271
def api_chat():
170-
middlewares_firewall=middlewares()
272+
"""
273+
Handle chat requests by processing the prompt and model, and returning the AI's response.
274+
"""
275+
middleware_response = middleware_check(type="api")
276+
if middleware_response:
277+
return middleware_response
278+
279+
data = request.json
280+
if not data or "prompt" not in data or "model" not in data:
281+
return jsonify({"error": "Invalid request data"}), 400
282+
283+
response_queue = queue.Queue()
284+
request_queue.put((data, response_queue))
285+
286+
response = response_queue.get()
287+
288+
if "response" not in response:
289+
return jsonify({"error": "Invalid response from the AI"}), 500
290+
291+
html_response = markdown.markdown(response["response"])
292+
293+
return jsonify({"response": html_response}), 200
171294

172295

296+
# ===================== MAIN EXECUTION =====================
173297
if __name__ == "__main__":
174-
app.run()
298+
app.run(host=config.get("host"), port=config.get("port"))

config.env

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ SIMPLE_AI_UI_SERVER_HOST=localhost
33
SIMPLE_AI_UI_SERVER_PORT=5000
44

55
# Users
6-
SIMPLE_AI_UI_AUTH_USERS=joe:1234,apple:1234
6+
SIMPLE_AI_UI_AUTH_USERS=junaid:junaid
77
SIMPLE_AI_UI_AUTH_NO_OF_USERS_PER_DAY=1000
8-
SIMPLE_AI_UI_AUTH_SHOW_USER_STATS=true
98

109
# API Requests
11-
SIMPLE_AI_UI_API_LIMIT_OF_REQS_PER_DAY=1000
12-
SIMPLE_AI_UI_API_NO_OF_DEVICES=1000
10+
SIMPLE_AI_UI_API_LIMIT_OF_REQS_PER_DAY=1000
11+
12+
# Security
13+
create_

0 commit comments

Comments
 (0)