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 ; } @@ -249,7 +267,9 @@ const UserGeneralSettings = () => { <>
{ + mutate('/api/v1/settings/public'); revalidateUser(); }} textOverride={intl.formatMessage( @@ -261,24 +281,18 @@ const UserGeneralSettings = () => { ) : ( <>
- { - addToast( - intl.formatMessage(messages.refreshedtoken), - { - appearance: 'success', - autoDismiss: true, - } - ); - revalidateUser(); - }} - svgIcon={} - textOverride={intl.formatMessage( - messages.refreshtoken - )} +