fix(stalker): anchor auth-failure body detection and share it across transports

The middleware's auth failures are bare plain-text bodies, but they were
matched by substring under a 200-character cap. A short page from something
in FRONT of the portal — a proxy or WAF answering
`<html><body>Access denied</body></html>` (38 characters) — therefore read as
the portal refusing authorization, which drives probe classification and the
lazy repair trigger: a portal that never answered at all could be re-probed
and reclassified. The body match is now anchored to the whole reply, with the
stock server's optional trailing counter still accepted. The structured
`js.error`/`js.msg` fields keep the wider phrase set, since a panel fills
those in deliberately.

The detection also moves to `@iptvnator/shared/interfaces`. It had to live
somewhere both transports can reach: the Electron main process is where these
bodies actually arrive and cannot import a renderer library, which is the
same reason the identity and URL builders were centralised there.
`stalker-portal-discovery.utils.ts` re-exports it, so no call site changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Fable 5 committed 2026-08-02 18:15:35 +02:00
1 parent b92503feae
commit 6cb195197d
6 files changed
+314 -102

No files matched your search

+11 -1
View File
@@ -71,7 +71,17 @@ Two portal modes exist, persisted per playlist as
except `handshake`, `get_profile`, `get_localization`, and `do_auth`
requires `Authorization: Bearer <token>`; auth failures are HTTP 200 with a
plain-text body (`Authorization failed.`, `Access denied.`,
`Unauthorized request.`), never a 401/403. While a full portal is the
`Unauthorized request.`), never a 401/403. Detection of those bodies — and
of the JSON envelope (`{js: {error|msg}}`) some panels answer instead —
lives in `libs/shared/interfaces/src/lib/stalker-auth-failure.util.ts`, so
the Electron main process (where the bodies actually arrive) and the
renderer classify identically; `stalker-portal-discovery.utils.ts`
re-exports it. The body match is anchored to the whole reply: these are
bare phrases, and a substring rule also accepted a short proxy or WAF page
containing one, which then triggered portal reclassification against a
portal that never answered. The structured `js.error`/`js.msg` fields keep
the wider phrase set (`Invalid token`, `Auth failed`, bare `unauthorized`),
since a panel fills those in deliberately. While a full portal is the
active playlist, `StalkerSessionService` keeps a **watchdog** running —
periodic authenticated `watchdog/get_events` pings (currently every 25 s;
the protocol default expects 120 s, tracked for a later PR) whose failures