From b27dbd7a155bed9490afdd9dc25ef8a7ca9311eb Mon Sep 17 00:00:00 2001 From: 0xsysr3ll <31414959+0xSysR3ll@users.noreply.github.com> Date: Fri, 16 May 2025 14:42:40 +0200 Subject: [PATCH] fix(jellyfin): clean up Jellyfin sessions on Jellyseerr logout (#1651) * fix(jellyfin): clean up Jellyfin sessions on Jellyseerr logout * refactor(auth): remove deleteUserDevice method and handle device deletion directly in logout process * refactor(auth): optimize logout route for Jellyfin/Emby Only query database for Jellyfin/Emby users during logout to avoid unnecessary database queries for Plex/local users. This improves performance by skipping the device deletion process when it's not needed. --- server/api/jellyfin.ts | 17 +++++++++ server/routes/auth.ts | 82 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 90 insertions(+), 9 deletions(-) diff --git a/server/api/jellyfin.ts b/server/api/jellyfin.ts index 42d6e3b70..4c3a32f42 100644 --- a/server/api/jellyfin.ts +++ b/server/api/jellyfin.ts @@ -22,6 +22,23 @@ export interface JellyfinUserResponse { PrimaryImageTag?: string; } +export interface JellyfinDevice { + Id: string; + Name: string; + LastUserName: string; + AppName: string; + AppVersion: string; + LastUserId: string; + DateLastActivity: string; + Capabilities: Record; +} + +export interface JellyfinDevicesResponse { + Items: JellyfinDevice[]; + TotalRecordCount: number; + StartIndex: number; +} + export interface JellyfinLoginResponse { User: JellyfinUserResponse; AccessToken: string; diff --git a/server/routes/auth.ts b/server/routes/auth.ts index e5b3d3295..4452a655b 100644 --- a/server/routes/auth.ts +++ b/server/routes/auth.ts @@ -12,7 +12,9 @@ import logger from '@server/logger'; import { isAuthenticated } from '@server/middleware/auth'; import { checkAvatarChanged } from '@server/routes/avatarproxy'; import { ApiError } from '@server/types/error'; +import { getAppVersion } from '@server/utils/appVersion'; import { getHostname } from '@server/utils/getHostname'; +import axios from 'axios'; import * as EmailValidator from 'email-validator'; import { Router } from 'express'; import net from 'net'; @@ -716,17 +718,79 @@ authRoutes.post('/local', async (req, res, next) => { } }); -authRoutes.post('/logout', (req, res, next) => { - req.session?.destroy((err) => { - if (err) { - return next({ - status: 500, - message: 'Something went wrong.', - }); +authRoutes.post('/logout', async (req, res, next) => { + try { + const userId = req.session?.userId; + if (!userId) { + return res.status(200).json({ status: 'ok' }); } - return res.status(200).json({ status: 'ok' }); - }); + const settings = getSettings(); + const isJellyfinOrEmby = + settings.main.mediaServerType === MediaServerType.JELLYFIN || + settings.main.mediaServerType === MediaServerType.EMBY; + + if (isJellyfinOrEmby) { + const user = await getRepository(User) + .createQueryBuilder('user') + .addSelect(['user.jellyfinUserId', 'user.jellyfinDeviceId']) + .where('user.id = :id', { id: userId }) + .getOne(); + + if (user?.jellyfinUserId && user.jellyfinDeviceId) { + try { + const baseUrl = getHostname(); + try { + await axios.delete(`${baseUrl}/Devices`, { + params: { Id: user.jellyfinDeviceId }, + headers: { + 'X-Emby-Authorization': `MediaBrowser Client="Jellyseerr", Device="Jellyseerr", DeviceId="jellyseerr", Version="${getAppVersion()}", Token="${ + settings.jellyfin.apiKey + }"`, + }, + }); + } catch (error) { + logger.error('Failed to delete Jellyfin device', { + label: 'Auth', + error: error instanceof Error ? error.message : 'Unknown error', + userId: user.id, + jellyfinUserId: user.jellyfinUserId, + }); + } + } catch (error) { + logger.error('Failed to delete Jellyfin device', { + label: 'Auth', + error: error instanceof Error ? error.message : 'Unknown error', + userId: user.id, + jellyfinUserId: user.jellyfinUserId, + }); + } + } + } + + req.session?.destroy((err: Error | null) => { + if (err) { + logger.error('Failed to destroy session', { + label: 'Auth', + error: err.message, + userId, + }); + return next({ status: 500, message: 'Failed to destroy session.' }); + } + logger.info('Successfully logged out user', { + label: 'Auth', + userId, + }); + res.status(200).json({ status: 'ok' }); + }); + } catch (error) { + logger.error('Error during logout process', { + label: 'Auth', + error: error instanceof Error ? error.message : 'Unknown error', + userId: req.session?.userId, + }); + next({ status: 500, message: 'Error during logout process.' }); + } }); authRoutes.post('/reset-password', async (req, res, next) => {