SmartHeal commited on
Commit
904b28b
·
verified ·
1 Parent(s): ffdcfd4

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +57 -24
src/streamlit_app.py CHANGED
@@ -1,3 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import json
2
  import time
3
  import base64
@@ -8,10 +27,9 @@ from dataclasses import dataclass, asdict
8
  from typing import List, Optional, Dict, Any
9
 
10
  import streamlit as st
11
- import pandas as pd # for Excel/CSV import
12
 
13
  # ---- Crypto deps ----
14
- # pip install: streamlit cryptography argon2-cffi pandas openpyxl
15
  from cryptography.hazmat.primitives.ciphers.aead import AESGCM
16
  from cryptography.hazmat.primitives import hashes
17
  from cryptography.hazmat.primitives.kdf.hkdf import HKDF
@@ -19,17 +37,35 @@ from cryptography.hazmat.primitives.hmac import HMAC
19
  from cryptography.hazmat.backends import default_backend
20
  from argon2.low_level import hash_secret_raw, Type
21
 
22
- APP_TITLE = "SmartPass — Local, Zero‑Knowledge Password Manager (uploads-enabled)"
23
  VERSION = 1
24
 
25
  # -------------------------- Uploads / temp dirs --------------------------
26
- UPLOAD_DIR = Path(os.environ.get("UPLOAD_DIR", "./uploads")).resolve()
27
- TMP_DIR = Path(os.environ.get("TMPDIR", "./tmp")).resolve()
 
 
 
 
28
  for p in (UPLOAD_DIR, TMP_DIR):
29
  p.mkdir(parents=True, exist_ok=True)
30
 
31
  # -------------------------- Utility helpers --------------------------
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  def b64e(b: bytes) -> str:
34
  return base64.b64encode(b).decode("utf-8")
35
 
@@ -235,13 +271,15 @@ def update_item(data_key: bytes, it: VaultItem, payload: Dict[str, Any]) -> Vaul
235
  # -------------------------- Import helpers (Excel/CSV) --------------------------
236
 
237
  def _read_tabular(file_or_path) -> pd.DataFrame:
238
- # Accept a file-like object or a path
239
  if hasattr(file_or_path, "read"):
240
- # Try both excel/csv from buffer name fallback
241
  try:
242
  return pd.read_excel(file_or_path)
243
  except Exception:
244
- file_or_path.seek(0)
 
 
 
245
  return pd.read_csv(file_or_path)
246
  name = str(file_or_path).lower()
247
  if name.endswith(".csv"):
@@ -330,7 +368,6 @@ with st.sidebar:
330
  st.error("Please enter your master password.")
331
  else:
332
  try:
333
- # Persist a copy of uploaded vault and load from disk
334
  saved_vault_path = UPLOAD_DIR / (up.name or "uploaded_vault.json")
335
  with open(saved_vault_path, "wb") as f:
336
  f.write(up.getbuffer())
@@ -369,11 +406,11 @@ with st.sidebar:
369
  st.session_state.vault = vdict
370
  st.session_state.keys = unlock_vault(vdict, new_pw)
371
  st.session_state.unlocked = True
372
- # Immediately save a new vault file under uploads
373
  new_path = UPLOAD_DIR / "vault.smartpass.json"
374
- new_path.write_text(json.dumps(vdict, separators=(",", ":")), encoding="utf-8")
375
  touch()
376
- st.success(f"New vault created, unlocked, and saved at {new_path}.")
377
 
378
  st.divider()
379
  st.subheader("Export / Backup")
@@ -388,8 +425,8 @@ with st.sidebar:
388
  )
389
  if st.button("Save vault to uploads/", use_container_width=True):
390
  path = UPLOAD_DIR / "vault.smartpass.json"
391
- path.write_text(export_json, encoding="utf-8")
392
- st.success(f"Saved: {path}")
393
 
394
  st.divider()
395
  st.subheader("Import from Excel/CSV")
@@ -421,9 +458,8 @@ with st.sidebar:
421
  added += 1
422
  st.session_state.vault["items"] = [it.to_dict() for it in items_local]
423
  re_hmac(st.session_state.vault, st.session_state.keys["mac_key"])
424
- # Save updated vault immediately
425
- (UPLOAD_DIR / "vault.smartpass.json").write_text(json.dumps(st.session_state.vault, separators=(",", ":")), encoding="utf-8")
426
- st.success(f"Imported {added} item(s). Saved updated vault to uploads/.")
427
  st.experimental_rerun()
428
  except Exception as e:
429
  st.error(f"Import failed: {e}")
@@ -498,8 +534,6 @@ if lookup.strip():
498
  }
499
  st.experimental_rerun()
500
 
501
- touch()
502
-
503
  # Decrypt all for listing
504
  vault_dict = st.session_state.vault
505
  keys = st.session_state.keys
@@ -549,8 +583,7 @@ for r in rows:
549
  items = [it for it in items if it.id != r['_id']]
550
  vault_dict["items"] = [it.to_dict() for it in items]
551
  re_hmac(vault_dict, keys["mac_key"])
552
- # Save updated vault immediately
553
- (UPLOAD_DIR / "vault.smartpass.json").write_text(json.dumps(vault_dict, separators=(",", ":")), encoding="utf-8")
554
  st.session_state.vault = vault_dict
555
  st.success("Deleted and saved.")
556
  st.experimental_rerun()
@@ -579,7 +612,7 @@ if st.session_state.get("add_type"):
579
  items.append(new_item)
580
  vault_dict["items"] = [it.to_dict() for it in items]
581
  re_hmac(vault_dict, keys["mac_key"])
582
- (UPLOAD_DIR / "vault.smartpass.json").write_text(json.dumps(vault_dict, separators=(",", ":")), encoding="utf-8")
583
  st.session_state.vault = vault_dict
584
  st.session_state["add_type"] = None
585
  st.success("Item added and saved.")
@@ -614,7 +647,7 @@ if st.session_state.get("edit_id"):
614
  items = [updated if it.id == eid else it for it in items]
615
  vault_dict["items"] = [it.to_dict() for it in items]
616
  re_hmac(vault_dict, keys["mac_key"])
617
- (UPLOAD_DIR / "vault.smartpass.json").write_text(json.dumps(vault_dict, separators=(",", ":")), encoding="utf-8")
618
  st.session_state.vault = vault_dict
619
  st.session_state["edit_id"] = None
620
  st.success("Updated and saved.")
@@ -633,4 +666,4 @@ with st.expander("Security Notes & Tips"):
633
  - **KDF tuning:** If your device is slow, reduce Argon2 memory/iterations in the sidebar.
634
  - **Backups:** Keep copies of your exported vault JSON in safe places.
635
  """
636
- )
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ SmartPass — Streamlit Password Manager (Hugging Face Spaces–ready, uploads-enabled)
4
+
5
+ • Zero-knowledge: Argon2id KDF → HKDF subkeys → AES-GCM per-item + HMAC integrity
6
+ • Excel/CSV import (auto-maps common headers)
7
+ • Quick Password Lookup (type a site/app and reveal password)
8
+ • Writes to /data by default on Spaces; configurable via UPLOAD_DIR/TMPDIR env vars
9
+
10
+ Run locally:
11
+ pip install streamlit cryptography argon2-cffi pandas openpyxl
12
+ streamlit run streamlit_app.py
13
+
14
+ On Hugging Face Spaces (recommended flags):
15
+ STREAMLIT_SERVER_ENABLE_XSRF_PROTECTION=false
16
+ STREAMLIT_SERVER_ENABLE_CORS=false
17
+
18
+ """
19
+ from __future__ import annotations
20
  import json
21
  import time
22
  import base64
 
27
  from typing import List, Optional, Dict, Any
28
 
29
  import streamlit as st
30
+ import pandas as pd # Excel/CSV import
31
 
32
  # ---- Crypto deps ----
 
33
  from cryptography.hazmat.primitives.ciphers.aead import AESGCM
34
  from cryptography.hazmat.primitives import hashes
35
  from cryptography.hazmat.primitives.kdf.hkdf import HKDF
 
37
  from cryptography.hazmat.backends import default_backend
38
  from argon2.low_level import hash_secret_raw, Type
39
 
40
+ APP_TITLE = "SmartPass — Local, Zero‑Knowledge Password Manager"
41
  VERSION = 1
42
 
43
  # -------------------------- Uploads / temp dirs --------------------------
44
+ # Detect Hugging Face Spaces and default to /data (writable). Allow env overrides.
45
+ IS_HF = any(os.environ.get(k) for k in ("HUGGINGFACE_SPACE", "SPACE_ID", "HF_SPACE_ID"))
46
+ DEFAULT_UPLOAD = "/data/uploads" if IS_HF else "./uploads"
47
+ DEFAULT_TMP = "/data/tmp" if IS_HF else "./tmp"
48
+ UPLOAD_DIR = Path(os.environ.get("UPLOAD_DIR", DEFAULT_UPLOAD)).resolve()
49
+ TMP_DIR = Path(os.environ.get("TMPDIR", DEFAULT_TMP)).resolve()
50
  for p in (UPLOAD_DIR, TMP_DIR):
51
  p.mkdir(parents=True, exist_ok=True)
52
 
53
  # -------------------------- Utility helpers --------------------------
54
 
55
+ def safe_write_text(path: Path, text: str) -> Path:
56
+ """Write text to path, creating parents. If it fails (e.g., perms),
57
+ retry under TMP_DIR. Returns the final Path written."""
58
+ try:
59
+ path.parent.mkdir(parents=True, exist_ok=True)
60
+ path.write_text(text, encoding="utf-8")
61
+ return path
62
+ except Exception:
63
+ alt = TMP_DIR / path.name
64
+ alt.parent.mkdir(parents=True, exist_ok=True)
65
+ alt.write_text(text, encoding="utf-8")
66
+ return alt
67
+
68
+
69
  def b64e(b: bytes) -> str:
70
  return base64.b64encode(b).decode("utf-8")
71
 
 
271
  # -------------------------- Import helpers (Excel/CSV) --------------------------
272
 
273
  def _read_tabular(file_or_path) -> pd.DataFrame:
274
+ # Accept file-like or path
275
  if hasattr(file_or_path, "read"):
 
276
  try:
277
  return pd.read_excel(file_or_path)
278
  except Exception:
279
+ try:
280
+ file_or_path.seek(0)
281
+ except Exception:
282
+ pass
283
  return pd.read_csv(file_or_path)
284
  name = str(file_or_path).lower()
285
  if name.endswith(".csv"):
 
368
  st.error("Please enter your master password.")
369
  else:
370
  try:
 
371
  saved_vault_path = UPLOAD_DIR / (up.name or "uploaded_vault.json")
372
  with open(saved_vault_path, "wb") as f:
373
  f.write(up.getbuffer())
 
406
  st.session_state.vault = vdict
407
  st.session_state.keys = unlock_vault(vdict, new_pw)
408
  st.session_state.unlocked = True
409
+ # Persist the new vault to uploads (fallback to TMP if needed)
410
  new_path = UPLOAD_DIR / "vault.smartpass.json"
411
+ _written = safe_write_text(new_path, json.dumps(vdict, separators=(",", ":")))
412
  touch()
413
+ st.success(f"New vault created, unlocked, and saved at {_written}.")
414
 
415
  st.divider()
416
  st.subheader("Export / Backup")
 
425
  )
426
  if st.button("Save vault to uploads/", use_container_width=True):
427
  path = UPLOAD_DIR / "vault.smartpass.json"
428
+ final_path = safe_write_text(path, export_json)
429
+ st.success(f"Saved: {final_path}")
430
 
431
  st.divider()
432
  st.subheader("Import from Excel/CSV")
 
458
  added += 1
459
  st.session_state.vault["items"] = [it.to_dict() for it in items_local]
460
  re_hmac(st.session_state.vault, st.session_state.keys["mac_key"])
461
+ safe_write_text(UPLOAD_DIR / "vault.smartpass.json", json.dumps(st.session_state.vault, separators=(",", ":")))
462
+ st.success(f"Imported {added} item(s). Updated vault saved.")
 
463
  st.experimental_rerun()
464
  except Exception as e:
465
  st.error(f"Import failed: {e}")
 
534
  }
535
  st.experimental_rerun()
536
 
 
 
537
  # Decrypt all for listing
538
  vault_dict = st.session_state.vault
539
  keys = st.session_state.keys
 
583
  items = [it for it in items if it.id != r['_id']]
584
  vault_dict["items"] = [it.to_dict() for it in items]
585
  re_hmac(vault_dict, keys["mac_key"])
586
+ safe_write_text(UPLOAD_DIR / "vault.smartpass.json", json.dumps(vault_dict, separators=(",", ":")))
 
587
  st.session_state.vault = vault_dict
588
  st.success("Deleted and saved.")
589
  st.experimental_rerun()
 
612
  items.append(new_item)
613
  vault_dict["items"] = [it.to_dict() for it in items]
614
  re_hmac(vault_dict, keys["mac_key"])
615
+ safe_write_text(UPLOAD_DIR / "vault.smartpass.json", json.dumps(vault_dict, separators=(",", ":")))
616
  st.session_state.vault = vault_dict
617
  st.session_state["add_type"] = None
618
  st.success("Item added and saved.")
 
647
  items = [updated if it.id == eid else it for it in items]
648
  vault_dict["items"] = [it.to_dict() for it in items]
649
  re_hmac(vault_dict, keys["mac_key"])
650
+ safe_write_text(UPLOAD_DIR / "vault.smartpass.json", json.dumps(vault_dict, separators=(",", ":")))
651
  st.session_state.vault = vault_dict
652
  st.session_state["edit_id"] = None
653
  st.success("Updated and saved.")
 
666
  - **KDF tuning:** If your device is slow, reduce Argon2 memory/iterations in the sidebar.
667
  - **Backups:** Keep copies of your exported vault JSON in safe places.
668
  """
669
+ )