Update app.py
Browse files
app.py
CHANGED
|
@@ -33,7 +33,7 @@ ALLOWED_EXTENSIONS = {
|
|
| 33 |
}
|
| 34 |
|
| 35 |
def get_file_type(filename):
|
| 36 |
-
"""Determine file type based on extension"""
|
| 37 |
if not filename:
|
| 38 |
return 'unknown'
|
| 39 |
|
|
@@ -163,7 +163,7 @@ def index():
|
|
| 163 |
|
| 164 |
@app.route("/api/store", methods=["POST"])
|
| 165 |
def store():
|
| 166 |
-
"""Store encrypted secret with enhanced
|
| 167 |
try:
|
| 168 |
form = request.form
|
| 169 |
data = form.get("data")
|
|
@@ -178,32 +178,23 @@ def store():
|
|
| 178 |
theme = form.get("theme", "default")
|
| 179 |
password_hint = form.get("password_hint", "")
|
| 180 |
|
| 181 |
-
# Handle file
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
file_name = secure_filename(file.filename)
|
| 199 |
-
file_type = get_file_type(file_name)
|
| 200 |
-
|
| 201 |
-
if file_type == 'unknown':
|
| 202 |
-
return jsonify({"error": "File type not supported"}), 400
|
| 203 |
-
|
| 204 |
-
# Read and encode file
|
| 205 |
-
file_content = file.read()
|
| 206 |
-
file_data = base64.b64encode(file_content).decode('utf-8')
|
| 207 |
|
| 208 |
# Generate IDs
|
| 209 |
secret_id = str(uuid.uuid4())
|
|
@@ -213,12 +204,13 @@ def store():
|
|
| 213 |
while short_id in SHORT_LINKS:
|
| 214 |
short_id = generate_short_id()
|
| 215 |
|
| 216 |
-
# Store secret
|
| 217 |
SECRETS[secret_id] = {
|
| 218 |
"data": data,
|
| 219 |
-
"
|
| 220 |
-
"
|
| 221 |
-
"
|
|
|
|
| 222 |
"expire_at": time.time() + ttl,
|
| 223 |
"view_once": view_once,
|
| 224 |
"delay_seconds": delay_seconds,
|
|
@@ -243,7 +235,7 @@ def store():
|
|
| 243 |
"short_url": f"{base_url}/s/{short_id}",
|
| 244 |
"qr_code": qr_code,
|
| 245 |
"expires_at": SECRETS[secret_id]["expire_at"],
|
| 246 |
-
"has_file":
|
| 247 |
})
|
| 248 |
|
| 249 |
except Exception as e:
|
|
@@ -251,7 +243,7 @@ def store():
|
|
| 251 |
|
| 252 |
@app.route("/api/fetch/<secret_id>")
|
| 253 |
def fetch(secret_id):
|
| 254 |
-
"""Fetch
|
| 255 |
try:
|
| 256 |
# Check if it's a short link
|
| 257 |
if secret_id in SHORT_LINKS:
|
|
@@ -272,7 +264,7 @@ def fetch(secret_id):
|
|
| 272 |
del SHORT_LINKS[short_id]
|
| 273 |
return jsonify({"error": "Secret has expired"}), 410
|
| 274 |
|
| 275 |
-
#
|
| 276 |
verify_only = request.args.get('verify_only', 'false').lower() == 'true'
|
| 277 |
|
| 278 |
# Only record access and increment count if NOT verify_only
|
|
@@ -292,11 +284,12 @@ def fetch(secret_id):
|
|
| 292 |
"access_count": secret["access_count"]
|
| 293 |
}
|
| 294 |
|
| 295 |
-
# Include file data if present
|
| 296 |
-
if secret.get("
|
| 297 |
-
response["
|
| 298 |
-
response["file_type"] = secret.get("file_type", "unknown")
|
| 299 |
response["file_name"] = secret.get("file_name", "unknown")
|
|
|
|
|
|
|
| 300 |
|
| 301 |
# Handle view-once deletion (only if not verify_only)
|
| 302 |
if secret["view_once"] and not verify_only:
|
|
@@ -355,7 +348,7 @@ def get_analytics(secret_id):
|
|
| 355 |
|
| 356 |
@app.route("/api/secrets")
|
| 357 |
def list_secrets():
|
| 358 |
-
"""List all active secrets
|
| 359 |
try:
|
| 360 |
current_time = time.time()
|
| 361 |
active_secrets = []
|
|
@@ -375,8 +368,10 @@ def list_secrets():
|
|
| 375 |
"created_at": secret["created_at"],
|
| 376 |
"expires_at": secret["expire_at"],
|
| 377 |
"view_once": secret["view_once"],
|
| 378 |
-
"has_file": secret.get("
|
| 379 |
-
"
|
|
|
|
|
|
|
| 380 |
"theme": secret.get("theme", "default"),
|
| 381 |
"access_count": secret.get("access_count", 0),
|
| 382 |
"preview": secret["data"][:100] + "..." if len(secret["data"]) > 100 else secret["data"]
|
|
@@ -459,16 +454,19 @@ def get_qr_code(secret_id):
|
|
| 459 |
|
| 460 |
@app.route("/api/stats")
|
| 461 |
def get_stats():
|
| 462 |
-
"""Get overall statistics"""
|
| 463 |
try:
|
| 464 |
total_secrets = len(SECRETS)
|
| 465 |
total_accesses = sum(len(analytics) for analytics in ANALYTICS.values())
|
| 466 |
|
| 467 |
-
# Count by file type
|
| 468 |
file_types = {}
|
| 469 |
for secret in SECRETS.values():
|
| 470 |
-
|
| 471 |
-
|
|
|
|
|
|
|
|
|
|
| 472 |
|
| 473 |
# Count by theme
|
| 474 |
themes = {}
|
|
@@ -481,7 +479,8 @@ def get_stats():
|
|
| 481 |
"total_accesses": total_accesses,
|
| 482 |
"file_types": file_types,
|
| 483 |
"themes": themes,
|
| 484 |
-
"active_short_links": len(SHORT_LINKS)
|
|
|
|
| 485 |
})
|
| 486 |
|
| 487 |
except Exception as e:
|
|
@@ -531,8 +530,8 @@ def internal_error(error):
|
|
| 531 |
if __name__ == "__main__":
|
| 532 |
print("π Sharelock Backend Starting...")
|
| 533 |
print("π Features enabled:")
|
| 534 |
-
print(" β
End-to-end encryption")
|
| 535 |
-
print(" β
|
| 536 |
print(" β
QR code generation")
|
| 537 |
print(" β
Analytics tracking")
|
| 538 |
print(" β
Short URLs")
|
|
@@ -540,6 +539,7 @@ if __name__ == "__main__":
|
|
| 540 |
print(" β
Multiple themes")
|
| 541 |
print(" β
Password hints")
|
| 542 |
print(" β
verify_only parameter support")
|
|
|
|
| 543 |
print("π Server running on http://0.0.0.0:7860")
|
| 544 |
|
| 545 |
app.run(host="0.0.0.0", port=7860, debug=True)
|
|
|
|
| 33 |
}
|
| 34 |
|
| 35 |
def get_file_type(filename):
|
| 36 |
+
"""Determine file type based on extension - NO CHANGES NEEDED"""
|
| 37 |
if not filename:
|
| 38 |
return 'unknown'
|
| 39 |
|
|
|
|
| 163 |
|
| 164 |
@app.route("/api/store", methods=["POST"])
|
| 165 |
def store():
|
| 166 |
+
"""Store encrypted secret with enhanced encrypted file support"""
|
| 167 |
try:
|
| 168 |
form = request.form
|
| 169 |
data = form.get("data")
|
|
|
|
| 178 |
theme = form.get("theme", "default")
|
| 179 |
password_hint = form.get("password_hint", "")
|
| 180 |
|
| 181 |
+
# Handle ENCRYPTED file data from frontend
|
| 182 |
+
encrypted_file = form.get("encrypted_file") # NEW: Get encrypted file data
|
| 183 |
+
file_name = form.get("file_name") # NEW: Get file name
|
| 184 |
+
file_type = form.get("file_type") # NEW: Get file type
|
| 185 |
+
file_size = form.get("file_size") # NEW: Get original file size
|
| 186 |
+
|
| 187 |
+
# REMOVE the old file handling code that processed raw files
|
| 188 |
+
# No need to handle 'file' in request.files anymore since frontend sends encrypted data
|
| 189 |
+
|
| 190 |
+
# Validate encrypted file data if present
|
| 191 |
+
if encrypted_file:
|
| 192 |
+
if not file_name or not file_type:
|
| 193 |
+
return jsonify({"error": "File name and type required for encrypted files"}), 400
|
| 194 |
+
|
| 195 |
+
# Optional: Validate file size (encrypted data will be larger due to base64 encoding)
|
| 196 |
+
if file_size and int(file_size) > MAX_FILE_SIZE:
|
| 197 |
+
return jsonify({"error": f"File too large. Max size: {MAX_FILE_SIZE/1024/1024:.1f}MB"}), 400
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
|
| 199 |
# Generate IDs
|
| 200 |
secret_id = str(uuid.uuid4())
|
|
|
|
| 204 |
while short_id in SHORT_LINKS:
|
| 205 |
short_id = generate_short_id()
|
| 206 |
|
| 207 |
+
# Store secret with encrypted file data
|
| 208 |
SECRETS[secret_id] = {
|
| 209 |
"data": data,
|
| 210 |
+
"encrypted_file": encrypted_file, # NEW: Store encrypted file data
|
| 211 |
+
"file_name": file_name, # NEW: Store file name
|
| 212 |
+
"file_type": file_type, # NEW: Store file type
|
| 213 |
+
"file_size": file_size, # NEW: Store original file size
|
| 214 |
"expire_at": time.time() + ttl,
|
| 215 |
"view_once": view_once,
|
| 216 |
"delay_seconds": delay_seconds,
|
|
|
|
| 235 |
"short_url": f"{base_url}/s/{short_id}",
|
| 236 |
"qr_code": qr_code,
|
| 237 |
"expires_at": SECRETS[secret_id]["expire_at"],
|
| 238 |
+
"has_file": encrypted_file is not None # MODIFIED: Check encrypted_file instead
|
| 239 |
})
|
| 240 |
|
| 241 |
except Exception as e:
|
|
|
|
| 243 |
|
| 244 |
@app.route("/api/fetch/<secret_id>")
|
| 245 |
def fetch(secret_id):
|
| 246 |
+
"""Fetch encrypted secret with encrypted file data"""
|
| 247 |
try:
|
| 248 |
# Check if it's a short link
|
| 249 |
if secret_id in SHORT_LINKS:
|
|
|
|
| 264 |
del SHORT_LINKS[short_id]
|
| 265 |
return jsonify({"error": "Secret has expired"}), 410
|
| 266 |
|
| 267 |
+
# Check for verify_only parameter
|
| 268 |
verify_only = request.args.get('verify_only', 'false').lower() == 'true'
|
| 269 |
|
| 270 |
# Only record access and increment count if NOT verify_only
|
|
|
|
| 284 |
"access_count": secret["access_count"]
|
| 285 |
}
|
| 286 |
|
| 287 |
+
# Include ENCRYPTED file data if present
|
| 288 |
+
if secret.get("encrypted_file"):
|
| 289 |
+
response["encrypted_file"] = secret["encrypted_file"] # MODIFIED: Return encrypted file
|
|
|
|
| 290 |
response["file_name"] = secret.get("file_name", "unknown")
|
| 291 |
+
response["file_type"] = secret.get("file_type", "unknown")
|
| 292 |
+
response["file_size"] = secret.get("file_size")
|
| 293 |
|
| 294 |
# Handle view-once deletion (only if not verify_only)
|
| 295 |
if secret["view_once"] and not verify_only:
|
|
|
|
| 348 |
|
| 349 |
@app.route("/api/secrets")
|
| 350 |
def list_secrets():
|
| 351 |
+
"""List all active secrets with encrypted file info"""
|
| 352 |
try:
|
| 353 |
current_time = time.time()
|
| 354 |
active_secrets = []
|
|
|
|
| 368 |
"created_at": secret["created_at"],
|
| 369 |
"expires_at": secret["expire_at"],
|
| 370 |
"view_once": secret["view_once"],
|
| 371 |
+
"has_file": secret.get("encrypted_file") is not None, # MODIFIED: Check encrypted_file
|
| 372 |
+
"file_name": secret.get("file_name"), # NEW: Include file name
|
| 373 |
+
"file_type": secret.get("file_type"), # MODIFIED: File type from metadata
|
| 374 |
+
"file_size": secret.get("file_size"), # NEW: Include file size
|
| 375 |
"theme": secret.get("theme", "default"),
|
| 376 |
"access_count": secret.get("access_count", 0),
|
| 377 |
"preview": secret["data"][:100] + "..." if len(secret["data"]) > 100 else secret["data"]
|
|
|
|
| 454 |
|
| 455 |
@app.route("/api/stats")
|
| 456 |
def get_stats():
|
| 457 |
+
"""Get overall statistics with encrypted file info"""
|
| 458 |
try:
|
| 459 |
total_secrets = len(SECRETS)
|
| 460 |
total_accesses = sum(len(analytics) for analytics in ANALYTICS.values())
|
| 461 |
|
| 462 |
+
# Count by file type (now from metadata, not actual files)
|
| 463 |
file_types = {}
|
| 464 |
for secret in SECRETS.values():
|
| 465 |
+
if secret.get("encrypted_file"): # MODIFIED: Check for encrypted_file
|
| 466 |
+
file_type = secret.get("file_type", "unknown")
|
| 467 |
+
file_types[file_type] = file_types.get(file_type, 0) + 1
|
| 468 |
+
else:
|
| 469 |
+
file_types["text"] = file_types.get("text", 0) + 1
|
| 470 |
|
| 471 |
# Count by theme
|
| 472 |
themes = {}
|
|
|
|
| 479 |
"total_accesses": total_accesses,
|
| 480 |
"file_types": file_types,
|
| 481 |
"themes": themes,
|
| 482 |
+
"active_short_links": len(SHORT_LINKS),
|
| 483 |
+
"encrypted_files": sum(1 for secret in SECRETS.values() if secret.get("encrypted_file")) # NEW
|
| 484 |
})
|
| 485 |
|
| 486 |
except Exception as e:
|
|
|
|
| 530 |
if __name__ == "__main__":
|
| 531 |
print("π Sharelock Backend Starting...")
|
| 532 |
print("π Features enabled:")
|
| 533 |
+
print(" β
End-to-end encryption (messages + files)") # MODIFIED
|
| 534 |
+
print(" β
Client-side file encryption (5MB max)") # MODIFIED
|
| 535 |
print(" β
QR code generation")
|
| 536 |
print(" β
Analytics tracking")
|
| 537 |
print(" β
Short URLs")
|
|
|
|
| 539 |
print(" β
Multiple themes")
|
| 540 |
print(" β
Password hints")
|
| 541 |
print(" β
verify_only parameter support")
|
| 542 |
+
print(" π Files never stored unencrypted on server") # NEW
|
| 543 |
print("π Server running on http://0.0.0.0:7860")
|
| 544 |
|
| 545 |
app.run(host="0.0.0.0", port=7860, debug=True)
|