From cd6bd6352fa0cf323b57eb63d08831a66fbf4992 Mon Sep 17 00:00:00 2001
From: arabcoders
Date: Fri, 16 May 2025 19:24:17 +0300
Subject: [PATCH] Finalize Plex OAuth flow in Backend add.
---
frontend/components/BackendAdd.vue | 158 ++++++++++++++++++++++
src/API/Backends/PlexToken.php | 2 +-
src/API/Backends/ValidateToken.php | 10 +-
src/Backends/Plex/Action/GetUserToken.php | 5 +-
src/Backends/Plex/Action/GetUsersList.php | 5 +-
src/Backends/Plex/Action/Progress.php | 1 +
6 files changed, 172 insertions(+), 9 deletions(-)
diff --git a/frontend/components/BackendAdd.vue b/frontend/components/BackendAdd.vue
index 8e73a7a5..a1b82a84 100644
--- a/frontend/components/BackendAdd.vue
+++ b/frontend/components/BackendAdd.vue
@@ -129,6 +129,44 @@
+
+
+
+
+
+
+
+
+
+
+ Check auth request.
+
+
+
+
+
+
+
+ Open Plex Auth Link
+
+
+
+
+
+
@@ -406,6 +444,126 @@ const force_import = ref(false)
const isLimited = ref(false)
const accessTokenResponse = ref({})
+const plex_oauth = ref({})
+const plex_oauth_loading = ref(false)
+const plex_timeout = ref(null)
+const plex_window = ref(null)
+
+const generate_plex_auth_request = async () => {
+ if (plex_oauth_loading.value) {
+ return
+ }
+
+ plex_oauth_loading.value = true
+
+ try {
+ const response = await request('/backends/plex/generate', {method: 'POST'})
+ const json = await parse_api_response(response)
+ if (200 !== response.status) {
+ n_proxy('error', 'Error', `${json.error.code}: ${json.error.message}`)
+ return
+ }
+ plex_oauth.value = json
+
+ await nextTick();
+
+ try {
+ const width = 500;
+ const height = 600;
+
+ const features = [
+ `width=${width}`,
+ `height=${height}`,
+ `top=${(window.screen.height / 2) - (height / 2)}`,
+ `left=${(window.screen.width / 2) - (width / 2)}`,
+ 'resizable=yes',
+ 'scrollbars=yes',
+ ].join(',');
+
+ plex_window.value = window.open(plex_oauth_url.value, 'plex_auth', features);
+ plex_timeout.value = setTimeout(() => plex_get_token(false), 3000)
+ await nextTick();
+
+ if (!plex_window.value) {
+ n_proxy('error', 'Error', 'Popup blocked. Please allow popups for this site.')
+ }
+ } catch (e) {
+ console.error(e)
+ n_proxy('error', 'Error', `Failed to open popup. Please manually click the link.`)
+ }
+ } catch (e) {
+ n_proxy('error', 'Error', `Request error. ${e.message}`, e)
+ } finally {
+ plex_oauth_loading.value = false
+ }
+}
+
+const plex_oauth_url = computed(() => {
+ if (Object.keys(plex_oauth.value).length < 1) {
+ return
+ }
+ const url = new URL('https://app.plex.tv/auth')
+ const params = new URLSearchParams()
+ params.set('code', plex_oauth.value['code'])
+ params.set('clientID', plex_oauth.value['X-Plex-Client-Identifier'])
+ params.set('context[device][product]', plex_oauth.value['X-Plex-Product'])
+ url.hash = '?' + params.toString()
+ return url.toString()
+})
+
+const plex_get_token = async (notify = true) => {
+ if (plex_oauth_loading.value) {
+ return
+ }
+
+ plex_oauth_loading.value = true
+
+ try {
+ if (plex_timeout.value) {
+ clearTimeout(plex_timeout.value)
+ plex_timeout.value = null
+ }
+ const response = await request('/backends/plex/check', {
+ method: 'POST',
+ body: JSON.stringify({
+ id: plex_oauth.value.id,
+ code: plex_oauth.value.code
+ })
+ })
+
+ const json = await parse_api_response(response)
+
+ if (200 !== response.status) {
+ n_proxy('error', 'Error', `${json.error.code}: ${json.error.message}`)
+ return
+ }
+
+ if (json?.authToken) {
+ backend.value.token = json.authToken
+ await nextTick();
+ plex_oauth.value = {}
+ notification('success', 'Success', `Plex token generated inserted successfully.`)
+ if (plex_window.value) {
+ try {
+ plex_window.value.close()
+ plex_window.value = null
+ } catch (e) {
+ }
+ }
+ } else {
+ if (true === notify) {
+ notification('warning', 'Warning', `Not authenticated yet. Login via the given link to authorize WatchState.`)
+ }
+ await nextTick();
+ plex_timeout.value = setTimeout(() => plex_get_token(false), 3000)
+ }
+ } catch (e) {
+ n_proxy('error', 'Error', `Request error. ${e.message}`, e)
+ } finally {
+ plex_oauth_loading.value = false
+ }
+}
+
const getUUid = async () => {
const required_values = ['type', 'token', 'url'];
diff --git a/src/API/Backends/PlexToken.php b/src/API/Backends/PlexToken.php
index 2c16d78a..542bbd02 100644
--- a/src/API/Backends/PlexToken.php
+++ b/src/API/Backends/PlexToken.php
@@ -63,7 +63,7 @@ final class PlexToken
);
}
- return api_response(Status::OK, $req->toArray());
+ return api_response(Status::OK, [...PlexClient::getHeaders(), ...$req->toArray()]);
}
/**
diff --git a/src/API/Backends/ValidateToken.php b/src/API/Backends/ValidateToken.php
index fe9f4657..9b6e0123 100644
--- a/src/API/Backends/ValidateToken.php
+++ b/src/API/Backends/ValidateToken.php
@@ -8,6 +8,7 @@ use App\Backends\Plex\PlexClient;
use App\Libs\Attributes\Route\Route;
use App\Libs\DataUtil;
use App\Libs\Enums\Http\Status;
+use App\Libs\Options;
use App\Libs\Traits\APITraits;
use Psr\Http\Message\ResponseInterface as iResponse;
use Psr\Http\Message\ServerRequestInterface as iRequest;
@@ -32,10 +33,15 @@ final class ValidateToken
}
try {
- $status = PlexClient::validate_token($http, $token);
+ $data = [];
+ $status = PlexClient::validate_token($http, $token, opts: [
+ Options::RAW_RESPONSE_CALLBACK => function ($stat) use (&$data) {
+ $data = $stat;
+ },
+ ]);
if (true === $status) {
- return api_message('Token is valid.', Status::OK);
+ return api_message('Token is valid.', Status::OK, $data);
}
return api_error('non 200 OK request received.', Status::UNAUTHORIZED);
diff --git a/src/Backends/Plex/Action/GetUserToken.php b/src/Backends/Plex/Action/GetUserToken.php
index 4e66500b..476f7032 100644
--- a/src/Backends/Plex/Action/GetUserToken.php
+++ b/src/Backends/Plex/Action/GetUserToken.php
@@ -9,7 +9,6 @@ use App\Backends\Common\Context;
use App\Backends\Common\Error;
use App\Backends\Common\Levels;
use App\Backends\Common\Response;
-use App\Backends\Plex\PlexClient;
use App\Libs\Container;
use App\Libs\Enums\Http\Method;
use App\Libs\Enums\Http\Status;
@@ -300,7 +299,7 @@ final class GetUserToken
$response = $this->http->request($method->value, (string)$url, [
'headers' => array_replace_recursive([
'X-Plex-Token' => $adminToken,
- ...PlexClient::getHeaders()
+ 'X-Plex-Client-Identifier' => $context->backendId,
], ag($opts, 'headers', [])),
...ag($opts, 'options', []),
]);
@@ -312,7 +311,7 @@ final class GetUserToken
$response = $this->http->request($method->value, (string)$url, [
'headers' => array_replace_recursive([
'X-Plex-Token' => $context->backendToken,
- ...PlexClient::getHeaders()
+ 'X-Plex-Client-Identifier' => $context->backendId,
], ag($opts, 'headers', [])),
...ag($opts, 'options', []),
]);
diff --git a/src/Backends/Plex/Action/GetUsersList.php b/src/Backends/Plex/Action/GetUsersList.php
index 4f0c4f52..958c82a6 100644
--- a/src/Backends/Plex/Action/GetUsersList.php
+++ b/src/Backends/Plex/Action/GetUsersList.php
@@ -9,7 +9,6 @@ use App\Backends\Common\Context;
use App\Backends\Common\Error;
use App\Backends\Common\Levels;
use App\Backends\Common\Response;
-use App\Backends\Plex\PlexClient;
use App\Libs\Container;
use App\Libs\Enums\Http\Status;
use App\Libs\Exceptions\Backends\InvalidArgumentException;
@@ -464,7 +463,7 @@ final class GetUsersList
$response = $this->http->request(ag($opts, 'method', 'GET'), (string)$url, [
'headers' => array_replace_recursive([
'X-Plex-Token' => $adminToken,
- ...PlexClient::getHeaders()
+ 'X-Plex-Client-Identifier' => $context->backendId,
], ag($opts, 'headers', [])),
]);
if (Status::OK === Status::from($response->getStatusCode())) {
@@ -480,7 +479,7 @@ final class GetUsersList
$response = $this->http->request(ag($opts, 'method', 'GET'), (string)$url, [
'headers' => array_replace_recursive([
'X-Plex-Token' => $context->backendToken,
- ...PlexClient::getHeaders()
+ 'X-Plex-Client-Identifier' => $context->backendId,
], ag($opts, 'headers', [])),
]);
diff --git a/src/Backends/Plex/Action/Progress.php b/src/Backends/Plex/Action/Progress.php
index 01dfdf93..7e3dc408 100644
--- a/src/Backends/Plex/Action/Progress.php
+++ b/src/Backends/Plex/Action/Progress.php
@@ -247,6 +247,7 @@ class Progress
'time' => $entity->getPlayProgress(),
// -- Without duration & client identifier plex ignore watch progress update.
'duration' => ag($remoteData, 'duration', 0),
+ 'X-Plex-Client-Identifier' => $context->backendId,
]));
$logContext['remote']['url'] = (string)$url;