Skip to content

Commit 4bae4c9

Browse files
committed
chore(decryption): Handle silent decryption's fails
1 parent 5047672 commit 4bae4c9

2 files changed

Lines changed: 71 additions & 17 deletions

File tree

biobot/app.py

Lines changed: 55 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
app.config["SESSION_PERMANENT"] = True
2424
app.config["PERMANENT_SESSION_LIFETIME"] = 86400 # 24 hours in seconds
2525

26-
MODEL_NAME = "gpt-5"
26+
MODEL_NAME = "gpt-5.4"
2727

2828
# ---------------------
2929
# Encryption helpers
@@ -35,23 +35,45 @@ def get_encryption_key():
3535
return key.encode("utf-8") if isinstance(key, str) else key
3636
return None
3737

38+
39+
def _is_encrypted(text):
40+
"""Check if a string looks like Fernet ciphertext."""
41+
if not text or not isinstance(text, str):
42+
return False
43+
return text.startswith("gAAAAAB")
44+
45+
3846
def encrypt_text(text):
39-
"""Encrypt text if encryption key is available, otherwise return as-is."""
47+
"""Encrypt text using the session encryption key."""
4048
key = get_encryption_key()
4149
if key and text:
4250
return encrypt(text, key)
4351
return text
4452

53+
4554
def decrypt_text(ciphertext):
46-
"""Decrypt text if encryption key is available, otherwise return as-is."""
55+
"""
56+
Decrypt text using the session encryption key.
57+
- If content is encrypted and key is available → decrypt normally
58+
- If content is encrypted but key is missing → return None (caller must handle)
59+
- If content is NOT encrypted (legacy plaintext) → return as-is
60+
"""
61+
if not ciphertext:
62+
return ciphertext
63+
64+
if not _is_encrypted(ciphertext):
65+
# Legacy unencrypted data — return as-is
66+
return ciphertext
67+
68+
# Content is encrypted — we need the key
4769
key = get_encryption_key()
48-
if key and ciphertext:
49-
try:
50-
return decrypt(ciphertext, key)
51-
except Exception:
52-
# Fallback for unencrypted legacy data
53-
return ciphertext
54-
return ciphertext
70+
if not key:
71+
return None # Signal that decryption failed — caller must handle
72+
73+
try:
74+
return decrypt(ciphertext, key)
75+
except Exception:
76+
return None # Corrupted or wrong key
5577

5678
# ---------------------
5779
# DB helper wrappers
@@ -90,7 +112,7 @@ def execute(conn, query, params=(), commit=False, returning=False):
90112
"content": """You are BioBot 🤖, an expert assistant specialized in lab automation, particularly with liquid handling robots.
91113
Your tasks:
92114
- A chat history is provided to help you recall previous interactions, but **do not process the entire history as new instructions**; use it only if you need to reference something the user said before. To answer, focus primarily on the **latest user message**.
93-
- If the user asks for code for protocols, generate clean, error-free Python code for operating lab robots.
115+
- If the user asks for code/scripts/files for protocols, generate clean, error-free code/scripts/files for operating lab robots.
94116
- Ask for more informations if you assume that there are not enough informations in order to generate the code. You are specialized, you know what informations to ask.
95117
- Do not answer queries that have nohing to do with your specialization which is lab automation, liquid handlers and other related fields. Decline kindly."""
96118
}
@@ -401,16 +423,32 @@ def chat_stream(chat_id):
401423
if conn:
402424
conn.close()
403425

404-
# Decrypt history for the LLM
405-
messages = [{"role": r["role"], "content": decrypt_text(r["content"])} for r in rows]
426+
# --- Validate session and encryption BEFORE any decryption ---
427+
enc_key = get_encryption_key()
428+
if not enc_key:
429+
# Session lost — clear it and tell the frontend
430+
session.clear()
431+
return jsonify({"error": "Your session has expired. Please log in again."}), 401
432+
433+
# Decrypt API key first — it's the clearest test of whether encryption is working
406434
user_api_key = decrypt_text(user["api_key"]) if user and user.get("api_key") else None
407435

408-
# Validate API key before starting the stream
409436
if not user_api_key:
410-
return Response("I don't have a valid API key configured. Please add your OpenAI API key in Settings.", mimetype="text/plain")
437+
return Response(
438+
"No API key found. Please add your OpenAI API key in Settings.",
439+
mimetype="text/plain"
440+
)
411441

412-
# Capture encryption key before entering generator (session may not be available later)
413-
enc_key = get_encryption_key()
442+
if not user_api_key.startswith("sk-"):
443+
# Key exists but decryption returned garbage — encryption key is wrong
444+
# This means the user's password changed or the salt was lost
445+
session.clear()
446+
return jsonify({"error": "Your session has expired. Please log in again."}), 401
447+
448+
# Decrypt chat history for the LLM
449+
messages = [{"role": r["role"], "content": decrypt_text(r["content"])} for r in rows]
450+
# Remove any messages that failed to decrypt (shouldn't happen if enc_key is valid, but safety net)
451+
messages = [m for m in messages if m["content"] is not None]
414452

415453
# ---- STREAM RESPONSE ----
416454
def generate():

biobot/static/script.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,22 @@ async function sendMessage() {
508508
body: JSON.stringify({ message, api_key: apiKey }),
509509
});
510510

511+
// Handle session expiration — show message, then redirect to login
512+
if (res.status === 401) {
513+
clearInterval(thinkingInterval);
514+
const thinkingElem = document.getElementById("thinking-message");
515+
if (thinkingElem) thinkingElem.remove();
516+
517+
const errorData = await res.json().catch(() => ({ error: "Session expired" }));
518+
const botDiv = addMessage("", "bot");
519+
appendChunkToBotMessage(botDiv, errorData.error || "Your session has expired. Redirecting to login...");
520+
521+
setTimeout(() => {
522+
window.location.href = "/login";
523+
}, 3000);
524+
return;
525+
}
526+
511527
if (!res.body) throw new Error("No response body");
512528

513529
const reader = res.body.getReader();

0 commit comments

Comments
 (0)