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)}")