mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-05-25 03:00:23 +00:00
Merge pull request #2894 — send Joplin token in Authorization header
# Conflicts: # CHANGELOG.md
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
- Auxiliary model settings now reject unknown task slots instead of allowing arbitrary keys under `config.yaml`'s `auxiliary` block. Valid slots and the `__reset__` sentinel continue to work.
|
||||
- Update Now no longer reports success or enters the restart wait flow when no WebUI or Agent update target is selected.
|
||||
- Cached WebUI agents no longer overwrite `prefill_messages` with an empty list when a later request does not include explicit prefill context.
|
||||
- Joplin notes drawer API calls now send the Web Clipper token in an `Authorization` header instead of placing it in the request URL query string.
|
||||
|
||||
## [v0.51.132] — 2026-05-24 — Release DD (stage-batch14 — 4-PR replayed-context + interrupted-response + shutdown affordance + passkey opt-in)
|
||||
|
||||
|
||||
+3
-3
@@ -12534,7 +12534,7 @@ def _joplin_connection_from_config() -> tuple[str, str]:
|
||||
def _joplin_api_get(path: str, params: dict | None = None) -> dict:
|
||||
"""Call the local Joplin Web Clipper API without logging credentials."""
|
||||
from urllib.parse import urlencode
|
||||
from urllib.request import urlopen
|
||||
from urllib.request import Request, urlopen
|
||||
from urllib.error import HTTPError, URLError
|
||||
|
||||
base_url, token = _joplin_connection_from_config()
|
||||
@@ -12542,10 +12542,10 @@ def _joplin_api_get(path: str, params: dict | None = None) -> dict:
|
||||
raise ValueError("Joplin token is not configured")
|
||||
safe_path = "/" + str(path or "").lstrip("/")
|
||||
query = dict(params or {})
|
||||
query["token"] = token
|
||||
url = f"{base_url}{safe_path}?{urlencode(query)}"
|
||||
request = Request(url, headers={"Authorization": f"token {token}"})
|
||||
try:
|
||||
with urlopen(url, timeout=8) as response:
|
||||
with urlopen(request, timeout=8) as response:
|
||||
raw = response.read(2_000_000).decode("utf-8", errors="replace")
|
||||
except HTTPError as exc:
|
||||
raise ValueError(f"Joplin API returned HTTP {exc.code}") from None
|
||||
|
||||
@@ -132,6 +132,39 @@ def test_joplin_get_note_validates_id_and_truncates_body(monkeypatch):
|
||||
assert "Preview truncated" in note["body"]
|
||||
|
||||
|
||||
def test_joplin_api_get_uses_authorization_header(monkeypatch):
|
||||
from api import routes
|
||||
|
||||
captured = {}
|
||||
|
||||
class FakeResponse:
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *_exc):
|
||||
return False
|
||||
|
||||
def read(self, _limit):
|
||||
return b'{"ok": true}'
|
||||
|
||||
def fake_urlopen(request, timeout):
|
||||
captured["url"] = request.full_url
|
||||
captured["authorization"] = request.get_header("Authorization")
|
||||
captured["timeout"] = timeout
|
||||
return FakeResponse()
|
||||
|
||||
monkeypatch.setattr(routes, "_joplin_connection_from_config", lambda: ("http://127.0.0.1:41184", "secret-token"))
|
||||
monkeypatch.setattr("urllib.request.urlopen", fake_urlopen)
|
||||
|
||||
data = routes._joplin_api_get("/notes", {"query": "hello world"})
|
||||
|
||||
assert data == {"ok": True}
|
||||
assert captured["timeout"] == 8
|
||||
assert "token=" not in captured["url"]
|
||||
assert "query=hello+world" in captured["url"]
|
||||
assert captured["authorization"] == "token secret-token"
|
||||
|
||||
|
||||
def test_joplin_recent_ai_notes_uses_configured_prefill_script(monkeypatch, tmp_path):
|
||||
from api import routes
|
||||
|
||||
|
||||
Reference in New Issue
Block a user