From 572a740d46098b57d56bfc5186be7b45963a2bcf Mon Sep 17 00:00:00 2001 From: MeghdadFadaee Date: Sun, 23 Aug 2026 16:01:09 +0330 Subject: [PATCH] feat: add admin-controlled authentication debug mode --- README.md | 5 +- dabestaniha-authenticate-bridge.php | 2 +- .../Controllers/AuthenticateController.php | 38 +++++++++----- .../Http/Controllers/InvocableController.php | 51 +++++++++++-------- .../SettingsPageFormController.php | 31 ++++++++++- src/app/Http/Requests/BaseFormRequest.php | 6 ++- src/configs/mahak.php | 5 +- src/helpers.php | 25 ++++++++- src/resources/views/debug-error.php | 31 +++++++++++ src/resources/views/server-error.php | 4 +- test/integration-authentication.php | 31 ++++++++++- 11 files changed, 187 insertions(+), 42 deletions(-) create mode 100644 src/resources/views/debug-error.php diff --git a/README.md b/README.md index 9b32b79..45d4e11 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,9 @@ Authenticate WordPress user via json web token from Laravel Application. - Login via JWT token - Admin settings page - Custom invalid token page +- Optional debug mode with full exception details and stack traces + +Debug mode is disabled by default. An administrator can enable it from **Settings > Mahak Authentication** while troubleshooting. Because diagnostic pages expose server paths and other technical details to visitors of the login URL, disable it again after diagnosing the problem. ## Usage @@ -32,4 +35,4 @@ Payload JWT: "username" : "test-user" } } -``` \ No newline at end of file +``` diff --git a/dabestaniha-authenticate-bridge.php b/dabestaniha-authenticate-bridge.php index 59a071e..9067735 100644 --- a/dabestaniha-authenticate-bridge.php +++ b/dabestaniha-authenticate-bridge.php @@ -3,7 +3,7 @@ /** * Plugin Name: Mahak Authenticate Bridge * Description: Authenticate WordPress user via json web token from Mahakiha Application. - * Version: 2.0 + * Version: 2.1.0 * Requires PHP: 7.4 * Author: Dabestaniha * diff --git a/src/app/Http/Controllers/AuthenticateController.php b/src/app/Http/Controllers/AuthenticateController.php index dfb5356..c53d558 100644 --- a/src/app/Http/Controllers/AuthenticateController.php +++ b/src/app/Http/Controllers/AuthenticateController.php @@ -4,6 +4,8 @@ namespace Dabestaniha\AuthenticateBridge\App\Http\Controllers; use Dabestaniha\AuthenticateBridge\App\Http\Requests\MahakAuthenticateRequest; use Dabestaniha\AuthenticateBridge\App\Support\JwtDecoder; +use RuntimeException; +use Throwable; use UnexpectedValueException; class AuthenticateController extends InvocableController @@ -31,18 +33,14 @@ class AuthenticateController extends InvocableController { $jwt_secret = get_configured_option('jwt-secret'); - try { - $decoded = JwtDecoder::decodeHs256($token, $jwt_secret); - $data = $decoded['data'] ?? null; + $decoded = JwtDecoder::decodeHs256($token, $jwt_secret); + $data = $decoded['data'] ?? null; - if (!is_array($data)) { - throw new UnexpectedValueException('JWT data must be an object.'); - } - - return $data; - } catch (\Throwable $e) { - wp_die(view('invalid-token'), 'Invalid Token', ['response' => 500]); + if (!is_array($data)) { + throw new UnexpectedValueException('JWT data must be an object.'); } + + return $data; } public function find_or_create_user(string $identifier_name, string $identifier_value, string $name): int @@ -63,17 +61,33 @@ class AuthenticateController extends InvocableController $user_id = wp_create_user($username, $random_password, $email); if (is_wp_error($user_id)) { - wp_die(view('invalid-token'), 'Invalid Token', ['response' => 500]); + throw new RuntimeException( + 'WordPress could not create the user: '.$user_id->get_error_message() + ); } - wp_update_user([ + $updated_user_id = wp_update_user([ 'ID' => $user_id, 'display_name' => $name, ]); + if (is_wp_error($updated_user_id)) { + throw new RuntimeException( + 'WordPress could not update the user: '.$updated_user_id->get_error_message() + ); + } + return $user_id; } + protected function handleException(Throwable $exception): void + { + $status = $exception->getCode() === 403 ? 403 : 500; + $fallbackView = $exception instanceof UnexpectedValueException ? 'invalid-token' : 'server-error'; + + mahak_render_error($exception, $status, $fallbackView); + } + public function login_user(int $user_id): bool { diff --git a/src/app/Http/Controllers/InvocableController.php b/src/app/Http/Controllers/InvocableController.php index 745c53a..05866d3 100644 --- a/src/app/Http/Controllers/InvocableController.php +++ b/src/app/Http/Controllers/InvocableController.php @@ -4,30 +4,41 @@ namespace Dabestaniha\AuthenticateBridge\App\Http\Controllers; use Dabestaniha\AuthenticateBridge\App\Http\Requests\BaseFormRequest; use ReflectionMethod; +use Throwable; abstract class InvocableController { public static function resolve(): void { - $reflection = new ReflectionMethod(static::class, '__invoke'); - - $args = []; - foreach ($reflection->getParameters() as $param) { - $class = $param->getType()->getName(); - - if (is_subclass_of($class, BaseFormRequest::class)) { - $request = new $class(); - - if (!$request->isRequestForThisRoute()) { - return; - } - - $request->validate(); - $args[] = $request; - } - } - $controller = new static(); - $controller(...$args); + + try { + $reflection = new ReflectionMethod(static::class, '__invoke'); + + $args = []; + foreach ($reflection->getParameters() as $param) { + $class = $param->getType()->getName(); + + if (is_subclass_of($class, BaseFormRequest::class)) { + $request = new $class(); + + if (!$request->isRequestForThisRoute()) { + return; + } + + $request->validate(); + $args[] = $request; + } + } + + $controller(...$args); + } catch (Throwable $exception) { + $controller->handleException($exception); + } } -} \ No newline at end of file + + protected function handleException(Throwable $exception): void + { + throw $exception; + } +} diff --git a/src/app/Http/Controllers/SettingsPageFormController.php b/src/app/Http/Controllers/SettingsPageFormController.php index f35d403..c784a0c 100644 --- a/src/app/Http/Controllers/SettingsPageFormController.php +++ b/src/app/Http/Controllers/SettingsPageFormController.php @@ -7,7 +7,16 @@ class SettingsPageFormController extends InvocableController public function __invoke() { foreach (config('mahak.options') as $configured => $options_key) { - register_setting(config('mahak.settings-group'), $options_key); + $args = []; + + if ($configured === 'debug-mode') { + $args['sanitize_callback'] = function ($value): int { + return (int) filter_var($value, FILTER_VALIDATE_BOOLEAN); + }; + $args['default'] = 0; + } + + register_setting(config('mahak.settings-group'), $options_key, $args); } add_settings_section( @@ -49,6 +58,14 @@ class SettingsPageFormController extends InvocableController config('mahak.section-id') ); + add_settings_field( + config('mahak.options.debug-mode'), + config('mahak.translations.debug-mode'), + fn () => $this->debug_mode(), + config('mahak.settings-page'), + config('mahak.section-id') + ); + $loginRouteOptionName = config('mahak.options.login-route'); add_filter("pre_update_option_{$loginRouteOptionName}", function ($value) { return trim($value, '/'); @@ -103,4 +120,14 @@ class SettingsPageFormController extends InvocableController echo ""; echo ''; } -} \ No newline at end of file + + public function debug_mode(): void + { + $name = config('mahak.options.debug-mode'); + $enabled = (bool) get_option($name, false); + + echo ""; + echo "'; + } +} diff --git a/src/app/Http/Requests/BaseFormRequest.php b/src/app/Http/Requests/BaseFormRequest.php index 7a075a9..236c370 100644 --- a/src/app/Http/Requests/BaseFormRequest.php +++ b/src/app/Http/Requests/BaseFormRequest.php @@ -3,6 +3,7 @@ namespace Dabestaniha\AuthenticateBridge\App\Http\Requests; use Dabestaniha\AuthenticateBridge\App\Support\Collection; +use UnexpectedValueException; abstract class BaseFormRequest { @@ -44,7 +45,10 @@ abstract class BaseFormRequest foreach ($rules as $rule) { if (!$this->validateValue($value, $rule)) { - wp_die(view('invalid-token'), 'Invalid Token', ['response' => 403]); + throw new UnexpectedValueException( + sprintf('The request field "%s" failed the "%s" validation rule.', $attribute, $rule), + 403 + ); } $data[$attribute] = $value; diff --git a/src/configs/mahak.php b/src/configs/mahak.php index a715794..900b20b 100644 --- a/src/configs/mahak.php +++ b/src/configs/mahak.php @@ -11,6 +11,7 @@ return [ 'user-identifier' => 'mahak_user_identifier', 'login-route' => 'mahak_login_route', 'after-login-route' => 'mahak_after_login_route', + 'debug-mode' => 'mahak_debug_mode', ], 'user-identifiers' => [ @@ -37,7 +38,9 @@ return [ 'user-identifier' => 'فیلد شناسایی کاربر', 'login-route' => 'مسیر لاگین ماهک', 'after-login-route' => 'مسیر بعد از لاگین', + 'debug-mode' => 'حالت اشکال‌زدایی', + 'debug-mode-description' => 'در صورت بروز خطا، جزئیات کامل فنی در صفحه نمایش داده شود. این گزینه را فقط هنگام عیب‌یابی فعال کنید.', 'new-user' => 'کاربر جدید', ], -]; \ No newline at end of file +]; diff --git a/src/helpers.php b/src/helpers.php index 0c318fc..d08b01c 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -1,9 +1,10 @@ $exception]), + 'Mahak Authentication Error', + ['response' => $status] + ); + } + + wp_die(view($fallbackView), 'Authentication Error', ['response' => $status]); + } +} + if (!function_exists('dd')) { function dd(): void { diff --git a/src/resources/views/debug-error.php b/src/resources/views/debug-error.php new file mode 100644 index 0000000..36d5275 --- /dev/null +++ b/src/resources/views/debug-error.php @@ -0,0 +1,31 @@ + + + + + + Mahak Authentication Error + + + +
+

Mahak Authentication Error

+
+
Exception
+
+
Message
+
getMessage()) ?>
+
Location
+
getFile().':'.$exception->getLine()) ?>
+
+

Stack trace

+
getTraceAsString()) ?>
+
+ + diff --git a/src/resources/views/server-error.php b/src/resources/views/server-error.php index 31ce4f6..50deb0b 100644 --- a/src/resources/views/server-error.php +++ b/src/resources/views/server-error.php @@ -1,5 +1,5 @@ - + @@ -11,4 +11,4 @@

مشکلی پیش آمده، لطفا تا دقایق دیگر دوباره امتحان کنید.

بازگشت به صفحه اصلی - \ No newline at end of file + diff --git a/test/integration-authentication.php b/test/integration-authentication.php index 0bb81a3..45a612a 100644 --- a/test/integration-authentication.php +++ b/test/integration-authentication.php @@ -246,8 +246,36 @@ function test_invalid_authentication(): void !header_contains($response['headers'], 'Set-Cookie: wordpress_logged_in_'), 'Invalid authentication request issued a logged-in cookie.' ); + assert_true( + strpos($response['body'], 'JWT signature is invalid.') === false, + 'Debug details were exposed while debug mode was disabled.' + ); - pass('Invalid JWT is rejected without login cookies'); + pass('Invalid JWT is rejected without login cookies or debug details'); +} + +function test_debug_error_details(): void +{ + update_option('mahak_debug_mode', 1); + + $response = http_request(TEST_SITE_URL.'/mahak/login/?token=not-a-jwt'); + + assert_true($response['status'] === 500, 'Debug error response should return HTTP 500.'); + assert_true( + strpos($response['body'], 'JWT must contain header, payload, and signature.') !== false, + 'Debug response did not contain the underlying exception message.' + ); + assert_true( + strpos($response['body'], 'UnexpectedValueException') !== false, + 'Debug response did not contain the exception class.' + ); + assert_true( + strpos($response['body'], 'Stack trace') !== false, + 'Debug response did not contain a stack trace.' + ); + + update_option('mahak_debug_mode', 0); + pass('Debug mode displays full exception details and stack trace'); } boot_wordpress(); @@ -256,5 +284,6 @@ activate_and_configure_plugin(); wait_for_http(); test_valid_authentication(); test_invalid_authentication(); +test_debug_error_details(); echo "[OK] Authentication integration test passed\n";