Added WS_API_AUTO environment variable which can autoconfigure the WebUI
This commit is contained in:
29
FAQ.md
29
FAQ.md
@@ -582,6 +582,31 @@ https://watchstate.example.org {
|
||||
|
||||
---
|
||||
|
||||
### WS_API_AUTO
|
||||
|
||||
The purpose of this environment variable is to automate the configuration process. It's mainly used for people who uses many browsers
|
||||
to access the `WebUI` and want to automate the configuration process. as it's requires the API settings to be configured before it
|
||||
can be used. This environment variable can be enabled by setting `WS_API_AUTO=true` in `${WS_DATA_PATH}/config/.env`.
|
||||
|
||||
#### Why you should use it?
|
||||
|
||||
You normally should not use it, as it's a **GREAT SECURITY RISK**. However, if you are using the tool in a secure environment
|
||||
and not worried about exposing your API key, you can use it to automate the configuration process.
|
||||
|
||||
#### Why you should not use it?
|
||||
|
||||
Because, by exposing your API key, you are also exposing every data you have in the tool. This is a **GREAT SECURITY RISK**,
|
||||
any person or bot that are able to access the `WebUI` will also be able to visit `/v1/api/system/auto` and get your API key. And with this key
|
||||
they can do anything they want with your data. including viewing your media servers api keys.
|
||||
|
||||
So, please while we have this option available, we strongly recommend not to use it if `WatchState` is exposed to the internet.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This environment variable is **GREAT SECURITY RISK**, and we strongly recommend not to use it if `WatchState` is exposed to the internet.
|
||||
> I cannot stress this enough, please do not use it unless you are in a secure environment.
|
||||
|
||||
---
|
||||
|
||||
### How to disable the included cache server and use external cache server?
|
||||
|
||||
Set this environment variable in your `compose.yaml` file `DISABLE_CACHE` with value of `1`. to use external redis server
|
||||
@@ -745,7 +770,7 @@ Once that is done you are ready to compile the `WebUI`.
|
||||
|
||||
```bash
|
||||
$ cd frontend
|
||||
$ yarn install --production --prefer-offline --frozen-lockfile
|
||||
$ yarn install --production --prefer-offline --frozen-lockfile && yarn run generate
|
||||
```
|
||||
|
||||
There should be a new directory called `exported`, you need to move that folder to the `public` directory.
|
||||
@@ -761,7 +786,7 @@ ws:/opt/app/public$ ls
|
||||
exported index.php
|
||||
```
|
||||
|
||||
There must be exactly one `index.php` file and one `exported` directory. inside that directory.
|
||||
There must be exactly one `index.php` file and one `exported` directory. inside that directory, or if you prefer, you can add `WS_WEBUI_PATH` environment variable to point to the `exported` directory.
|
||||
|
||||
* link the app to the frontend proxy. For caddy, you can use the following configuration.
|
||||
|
||||
|
||||
16
NEWS.md
16
NEWS.md
@@ -1,5 +1,21 @@
|
||||
# Old Updates
|
||||
|
||||
### 2024-05-14
|
||||
|
||||
We are happy to announce the beta testing of the `WebUI`. To get started on using it you just need to visit the url `http://localhost:8080` We are supposed to
|
||||
enabled it by default tomorrow, but we decided to give you a head start. We are looking forward to your feedback. If you don't use the `WebUI` then you need to
|
||||
add the environment variable `WEBUI_ENABLED=0` in your `compose.yaml` file. and restart the container.
|
||||
|
||||
### 2024-05-13
|
||||
|
||||
In preparation for the beta testing of `WebUI` in two days, we have made little breaking change, we have changed the
|
||||
environment variable `WS_WEBUI_ENABLED` to just `WEBUI_ENABLED`, We made this change to make sure people don't disable
|
||||
the `WebUI`by mistake via the environment page in the `WebUI`. The `WebUI` will be enabled by default, in two days from
|
||||
now, to disable it from now add `WEBUI_ENABLED=false` to your `compose.yaml` file. As this environment variable is
|
||||
system level, it cannot be set via `.env` file.
|
||||
|
||||
Note: `WS_WEBUI_ENABLED` will be gone in few weeks, However it will still work for now, if `WEBUI_ENABLED` is not set.
|
||||
|
||||
### 2024-05-05
|
||||
|
||||
**Edit** - We received requests that people are exposing watchstate externally, and there was concern that having open
|
||||
|
||||
26
README.md
26
README.md
@@ -7,29 +7,29 @@
|
||||
This tool primary goal is to sync your backends play state without relying on third party services,
|
||||
out of the box, this tool support `Jellyfin`, `Plex` and `Emby` media servers.
|
||||
|
||||
## updates
|
||||
## Updates
|
||||
|
||||
### 2024-05-14
|
||||
### 2024-06-23
|
||||
|
||||
We are happy to announce the beta testing of the `WebUI`. To get started on using it you just need to visit the url `http://localhost:8080` We are supposed to
|
||||
enabled it by default tomorrow, but we decided to give you a head start. We are looking forward to your feedback. If you don't use the `WebUI` then you need to
|
||||
add the environment variable `WEBUI_ENABLED=0` in your `compose.yaml` file. and restart the container.
|
||||
WE are happy to announce that the `WebUI` is ready for wider usage and we are planning to release it in the next few months.
|
||||
We are actively working on it to improve it. If you have any feedback or suggestions, please let us know. We feel it's almost future complete
|
||||
for the things that we want.
|
||||
|
||||
### 2024-05-13
|
||||
On another related news, we have added new environment variable `WS_API_AUTO` "disabled by default" which can be used
|
||||
to automatically expose your **API KEY/TOKEN**. This is useful for users who are using the `WebUI` from many different browsers
|
||||
and want to automate the configuration process.
|
||||
|
||||
In preparation for the beta testing of `WebUI` in two days, we have made little breaking change, we have changed the
|
||||
environment variable `WS_WEBUI_ENABLED` to just `WEBUI_ENABLED`, We made this change to make sure people don't disable
|
||||
the `WebUI`by mistake via the environment page in the `WebUI`. The `WebUI` will be enabled by default, in two days from
|
||||
now, to disable it from now add `WEBUI_ENABLED=false` to your `compose.yaml` file. As this environment variable is
|
||||
system level, it cannot be set via `.env` file.
|
||||
While the `WebUI` is included in the main project, it's a standalone feature and requires the API settings to be configured before it
|
||||
can be used. This environment variable can be enabled by setting `WS_API_AUTO=true` in `${WS_DATA_PATH}/config/.env`.
|
||||
|
||||
Note: `WS_WEBUI_ENABLED` will be gone in few weeks, However it will still work for now, if `WEBUI_ENABLED` is not set.
|
||||
> [!IMPORTANT]
|
||||
> This environment variable is **GREAT SECURITY RISK**, and we strongly recommend not to use it if `WatchState` is exposed to the internet.
|
||||
|
||||
Refer to [NEWS](NEWS.md) for old updates.
|
||||
|
||||
# Features
|
||||
|
||||
* **NEW** WebUI. (Preview).
|
||||
* WebUI.
|
||||
* Sync backends play state (from many to many).
|
||||
* Backup your backends play state into `portable` format.
|
||||
* Receive Webhook events from media backends.
|
||||
|
||||
@@ -34,6 +34,7 @@ return (function () {
|
||||
'prefix' => '/v1/api',
|
||||
'key' => env('WS_API_KEY', null),
|
||||
'secure' => (bool)env('WS_SECURE_API_ENDPOINTS', false),
|
||||
'auto' => (bool)env('WS_API_AUTO', false),
|
||||
'pattern_match' => [
|
||||
'backend' => '[a-zA-Z0-9_-]+',
|
||||
],
|
||||
|
||||
@@ -150,6 +150,12 @@ return (function () {
|
||||
'description' => 'Expose debug information in the API when an error occurs.',
|
||||
'type' => 'bool',
|
||||
],
|
||||
[
|
||||
'key' => 'WS_API_AUTO',
|
||||
'description' => 'PUBLICLY EXPOSE the api token for automated WebUI configuration. This should NEVER be enabled if WatchState is exposed to the internet.',
|
||||
'danger' => true,
|
||||
'type' => 'bool',
|
||||
],
|
||||
];
|
||||
|
||||
$validateCronExpression = function (string $value): string {
|
||||
|
||||
@@ -26,7 +26,7 @@ onMounted(() => fetch(`${api_url.value}${props.file}`).then(response => response
|
||||
text = text.replace(/\[!IMPORTANT\]/g, `
|
||||
<span class="is-block title is-4">
|
||||
<span class="icon-text">
|
||||
<span class="icon"><i class="fas fa-exclamation-triangle has-text-danger"></i></span>
|
||||
<span class="icon"><i class="fas fa-exclamation-triangle has-text-danger fa-fade"></i></span>
|
||||
<span>Important</span>
|
||||
</span>
|
||||
</span>`)
|
||||
|
||||
@@ -252,13 +252,38 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column is-12 mt-2">
|
||||
<Message title="Information" message_class="has-background-info-90 has-text-dark"
|
||||
icon="fas fa-info-circle">
|
||||
<p>
|
||||
It's possible to automatically setup the API connection for this client and <strong
|
||||
class="has-text-danger">ALL VISITORS</strong> by setting the following environment variable <code>WS_API_AUTO=true</code>
|
||||
in <code>/config/.env</code> file. Understand that this option <strong class="has-text-danger">PUBLICLY
|
||||
EXPOSES YOUR API TOKEN</strong> to <u>ALL VISITORS</u>. Anyone who is able to reach this page will be
|
||||
granted access to your <code>WatchState API</code> which exposes your other media backends data including
|
||||
their secrets. <strong>this option is great security risk and SHOULD NEVER be used if
|
||||
<code>WatchState</code> is exposed to the internet.</strong>
|
||||
</p>
|
||||
|
||||
<p>Please visit
|
||||
<span class="icon">
|
||||
<i class="fab fa-github"></i>
|
||||
</span>
|
||||
<NuxtLink target="_blank" to="https://github.com/arabcoders/watchstate/blob/master/FAQ.md#ws_api_auto">
|
||||
This link
|
||||
</NuxtLink>
|
||||
. to learn more, this environment variable is important enough to have its own section entry in the FAQ.
|
||||
</p>
|
||||
</Message>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="!api_url || !api_token">
|
||||
<template v-if="!hasAPISettings">
|
||||
<no-api/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<slot v-if="!showConnection"/>
|
||||
<slot v-if="!showConnection && hasAPISettings"/>
|
||||
</template>
|
||||
|
||||
<div class="columns is-multiline is-mobile mt-3">
|
||||
@@ -297,7 +322,7 @@ import {dEvent} from '~/utils/index.js'
|
||||
const selectedTheme = useStorage('theme', (() => window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')())
|
||||
const showConnection = ref(false)
|
||||
|
||||
const real_api_url = useStorage('api_url', '')
|
||||
const real_api_url = useStorage('api_url', window.location.origin)
|
||||
const real_api_path = useStorage('api_path', '/v1/api')
|
||||
const real_api_token = useStorage('api_token', '')
|
||||
|
||||
@@ -357,9 +382,12 @@ const applyPreferredColorScheme = scheme => {
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
|
||||
applyPreferredColorScheme(selectedTheme.value)
|
||||
|
||||
if ('' === api_token.value) {
|
||||
await autoConfig()
|
||||
}
|
||||
|
||||
if ('' === api_token.value || '' === api_url.value) {
|
||||
showConnection.value = true
|
||||
return
|
||||
@@ -430,18 +458,47 @@ const getVersion = async (updateStatus = true) => {
|
||||
api_status.value = true
|
||||
api_response.value = 'Status: OK'
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
return 'Unknown'
|
||||
}
|
||||
}
|
||||
|
||||
const autoConfig = async () => {
|
||||
try {
|
||||
const response = await fetch(`${api_url.value}${api_path.value}/system/auto`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({'origin': window.location.origin})
|
||||
})
|
||||
|
||||
const json = await response.json()
|
||||
|
||||
if (200 !== response.status) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!api_url.value) {
|
||||
api_url.value = json.url
|
||||
}
|
||||
|
||||
if (!api_path.value) {
|
||||
api_path.value = json.path
|
||||
}
|
||||
|
||||
if (!api_token.value) {
|
||||
api_token.value = json.token
|
||||
}
|
||||
|
||||
await testApi();
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
const setOrigin = () => api_url.value = window.location.origin;
|
||||
|
||||
const hasAPISettings = computed(() => '' !== real_api_token.value && '' !== real_api_url.value)
|
||||
|
||||
const closeOverlay = () => {
|
||||
loadFile.value = ''
|
||||
}
|
||||
|
||||
const closeOverlay = () => loadFile.value = ''
|
||||
</script>
|
||||
|
||||
@@ -88,10 +88,12 @@
|
||||
<input class="input" id="form_value" type="text" placeholder="Value" v-model="form_value">
|
||||
<div class="icon is-small is-left"><i class="fas fa-font"></i></div>
|
||||
</template>
|
||||
<div>
|
||||
<p class="help" v-html="getHelp(form_key)"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<div class="card-footer-item">
|
||||
<button class="button is-fullwidth is-primary" type="submit" :disabled="!form_key || '' === form_value">
|
||||
@@ -115,11 +117,23 @@
|
||||
</div>
|
||||
<div v-else class="column is-12" v-if="items">
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-4" v-for="item in filteredRows(items)" :key="item.key">
|
||||
<div class="card">
|
||||
<div class="column" v-for="item in filteredRows(items)" :key="item.key"
|
||||
:class="{'is-4':!item?.danger,'is-12':item.danger}">
|
||||
<div class="card" :class="{ 'is-danger': item?.danger }">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title is-unselectable">
|
||||
<span class="has-tooltip is-clickable" v-tooltip="item.description">{{ item.key }}</span>
|
||||
<template v-if="item?.danger">
|
||||
<span class="title is-5 ">
|
||||
<span class="icon" v-tooltip="'This option is considered dangerous.'">
|
||||
<i class="has-text-danger fas fa-exclamation-triangle"></i>
|
||||
</span> {{ item.key }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="has-tooltip is-clickable" v-tooltip="item.description">
|
||||
{{ item.key }}
|
||||
</span>
|
||||
</template>
|
||||
</p>
|
||||
<span class="card-header-icon" v-if="item.mask" @click="item.mask = false" v-tooltip="'Unmask the value'">
|
||||
<span class="icon"><i class="fas fa-unlock"></i></span>
|
||||
@@ -140,6 +154,10 @@
|
||||
:class="{ 'is-masked': item.mask, 'is-unselectable': item.mask }"
|
||||
@click="(e) => e.target.classList.toggle('is-text-overflow')">
|
||||
{{ item.value }}</p>
|
||||
|
||||
<p v-if="item?.danger" class="title is-5 has-text-danger">
|
||||
{{ item.description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="card-footer">
|
||||
@@ -181,6 +199,10 @@
|
||||
<li>Some values are too large to fit into the view, clicking on the value will show the full value.</li>
|
||||
<li>These values are loaded from the <code>{{ file }}</code> file.</li>
|
||||
<li>To add a new variable click on the <i class="fa fa-add"></i> button.</li>
|
||||
<li>Environment variables with <span class="has-text-danger">red borders</span> and <i
|
||||
class="fas fa-exclamation-triangle"></i> icon are considered
|
||||
dangerous. Please be careful when editing them.
|
||||
</li>
|
||||
</ul>
|
||||
</Message>
|
||||
</div>
|
||||
@@ -359,6 +381,10 @@ const getHelp = key => {
|
||||
|
||||
let text = `${data[0].description}`
|
||||
|
||||
if (data[0]?.danger) {
|
||||
text = `<span class="has-text-danger title is-5"> <i class="has-text-warning fas fa-exclamation-triangle fa-bounce"></i> ${text}</span>`
|
||||
}
|
||||
|
||||
if (data[0]?.type) {
|
||||
text += ` Expects: <code>${data[0].type}</code>`
|
||||
}
|
||||
|
||||
33
src/API/System/AutoConfig.php
Normal file
33
src/API/System/AutoConfig.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\API\System;
|
||||
|
||||
use App\Libs\Attributes\Route\Post;
|
||||
use App\Libs\Config;
|
||||
use App\Libs\DataUtil;
|
||||
use App\Libs\HTTP_STATUS;
|
||||
use Psr\Http\Message\ResponseInterface as iResponse;
|
||||
use Psr\Http\Message\ServerRequestInterface as iRequest;
|
||||
|
||||
final class AutoConfig
|
||||
{
|
||||
public const string URL = '%{api.prefix}/system/auto';
|
||||
|
||||
#[Post(self::URL . '[/]', name: 'system.autoconfig')]
|
||||
public function __invoke(iRequest $request): iResponse
|
||||
{
|
||||
if (false === (bool)Config::get('api.auto', false)) {
|
||||
return api_error('auto configuration is disabled.', HTTP_STATUS::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
$data = DataUtil::fromRequest($request);
|
||||
|
||||
return api_response(HTTP_STATUS::HTTP_OK, [
|
||||
'url' => $data->get('origin', ag($_SERVER, 'HTTP_ORIGIN', 'localhost')),
|
||||
'path' => Config::get('api.prefix'),
|
||||
'token' => Config::get('api.key'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Libs\Middlewares;
|
||||
|
||||
use App\API\System\AutoConfig;
|
||||
use App\API\System\HealthCheck;
|
||||
use App\Libs\Config;
|
||||
use App\Libs\HTTP_STATUS;
|
||||
@@ -22,6 +23,7 @@ final class APIKeyRequiredMiddleware implements MiddlewareInterface
|
||||
*/
|
||||
private const array PUBLIC_ROUTES = [
|
||||
HealthCheck::URL,
|
||||
AutoConfig::URL,
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user