require password for sensitive read endpoints

This commit is contained in:
thePR0M3TH3AN
2025-08-03 14:12:24 -04:00
parent 68341db0fe
commit 3a19ef9c2a
4 changed files with 22 additions and 8 deletions

View File

@@ -19,7 +19,7 @@ Keep this token secret and avoid logging it. Tokens expire after a few minutes a
## Endpoints
- `GET /api/v1/entry?query=<text>` Search entries matching a query.
- `GET /api/v1/entry/{id}` Retrieve a single entry by its index.
- `GET /api/v1/entry/{id}` Retrieve a single entry by its index. Requires an `X-SeedPass-Password` header.
- `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.
@@ -31,8 +31,8 @@ Keep this token secret and avoid logging it. Tokens expire after a few minutes a
- `POST /api/v1/fingerprint` Add a new seed fingerprint.
- `DELETE /api/v1/fingerprint/{fp}` Remove a fingerprint.
- `POST /api/v1/fingerprint/select` Switch the active fingerprint.
- `GET /api/v1/totp/export` Export all TOTP entries as JSON.
- `GET /api/v1/totp` Return current TOTP codes and remaining time.
- `GET /api/v1/totp/export` Export all TOTP entries as JSON. Requires an `X-SeedPass-Password` header.
- `GET /api/v1/totp` Return current TOTP codes and remaining time. Requires an `X-SeedPass-Password` header.
- `GET /api/v1/stats` Return statistics about the active seed profile.
- `GET /api/v1/notifications` Retrieve and clear queued notifications. Messages appear in the persistent notification box but remain queued until fetched.
- `GET /api/v1/nostr/pubkey` Fetch the Nostr public key for the active seed.

View File

@@ -139,8 +139,13 @@ def search_entry(query: str, authorization: str | None = Header(None)) -> List[A
@app.get("/api/v1/entry/{entry_id}")
def get_entry(entry_id: int, authorization: str | None = Header(None)) -> Any:
def get_entry(
entry_id: int,
authorization: str | None = Header(None),
password: str | None = Header(None, alias="X-SeedPass-Password"),
) -> Any:
_check_token(authorization)
_require_password(password)
assert _pm is not None
entry = _pm.entry_manager.retrieve_entry(entry_id)
if entry is None:
@@ -417,17 +422,25 @@ def select_fingerprint(
@app.get("/api/v1/totp/export")
def export_totp(authorization: str | None = Header(None)) -> dict:
def export_totp(
authorization: str | None = Header(None),
password: str | None = Header(None, alias="X-SeedPass-Password"),
) -> dict:
"""Return all stored TOTP entries in JSON format."""
_check_token(authorization)
_require_password(password)
assert _pm is not None
return _pm.entry_manager.export_totp_entries(_pm.parent_seed)
@app.get("/api/v1/totp")
def get_totp_codes(authorization: str | None = Header(None)) -> dict:
def get_totp_codes(
authorization: str | None = Header(None),
password: str | None = Header(None, alias="X-SeedPass-Password"),
) -> dict:
"""Return active TOTP codes with remaining seconds."""
_check_token(authorization)
_require_password(password)
assert _pm is not None
entries = _pm.entry_manager.list_entries(
filter_kind=EntryType.TOTP.value, include_archived=False

View File

@@ -78,6 +78,7 @@ def test_get_entry_by_id(client):
headers = {
"Authorization": f"Bearer {token}",
"Origin": "http://example.com",
"X-SeedPass-Password": "pw",
}
res = cl.get("/api/v1/entry/1", headers=headers)
assert res.status_code == 200

View File

@@ -136,7 +136,7 @@ def test_totp_export_endpoint(client):
cl, token = client
api._pm.entry_manager.export_totp_entries = lambda seed: {"entries": ["x"]}
api._pm.parent_seed = "seed"
headers = {"Authorization": f"Bearer {token}"}
headers = {"Authorization": f"Bearer {token}", "X-SeedPass-Password": "pw"}
res = cl.get("/api/v1/totp/export", headers=headers)
assert res.status_code == 200
assert res.json() == {"entries": ["x"]}
@@ -148,7 +148,7 @@ def test_totp_codes_endpoint(client):
api._pm.entry_manager.get_totp_code = lambda i, s: "123456"
api._pm.entry_manager.get_totp_time_remaining = lambda i: 30
api._pm.parent_seed = "seed"
headers = {"Authorization": f"Bearer {token}"}
headers = {"Authorization": f"Bearer {token}", "X-SeedPass-Password": "pw"}
res = cl.get("/api/v1/totp", headers=headers)
assert res.status_code == 200
assert res.json() == {