Add default-server ntfy auth (fix 403 on protected topics)
build-and-push / docker (push) Has been cancelled
build-and-push / docker (push) Has been cancelled
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) <noreply@anthropic.com>
This commit is contained in:
+12
-3
@@ -95,6 +95,12 @@ async def dispatch(feed: Feed, settings: Settings, msg: Message) -> DispatchResu
|
|||||||
server = feed.ntfy_server.strip() or settings.default_ntfy_server
|
server = feed.ntfy_server.strip() or settings.default_ntfy_server
|
||||||
full_title = f"{msg.source}: {msg.title}" if msg.source else msg.title
|
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) ---
|
# --- ntfy (default channel; requires a topic) ---
|
||||||
if feed.ntfy_topic.strip():
|
if feed.ntfy_topic.strip():
|
||||||
try:
|
try:
|
||||||
@@ -107,9 +113,9 @@ async def dispatch(feed: Feed, settings: Settings, msg: Message) -> DispatchResu
|
|||||||
tags=feed.tags,
|
tags=feed.tags,
|
||||||
priority=feed.priority,
|
priority=feed.priority,
|
||||||
attach=msg.image if feed.attach_image else "",
|
attach=msg.image if feed.attach_image else "",
|
||||||
token=feed.ntfy_token,
|
token=token,
|
||||||
username=feed.ntfy_username,
|
username=username,
|
||||||
password=feed.ntfy_password,
|
password=password,
|
||||||
)
|
)
|
||||||
result.channels.append("ntfy")
|
result.channels.append("ntfy")
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
@@ -146,6 +152,9 @@ async def send_admin_alert(settings: Settings, text: str) -> None:
|
|||||||
message=text,
|
message=text,
|
||||||
tags="warning",
|
tags="warning",
|
||||||
priority=4,
|
priority=4,
|
||||||
|
token=settings.default_ntfy_token,
|
||||||
|
username=settings.default_ntfy_username,
|
||||||
|
password=settings.default_ntfy_password,
|
||||||
)
|
)
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
log.warning("admin alert failed: %s", exc)
|
log.warning("admin alert failed: %s", exc)
|
||||||
|
|||||||
+11
@@ -384,6 +384,9 @@ def read_settings(session: Session = Depends(get_session), _: User = Depends(req
|
|||||||
s = get_settings(session)
|
s = get_settings(session)
|
||||||
return {
|
return {
|
||||||
"default_ntfy_server": s.default_ntfy_server,
|
"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,
|
"check_interval": s.check_interval,
|
||||||
"auth_enabled": s.auth_enabled,
|
"auth_enabled": s.auth_enabled,
|
||||||
"telegram_enabled": s.telegram_enabled,
|
"telegram_enabled": s.telegram_enabled,
|
||||||
@@ -410,6 +413,9 @@ def write_settings(
|
|||||||
raise HTTPException(400, "Создайте хотя бы одного пользователя перед включением авторизации")
|
raise HTTPException(400, "Создайте хотя бы одного пользователя перед включением авторизации")
|
||||||
|
|
||||||
s.default_ntfy_server = data.default_ntfy_server.strip() or "https://ntfy.sh"
|
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.check_interval = data.check_interval
|
||||||
s.auth_enabled = data.auth_enabled
|
s.auth_enabled = data.auth_enabled
|
||||||
s.telegram_enabled = data.telegram_enabled
|
s.telegram_enabled = data.telegram_enabled
|
||||||
@@ -520,6 +526,8 @@ async def test_notification(
|
|||||||
server = data.server.strip() or s.default_ntfy_server
|
server = data.server.strip() or s.default_ntfy_server
|
||||||
if not data.topic.strip():
|
if not data.topic.strip():
|
||||||
raise HTTPException(400, "Укажите тему")
|
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:
|
try:
|
||||||
await ntfy.publish(
|
await ntfy.publish(
|
||||||
server=server,
|
server=server,
|
||||||
@@ -528,6 +536,9 @@ async def test_notification(
|
|||||||
message="Тестовое уведомление — всё работает!",
|
message="Тестовое уведомление — всё работает!",
|
||||||
tags="white_check_mark",
|
tags="white_check_mark",
|
||||||
priority=3,
|
priority=3,
|
||||||
|
token=s.default_ntfy_token,
|
||||||
|
username=s.default_ntfy_username,
|
||||||
|
password=s.default_ntfy_password,
|
||||||
)
|
)
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
raise HTTPException(502, f"Не удалось отправить: {exc}")
|
raise HTTPException(502, f"Не удалось отправить: {exc}")
|
||||||
|
|||||||
@@ -90,6 +90,11 @@ class Settings(SQLModel, table=True):
|
|||||||
|
|
||||||
id: Optional[int] = Field(default=1, primary_key=True)
|
id: Optional[int] = Field(default=1, primary_key=True)
|
||||||
default_ntfy_server: str = "https://ntfy.sh"
|
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)
|
check_interval: int = 5 # minutes (global default)
|
||||||
|
|
||||||
# Auth toggle (per-user credentials live in the User table).
|
# Auth toggle (per-user credentials live in the User table).
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ class FeedIn(BaseModel):
|
|||||||
|
|
||||||
class SettingsIn(BaseModel):
|
class SettingsIn(BaseModel):
|
||||||
default_ntfy_server: str = "https://ntfy.sh"
|
default_ntfy_server: str = "https://ntfy.sh"
|
||||||
|
default_ntfy_token: str = ""
|
||||||
|
default_ntfy_username: str = ""
|
||||||
|
default_ntfy_password: str = ""
|
||||||
check_interval: int = 5
|
check_interval: int = 5
|
||||||
auth_enabled: bool = False
|
auth_enabled: bool = False
|
||||||
# Telegram
|
# Telegram
|
||||||
|
|||||||
@@ -410,6 +410,9 @@ sForm.addEventListener("submit", async e => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const payload = {
|
const payload = {
|
||||||
default_ntfy_server: sForm.default_ntfy_server.value.trim(),
|
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),
|
check_interval: parseInt(sForm.check_interval.value, 10),
|
||||||
auth_enabled: sForm.auth_enabled.checked,
|
auth_enabled: sForm.auth_enabled.checked,
|
||||||
telegram_enabled: sForm.telegram_enabled.checked,
|
telegram_enabled: sForm.telegram_enabled.checked,
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ const I18N = {
|
|||||||
"settings.ntfy": "ntfy",
|
"settings.ntfy": "ntfy",
|
||||||
"settings.defaultServer": "Сервер ntfy по умолчанию",
|
"settings.defaultServer": "Сервер ntfy по умолчанию",
|
||||||
"settings.defaultServerHint": "Используется для лент, у которых не задан собственный сервер.",
|
"settings.defaultServerHint": "Используется для лент, у которых не задан собственный сервер.",
|
||||||
|
"settings.ntfyAuth": "Авторизация на сервере ntfy",
|
||||||
|
"settings.ntfyAuthHint": "Нужно, если на сервере включён контроль доступа (ошибка 403 при отправке). Применяется к тесту, алертам и лентам без собственной авторизации.",
|
||||||
"settings.testPh": "тема для теста, напр. my-news",
|
"settings.testPh": "тема для теста, напр. my-news",
|
||||||
"settings.testBtn": "Отправить тест",
|
"settings.testBtn": "Отправить тест",
|
||||||
"settings.check": "Проверка",
|
"settings.check": "Проверка",
|
||||||
@@ -193,6 +195,8 @@ const I18N = {
|
|||||||
"settings.ntfy": "ntfy",
|
"settings.ntfy": "ntfy",
|
||||||
"settings.defaultServer": "Default ntfy server",
|
"settings.defaultServer": "Default ntfy server",
|
||||||
"settings.defaultServerHint": "Used for feeds that don't define their own 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.testPh": "topic to test, e.g. my-news",
|
||||||
"settings.testBtn": "Send test",
|
"settings.testBtn": "Send test",
|
||||||
"settings.check": "Polling",
|
"settings.check": "Polling",
|
||||||
|
|||||||
@@ -91,6 +91,18 @@
|
|||||||
<input type="text" name="default_ntfy_server" placeholder="https://ntfy.sh">
|
<input type="text" name="default_ntfy_server" placeholder="https://ntfy.sh">
|
||||||
<small class="muted" data-i18n="settings.defaultServerHint"></small>
|
<small class="muted" data-i18n="settings.defaultServerHint"></small>
|
||||||
</label>
|
</label>
|
||||||
|
<details class="adv">
|
||||||
|
<summary data-i18n="settings.ntfyAuth">Авторизация на сервере ntfy</summary>
|
||||||
|
<label><span data-i18n="feed.token">Access token</span> <small class="muted" data-i18n="feed.tokenHint"></small>
|
||||||
|
<input type="text" name="default_ntfy_token" placeholder="tk_..."></label>
|
||||||
|
<div class="grid-2">
|
||||||
|
<label><span data-i18n="feed.login">Логин</span>
|
||||||
|
<input type="text" name="default_ntfy_username" autocomplete="off"></label>
|
||||||
|
<label><span data-i18n="feed.password">Пароль</span>
|
||||||
|
<input type="password" name="default_ntfy_password" autocomplete="new-password"></label>
|
||||||
|
</div>
|
||||||
|
<small class="muted" data-i18n="settings.ntfyAuthHint"></small>
|
||||||
|
</details>
|
||||||
<div class="inline-test">
|
<div class="inline-test">
|
||||||
<input type="text" id="test-topic" data-i18n-ph="settings.testPh">
|
<input type="text" id="test-topic" data-i18n-ph="settings.testPh">
|
||||||
<button type="button" class="btn ghost" id="test-btn" data-i18n="settings.testBtn">Отправить тест</button>
|
<button type="button" class="btn ghost" id="test-btn" data-i18n="settings.testBtn">Отправить тест</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user