From 7a4e0ea213355108b66b4ab492c72443013ea132 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Tue, 3 Feb 2026 15:07:34 +0000 Subject: [PATCH] fix: Normalize state param for auth redirect URLs Fixes #443 --- src/backend/common/vendor/LastfmApiClient.ts | 4 ++-- src/backend/scrobblers/ScrobbleClients.ts | 4 ++-- src/backend/server/auth.ts | 8 ++++---- src/backend/sources/ScrobbleSources.ts | 4 ++-- src/backend/sources/SpotifySource.ts | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/backend/common/vendor/LastfmApiClient.ts b/src/backend/common/vendor/LastfmApiClient.ts index 0d9ed1bc..5b90a8b7 100644 --- a/src/backend/common/vendor/LastfmApiClient.ts +++ b/src/backend/common/vendor/LastfmApiClient.ts @@ -10,7 +10,7 @@ import { UpstreamError } from "../errors/UpstreamError.js"; import { AbstractApiOptions, DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions, InternalConfigOptional } from "../infrastructure/Atomic.js"; import { LastfmData } from "../infrastructure/config/client/lastfm.js"; import AbstractApiClient from "./AbstractApiClient.js"; -import { parseArtistCredits } from "../../utils/StringUtils.js"; +import { normalizeStr, parseArtistCredits } from "../../utils/StringUtils.js"; import { LastFMUser, LastFMAuth, LastFMTrack, LastFMUserGetRecentTracksResponse, LastFMBooleanNumber, LastFMUpdateNowPlayingResponse, LastFMUserGetInfoResponse } from 'lastfm-ts-api'; import clone from 'clone'; import { IncomingMessage } from "http"; @@ -82,7 +82,7 @@ export default class LastfmApiClient extends AbstractApiClient { } } - this.redirectUri = `${redirectUri ?? joinedUrl(localUrl, `${cbPrefix}/callback`).href}?state=${name}`; + this.redirectUri = `${redirectUri ?? joinedUrl(localUrl, `${cbPrefix}/callback`).href}?state=${normalizeStr(this.name, {keepSingleWhitespace: false})}`; this.logger.info(`Using ${this.url.normal} for API calls`); this.logger.info(`Redirect Uri: ${this.redirectUri}`); diff --git a/src/backend/scrobblers/ScrobbleClients.ts b/src/backend/scrobblers/ScrobbleClients.ts index fd96ec46..ccf0068d 100644 --- a/src/backend/scrobblers/ScrobbleClients.ts +++ b/src/backend/scrobblers/ScrobbleClients.ts @@ -59,11 +59,11 @@ export default class ScrobbleClients { }); } - getByName = (name: any) => this.clients.find(x => x.name === name) + getByName = (name: any, safe: boolean = false) => this.clients.find(x => (safe ? x.getSafeExternalName() : x.name) === name) getByType = (type: any) => this.clients.filter(x => x.type === type) - getByNameAndType = (name: string, type: ClientType) => this.clients.find(x => x.name === name && x.type === type) + getByNameAndType = (name: string, type: ClientType, safe: boolean = false) => this.clients.find(x => (safe ? x.getSafeExternalName() : x.name) === name && x.type === type) async getStatusSummary(type?: string, name?: string): Promise<[boolean, string[]]> { let clients: AbstractScrobbleClient[] = []; diff --git a/src/backend/server/auth.ts b/src/backend/server/auth.ts index 942ddaa5..659db8aa 100644 --- a/src/backend/server/auth.ts +++ b/src/backend/server/auth.ts @@ -94,13 +94,13 @@ export const setupAuthRoutes = (app: ExpressWithAsync, logger: Logger, sourceMid } = req; let entity: LastfmScrobbler | LastfmSource | LibrefmScrobbler | LibrefmSource | undefined; if(req.url.includes('lastfm')) { - entity = scrobbleClients.getByNameAndType(state as string, 'lastfm') as (LastfmScrobbler | LastfmSource | undefined); + entity = scrobbleClients.getByNameAndType(state as string, 'lastfm', true) as (LastfmScrobbler | LastfmSource | undefined); } else { - entity = scrobbleClients.getByNameAndType(state as string, 'librefm') as (LibrefmScrobbler | LibrefmSource | undefined); + entity = scrobbleClients.getByNameAndType(state as string, 'librefm', true) as (LibrefmScrobbler | LibrefmSource | undefined); } if(entity === undefined) { - entity = scrobbleSources.getByName(state) as LastfmSource | LibrefmSource; + entity = scrobbleSources.getByName(state, true) as LastfmSource | LibrefmSource; } try { if(entity === undefined) { @@ -142,7 +142,7 @@ export const setupAuthRoutes = (app: ExpressWithAsync, logger: Logger, sourceMid // but eventually should update all source callbacks to url specific URLS to avoid ambiguity... // wish we could use state param to identify name/source but not all auth strategies and auth provides may provide access to that logger.info('Received auth code callback from Spotify', {label: 'Spotify'}); - const source = scrobbleSources.getByNameAndType(state as string, 'spotify') as SpotifySource; + const source = scrobbleSources.getByNameAndType(state as string, 'spotify', true) as SpotifySource; const tokenResult = await source.handleAuthCodeCallback(req.query); let responseContent = 'OK'; if (tokenResult === true) { diff --git a/src/backend/sources/ScrobbleSources.ts b/src/backend/sources/ScrobbleSources.ts index 7c61ff9e..45d38a6c 100644 --- a/src/backend/sources/ScrobbleSources.ts +++ b/src/backend/sources/ScrobbleSources.ts @@ -65,11 +65,11 @@ export default class ScrobbleSources { } } - getByName = (name: any) => this.sources.find(x => x.name === name) + getByName = (name: any, safe: boolean = false) => this.sources.find(x => (safe ? x.getSafeExternalName() : x.name) === name) getByType = (type: any) => this.sources.filter(x => x.type === type) - getByNameAndType = (name: string, type: SourceType) => this.sources.find(x => x.name === name && x.type === type) + getByNameAndType = (name: string, type: SourceType, safe: boolean = false) => this.sources.find(x => (safe ? x.getSafeExternalName() : x.name) === name && x.type === type) async getStatusSummary(type?: string, name?: string): Promise<[boolean, string[]]> { let sources: AbstractSource[] = []; diff --git a/src/backend/sources/SpotifySource.ts b/src/backend/sources/SpotifySource.ts index d92ea998..7595ead5 100644 --- a/src/backend/sources/SpotifySource.ts +++ b/src/backend/sources/SpotifySource.ts @@ -346,7 +346,7 @@ export default class SpotifySource extends MemoryPositionalSource { } } - createAuthUrl = () => this.spotifyApi.createAuthorizeURL(scopes, this.name) + createAuthUrl = () => this.spotifyApi.createAuthorizeURL(scopes, this.getSafeExternalName()) handleAuthCodeCallback = async ({ error, -- 2.51.2