Finalize Plex OAuth flow in Backend add.

This commit is contained in:
arabcoders
2025-05-16 19:24:17 +03:00
parent 7c20a238c1
commit cd6bd6352f
6 changed files with 172 additions and 9 deletions

View File

@@ -129,6 +129,44 @@
</p> </p>
</div> </div>
</div> </div>
<div class="control" v-if="'plex' === backend.type && !backend.token">
<button type="button" class="button is-warning" v-if="Object.keys(plex_oauth).length < 1"
:disabled="plex_oauth_loading" @click="generate_plex_auth_request">
<span class="icon-text">
<template v-if="plex_oauth_loading">
<span class="icon"><i class="fas fa-spinner fa-pulse"/></span>
<span>Generating link</span>
</template>
<template v-else>
<span class="icon"><i class="fas fa-external-link-alt"/></span>
<span>Sign-in via Plex</span>
</template>
</span>
</button>
<template v-if="plex_oauth_url">
<div class="field is-grouped">
<div class="control">
<NuxtLink @click="plex_get_token" type="button" :disabled="plex_oauth_loading">
<span class="icon-text">
<span class="icon"><i class="fas"
:class="{'fa-check-double': !plex_oauth_loading,'fa-spinner fa-pulse': plex_oauth_loading}"/></span>
<span>Check auth request.</span>
</span>
</NuxtLink>
</div>
<div class="control">
<NuxtLink :href="plex_oauth_url" target="_blank">
<span class="icon-text">
<span class="icon"><i class="fas fa-external-link-alt"/></span>
<span>Open Plex Auth Link</span>
</span>
</NuxtLink>
</div>
</div>
</template>
</div>
</div> </div>
<template v-if="'plex' === backend.type"> <template v-if="'plex' === backend.type">
@@ -406,6 +444,126 @@ const force_import = ref(false)
const isLimited = ref(false) const isLimited = ref(false)
const accessTokenResponse = ref({}) 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 getUUid = async () => {
const required_values = ['type', 'token', 'url']; const required_values = ['type', 'token', 'url'];

View File

@@ -63,7 +63,7 @@ final class PlexToken
); );
} }
return api_response(Status::OK, $req->toArray()); return api_response(Status::OK, [...PlexClient::getHeaders(), ...$req->toArray()]);
} }
/** /**

View File

@@ -8,6 +8,7 @@ use App\Backends\Plex\PlexClient;
use App\Libs\Attributes\Route\Route; use App\Libs\Attributes\Route\Route;
use App\Libs\DataUtil; use App\Libs\DataUtil;
use App\Libs\Enums\Http\Status; use App\Libs\Enums\Http\Status;
use App\Libs\Options;
use App\Libs\Traits\APITraits; use App\Libs\Traits\APITraits;
use Psr\Http\Message\ResponseInterface as iResponse; use Psr\Http\Message\ResponseInterface as iResponse;
use Psr\Http\Message\ServerRequestInterface as iRequest; use Psr\Http\Message\ServerRequestInterface as iRequest;
@@ -32,10 +33,15 @@ final class ValidateToken
} }
try { 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) { 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); return api_error('non 200 OK request received.', Status::UNAUTHORIZED);

View File

@@ -9,7 +9,6 @@ use App\Backends\Common\Context;
use App\Backends\Common\Error; use App\Backends\Common\Error;
use App\Backends\Common\Levels; use App\Backends\Common\Levels;
use App\Backends\Common\Response; use App\Backends\Common\Response;
use App\Backends\Plex\PlexClient;
use App\Libs\Container; use App\Libs\Container;
use App\Libs\Enums\Http\Method; use App\Libs\Enums\Http\Method;
use App\Libs\Enums\Http\Status; use App\Libs\Enums\Http\Status;
@@ -300,7 +299,7 @@ final class GetUserToken
$response = $this->http->request($method->value, (string)$url, [ $response = $this->http->request($method->value, (string)$url, [
'headers' => array_replace_recursive([ 'headers' => array_replace_recursive([
'X-Plex-Token' => $adminToken, 'X-Plex-Token' => $adminToken,
...PlexClient::getHeaders() 'X-Plex-Client-Identifier' => $context->backendId,
], ag($opts, 'headers', [])), ], ag($opts, 'headers', [])),
...ag($opts, 'options', []), ...ag($opts, 'options', []),
]); ]);
@@ -312,7 +311,7 @@ final class GetUserToken
$response = $this->http->request($method->value, (string)$url, [ $response = $this->http->request($method->value, (string)$url, [
'headers' => array_replace_recursive([ 'headers' => array_replace_recursive([
'X-Plex-Token' => $context->backendToken, 'X-Plex-Token' => $context->backendToken,
...PlexClient::getHeaders() 'X-Plex-Client-Identifier' => $context->backendId,
], ag($opts, 'headers', [])), ], ag($opts, 'headers', [])),
...ag($opts, 'options', []), ...ag($opts, 'options', []),
]); ]);

View File

@@ -9,7 +9,6 @@ use App\Backends\Common\Context;
use App\Backends\Common\Error; use App\Backends\Common\Error;
use App\Backends\Common\Levels; use App\Backends\Common\Levels;
use App\Backends\Common\Response; use App\Backends\Common\Response;
use App\Backends\Plex\PlexClient;
use App\Libs\Container; use App\Libs\Container;
use App\Libs\Enums\Http\Status; use App\Libs\Enums\Http\Status;
use App\Libs\Exceptions\Backends\InvalidArgumentException; use App\Libs\Exceptions\Backends\InvalidArgumentException;
@@ -464,7 +463,7 @@ final class GetUsersList
$response = $this->http->request(ag($opts, 'method', 'GET'), (string)$url, [ $response = $this->http->request(ag($opts, 'method', 'GET'), (string)$url, [
'headers' => array_replace_recursive([ 'headers' => array_replace_recursive([
'X-Plex-Token' => $adminToken, 'X-Plex-Token' => $adminToken,
...PlexClient::getHeaders() 'X-Plex-Client-Identifier' => $context->backendId,
], ag($opts, 'headers', [])), ], ag($opts, 'headers', [])),
]); ]);
if (Status::OK === Status::from($response->getStatusCode())) { if (Status::OK === Status::from($response->getStatusCode())) {
@@ -480,7 +479,7 @@ final class GetUsersList
$response = $this->http->request(ag($opts, 'method', 'GET'), (string)$url, [ $response = $this->http->request(ag($opts, 'method', 'GET'), (string)$url, [
'headers' => array_replace_recursive([ 'headers' => array_replace_recursive([
'X-Plex-Token' => $context->backendToken, 'X-Plex-Token' => $context->backendToken,
...PlexClient::getHeaders() 'X-Plex-Client-Identifier' => $context->backendId,
], ag($opts, 'headers', [])), ], ag($opts, 'headers', [])),
]); ]);

View File

@@ -247,6 +247,7 @@ class Progress
'time' => $entity->getPlayProgress(), 'time' => $entity->getPlayProgress(),
// -- Without duration & client identifier plex ignore watch progress update. // -- Without duration & client identifier plex ignore watch progress update.
'duration' => ag($remoteData, 'duration', 0), 'duration' => ag($remoteData, 'duration', 0),
'X-Plex-Client-Identifier' => $context->backendId,
])); ]));
$logContext['remote']['url'] = (string)$url; $logContext['remote']['url'] = (string)$url;