mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-08 23:38:49 +00:00
store password hash in config
This commit is contained in:
@@ -37,7 +37,11 @@ class ConfigManager:
|
|||||||
"""
|
"""
|
||||||
if not self.config_path.exists():
|
if not self.config_path.exists():
|
||||||
logger.info("Config file not found; returning defaults")
|
logger.info("Config file not found; returning defaults")
|
||||||
return {"relays": list(DEFAULT_NOSTR_RELAYS), "pin_hash": ""}
|
return {
|
||||||
|
"relays": list(DEFAULT_NOSTR_RELAYS),
|
||||||
|
"pin_hash": "",
|
||||||
|
"password_hash": "",
|
||||||
|
}
|
||||||
try:
|
try:
|
||||||
data = self.vault.load_config()
|
data = self.vault.load_config()
|
||||||
if not isinstance(data, dict):
|
if not isinstance(data, dict):
|
||||||
@@ -45,6 +49,14 @@ class ConfigManager:
|
|||||||
# Ensure defaults for missing keys
|
# Ensure defaults for missing keys
|
||||||
data.setdefault("relays", list(DEFAULT_NOSTR_RELAYS))
|
data.setdefault("relays", list(DEFAULT_NOSTR_RELAYS))
|
||||||
data.setdefault("pin_hash", "")
|
data.setdefault("pin_hash", "")
|
||||||
|
data.setdefault("password_hash", "")
|
||||||
|
|
||||||
|
# Migrate legacy hashed_password.enc if present and password_hash is missing
|
||||||
|
legacy_file = self.fingerprint_dir / "hashed_password.enc"
|
||||||
|
if not data.get("password_hash") and legacy_file.exists():
|
||||||
|
with open(legacy_file, "rb") as f:
|
||||||
|
data["password_hash"] = f.read().decode()
|
||||||
|
self.save_config(data)
|
||||||
if require_pin and data.get("pin_hash"):
|
if require_pin and data.get("pin_hash"):
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
pin = getpass.getpass("Enter settings PIN: ").strip()
|
pin = getpass.getpass("Enter settings PIN: ").strip()
|
||||||
@@ -95,3 +107,9 @@ class ConfigManager:
|
|||||||
self.set_pin(new_pin)
|
self.set_pin(new_pin)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def set_password_hash(self, password_hash: str) -> None:
|
||||||
|
"""Persist the bcrypt password hash in the config."""
|
||||||
|
config = self.load_config(require_pin=False)
|
||||||
|
config["password_hash"] = password_hash
|
||||||
|
self.save_config(config)
|
||||||
|
@@ -1164,13 +1164,20 @@ class PasswordManager:
|
|||||||
bool: True if the password is correct, False otherwise.
|
bool: True if the password is correct, False otherwise.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
hashed_password_file = self.fingerprint_dir / "hashed_password.enc"
|
config = self.config_manager.load_config(require_pin=False)
|
||||||
if not hashed_password_file.exists():
|
stored_hash = config.get("password_hash", "").encode()
|
||||||
logging.error("Hashed password file not found.")
|
if not stored_hash:
|
||||||
print(colored("Error: Hashed password file not found.", "red"))
|
# Fallback to legacy file if hash not present in config
|
||||||
return False
|
legacy_file = self.fingerprint_dir / "hashed_password.enc"
|
||||||
with open(hashed_password_file, "rb") as f:
|
if legacy_file.exists():
|
||||||
stored_hash = f.read()
|
with open(legacy_file, "rb") as f:
|
||||||
|
stored_hash = f.read()
|
||||||
|
self.config_manager.set_password_hash(stored_hash.decode())
|
||||||
|
else:
|
||||||
|
logging.error("Hashed password not found.")
|
||||||
|
print(colored("Error: Hashed password not found.", "red"))
|
||||||
|
return False
|
||||||
|
|
||||||
is_correct = bcrypt.checkpw(password.encode("utf-8"), stored_hash)
|
is_correct = bcrypt.checkpw(password.encode("utf-8"), stored_hash)
|
||||||
if is_correct:
|
if is_correct:
|
||||||
logging.debug("Password verification successful.")
|
logging.debug("Password verification successful.")
|
||||||
@@ -1206,19 +1213,27 @@ class PasswordManager:
|
|||||||
This should be called during the initial setup.
|
This should be called during the initial setup.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
hashed_password_file = self.fingerprint_dir / "hashed_password.enc"
|
hashed = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode()
|
||||||
hashed = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt())
|
if self.config_manager:
|
||||||
with open(hashed_password_file, "wb") as f:
|
self.config_manager.set_password_hash(hashed)
|
||||||
f.write(hashed)
|
else:
|
||||||
os.chmod(hashed_password_file, 0o600)
|
# Fallback to legacy file method if config_manager unavailable
|
||||||
|
hashed_password_file = self.fingerprint_dir / "hashed_password.enc"
|
||||||
|
with open(hashed_password_file, "wb") as f:
|
||||||
|
f.write(hashed.encode())
|
||||||
|
os.chmod(hashed_password_file, 0o600)
|
||||||
logging.info("User password hashed and stored successfully.")
|
logging.info("User password hashed and stored successfully.")
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# If bcrypt.hashpw is not available, try using bcrypt directly
|
# If bcrypt.hashpw is not available, try using bcrypt directly
|
||||||
salt = bcrypt.gensalt()
|
salt = bcrypt.gensalt()
|
||||||
hashed = bcrypt.hashpw(password.encode("utf-8"), salt)
|
hashed = bcrypt.hashpw(password.encode("utf-8"), salt).decode()
|
||||||
with open(hashed_password_file, "wb") as f:
|
if self.config_manager:
|
||||||
f.write(hashed)
|
self.config_manager.set_password_hash(hashed)
|
||||||
os.chmod(hashed_password_file, 0o600)
|
else:
|
||||||
|
hashed_password_file = self.fingerprint_dir / "hashed_password.enc"
|
||||||
|
with open(hashed_password_file, "wb") as f:
|
||||||
|
f.write(hashed.encode())
|
||||||
|
os.chmod(hashed_password_file, 0o600)
|
||||||
logging.info(
|
logging.info(
|
||||||
"User password hashed and stored successfully (using alternative method)."
|
"User password hashed and stored successfully (using alternative method)."
|
||||||
)
|
)
|
||||||
|
@@ -23,6 +23,7 @@ def test_config_defaults_and_round_trip():
|
|||||||
cfg = cfg_mgr.load_config(require_pin=False)
|
cfg = cfg_mgr.load_config(require_pin=False)
|
||||||
assert cfg["relays"] == list(DEFAULT_RELAYS)
|
assert cfg["relays"] == list(DEFAULT_RELAYS)
|
||||||
assert cfg["pin_hash"] == ""
|
assert cfg["pin_hash"] == ""
|
||||||
|
assert cfg["password_hash"] == ""
|
||||||
|
|
||||||
cfg_mgr.set_pin("1234")
|
cfg_mgr.set_pin("1234")
|
||||||
cfg_mgr.set_relays(["wss://example.com"], require_pin=False)
|
cfg_mgr.set_relays(["wss://example.com"], require_pin=False)
|
||||||
@@ -64,7 +65,9 @@ def test_config_file_encrypted_after_save():
|
|||||||
assert raw != json.dumps(data).encode()
|
assert raw != json.dumps(data).encode()
|
||||||
|
|
||||||
loaded = cfg_mgr.load_config(require_pin=False)
|
loaded = cfg_mgr.load_config(require_pin=False)
|
||||||
assert loaded == data
|
assert loaded["relays"] == data["relays"]
|
||||||
|
assert loaded["pin_hash"] == data["pin_hash"]
|
||||||
|
assert loaded["password_hash"] == ""
|
||||||
|
|
||||||
|
|
||||||
def test_set_relays_persists_changes():
|
def test_set_relays_persists_changes():
|
||||||
@@ -86,3 +89,24 @@ def test_set_relays_requires_at_least_one():
|
|||||||
cfg_mgr = ConfigManager(vault, Path(tmpdir))
|
cfg_mgr = ConfigManager(vault, Path(tmpdir))
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
cfg_mgr.set_relays([], require_pin=False)
|
cfg_mgr.set_relays([], require_pin=False)
|
||||||
|
|
||||||
|
|
||||||
|
def test_password_hash_migrates_from_file(tmp_path):
|
||||||
|
key = Fernet.generate_key()
|
||||||
|
enc_mgr = EncryptionManager(key, tmp_path)
|
||||||
|
vault = Vault(enc_mgr, tmp_path)
|
||||||
|
cfg_mgr = ConfigManager(vault, tmp_path)
|
||||||
|
|
||||||
|
# save legacy config without password_hash
|
||||||
|
legacy_cfg = {"relays": ["wss://r"], "pin_hash": ""}
|
||||||
|
cfg_mgr.save_config(legacy_cfg)
|
||||||
|
|
||||||
|
hashed = bcrypt.hashpw(b"pw", bcrypt.gensalt())
|
||||||
|
(tmp_path / "hashed_password.enc").write_bytes(hashed)
|
||||||
|
|
||||||
|
cfg = cfg_mgr.load_config(require_pin=False)
|
||||||
|
assert cfg["password_hash"] == hashed.decode()
|
||||||
|
# subsequent loads should read from config
|
||||||
|
(tmp_path / "hashed_password.enc").unlink()
|
||||||
|
cfg2 = cfg_mgr.load_config(require_pin=False)
|
||||||
|
assert cfg2["password_hash"] == hashed.decode()
|
||||||
|
Reference in New Issue
Block a user