fix(stalker): key every session by endpoint, identity and credentials

Four review findings, all the same root cause — the session key was not applied
consistently:

- The in-run token cache still used an identity-only key, so editing the portal
  URL without restarting returned the cached token and sent that bearer to the
  newly configured host. Both caches now use one key.
- Credentials were in neither key, so changing a status-2 portal's login kept
  serving the previous account's session indefinitely.
- A stored token with NO recorded fingerprint was accepted. Rows written before
  the fingerprint existed carry exactly that, and re-presenting one after an
  edit is the disclosure the fingerprint prevents. Missing now counts as
  unverified; such a row owes a full profile anyway, so nothing is lost.
- `StreamResolverService` read `playlist.isFullStalkerPortal` directly instead
  of the shared predicate, so a legacy row with an absent flag but a canonical
  URL skipped authentication — a restored older backup opened a direct-URL
  radio favorite with no Bearer header.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Fable 5 committed 2026-08-03 18:41:55 +02:00
1 parent d2f28334de
commit 226ffbb9e6
7 files changed
+142 -46

No files matched your search

@@ -8,6 +8,7 @@ import {
Channel,
EpgItem,
EpgProgram,
isFullStalkerPortalPlaylist,
Playlist,
isStalkerStreamCredentialSafe,
ResolvedPortalPlayback,
@@ -568,7 +569,11 @@ export class StreamResolverService {
playlist: Playlist | undefined
): Promise<string | null> {
const cached = this.stalkerSession.getCachedToken(playlistId);
if (cached || !playlist?.isFullStalkerPortal) {
// The shared mode contract, not the raw flag: a legacy row with an
// absent flag but a canonical URL IS a full portal, and reading the
// property directly would skip authentication for it — a restored
// older backup opens a direct-URL radio favorite with no Bearer.
if (cached || !playlist || !isFullStalkerPortalPlaylist(playlist)) {
return cached;
}