diff --git a/overseerr-api.yml b/overseerr-api.yml
index 7ebacf280..9f56a042b 100644
--- a/overseerr-api.yml
+++ b/overseerr-api.yml
@@ -3329,6 +3329,15 @@ paths:
type: string
required:
- authToken
+ /auth/plex/refresh:
+ get:
+ summary: Refresh the users Plex token
+ description: Will refresh a users plex token. If it is already expired, this will have no effect.
+ tags:
+ - auth
+ responses:
+ '204':
+ description: OK
/auth/plex/unlink:
get:
summary: Unlink Plex from currently logged in account
diff --git a/server/api/plextv.ts b/server/api/plextv.ts
index 2fc4523a8..9b8d2901f 100644
--- a/server/api/plextv.ts
+++ b/server/api/plextv.ts
@@ -365,7 +365,7 @@ class PlexTvAPI extends ExternalAPI {
}
}
- public async pingToken() {
+ public async pingToken(displayName?: string) {
try {
const response = await this.axios.get('/api/v2/ping', {
headers: {
@@ -379,6 +379,7 @@ class PlexTvAPI extends ExternalAPI {
logger.error('Failed to ping token', {
label: 'Plex Refresh Token',
errorMessage: e.message,
+ userDisplayName: displayName,
});
}
}
diff --git a/server/lib/availabilitySync.ts b/server/lib/availabilitySync.ts
index f5bb3affd..0421b33c4 100644
--- a/server/lib/availabilitySync.ts
+++ b/server/lib/availabilitySync.ts
@@ -45,7 +45,7 @@ class AvailabilitySync {
if (admin?.plexToken) {
this.plexClient = new PlexAPI({ plexToken: admin.plexToken });
} else {
- logger.warn('Plex is not configured. Skipping availability sync.');
+ logger.warn('Plex is not configured. Skipping Plex availability sync.');
}
for await (const media of this.loadAvailableMediaPaginated(pageSize)) {
diff --git a/server/lib/refreshToken.ts b/server/lib/refreshToken.ts
index ac7bd3463..4528fe2ce 100644
--- a/server/lib/refreshToken.ts
+++ b/server/lib/refreshToken.ts
@@ -28,7 +28,7 @@ class RefreshToken {
}
const plexTvApi = new PlexTvAPI(user.plexToken);
- plexTvApi.pingToken();
+ plexTvApi.pingToken(user.displayName);
}
}
diff --git a/server/routes/auth.ts b/server/routes/auth.ts
index 8ce74f703..3117128dd 100644
--- a/server/routes/auth.ts
+++ b/server/routes/auth.ts
@@ -189,6 +189,34 @@ authRoutes.post('/plex', async (req, res, next) => {
}
});
+authRoutes.get('/plex/refresh', isAuthenticated(), async (req, res, next) => {
+ const userRepository = getRepository(User);
+ try {
+ if (!req.user) {
+ throw new Error('User data is not present in request.');
+ }
+
+ const user = await userRepository.findOneByOrFail({ id: req.user.id });
+
+ if (user.plexToken) {
+ const plexTvApi = new PlexTvAPI(user.plexToken);
+ plexTvApi.pingToken(user.displayName);
+ }
+
+ return res.status(204).send();
+ } catch (e) {
+ logger.error('Something went wrong while refreshing a users Plex token', {
+ label: 'API',
+ errorMessage: e.message,
+ userId: req.user?.id,
+ });
+ return next({
+ status: 500,
+ message: 'Unable to refresh Plex token.',
+ });
+ }
+});
+
authRoutes.get('/plex/unlink', isAuthenticated(), async (req, res, next) => {
const userRepository = getRepository(User);
try {
diff --git a/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx b/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx
index 9ed59477e..44c9869ae 100644
--- a/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx
+++ b/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx
@@ -27,7 +27,7 @@ import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
-import useSWR from 'swr';
+import useSWR, { mutate } from 'swr';
import * as Yup from 'yup';
const messages = defineMessages({
@@ -67,6 +67,7 @@ const messages = defineMessages({
'Only the logged in user can edit their own connected accounts.',
connectplexaccount: 'Connect Plex Account',
refreshedtoken: 'Refreshed Plex token.',
+ refreshfailure: 'Failed to refresh Plex token.',
refreshtoken: 'Refresh Token',
mustsetpasswordplex: 'You must set a password before disconnecting Plex.',
disconnectPlex: 'Disconnect Plex',
@@ -122,6 +123,7 @@ const UserGeneralSettings = () => {
autoDismiss: true,
});
revalidateUser();
+ mutate('/api/v1/settings/public');
} catch (e) {
addToast(intl.formatMessage(messages.plexdisconnectedfailure), {
appearance: 'error',
@@ -130,6 +132,22 @@ const UserGeneralSettings = () => {
}
};
+ const refreshToken = async () => {
+ try {
+ await axios.get('/api/v1/auth/plex/refresh');
+ addToast(intl.formatMessage(messages.refreshedtoken), {
+ appearance: 'success',
+ autoDismiss: true,
+ });
+ revalidateUser();
+ } catch (e) {
+ addToast(intl.formatMessage(messages.refreshfailure), {
+ appearance: 'error',
+ autoDismiss: true,
+ });
+ }
+ };
+
if (!data && !error) {
return