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