From b0663356e2dea513b5f7c17694f1259d56842819 Mon Sep 17 00:00:00 2001 From: arabcoders Date: Thu, 15 May 2025 18:20:37 +0300 Subject: [PATCH] Finalizing auth migration --- config/config.php | 21 ++- config/env.spec.php | 46 +++++- frontend/components/Settings.vue | 137 ++++++++++++++++- frontend/layouts/default.vue | 3 +- frontend/pages/auth.vue | 86 ++++++++--- frontend/store/auth.js | 13 +- src/API/System/Auth.php | 101 ++++++++++-- src/API/System/Env.php | 29 ++-- src/Commands/System/APIKeyCommand.php | 46 ++---- src/Commands/System/ResetPasswordCommand.php | 15 +- src/Libs/IpUtils.php | 152 +++++++++++++++++++ src/Libs/TokenUtil.php | 30 ++-- 12 files changed, 557 insertions(+), 122 deletions(-) create mode 100644 src/Libs/IpUtils.php diff --git a/config/config.php b/config/config.php index 4e6dfe24..b8b2d7a3 100644 --- a/config/config.php +++ b/config/config.php @@ -18,13 +18,13 @@ use Monolog\Level; return (function () { $inContainer = inContainer(); - $progressTimeCheck = fn(int $v, int $d): int => 0 === $v || $v >= 180 ? $v : $d; + $progressTimeCheck = fn (int $v, int $d): int => 0 === $v || $v >= 180 ? $v : $d; $config = [ 'name' => 'WatchState', 'version' => '$(version_via_ci)', 'tz' => env('WS_TZ', env('TZ', 'UTC')), - 'path' => fixPath(env('WS_DATA_PATH', fn() => $inContainer ? '/config' : __DIR__ . '/../var')), + 'path' => fixPath(env('WS_DATA_PATH', fn () => $inContainer ? '/config' : __DIR__ . '/../var')), 'logs' => [ 'context' => (bool)env('WS_LOGS_CONTEXT', false), 'prune' => [ @@ -44,7 +44,7 @@ return (function () { 'encode' => JSON_INVALID_UTF8_IGNORE | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE, 'headers' => [ 'Content-Type' => 'application/json', - 'X-Application-Version' => fn() => getAppVersion(), + 'X-Application-Version' => fn () => getAppVersion(), 'Access-Control-Allow-Origin' => '*', ], ], @@ -70,6 +70,14 @@ return (function () { 'trust' => [ 'proxy' => (bool)env('WS_TRUST_PROXY', false), 'header' => (string)env('WS_TRUST_HEADER', 'X-Forwarded-For'), + 'local' => (bool)env('WS_TRUST_LOCAL', false), + 'localnet' => [ + '192.168.0.0/16', // RFC-1918 A-block. + '127.0.0.1/32', // localhost IPv4 + '10.0.0.0/8', // RFC-1918 C-block. + '::1/128', // localhost IPv6 + '172.16.0.0/12' // RFC-1918 B-block. + ], ], 'sync' => [ 'progress' => (bool)env('WS_SYNC_PROGRESS', true), @@ -154,14 +162,14 @@ return (function () { $config['profiler'] = [ 'save' => (bool)env('WS_PROFILER_SAVE', true), - 'path' => env('WS_PROFILER_PATH', fn() => ag($config, 'tmpDir') . '/profiler'), + 'path' => env('WS_PROFILER_PATH', fn () => ag($config, 'tmpDir') . '/profiler'), 'collector' => env('WS_PROFILER_COLLECTOR', null), ]; $config['cache'] = [ 'prefix' => env('WS_CACHE_PREFIX', null), 'url' => env('WS_CACHE_URL', 'redis://127.0.0.1:6379'), - 'path' => env('WS_CACHE_PATH', fn() => ag($config, 'tmpDir') . '/cache'), + 'path' => env('WS_CACHE_PATH', fn () => ag($config, 'tmpDir') . '/cache'), ]; $config['logger'] = [ @@ -345,11 +353,12 @@ return (function () { $config['password'] = [ 'prefix' => 'ws_hash@:', 'algo' => PASSWORD_BCRYPT, - 'options' => ['cost' => 10], + 'options' => ['cost' => 12], ]; $config['system'] = [ 'user' => env('WS_SYSTEM_USER', null), + 'secret' => env('WS_SYSTEM_SECRET', null), 'password' => env('WS_SYSTEM_PASSWORD', null), ]; diff --git a/config/env.spec.php b/config/env.spec.php index 087e1f93..b11d0240 100644 --- a/config/env.spec.php +++ b/config/env.spec.php @@ -28,6 +28,17 @@ return (function () { 'key' => 'WS_TZ', 'description' => 'Set the Tool timezone.', 'type' => 'string', + 'validate' => function (mixed $value): string { + if (is_numeric($value) || empty($value)) { + throw new ValidationException('Invalid timezone. Empty value.'); + } + + try { + return new DateTimeZone($value)->getName(); + } catch (Throwable) { + throw new ValidationException("Invalid timezone '{$value}'."); + } + }, ], [ 'key' => 'WS_LOGS_CONTEXT', @@ -54,6 +65,12 @@ return (function () { 'description' => 'Trust the IP from the WS_TRUST_HEADER header.', 'type' => 'bool', ], + [ + 'key' => 'WS_TRUST_LOCAL', + 'description' => 'Bypass the authentication layer for local IP Addresses for WebUI.', + 'type' => 'bool', + 'danger' => true, + ], [ 'key' => 'WS_TRUST_HEADER', 'description' => 'The header which contains the true user IP.', @@ -85,6 +102,7 @@ return (function () { 'description' => 'The API key to allow access to the API.', 'type' => 'string', 'mask' => true, + 'protected' => true, ], [ 'key' => 'WS_LOGS_PRUNE_AFTER', @@ -226,7 +244,7 @@ return (function () { ], [ 'key' => 'WS_SYSTEM_USER', - 'description' => 'The login user name', + 'description' => 'The login user name.', 'type' => 'string', 'validate' => function (mixed $value): string { if (!is_numeric($value) && empty($value)) { @@ -241,7 +259,7 @@ return (function () { return $value; }, 'mask' => true, - 'hidden' => true, + 'protected' => true, ], [ 'key' => 'WS_SYSTEM_PASSWORD', @@ -267,7 +285,29 @@ return (function () { return $prefix . $hash; }, 'mask' => true, - 'hidden' => true, + 'protected' => true, + ], + [ + 'key' => 'WS_SYSTEM_SECRET', + 'description' => 'The secret key which is used to sign sucessful auth requests.', + 'type' => 'string', + 'validate' => function (mixed $value): string { + if (empty($value)) { + throw new ValidationException('Invalid secret. Empty value.'); + } + + if (false === is_string($value)) { + throw new ValidationException('Invalid secret. Must be a string.'); + } + + if (strlen($value) < 32) { + throw new ValidationException('Invalid secret. Must be at least 32 characters long.'); + } + + return $value; + }, + 'mask' => true, + 'protected' => true, ], ]; diff --git a/frontend/components/Settings.vue b/frontend/components/Settings.vue index f9628f9f..457b023a 100644 --- a/frontend/components/Settings.vue +++ b/frontend/components/Settings.vue @@ -1,13 +1,67 @@ diff --git a/frontend/layouts/default.vue b/frontend/layouts/default.vue index 486374da..9d80d521 100644 --- a/frontend/layouts/default.vue +++ b/frontend/layouts/default.vue @@ -193,8 +193,7 @@ diff --git a/frontend/pages/auth.vue b/frontend/pages/auth.vue index 82202230..557eb24a 100644 --- a/frontend/pages/auth.vue +++ b/frontend/pages/auth.vue @@ -6,19 +6,48 @@
- + {{ error }}
-
+
+
+

+ + How to Reset system login +

+

+ To reset your system password, you need to run the following command from your docker host +

+

+ {{ reset_cmd }} +

+

+ + + Copy command + +

+
+
+
+ +
+
+
+ +
- + autocomplete="username" name="username" v-model="user.username" autofocus> +
@@ -27,17 +56,14 @@
- - + +
-
@@ -46,12 +72,19 @@