From e2235b61e12e3b1fd8103769d30d9c447d8f4d79 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Wed, 9 Jul 2025 15:46:37 -0400 Subject: [PATCH] Add change-password command and endpoint --- docs/advanced_cli.md | 2 ++ docs/api_reference.md | 1 + src/seedpass/api.py | 9 +++++++++ src/seedpass/cli.py | 7 +++++++ src/tests/test_api.py | 14 ++++++++++++++ src/tests/test_typer_cli.py | 13 +++++++++++++ 6 files changed, 46 insertions(+) diff --git a/docs/advanced_cli.md b/docs/advanced_cli.md index f540236..5a8c0d6 100644 --- a/docs/advanced_cli.md +++ b/docs/advanced_cli.md @@ -68,6 +68,7 @@ Manage the entire vault for a profile. | Action | Command | Examples | | :--- | :--- | :--- | | Export the vault | `vault export` | `seedpass vault export --file backup.json` | +| Change the master password | `vault change-password` | `seedpass vault change-password` | ### Nostr Commands @@ -147,6 +148,7 @@ Code: 123456 ### `vault` Commands - **`seedpass vault export`** – Export the entire vault to an encrypted JSON file. +- **`seedpass vault change-password`** – Change the master password used for encryption. ### `nostr` Commands diff --git a/docs/api_reference.md b/docs/api_reference.md index 7d21d68..225b0c5 100644 --- a/docs/api_reference.md +++ b/docs/api_reference.md @@ -28,6 +28,7 @@ Keep this token secret. Every request must include it in the `Authorization` hea - `DELETE /api/v1/fingerprint/{fp}` – Remove a fingerprint. - `POST /api/v1/fingerprint/select` – Switch the active fingerprint. - `GET /api/v1/nostr/pubkey` – Fetch the Nostr public key for the active seed. +- `POST /api/v1/change-password` – Change the master password for the active profile. - `POST /api/v1/shutdown` – Stop the server gracefully. ## Example Requests diff --git a/src/seedpass/api.py b/src/seedpass/api.py index ba6d35a..e426ff7 100644 --- a/src/seedpass/api.py +++ b/src/seedpass/api.py @@ -311,6 +311,15 @@ def get_nostr_pubkey(authorization: str | None = Header(None)) -> Any: return {"npub": _pm.nostr_client.key_manager.get_npub()} +@app.post("/api/v1/change-password") +def change_password(authorization: str | None = Header(None)) -> dict[str, str]: + """Change the master password for the active profile.""" + _check_token(authorization) + assert _pm is not None + _pm.change_password() + return {"status": "ok"} + + @app.post("/api/v1/shutdown") async def shutdown_server(authorization: str | None = Header(None)) -> dict[str, str]: _check_token(authorization) diff --git a/src/seedpass/cli.py b/src/seedpass/cli.py index cb82902..7b2a308 100644 --- a/src/seedpass/cli.py +++ b/src/seedpass/cli.py @@ -324,6 +324,13 @@ def vault_export( typer.echo(str(file)) +@vault_app.command("change-password") +def vault_change_password(ctx: typer.Context) -> None: + """Change the master password used for encryption.""" + pm = _get_pm(ctx) + pm.change_password() + + @nostr_app.command("sync") def nostr_sync(ctx: typer.Context) -> None: """Sync with configured Nostr relays.""" diff --git a/src/tests/test_api.py b/src/tests/test_api.py index 61c257c..a16adbf 100644 --- a/src/tests/test_api.py +++ b/src/tests/test_api.py @@ -158,6 +158,19 @@ def test_update_config(client): assert res.headers.get("access-control-allow-origin") == "http://example.com" +def test_change_password_route(client): + cl, token = client + called = {} + + api._pm.change_password = lambda: called.setdefault("called", True) + headers = {"Authorization": f"Bearer {token}", "Origin": "http://example.com"} + res = cl.post("/api/v1/change-password", headers=headers) + assert res.status_code == 200 + assert res.json() == {"status": "ok"} + assert called.get("called") is True + assert res.headers.get("access-control-allow-origin") == "http://example.com" + + def test_update_config_unknown_key(client): cl, token = client headers = {"Authorization": f"Bearer {token}", "Origin": "http://example.com"} @@ -206,6 +219,7 @@ def test_shutdown(client, monkeypatch): ("put", "/api/v1/config/inactivity_timeout"), ("post", "/api/v1/entry/1/archive"), ("post", "/api/v1/entry/1/unarchive"), + ("post", "/api/v1/change-password"), ], ) def test_invalid_token_other_endpoints(client, method, path): diff --git a/src/tests/test_typer_cli.py b/src/tests/test_typer_cli.py index 603b728..db96863 100644 --- a/src/tests/test_typer_cli.py +++ b/src/tests/test_typer_cli.py @@ -81,6 +81,19 @@ def test_vault_export(monkeypatch, tmp_path): assert called["path"] == out_path +def test_vault_change_password(monkeypatch): + called = {} + + def change_pw(): + called["called"] = True + + pm = SimpleNamespace(change_password=change_pw, select_fingerprint=lambda fp: None) + monkeypatch.setattr(cli, "PasswordManager", lambda: pm) + result = runner.invoke(app, ["vault", "change-password"]) + assert result.exit_code == 0 + assert called.get("called") is True + + def test_nostr_get_pubkey(monkeypatch): pm = SimpleNamespace( nostr_client=SimpleNamespace(