Add change-password command and endpoint

This commit is contained in:
thePR0M3TH3AN
2025-07-09 15:46:37 -04:00
parent 94a5756954
commit e2235b61e1
6 changed files with 46 additions and 0 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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."""

View File

@@ -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):

View File

@@ -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(