From 63c88a463d8c582b36cb4c4df0776f5d7d09c183 Mon Sep 17 00:00:00 2001 From: Jeppe Bundgaard Date: Mon, 23 Feb 2026 22:44:06 +0100 Subject: [PATCH] Refactor passkey and token processing for improved consistency and validation - Update passkey data mapping to use associative arrays and handle JSON decoding for `transports`. - Refactor token caching logic to validate database existence and clear stale entries. - Improve 2FA handling by centralizing `token->type` and `user_id` processing for reuse. --- services/nginx/app/objects/tokens_o.php | 21 +++++++++++++---- services/nginx/app/routes/authRoute.php | 13 +++++++---- services/nginx/app/routes/passkeysRoute.php | 26 +++++++++++++-------- 3 files changed, 41 insertions(+), 19 deletions(-) diff --git a/services/nginx/app/objects/tokens_o.php b/services/nginx/app/objects/tokens_o.php index b31a05b7..cb3668cc 100644 --- a/services/nginx/app/objects/tokens_o.php +++ b/services/nginx/app/objects/tokens_o.php @@ -23,7 +23,15 @@ class tokens_o extends db public function objectChanged(): void { // Invalidate the cache - redis->clear_token($this->token->value()); + if ($this->id > 0) { + try { + $token = $this->token->value(); + redis->clear_token($token); + } catch (\Exception $e) { + // If the record is already gone or token cannot be retrieved, we can't clear by token string. + // This can happen during a hard delete where objectChanged is called after the record is deleted. + } + } } public function create(int $user_id, string $token, string $type = 'AUTH_TOKEN'): void @@ -58,9 +66,14 @@ class tokens_o extends db // Check if the token is cached $cached = redis->get_token($token); if ($cached) { - $this->id = $cached['id']; - $this->getObjectProperties(); - return $this; + $this->id = (int)$cached['id']; + // Verify that the token still exists in the database to avoid stale cache issues. + if ($this->exists()) { + $this->getObjectProperties(); + return $this; + } + // If it doesn't exist, clear the stale cache entry and proceed to check the database. + redis->clear_token($token); } // Avoid SQL injection $token = $db->escape_string($token); diff --git a/services/nginx/app/routes/authRoute.php b/services/nginx/app/routes/authRoute.php index 8caf8357..ef4d7d34 100644 --- a/services/nginx/app/routes/authRoute.php +++ b/services/nginx/app/routes/authRoute.php @@ -189,15 +189,18 @@ class authRoute } $auth = new authentication(); - if ($token->type->value() === '2FA_VERIFICATION_USER') { - $user = (new users_o())->getUserById($token->user_id->value()); + $token_type = $token->type->value(); + $user_id = (int)$token->user_id->value(); + + if ($token_type === '2FA_VERIFICATION_USER') { + $user = (new users_o())->getUserById($user_id); if ($user->exists() && $auth->verify_2fa_code($user, $code)) { $token_o->delete($token_str); - $new_token = $auth->create_employee_token($token->user_id->value()); // Works for both users and employees + $new_token = $auth->create_employee_token($user_id); // Works for both users and employees $response->success(['token' => $new_token]); } - } elseif ($token->type->value() === '2FA_VERIFICATION_SUBUSER') { - $subuser = (new subusers_o())->select($token->user_id->value()); + } elseif ($token_type === '2FA_VERIFICATION_SUBUSER') { + $subuser = (new subusers_o())->select($user_id); if ($auth->verify_2fa_code($subuser, $code)) { $token_o->delete($token_str); $new_token = $subuser->generateSession(); diff --git a/services/nginx/app/routes/passkeysRoute.php b/services/nginx/app/routes/passkeysRoute.php index 16000e3b..d4f34836 100644 --- a/services/nginx/app/routes/passkeysRoute.php +++ b/services/nginx/app/routes/passkeysRoute.php @@ -25,17 +25,23 @@ class passkeysRoute $passkeys = new passkeys_o(); // Restrict to current user (customer) and non-subuser records - $passkeys->setAdditionalWhereClause('WHERE user_id = ' . (int)$user->id . ' AND is_subuser = 0 AND deleted_at IS NULL'); - $list = $passkeys->listObjects(function ($o) { + $passkeys->setAdditionalWhereClause('`user_id` = ' . (int)$user->id . ' AND `is_subuser` = 0'); + $list = $passkeys->listObjectsWithPaginationIfSet(function ($o) { + // $o is an associative array from the database + $transports = null; + if (isset($o['transports'])) { + $decoded = json_decode($o['transports'], true); + $transports = is_array($decoded) ? $decoded : null; + } return [ - 'id' => $o->id, - 'credential_id' => $o->credential_id->value(), - 'name' => $o->name->value(), - 'algorithm' => $o->algorithm->value(), - 'transports' => $o->transports->value(), - 'sign_count' => $o->sign_count->value(), - 'created_at' => $o->created_at->value(), - 'updated_at' => $o->updated_at->value(), + 'id' => isset($o['id']) ? (int)$o['id'] : null, + 'credential_id' => $o['credential_id'] ?? null, + 'name' => $o['name'] ?? null, + 'algorithm' => $o['algorithm'] ?? null, + 'transports' => $transports, + 'sign_count' => isset($o['sign_count']) ? (int)$o['sign_count'] : null, + 'created_at' => $o['created_at'] ?? null, + 'updated_at' => $o['updated_at'] ?? null, ]; });