From e696537fe1dcfe637c12d951c71854a2d1589006 Mon Sep 17 00:00:00 2001 From: dimon Date: Tue, 2 Jun 2026 21:47:12 +0800 Subject: [PATCH] Add default-server ntfy auth (fix 403 on protected topics) The "send test" action, admin alerts and feeds without their own credentials now use configurable default-server token / basic auth, so publishing works against ntfy servers with access control enabled. Co-Authored-By: Claude Opus 4.8 (1M context) --- app/delivery.py | 15 ++++++++++++--- app/main.py | 11 +++++++++++ app/models.py | 5 +++++ app/schemas.py | 3 +++ app/static/app.js | 3 +++ app/static/i18n.js | 4 ++++ app/templates/index.html | 12 ++++++++++++ 7 files changed, 50 insertions(+), 3 deletions(-) diff --git a/app/delivery.py b/app/delivery.py index 70c5c2e..9b445ad 100644 --- a/app/delivery.py +++ b/app/delivery.py @@ -95,6 +95,12 @@ async def dispatch(feed: Feed, settings: Settings, msg: Message) -> DispatchResu server = feed.ntfy_server.strip() or settings.default_ntfy_server full_title = f"{msg.source}: {msg.title}" if msg.source else msg.title + # Per-feed auth wins; otherwise fall back to the default-server credentials. + has_feed_auth = bool(feed.ntfy_token.strip() or feed.ntfy_username.strip()) + token = feed.ntfy_token if has_feed_auth else settings.default_ntfy_token + username = feed.ntfy_username if has_feed_auth else settings.default_ntfy_username + password = feed.ntfy_password if has_feed_auth else settings.default_ntfy_password + # --- ntfy (default channel; requires a topic) --- if feed.ntfy_topic.strip(): try: @@ -107,9 +113,9 @@ async def dispatch(feed: Feed, settings: Settings, msg: Message) -> DispatchResu tags=feed.tags, priority=feed.priority, attach=msg.image if feed.attach_image else "", - token=feed.ntfy_token, - username=feed.ntfy_username, - password=feed.ntfy_password, + token=token, + username=username, + password=password, ) result.channels.append("ntfy") except Exception as exc: # noqa: BLE001 @@ -146,6 +152,9 @@ async def send_admin_alert(settings: Settings, text: str) -> None: message=text, tags="warning", priority=4, + token=settings.default_ntfy_token, + username=settings.default_ntfy_username, + password=settings.default_ntfy_password, ) except Exception as exc: # noqa: BLE001 log.warning("admin alert failed: %s", exc) diff --git a/app/main.py b/app/main.py index 8ea7bf4..9de796f 100644 --- a/app/main.py +++ b/app/main.py @@ -384,6 +384,9 @@ def read_settings(session: Session = Depends(get_session), _: User = Depends(req s = get_settings(session) return { "default_ntfy_server": s.default_ntfy_server, + "default_ntfy_token": s.default_ntfy_token, + "default_ntfy_username": s.default_ntfy_username, + "default_ntfy_password": s.default_ntfy_password, "check_interval": s.check_interval, "auth_enabled": s.auth_enabled, "telegram_enabled": s.telegram_enabled, @@ -410,6 +413,9 @@ def write_settings( raise HTTPException(400, "Создайте хотя бы одного пользователя перед включением авторизации") s.default_ntfy_server = data.default_ntfy_server.strip() or "https://ntfy.sh" + s.default_ntfy_token = data.default_ntfy_token.strip() + s.default_ntfy_username = data.default_ntfy_username.strip() + s.default_ntfy_password = data.default_ntfy_password s.check_interval = data.check_interval s.auth_enabled = data.auth_enabled s.telegram_enabled = data.telegram_enabled @@ -520,6 +526,8 @@ async def test_notification( server = data.server.strip() or s.default_ntfy_server if not data.topic.strip(): raise HTTPException(400, "Укажите тему") + # Use a custom server's own auth only if it matches the default; otherwise + # fall back to the configured default-server credentials. try: await ntfy.publish( server=server, @@ -528,6 +536,9 @@ async def test_notification( message="Тестовое уведомление — всё работает!", tags="white_check_mark", priority=3, + token=s.default_ntfy_token, + username=s.default_ntfy_username, + password=s.default_ntfy_password, ) except Exception as exc: # noqa: BLE001 raise HTTPException(502, f"Не удалось отправить: {exc}") diff --git a/app/models.py b/app/models.py index a9a221e..edec434 100644 --- a/app/models.py +++ b/app/models.py @@ -90,6 +90,11 @@ class Settings(SQLModel, table=True): id: Optional[int] = Field(default=1, primary_key=True) default_ntfy_server: str = "https://ntfy.sh" + # Default-server auth, used as a fallback for feeds without their own and + # for the "send test" action (for ntfy servers with access control). + default_ntfy_token: str = "" + default_ntfy_username: str = "" + default_ntfy_password: str = "" check_interval: int = 5 # minutes (global default) # Auth toggle (per-user credentials live in the User table). diff --git a/app/schemas.py b/app/schemas.py index bc3215b..5d51779 100644 --- a/app/schemas.py +++ b/app/schemas.py @@ -47,6 +47,9 @@ class FeedIn(BaseModel): class SettingsIn(BaseModel): default_ntfy_server: str = "https://ntfy.sh" + default_ntfy_token: str = "" + default_ntfy_username: str = "" + default_ntfy_password: str = "" check_interval: int = 5 auth_enabled: bool = False # Telegram diff --git a/app/static/app.js b/app/static/app.js index 68cce4b..51a2e40 100644 --- a/app/static/app.js +++ b/app/static/app.js @@ -410,6 +410,9 @@ sForm.addEventListener("submit", async e => { e.preventDefault(); const payload = { default_ntfy_server: sForm.default_ntfy_server.value.trim(), + default_ntfy_token: sForm.default_ntfy_token.value.trim(), + default_ntfy_username: sForm.default_ntfy_username.value.trim(), + default_ntfy_password: sForm.default_ntfy_password.value, check_interval: parseInt(sForm.check_interval.value, 10), auth_enabled: sForm.auth_enabled.checked, telegram_enabled: sForm.telegram_enabled.checked, diff --git a/app/static/i18n.js b/app/static/i18n.js index 8b4b7c7..3384447 100644 --- a/app/static/i18n.js +++ b/app/static/i18n.js @@ -46,6 +46,8 @@ const I18N = { "settings.ntfy": "ntfy", "settings.defaultServer": "Сервер ntfy по умолчанию", "settings.defaultServerHint": "Используется для лент, у которых не задан собственный сервер.", + "settings.ntfyAuth": "Авторизация на сервере ntfy", + "settings.ntfyAuthHint": "Нужно, если на сервере включён контроль доступа (ошибка 403 при отправке). Применяется к тесту, алертам и лентам без собственной авторизации.", "settings.testPh": "тема для теста, напр. my-news", "settings.testBtn": "Отправить тест", "settings.check": "Проверка", @@ -193,6 +195,8 @@ const I18N = { "settings.ntfy": "ntfy", "settings.defaultServer": "Default ntfy server", "settings.defaultServerHint": "Used for feeds that don't define their own server.", + "settings.ntfyAuth": "ntfy server authentication", + "settings.ntfyAuthHint": "Needed if the server has access control enabled (403 on publish). Applies to the test, alerts and feeds without their own auth.", "settings.testPh": "topic to test, e.g. my-news", "settings.testBtn": "Send test", "settings.check": "Polling", diff --git a/app/templates/index.html b/app/templates/index.html index ed3e0d6..fea2f7b 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -91,6 +91,18 @@ +
+ Авторизация на сервере ntfy + +
+ + +
+ +