mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-08 07:18:47 +00:00
Merge pull request #443 from PR0M3TH3AN/codex/add-secret-mode-toggle-and-api-endpoint
Add CLI config toggle-secret-mode and API endpoint
This commit is contained in:
@@ -171,6 +171,7 @@ Code: 123456
|
||||
|
||||
- **`seedpass config get <key>`** – Retrieve a configuration value such as `inactivity_timeout`, `secret_mode`, or `auto_sync`.
|
||||
- **`seedpass config set <key> <value>`** – Update a configuration option. Example: `seedpass config set inactivity_timeout 300`.
|
||||
- **`seedpass config toggle-secret-mode`** – Interactively enable or disable Secret Mode and set the clipboard delay.
|
||||
|
||||
### `fingerprint` Commands
|
||||
|
||||
|
@@ -20,6 +20,7 @@ Keep this token secret. Every request must include it in the `Authorization` hea
|
||||
- `POST /api/v1/entry` – Create a new entry of any supported type.
|
||||
- `PUT /api/v1/entry/{id}` – Modify an existing entry.
|
||||
- `PUT /api/v1/config/{key}` – Update a configuration value.
|
||||
- `POST /api/v1/secret-mode` – Enable or disable Secret Mode and set the clipboard delay.
|
||||
- `POST /api/v1/entry/{id}/archive` – Archive an entry.
|
||||
- `POST /api/v1/entry/{id}/unarchive` – Unarchive an entry.
|
||||
- `GET /api/v1/config/{key}` – Return the value for a configuration key.
|
||||
@@ -95,6 +96,17 @@ curl -X PUT http://127.0.0.1:8000/api/v1/config/inactivity_timeout \
|
||||
-d '{"value": 300}'
|
||||
```
|
||||
|
||||
### Toggling Secret Mode
|
||||
|
||||
Send both `enabled` and `delay` values to `/api/v1/secret-mode`:
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:8000/api/v1/secret-mode \
|
||||
-H "Authorization: Bearer <token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"enabled": true, "delay": 20}'
|
||||
```
|
||||
|
||||
### Switching Fingerprints
|
||||
|
||||
Change the active seed profile via `POST /api/v1/fingerprint/select`:
|
||||
|
@@ -266,6 +266,25 @@ def update_config(
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
@app.post("/api/v1/secret-mode")
|
||||
def set_secret_mode(
|
||||
data: dict, authorization: str | None = Header(None)
|
||||
) -> dict[str, str]:
|
||||
"""Enable/disable secret mode and set the clipboard delay."""
|
||||
_check_token(authorization)
|
||||
assert _pm is not None
|
||||
enabled = data.get("enabled")
|
||||
delay = data.get("delay")
|
||||
if enabled is None or delay is None:
|
||||
raise HTTPException(status_code=400, detail="Missing fields")
|
||||
cfg = _pm.config_manager
|
||||
cfg.set_secret_mode_enabled(bool(enabled))
|
||||
cfg.set_clipboard_clear_delay(int(delay))
|
||||
_pm.secret_mode_enabled = bool(enabled)
|
||||
_pm.clipboard_clear_delay = int(delay)
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
@app.get("/api/v1/fingerprint")
|
||||
def list_fingerprints(authorization: str | None = Header(None)) -> List[str]:
|
||||
_check_token(authorization)
|
||||
|
@@ -450,6 +450,57 @@ def config_set(ctx: typer.Context, key: str, value: str) -> None:
|
||||
typer.echo("Updated")
|
||||
|
||||
|
||||
@config_app.command("toggle-secret-mode")
|
||||
def config_toggle_secret_mode(ctx: typer.Context) -> None:
|
||||
"""Interactively enable or disable secret mode."""
|
||||
pm = _get_pm(ctx)
|
||||
cfg = pm.config_manager
|
||||
try:
|
||||
enabled = cfg.get_secret_mode_enabled()
|
||||
delay = cfg.get_clipboard_clear_delay()
|
||||
except Exception as exc: # pragma: no cover - pass through errors
|
||||
typer.echo(f"Error loading settings: {exc}")
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
typer.echo(f"Secret mode is currently {'ON' if enabled else 'OFF'}")
|
||||
choice = (
|
||||
typer.prompt(
|
||||
"Enable secret mode? (y/n, blank to keep)", default="", show_default=False
|
||||
)
|
||||
.strip()
|
||||
.lower()
|
||||
)
|
||||
if choice in ("y", "yes"):
|
||||
enabled = True
|
||||
elif choice in ("n", "no"):
|
||||
enabled = False
|
||||
|
||||
inp = typer.prompt(
|
||||
f"Clipboard clear delay in seconds [{delay}]", default="", show_default=False
|
||||
).strip()
|
||||
if inp:
|
||||
try:
|
||||
delay = int(inp)
|
||||
if delay <= 0:
|
||||
typer.echo("Delay must be positive")
|
||||
raise typer.Exit(code=1)
|
||||
except ValueError:
|
||||
typer.echo("Invalid number")
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
try:
|
||||
cfg.set_secret_mode_enabled(enabled)
|
||||
cfg.set_clipboard_clear_delay(delay)
|
||||
pm.secret_mode_enabled = enabled
|
||||
pm.clipboard_clear_delay = delay
|
||||
except Exception as exc: # pragma: no cover - pass through errors
|
||||
typer.echo(f"Error: {exc}")
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
status = "enabled" if enabled else "disabled"
|
||||
typer.echo(f"Secret mode {status}.")
|
||||
|
||||
|
||||
@fingerprint_app.command("list")
|
||||
def fingerprint_list(ctx: typer.Context) -> None:
|
||||
"""List available seed profiles."""
|
||||
|
@@ -275,3 +275,28 @@ def test_vault_lock_endpoint(client):
|
||||
api._pm.unlock_vault = lambda: setattr(api._pm, "locked", False)
|
||||
api._pm.unlock_vault()
|
||||
assert api._pm.locked is False
|
||||
|
||||
|
||||
def test_secret_mode_endpoint(client):
|
||||
cl, token = client
|
||||
called = {}
|
||||
|
||||
def set_secret(val):
|
||||
called.setdefault("enabled", val)
|
||||
|
||||
def set_delay(val):
|
||||
called.setdefault("delay", val)
|
||||
|
||||
api._pm.config_manager.set_secret_mode_enabled = set_secret
|
||||
api._pm.config_manager.set_clipboard_clear_delay = set_delay
|
||||
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
res = cl.post(
|
||||
"/api/v1/secret-mode",
|
||||
json={"enabled": True, "delay": 12},
|
||||
headers=headers,
|
||||
)
|
||||
assert res.status_code == 200
|
||||
assert res.json() == {"status": "ok"}
|
||||
assert called["enabled"] is True
|
||||
assert called["delay"] == 12
|
||||
|
44
src/tests/test_cli_toggle_secret_mode.py
Normal file
44
src/tests/test_cli_toggle_secret_mode.py
Normal file
@@ -0,0 +1,44 @@
|
||||
import types
|
||||
from types import SimpleNamespace
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from seedpass.cli import app
|
||||
from seedpass import cli
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
|
||||
def _make_pm(called, enabled=False, delay=45):
|
||||
cfg = SimpleNamespace(
|
||||
get_secret_mode_enabled=lambda: enabled,
|
||||
get_clipboard_clear_delay=lambda: delay,
|
||||
set_secret_mode_enabled=lambda v: called.setdefault("enabled", v),
|
||||
set_clipboard_clear_delay=lambda v: called.setdefault("delay", v),
|
||||
)
|
||||
pm = SimpleNamespace(
|
||||
config_manager=cfg,
|
||||
secret_mode_enabled=enabled,
|
||||
clipboard_clear_delay=delay,
|
||||
select_fingerprint=lambda fp: None,
|
||||
)
|
||||
return pm
|
||||
|
||||
|
||||
def test_toggle_secret_mode_updates(monkeypatch):
|
||||
called = {}
|
||||
pm = _make_pm(called)
|
||||
monkeypatch.setattr(cli, "PasswordManager", lambda: pm)
|
||||
result = runner.invoke(app, ["config", "toggle-secret-mode"], input="y\n10\n")
|
||||
assert result.exit_code == 0
|
||||
assert called == {"enabled": True, "delay": 10}
|
||||
assert "Secret mode enabled." in result.stdout
|
||||
|
||||
|
||||
def test_toggle_secret_mode_keep(monkeypatch):
|
||||
called = {}
|
||||
pm = _make_pm(called, enabled=True, delay=30)
|
||||
monkeypatch.setattr(cli, "PasswordManager", lambda: pm)
|
||||
result = runner.invoke(app, ["config", "toggle-secret-mode"], input="\n\n")
|
||||
assert result.exit_code == 0
|
||||
assert called == {"enabled": True, "delay": 30}
|
||||
assert "Secret mode enabled." in result.stdout
|
Reference in New Issue
Block a user