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.
This commit is contained in:
0xsysr3ll
2025-05-16 14:42:40 +02:00
committed by GitHub
parent 78afd02d3d
commit b27dbd7a15
2 changed files with 90 additions and 9 deletions

View File

@@ -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<string, unknown>;
}
export interface JellyfinDevicesResponse {
Items: JellyfinDevice[];
TotalRecordCount: number;
StartIndex: number;
}
export interface JellyfinLoginResponse {
User: JellyfinUserResponse;
AccessToken: string;

View File

@@ -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) => {