diff --git a/README.md b/README.md
index 6e16fca..d1eb0f2 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ Authenticate WordPress user via json web token from Laravel Application.
- 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.
+Debug mode is disabled by default. An administrator can enable it from **Settings > Mahak Authentication** while troubleshooting. The debug page includes server clock/timezone data, safe JWT timing claims, and a Copy Markdown report. JWT contents, secrets, request parameters, and stack-trace arguments are omitted. Because diagnostic pages still expose server paths and other technical details to visitors of the login URL, disable it again after diagnosing the problem.
## Updates
diff --git a/dabestaniha-authenticate-bridge.php b/dabestaniha-authenticate-bridge.php
index 7c45649..fdaf433 100644
--- a/dabestaniha-authenticate-bridge.php
+++ b/dabestaniha-authenticate-bridge.php
@@ -4,7 +4,7 @@
* Plugin Name: Mahak Authenticate Bridge
* Plugin URI: https://github.com/dabestaniha/mahak-authenticate-bridge
* Description: Authenticate WordPress user via json web token from Mahakiha Application.
- * Version: 2.2.0
+ * Version: 2.2.1
* Requires PHP: 7.4
* Update URI: https://github.com/dabestaniha/mahak-authenticate-bridge
* Author: Dabestaniha
@@ -18,7 +18,7 @@ use Dabestaniha\AuthenticateBridge\App\Http\Controllers\SettingsPageMenuControll
use Dabestaniha\AuthenticateBridge\App\Http\Controllers\SettingsPageFormController;
use Dabestaniha\AuthenticateBridge\App\Support\PluginUpdater;
-define('MAHAK_AUTHENTICATE_BRIDGE_VERSION', '2.2.0');
+define('MAHAK_AUTHENTICATE_BRIDGE_VERSION', '2.2.1');
define('MAHAK_AUTHENTICATE_BRIDGE_FILE', __FILE__);
require_once __DIR__.'/src/Autoloader.php';
diff --git a/src/app/Http/Controllers/AuthenticateController.php b/src/app/Http/Controllers/AuthenticateController.php
index c53d558..88b94ec 100644
--- a/src/app/Http/Controllers/AuthenticateController.php
+++ b/src/app/Http/Controllers/AuthenticateController.php
@@ -10,10 +10,16 @@ use UnexpectedValueException;
class AuthenticateController extends InvocableController
{
+ private array $debugContext = [];
+
public function __invoke(MahakAuthenticateRequest $request)
{
$token = $request->string('token');
+ if (mahak_debug_enabled()) {
+ $this->debugContext['jwt_timestamps'] = $this->inspectJwtTimestamps($token);
+ }
+
$payload = $this->decrypt_jwt_token($token);
$identifier_name = get_configured_option('user-identifier');
@@ -85,7 +91,36 @@ class AuthenticateController extends InvocableController
$status = $exception->getCode() === 403 ? 403 : 500;
$fallbackView = $exception instanceof UnexpectedValueException ? 'invalid-token' : 'server-error';
- mahak_render_error($exception, $status, $fallbackView);
+ mahak_render_error($exception, $status, $fallbackView, $this->debugContext);
+ }
+
+ private function inspectJwtTimestamps(string $token): array
+ {
+ $segments = explode('.', $token);
+
+ if (count($segments) !== 3) {
+ return [];
+ }
+
+ $encodedPayload = strtr($segments[1], '-_', '+/');
+ $encodedPayload .= str_repeat('=', (4 - strlen($encodedPayload) % 4) % 4);
+ $json = base64_decode($encodedPayload, true);
+ $payload = $json === false ? null : json_decode($json, true);
+
+ if (!is_array($payload)) {
+ return [];
+ }
+
+ $timestamps = [];
+
+ foreach (['iat', 'nbf', 'exp'] as $claim) {
+ if (array_key_exists($claim, $payload)) {
+ $value = is_scalar($payload[$claim]) ? (string) $payload[$claim] : '[non-scalar]';
+ $timestamps[$claim] = strlen($value) > 64 ? substr($value, 0, 64).'…' : $value;
+ }
+ }
+
+ return $timestamps;
}
diff --git a/src/helpers.php b/src/helpers.php
index d08b01c..14a96bc 100644
--- a/src/helpers.php
+++ b/src/helpers.php
@@ -20,11 +20,16 @@ if (!function_exists('mahak_debug_enabled')) {
}
if (!function_exists('mahak_render_error')) {
- function mahak_render_error(\Throwable $exception, int $status = 500, string $fallbackView = 'server-error'): void
+ function mahak_render_error(
+ \Throwable $exception,
+ int $status = 500,
+ string $fallbackView = 'server-error',
+ array $context = []
+ ): void
{
if (mahak_debug_enabled()) {
wp_die(
- view('debug-error', ['exception' => $exception]),
+ view('debug-error', ['report' => mahak_build_debug_report($exception, $context)]),
'Mahak Authentication Error',
['response' => $status]
);
@@ -34,6 +39,135 @@ if (!function_exists('mahak_render_error')) {
}
}
+if (!function_exists('mahak_build_debug_report')) {
+ function mahak_build_debug_report(\Throwable $exception, array $context = []): array
+ {
+ $now = time();
+ $wordpressTimezone = function_exists('wp_timezone_string')
+ ? wp_timezone_string()
+ : (string) get_option('timezone_string', '');
+
+ if ($wordpressTimezone === '') {
+ $wordpressTimezone = 'UTC offset '.(string) get_option('gmt_offset', 0);
+ }
+
+ $server = [
+ 'Unix timestamp' => (string) $now,
+ 'UTC time' => gmdate('Y-m-d H:i:s \U\T\C', $now),
+ 'PHP local time' => date('Y-m-d H:i:s P T', $now),
+ 'PHP timezone' => date_default_timezone_get(),
+ 'PHP date.timezone' => (string) (ini_get('date.timezone') ?: '[not set]'),
+ 'WordPress timezone' => $wordpressTimezone,
+ 'WordPress version' => get_bloginfo('version'),
+ 'PHP version' => PHP_VERSION,
+ 'Plugin version' => defined('MAHAK_AUTHENTICATE_BRIDGE_VERSION')
+ ? MAHAK_AUTHENTICATE_BRIDGE_VERSION
+ : '[unknown]',
+ ];
+
+ $jwtTimestamps = [];
+
+ foreach (($context['jwt_timestamps'] ?? []) as $claim => $value) {
+ $jwtTimestamps[strtoupper((string) $claim)] = mahak_format_jwt_timestamp((string) $value, $now);
+ }
+
+ $trace = mahak_sanitized_exception_trace($exception);
+ $exceptionData = [
+ 'Type' => get_class($exception),
+ 'Message' => $exception->getMessage(),
+ 'Location' => $exception->getFile().':'.$exception->getLine(),
+ ];
+
+ $markdown = "# Mahak Authentication Error\n\n## Exception\n\n";
+
+ foreach ($exceptionData as $label => $value) {
+ $markdown .= '- '.$label.': `'.mahak_markdown_value($value)."`\n";
+ }
+
+ $markdown .= "\n## WordPress server diagnostics\n\n| Field | Value |\n|---|---|\n";
+
+ foreach ($server as $label => $value) {
+ $markdown .= '| '.mahak_markdown_value($label).' | `'.mahak_markdown_value($value)."` |\n";
+ }
+
+ $markdown .= "\n## JWT timing claims\n\n";
+
+ if ($jwtTimestamps === []) {
+ $markdown .= "No readable JWT timing claims were available.\n";
+ } else {
+ $markdown .= "| Claim | Value |\n|---|---|\n";
+
+ foreach ($jwtTimestamps as $label => $value) {
+ $markdown .= '| '.mahak_markdown_value($label).' | `'.mahak_markdown_value($value)."` |\n";
+ }
+ }
+
+ $markdown .= "\n## Sanitized stack trace\n\n```text\n".str_replace('```', "'''", $trace)."\n```\n";
+ $markdown .= "\n> JWT contents, request parameters, and function arguments are intentionally omitted.\n";
+
+ return [
+ 'exception' => $exceptionData,
+ 'server' => $server,
+ 'jwt_timestamps' => $jwtTimestamps,
+ 'trace' => $trace,
+ 'markdown' => $markdown,
+ ];
+ }
+}
+
+if (!function_exists('mahak_format_jwt_timestamp')) {
+ function mahak_format_jwt_timestamp(string $value, int $serverNow): string
+ {
+ if (!is_numeric($value)) {
+ return $value.' (not numeric)';
+ }
+
+ $numericValue = (float) $value;
+ $note = '';
+
+ if (is_infinite($numericValue) || is_nan($numericValue)) {
+ return $value.' (invalid numeric value)';
+ }
+
+ if (abs($numericValue) >= 100000000000) {
+ $numericValue /= 1000;
+ $note = '; appears to use milliseconds';
+ }
+
+ $timestamp = (int) $numericValue;
+ $difference = $timestamp - $serverNow;
+ $differenceLabel = ($difference >= 0 ? '+' : '').$difference.' seconds vs server';
+
+ return $value.' ('.gmdate('Y-m-d H:i:s \U\T\C', $timestamp).'; '.$differenceLabel.$note.')';
+ }
+}
+
+if (!function_exists('mahak_sanitized_exception_trace')) {
+ function mahak_sanitized_exception_trace(\Throwable $exception): string
+ {
+ $lines = [];
+
+ foreach ($exception->getTrace() as $index => $frame) {
+ $location = isset($frame['file'])
+ ? $frame['file'].':'.($frame['line'] ?? '?')
+ : '[internal function]';
+ $call = ($frame['class'] ?? '').($frame['type'] ?? '').($frame['function'] ?? '[unknown]').'()';
+ $lines[] = '#'.$index.' '.$location.' '.$call;
+ }
+
+ $lines[] = '#'.count($lines).' {main}';
+
+ return implode("\n", $lines);
+ }
+}
+
+if (!function_exists('mahak_markdown_value')) {
+ function mahak_markdown_value(string $value): string
+ {
+ return str_replace(["\r", "\n", '|', '`'], [' ', ' ', '\\|', "'"], $value);
+ }
+}
+
if (!function_exists('dd')) {
function dd(): void
{
diff --git a/src/resources/views/debug-error.php b/src/resources/views/debug-error.php
index 36d5275..b2caf96 100644
--- a/src/resources/views/debug-error.php
+++ b/src/resources/views/debug-error.php
@@ -8,24 +8,83 @@
body { margin: 2rem; color: #1d2327; background: #f0f0f1; font: 14px/1.5 monospace; }
main { max-width: 1100px; margin: auto; padding: 2rem; background: #fff; border-left: 4px solid #d63638; box-shadow: 0 1px 3px rgba(0, 0, 0, .12); }
h1 { margin-top: 0; color: #d63638; font: 24px/1.3 sans-serif; }
- dt { margin-top: 1rem; font-weight: 700; }
- dd { margin: .25rem 0 0; overflow-wrap: anywhere; }
- pre { overflow: auto; padding: 1rem; color: #f0f0f1; background: #1d2327; white-space: pre-wrap; }
+ h2 { margin-top: 2rem; font: 20px/1.3 sans-serif; }
+ table { width: 100%; border-collapse: collapse; }
+ th, td { padding: .55rem; border: 1px solid #c3c4c7; text-align: left; vertical-align: top; overflow-wrap: anywhere; }
+ th { width: 220px; background: #f6f7f7; }
+ pre, textarea { box-sizing: border-box; width: 100%; padding: 1rem; color: #f0f0f1; background: #1d2327; white-space: pre-wrap; }
+ textarea { min-height: 240px; resize: vertical; }
+ button { padding: .55rem 1rem; border: 1px solid #2271b1; border-radius: 3px; color: #fff; background: #2271b1; cursor: pointer; }
+ #copy-status { margin-left: .75rem; font-family: sans-serif; }
+ .privacy-note { padding: .75rem; border-left: 4px solid #72aee6; background: #f0f6fc; font-family: sans-serif; }
Mahak Authentication Error
-
- - Exception
- - = esc_html(get_class($exception)) ?>
- - Message
- - = esc_html($exception->getMessage()) ?>
- - Location
- - = esc_html($exception->getFile().':'.$exception->getLine()) ?>
-
- Stack trace
- = esc_html($exception->getTraceAsString()) ?>
+
+ Exception
+
+ $value): ?>
+ | = esc_html($label) ?> | = esc_html($value) ?> |
+
+
+
+ WordPress server diagnostics
+
+ $value): ?>
+ | = esc_html($label) ?> | = esc_html($value) ?> |
+
+
+
+ JWT timing claims
+
+ No readable JWT timing claims were available.
+
+
+ $value): ?>
+ | = esc_html($label) ?> | = esc_html($value) ?> |
+
+
+
+
+ Sanitized stack trace
+ = esc_html($report['trace']) ?>
+ JWT contents, request parameters, and function arguments are intentionally omitted.
+
+ Copyable report
+
+
+