mirror of
https://github.com/Nighthawk42/bnet_auth_tool.git
synced 2026-08-30 14:32:26 +00:00
Replace the single-file v1.3.1 script with a proper src/bnet_auth_tool/ package: config (YAML settings + platformdirs), crypto (scrypt + AES-256-GCM with a versioned header; legacy PBKDF2 100k/600k still decrypt), storage (single encrypted vault), fileio (atomic 0600 writes), api, totp, migrate, and a cli with an interactive menu plus argparse subcommands. Security/audit fixes: drop catch-all excepts, stop leaking server bodies/tokens in errors, atomic permission-hardened writes, explicit Ctrl-C handling, versioned format header, best-effort passphrase scrubbing. Add packaging (pyproject for uv, organized requirements.txt fallback, uv.lock), .gitignore, settings.yaml, CLAUDE.md/AGENTS.md, 22 pytest tests, and ruff config. Online attach/retrieve is preserved but kept labelled unverified. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
88 lines
3.0 KiB
Python
88 lines
3.0 KiB
Python
"""Legacy file discovery and migration into the vault."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
from bnet_auth_tool.crypto import EncryptionManager
|
|
from bnet_auth_tool.migrate import (
|
|
discover_legacy_files,
|
|
migrate_files,
|
|
normalise_entry,
|
|
)
|
|
from bnet_auth_tool.storage import Vault
|
|
|
|
PLAIN = {
|
|
"serial": "US-1111",
|
|
"restoreCode": "AAAA-BBBB",
|
|
"deviceSecret": "deadbeef",
|
|
"base32Secret": "32W353Y",
|
|
"totpUrl": "otpauth://totp/Battle.net:US-1111?secret=32W353Y",
|
|
"timestamp": "2024-01-01T00:00:00+00:00",
|
|
}
|
|
|
|
|
|
def _vault(tmp_path, settings, passphrase="pw"):
|
|
return Vault(EncryptionManager(passphrase, settings.crypto), tmp_path / "vault.json").load()
|
|
|
|
|
|
def test_discover_matches_expected_names(tmp_path):
|
|
(tmp_path / "battlenet_authenticator_US-1.json").write_text("{}")
|
|
(tmp_path / "reconstructed_US-2.json").write_text("{}")
|
|
(tmp_path / "unrelated.json").write_text("{}")
|
|
names = {p.name for p in discover_legacy_files(tmp_path)}
|
|
assert names == {"battlenet_authenticator_US-1.json", "reconstructed_US-2.json"}
|
|
|
|
|
|
def test_normalise_derives_missing_fields(settings):
|
|
entry = normalise_entry({"serial": "US-9", "deviceSecret": "deadbeef"}, settings)
|
|
assert entry["base32Secret"] == "32W353Y"
|
|
assert entry["totpUrl"].startswith("otpauth://totp/Battle.net:US-9")
|
|
|
|
|
|
def test_migrate_plaintext_file(tmp_path, settings):
|
|
f = tmp_path / "battlenet_authenticator_US-1111.json"
|
|
f.write_text(json.dumps(PLAIN))
|
|
vault = _vault(tmp_path, settings)
|
|
|
|
outcomes = migrate_files([f], vault, settings, lambda p: None)
|
|
vault.save()
|
|
|
|
assert outcomes[0].status == "imported"
|
|
assert vault.get("US-1111")["deviceSecret"] == "deadbeef"
|
|
assert vault.get("US-1111")["addedAt"] == PLAIN["timestamp"]
|
|
|
|
|
|
def test_migrate_encrypted_file(tmp_path, settings):
|
|
f = tmp_path / "battlenet_authenticator_US-2222.json"
|
|
blob = EncryptionManager("filepw", settings.crypto).encrypt({**PLAIN, "serial": "US-2222"})
|
|
f.write_bytes(blob)
|
|
vault = _vault(tmp_path, settings)
|
|
|
|
outcomes = migrate_files([f], vault, settings, lambda p: "filepw")
|
|
vault.save()
|
|
|
|
assert outcomes[0].status == "imported"
|
|
assert vault.get("US-2222") is not None
|
|
|
|
|
|
def test_migrate_skips_without_passphrase(tmp_path, settings):
|
|
f = tmp_path / "battlenet_authenticator_US-3333.json"
|
|
f.write_bytes(EncryptionManager("x", settings.crypto).encrypt({**PLAIN, "serial": "US-3333"}))
|
|
vault = _vault(tmp_path, settings)
|
|
|
|
outcomes = migrate_files([f], vault, settings, lambda p: None)
|
|
assert outcomes[0].status == "skipped"
|
|
assert len(vault) == 0
|
|
|
|
|
|
def test_migrate_no_overwrite_existing(tmp_path, settings):
|
|
f = tmp_path / "battlenet_authenticator_US-1111.json"
|
|
f.write_text(json.dumps(PLAIN))
|
|
vault = _vault(tmp_path, settings)
|
|
vault.add({"serial": "US-1111", "deviceSecret": "old"})
|
|
|
|
outcomes = migrate_files([f], vault, settings, lambda p: None)
|
|
assert outcomes[0].status == "skipped"
|
|
assert vault.get("US-1111")["deviceSecret"] == "old"
|