re-enforce the backend/user name requirement of being a-z_0-9, due to recent refactor the check was not working as expected we added new tests to cover invalid names.
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
<template>
|
||||
<Message title="Important" message_class="has-background-warning-80 has-text-dark" icon="fas fa-info-circle">
|
||||
<ul>
|
||||
<li>
|
||||
WatchState is single user tool. It doesn't support syncing multiple users play state.
|
||||
<li v-if="api_user === 'main'">
|
||||
Support for sub users is in early stages. For more information please visit
|
||||
<NuxtLink target="_blank" v-text="'Visit this link'"
|
||||
to="https://github.com/arabcoders/watchstate/blob/master/FAQ.md#is-there-support-for-multi-user-setup"/>
|
||||
to learn more.
|
||||
to learn more. <b>DO NOT</b> add sub users backends directly. Use the create sub-users button after setting up
|
||||
the main user.
|
||||
</li>
|
||||
<li>
|
||||
If you are adding new backend that is fresh and doesn't have your current watch state, you should turn off
|
||||
@@ -17,11 +18,10 @@
|
||||
</ul>
|
||||
</Message>
|
||||
|
||||
|
||||
<form id="backend_add_form" @submit.prevent="stage<4 ? changeStep() : addBackend()">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<p class="card-header-title is-justify-center">Add Backend</p>
|
||||
<p class="card-header-title">Add backend to '<u class="has-text-danger">{{ api_user }}</u>' user config.</p>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
@@ -32,6 +32,25 @@
|
||||
</Message>
|
||||
</div>
|
||||
<template v-if="stage>=0">
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Local User</label>
|
||||
<div class="control has-icons-left">
|
||||
<div class="select is-fullwidth">
|
||||
<select class="is-capitalized" disabled>
|
||||
<option v-text="api_user"/>
|
||||
</select>
|
||||
</div>
|
||||
<div class="icon is-left">
|
||||
<i class="fas fa-users"></i>
|
||||
</div>
|
||||
<p class="help">
|
||||
The local user which this backend will be associated with. You can change this user via the users icon
|
||||
on top.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Type</label>
|
||||
<div class="control has-icons-left">
|
||||
@@ -46,8 +65,8 @@
|
||||
<i class="fas fa-server"></i>
|
||||
</div>
|
||||
<p class="help">
|
||||
Select the type of backend you want to add. Supported backends are: <code>{{
|
||||
supported.join(', ')
|
||||
The backend server type. The supported types are <code>{{
|
||||
supported.map(v => ucFirst(v)).join(', ')
|
||||
}}</code>.
|
||||
</p>
|
||||
</div>
|
||||
@@ -61,8 +80,8 @@
|
||||
<i class="fas fa-id-badge"></i>
|
||||
</div>
|
||||
<p class="help">
|
||||
Choose a unique name for this backend. You cannot change it later. Backend name must be in <code>lower
|
||||
case a-z, 0-9 and _</code> only.
|
||||
Choose a unique name for this backend. <b class="has-text-danger">You CANNOT change it later</b>.
|
||||
Backend name must be in <code>lower case a-z, 0-9 and _</code> and cannot start with number.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -94,7 +113,8 @@
|
||||
<template v-if="'plex'===backend.type">
|
||||
Enter the <code>X-Plex-Token</code>.
|
||||
<NuxtLink target="_blank" to="https://support.plex.tv/articles/204059436"
|
||||
v-text="'Visit This article for more information.'"/>
|
||||
v-text="'Visit This link'"/>
|
||||
to learn how to get the token.
|
||||
</template>
|
||||
<template v-else>
|
||||
Generate a new API Key from <code>Dashboard > Settings > API Keys</code>.<br>
|
||||
@@ -114,7 +134,8 @@
|
||||
<input class="input" type="text" v-model="backend.options.PLEX_USER_PIN" :disabled="stage > 1">
|
||||
<div class="icon is-left"><i class="fas fa-key"></i></div>
|
||||
<p class="help">
|
||||
If the user you going to select is using <code>PIN</code> to login, enter the PIN here.
|
||||
If the user you are going to select has <code>PIN</code> enabled, you need to enter the pin here.
|
||||
Otherwise it will fail to authenticate.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -292,7 +313,7 @@
|
||||
</div>
|
||||
|
||||
<div class="card-footer-item" v-if="stage < maxStages">
|
||||
<button class="button is-fullwidth is-info" type="submit" @click="changeStep()">
|
||||
<button class="button is-fullwidth is-info" type="button" @click="changeStep()">
|
||||
<span class="icon"><i class="fas fa-arrow-right"></i></span>
|
||||
<span>Next Step</span>
|
||||
</button>
|
||||
@@ -311,7 +332,8 @@
|
||||
<script setup>
|
||||
import 'assets/css/bulma-switch.css'
|
||||
import request from '~/utils/request'
|
||||
import {awaitElement, explode, notification} from '~/utils/index'
|
||||
import {awaitElement, explode, notification, ucFirst} from '~/utils/index'
|
||||
import {useStorage} from "@vueuse/core";
|
||||
|
||||
const emit = defineEmits(['addBackend', 'forceExport', 'runImport'])
|
||||
|
||||
@@ -343,6 +365,7 @@ const backend = ref({
|
||||
},
|
||||
options: {}
|
||||
})
|
||||
const api_user = useStorage('api_user', 'main')
|
||||
const users = ref([])
|
||||
const supported = ref([])
|
||||
const servers = ref([])
|
||||
@@ -518,8 +541,7 @@ const getUsers = async (showAlert = true) => {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const response = await request('/system/supported')
|
||||
supported.value = await response.json()
|
||||
supported.value = await (await request('/system/supported')).json()
|
||||
backend.value.type = supported.value[0]
|
||||
})
|
||||
|
||||
@@ -538,6 +560,11 @@ const changeStep = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
if (false === /^[a-z_0-9]+$/.test(backend.value.name)) {
|
||||
notification('error', 'Error', `Backend name must be in lower case a-z, 0-9 and _ only.`)
|
||||
return
|
||||
}
|
||||
|
||||
if (props.backends.find(b => b.name === backend.value.name)) {
|
||||
notification('error', 'Error', `Backend with name '${backend.value.name}' already exists.`)
|
||||
return
|
||||
|
||||
@@ -45,10 +45,26 @@
|
||||
<form id="backend_edit_form" @submit.prevent="saveContent">
|
||||
<div class="card">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title is-justify-center">Edit Backend - {{ backend.name }}</p>
|
||||
<p class="card-header-title">
|
||||
Edit Backend: <u class="has-text-danger">{{ api_user }}</u>@{{ backend.name }}</p>
|
||||
</header>
|
||||
|
||||
<div class="card-content">
|
||||
<div class="field">
|
||||
<label class="label">Local User</label>
|
||||
<div class="control has-icons-left">
|
||||
<div class="select is-fullwidth">
|
||||
<select class="is-capitalized" disabled>
|
||||
<option v-text="api_user"/>
|
||||
</select>
|
||||
</div>
|
||||
<div class="icon is-left">
|
||||
<i class="fas fa-users"></i>
|
||||
</div>
|
||||
<p class="help">The local user which this backend is associated with.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Name</label>
|
||||
<div class="control has-icons-left">
|
||||
@@ -70,6 +86,11 @@
|
||||
<div class="icon is-left">
|
||||
<i class="fas fa-server"></i>
|
||||
</div>
|
||||
<p class="help">
|
||||
The backend server type. The supported types are <code>{{
|
||||
supported.map(v => ucFirst(v)).join(', ')
|
||||
}}</code>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -347,6 +368,8 @@
|
||||
import 'assets/css/bulma-switch.css'
|
||||
import {notification, ucFirst} from '~/utils/index'
|
||||
import Message from '~/components/Message'
|
||||
import {useStorage} from "@vueuse/core";
|
||||
import request from "~/utils/request.js";
|
||||
|
||||
const id = useRoute().params.backend
|
||||
const redirect = useRoute().query?.redirect ?? `/backend/${id}`
|
||||
@@ -363,9 +386,11 @@ const backend = ref({
|
||||
webhook: {match: {user: false, uuid: false}},
|
||||
options: {}
|
||||
})
|
||||
|
||||
const showOptions = ref(false)
|
||||
const isLoading = ref(true)
|
||||
const users = ref([])
|
||||
const supported = ref([])
|
||||
const usersLoading = ref(false)
|
||||
const uuidLoading = ref(false)
|
||||
const optionsList = ref([])
|
||||
@@ -375,6 +400,7 @@ const exposeToken = ref(false)
|
||||
const servers = ref([])
|
||||
const serversLoading = ref(false)
|
||||
const isLimitedToken = computed(() => Boolean(backend.value.options?.is_limited_token))
|
||||
const api_user = useStorage('api_user', 'main')
|
||||
|
||||
const selectedOptionHelp = computed(() => {
|
||||
const option = optionsList.value.find(v => v.key === selectedOption.value)
|
||||
@@ -384,6 +410,8 @@ const selectedOptionHelp = computed(() => {
|
||||
useHead({title: 'Backends - Edit: ' + id})
|
||||
|
||||
const loadContent = async () => {
|
||||
supported.value = await (await request('/system/supported')).json()
|
||||
|
||||
const content = await request(`/backend/${id}`)
|
||||
let json = await content.json()
|
||||
|
||||
|
||||
@@ -53,7 +53,10 @@ final class Add
|
||||
}
|
||||
|
||||
if (false === isValidName($name)) {
|
||||
return api_error('Invalid name was given.', Status::BAD_REQUEST);
|
||||
return api_error(
|
||||
'Invalid name was given. Backend name must only contain [lowercase a-z, 0-9, _].',
|
||||
Status::BAD_REQUEST
|
||||
);
|
||||
}
|
||||
|
||||
$backend = $this->getBackends(name: $name, userContext: $userContext);
|
||||
|
||||
@@ -5,15 +5,15 @@ declare(strict_types=1);
|
||||
namespace App\API\System;
|
||||
|
||||
use App\Libs\Attributes\Route\Delete;
|
||||
use App\Libs\Exceptions\RuntimeException;
|
||||
use App\Libs\Enums\Http\Status;
|
||||
use Psr\Http\Message\ResponseInterface as iResponse;
|
||||
use Psr\Http\Message\ServerRequestInterface as iRequest;
|
||||
use Redis;
|
||||
use RedisException;
|
||||
use App\Libs\Exceptions\RuntimeException;
|
||||
use App\Libs\Mappers\ImportInterface as iImport;
|
||||
use App\Libs\Traits\APITraits;
|
||||
use Psr\Http\Message\ResponseInterface as iResponse;
|
||||
use Psr\Http\Message\ServerRequestInterface as iRequest;
|
||||
use Psr\Log\LoggerInterface as iLogger;
|
||||
use Redis;
|
||||
use RedisException;
|
||||
|
||||
final class Reset
|
||||
{
|
||||
@@ -33,10 +33,7 @@ final class Reset
|
||||
|
||||
try {
|
||||
$ns = getAppVersion();
|
||||
|
||||
if (true === isValidName($user)) {
|
||||
$ns .= isValidName($user) ? '.' . $user : '.' . md5($user);
|
||||
}
|
||||
$ns .= isValidName($user) ? '.' . $user : '.' . md5($user);
|
||||
|
||||
$keys = $redis->keys("{$ns}*");
|
||||
|
||||
|
||||
@@ -542,7 +542,7 @@ class CreateUsersCommand extends Command
|
||||
|
||||
// Build final row: "name" + sub-array "backends"
|
||||
$row = [
|
||||
'name' => $finalName,
|
||||
'name' => strtolower($finalName),
|
||||
'backends' => [],
|
||||
];
|
||||
|
||||
|
||||
@@ -904,7 +904,11 @@ if (!function_exists('isValidName')) {
|
||||
*/
|
||||
function isValidName(string $name): bool
|
||||
{
|
||||
return 1 === preg_match('/^\w+$/', $name);
|
||||
if (true === ctype_digit($name[0])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return 1 === preg_match('/^[a-z_0-9]+$/', $name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2413,10 +2417,7 @@ if (!function_exists('perUserCacheAdapter')) {
|
||||
}
|
||||
|
||||
$ns = getAppVersion();
|
||||
|
||||
if (true === isValidName($user)) {
|
||||
$ns .= isValidName($user) ? '.' . $user : '.' . md5($user);
|
||||
}
|
||||
$ns .= isValidName($user) ? '.' . $user : '.' . md5($user);
|
||||
|
||||
try {
|
||||
$backend = new RedisAdapter(redis: Container::get(Redis::class), namespace: $ns);
|
||||
|
||||
@@ -680,20 +680,13 @@ class HelpersTest extends TestCase
|
||||
{
|
||||
$this->assertTrue(isValidName('foo'), 'When name is valid, true is returned.');
|
||||
$this->assertTrue(isValidName('foo_bar'), 'When name is valid, true is returned.');
|
||||
$this->assertFalse(isValidName('foo_baR'), 'When name is invalid, false is returned.');
|
||||
$this->assertFalse(isValidName('3oo_bar'), 'When name is invalid, false is returned.');
|
||||
|
||||
$invalidNames = [
|
||||
'foo bar',
|
||||
'foo-bar',
|
||||
'foo/bar',
|
||||
'foo?bar',
|
||||
'foo*bar',
|
||||
];
|
||||
$invalidNames = ['foo bar', 'foo-bar', 'foo/bar', 'foo?bar', 'foo*bar', '1foo', 'foo_baR', 'FOOBAR'];
|
||||
|
||||
foreach ($invalidNames as $name) {
|
||||
$this->assertFalse(
|
||||
isValidName($name),
|
||||
"When name ({$name}) is invalid, false is returned."
|
||||
);
|
||||
$this->assertFalse(isValidName($name), "When given name is '{$name}', false should be is returned.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user