mirror of
https://github.com/fallenbagel/jellyseerr.git
synced 2025-12-23 18:29:19 -05:00
fix(webpush): use transaction for race condition prevention
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
This commit is contained in:
@@ -4,7 +4,7 @@ import TautulliAPI from '@server/api/tautulli';
|
||||
import { MediaType } from '@server/constants/media';
|
||||
import { MediaServerType } from '@server/constants/server';
|
||||
import { UserType } from '@server/constants/user';
|
||||
import { getRepository } from '@server/datasource';
|
||||
import dataSource, { getRepository } from '@server/datasource';
|
||||
import Media from '@server/entity/Media';
|
||||
import { MediaRequest } from '@server/entity/MediaRequest';
|
||||
import { User } from '@server/entity/User';
|
||||
@@ -25,6 +25,7 @@ import { getHostname } from '@server/utils/getHostname';
|
||||
import { Router } from 'express';
|
||||
import gravatarUrl from 'gravatar-url';
|
||||
import { findIndex, sortBy } from 'lodash';
|
||||
import type { EntityManager } from 'typeorm';
|
||||
import { In, Not } from 'typeorm';
|
||||
import userSettingsRoutes from './usersettings';
|
||||
|
||||
@@ -224,39 +225,63 @@ router.post<
|
||||
return res.status(204).send();
|
||||
}
|
||||
|
||||
// Clean up old subscriptions from the same device (userAgent) for this user
|
||||
// iOS can silently refresh endpoints, leaving stale subscriptions in the database
|
||||
// Only clean up if we're creating a new subscription (not updating an existing one)
|
||||
if (req.body.userAgent) {
|
||||
const staleSubscriptions = await userPushSubRepository.find({
|
||||
relations: { user: true },
|
||||
where: {
|
||||
// This prevents race conditions where two requests both pass the checks
|
||||
await dataSource.transaction(
|
||||
async (transactionalEntityManager: EntityManager) => {
|
||||
const transactionalRepo =
|
||||
transactionalEntityManager.getRepository(UserPushSubscription);
|
||||
|
||||
const finalCheck = await transactionalRepo.findOne({
|
||||
relations: { user: true },
|
||||
where: [
|
||||
{ auth: req.body.auth, user: { id: req.user?.id } },
|
||||
{ endpoint: req.body.endpoint, user: { id: req.user?.id } },
|
||||
],
|
||||
});
|
||||
|
||||
if (finalCheck) {
|
||||
logger.debug(
|
||||
'Duplicate subscription detected. Skipping registration.',
|
||||
{ label: 'API' }
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up old subscriptions from the same device (userAgent) for this user
|
||||
// iOS can silently refresh endpoints, leaving stale subscriptions in the database
|
||||
// Only clean up if we're creating a new subscription (not updating an existing one)
|
||||
if (req.body.userAgent) {
|
||||
const staleSubscriptions = await transactionalRepo.find({
|
||||
relations: { user: true },
|
||||
where: {
|
||||
userAgent: req.body.userAgent,
|
||||
user: { id: req.user?.id },
|
||||
// Only remove subscriptions with different endpoints (stale ones)
|
||||
// Keep subscriptions that might be from different browsers/tabs
|
||||
endpoint: Not(req.body.endpoint),
|
||||
},
|
||||
});
|
||||
|
||||
if (staleSubscriptions.length > 0) {
|
||||
await transactionalRepo.remove(staleSubscriptions);
|
||||
logger.debug(
|
||||
`Removed ${staleSubscriptions.length} stale push subscription(s) from same device.`,
|
||||
{ label: 'API' }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const userPushSubscription = new UserPushSubscription({
|
||||
auth: req.body.auth,
|
||||
endpoint: req.body.endpoint,
|
||||
p256dh: req.body.p256dh,
|
||||
userAgent: req.body.userAgent,
|
||||
user: { id: req.user?.id },
|
||||
// Only remove subscriptions with different endpoints (stale ones)
|
||||
// Keep subscriptions that might be from different browsers/tabs
|
||||
endpoint: Not(req.body.endpoint),
|
||||
},
|
||||
});
|
||||
user: req.user,
|
||||
});
|
||||
|
||||
if (staleSubscriptions.length > 0) {
|
||||
await userPushSubRepository.remove(staleSubscriptions);
|
||||
logger.debug(
|
||||
`Removed ${staleSubscriptions.length} stale push subscription(s) from same device.`,
|
||||
{ label: 'API' }
|
||||
);
|
||||
await transactionalRepo.save(userPushSubscription);
|
||||
}
|
||||
}
|
||||
|
||||
const userPushSubscription = new UserPushSubscription({
|
||||
auth: req.body.auth,
|
||||
endpoint: req.body.endpoint,
|
||||
p256dh: req.body.p256dh,
|
||||
userAgent: req.body.userAgent,
|
||||
user: req.user,
|
||||
});
|
||||
|
||||
await userPushSubRepository.save(userPushSubscription);
|
||||
);
|
||||
|
||||
return res.status(204).send();
|
||||
} catch (e) {
|
||||
|
||||
Reference in New Issue
Block a user