From d56903cbbe7f07091827fb42ec8a73054dffec83 Mon Sep 17 00:00:00 2001 From: Deepti-yb Date: Tue, 6 Aug 2024 08:37:15 +0000 Subject: [PATCH] [PLAT-14850]API Token authentication loops through the users and checks token against each of these. Summary: API calls are taking longer than 30 seconds per request because we iterate through the list of Users to authenticate using the API token due to the block of ``` List usersList = find.query().where().isNotNull("apiToken").findList(); for (Users user : usersList) { if (Users.hasher.isValid(apiToken, user.getApiToken())) { return user; } } ``` This was introduced in https://phorge.dev.yugabyte.com/D35345 to store hash of the API Tokens in the YBA DB. The proposal to speed up the look up is to return `userUUID$apiToken` as the new API token that can be used to extract the user and verify the request token with the hashed token from the DB. The older look up is still supported, and clients wishing to increase performance can regenerate the API token to use the new workflow Test Plan: Tested with API calls, UI, CLI and Terraform for various auth methods that involve API tokens Works with both the older API tokens generated by the customer before this change and the new format of the API tokens Reviewers: #yba-api-review, sneelakantan, sanketh, nbhatia, amalyshev Reviewed By: #yba-api-review, sneelakantan, amalyshev Subscribers: yugaware Differential Revision: https://phorge.dev.yugabyte.com/D37083 --- .../java/com/yugabyte/yw/models/Users.java | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/managed/src/main/java/com/yugabyte/yw/models/Users.java b/managed/src/main/java/com/yugabyte/yw/models/Users.java index f2d4db6f2a8f..94e7f0045632 100644 --- a/managed/src/main/java/com/yugabyte/yw/models/Users.java +++ b/managed/src/main/java/com/yugabyte/yw/models/Users.java @@ -406,6 +406,7 @@ public String upsertApiToken() { } public String upsertApiToken(Long version) { + String apiTokenFormatVersion = "2"; UUID uuidToLock = uuid != null ? uuid : NULL_UUID; usersLock.acquireLock(uuidToLock); try { @@ -420,7 +421,9 @@ public String upsertApiToken(Long version) { apiTokenVersion = apiTokenVersion == null ? 1L : apiTokenVersion + 1; save(); - return apiTokenUnhashed; + // new format of api token = apiTokenFormatVersion$userUUID$apiTokenUnhashed + return apiTokenFormatVersion + "$" + uuid + "$" + apiTokenUnhashed; + } finally { usersLock.releaseLock(uuidToLock); } @@ -472,12 +475,41 @@ public static Users authWithApiToken(String apiToken) { return null; } + // Supporting the 2 formats of api token + // 1. apiTokenFormatVersion$userUUID$apiToken (newer format) + // 2. apiToken (older format) + + // The second format would lead to performance degradation in the case of more than 10 users + // Recommended to reissue the token (which will follow the first format) + try { - List usersList = find.query().where().isNotNull("apiToken").findList(); - for (Users user : usersList) { - if (Users.hasher.isValid(apiToken, user.getApiToken())) { - return user; + if (apiToken.contains("$")) { + // to authenticate new format of api token = apiTokenFormatVersion$userUUID$apiTokenUnhashed + String[] parts = apiToken.split("\\$"); + UUID userUUID = UUID.fromString(parts[1]); + String apiTokenUnhashed = parts[2]; + Users userWithToken = find.query().where().eq("uuid", userUUID).findOne(); + if (userWithToken != null) { + if (Users.hasher.isValid(apiTokenUnhashed, userWithToken.getApiToken())) { + return userWithToken; + } + } + } else { + // to authenticate old format of api token + LOG.warn("Using older API token format. Renew to improve performance."); + List usersList = find.query().where().isNotNull("apiToken").findList(); + long startTime = System.currentTimeMillis(); + for (Users user : usersList) { + if (Users.hasher.isValid(apiToken, user.getApiToken())) { + LOG.info( + "Authentication using API token. Completed time: {} ms", + System.currentTimeMillis() - startTime); + return user; + } } + LOG.info( + "Authentication using API token. Completed time: {} ms", + System.currentTimeMillis() - startTime); } return null; } catch (Exception e) {