File size: 4,927 Bytes
92bdcb0 fa85d00 92bdcb0 fa85d00 92bdcb0 fa85d00 92bdcb0 fa85d00 92bdcb0 fa85d00 92bdcb0 fa85d00 92bdcb0 fa85d00 92bdcb0 fa85d00 92bdcb0 fa85d00 92bdcb0 fa85d00 92bdcb0 fa85d00 92bdcb0 fa85d00 92bdcb0 fa85d00 92bdcb0 fa85d00 2d68192 fa85d00 92bdcb0 fa85d00 92bdcb0 fa85d00 92bdcb0 fa85d00 92bdcb0 fa85d00 92bdcb0 fa85d00 92bdcb0 fa85d00 92bdcb0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | import logging
import sys
from uuid import uuid4
import base64
from fastapi import HTTPException
from cryptography.fernet import Fernet
from src.database import supabase
from src.security import generate_keys
from src.models import PayloadRequest, RetrieveGroupRequest
# --- LOGGING CONFIGURATION ---
logger = logging.getLogger("qecho")
logger.setLevel(logging.INFO)
# Avoid adding duplicate handlers if re-imported
if not logger.handlers:
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
logger.addHandler(handler)
# -----------------------------
async def process_store_data(payload: PayloadRequest):
if not payload.data:
raise HTTPException(status_code=400, detail="Data field is required")
logger.info(f"Attempting to store data for group: {payload.group_name}")
try:
# 1. Generate Keys
logger.info("Step 1: Generating Keys...")
enc_key, db_index = generate_keys(payload.group_name, payload.password)
logger.info(f"Keys generated. Blind Index hash: {db_index[:8]}...")
# 2. Encrypt
logger.info("Step 2: Encrypting payload...")
f = Fernet(enc_key)
encrypted_bytes = f.encrypt(payload.data.encode())
encrypted_string = base64.urlsafe_b64encode(encrypted_bytes).decode('utf-8')
record_id = str(uuid4())
logger.info(f"Payload encrypted. Record ID: {record_id}")
# 3. Save to Supabase using the Blind Index
logger.info("Step 3: Inserting into Supabase...")
data, count = supabase.table("secrets").insert({
"id": record_id,
"encrypted_data": encrypted_string,
"group_hash": db_index
}).execute()
logger.info("Supabase insert successful.")
return {"status": "success", "uuid": record_id, "message": "Securely stored."}
except Exception as e:
logger.exception("CRITICAL ERROR during store_data:")
# We can optionally expose the error detail to the user for debugging purposes
# since this is a dev/security tool, but be careful not to leak secrets.
raise HTTPException(status_code=500, detail=f"Save failed: {str(e)}")
async def process_retrieve_data(payload: RetrieveGroupRequest):
logger.info(f"Attempting to retrieve data for group: {payload.group_name} with limit {payload.limit}")
try:
# 1. Generate Keys
logger.info("Step 1: Generating Keys...")
enc_key, db_index = generate_keys(payload.group_name, payload.password)
logger.info(f"Keys generated. Blind Index hash: {db_index[:8]}...")
# 2. Fetch
logger.info("Step 2: select from Supabase...")
response = supabase.table("secrets") \
.select("id, encrypted_data, created_at") \
.eq("group_hash", db_index) \
.order("created_at", desc=True) \
.limit(payload.limit) \
.execute()
if not response.data:
logger.info("No records found for this group.")
return {"messages": []}
logger.info(f"Found {len(response.data)} records. Attempting decryption...")
# 3. Decrypt
f = Fernet(enc_key)
decrypted_messages = []
ids_to_delete = [] # Keep track of what we successfully decrypted
for row in response.data:
try:
enc_bytes = base64.urlsafe_b64decode(row['encrypted_data'])
dec_msg = f.decrypt(enc_bytes).decode()
decrypted_messages.append({
"id": row['id'],
"timestamp": row['created_at'],
"content": dec_msg
})
# If decryption worked, mark for deletion
ids_to_delete.append(row['id'])
except Exception as decrypt_err:
logger.warning(f"Failed to decrypt message {row['id']}: {decrypt_err}")
continue
logger.info(f"Successfully decrypted {len(decrypted_messages)} messages.")
# 4. BURN AFTER READING (If requested)
if payload.burn and ids_to_delete:
logger.info(f"Burn requested. Deleting {len(ids_to_delete)} messages...")
supabase.table("secrets").delete().in_("id", ids_to_delete).execute()
logger.info("Burn complete.")
# Append a meta-message so the user knows
decrypted_messages.append({
"system_info": f"Securely burned {len(ids_to_delete)} messages from the server."
})
return {"messages": decrypted_messages}
except Exception as e:
logger.exception("CRITICAL ERROR during retrieve_data:")
raise HTTPException(status_code=500, detail=f"Retrieve failed: {str(e)}")
|