From f93db75ad05d4f134f30ee5029c34ecc031b9ed5 Mon Sep 17 00:00:00 2001 From: vsolovei-smartling Date: Fri, 27 Feb 2026 15:06:30 +0100 Subject: [PATCH] feat: add authenticator invalidate (SHOP-231) --- .../api/v2/client/auth/Authenticator.java | 11 ++- .../api/v2/client/auth/AuthenticatorTest.java | 86 +++++++++++++++++++ 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/smartling-api-commons/src/main/java/com/smartling/api/v2/client/auth/Authenticator.java b/smartling-api-commons/src/main/java/com/smartling/api/v2/client/auth/Authenticator.java index 1f062f53..5d46bd1c 100644 --- a/smartling-api-commons/src/main/java/com/smartling/api/v2/client/auth/Authenticator.java +++ b/smartling-api-commons/src/main/java/com/smartling/api/v2/client/auth/Authenticator.java @@ -116,11 +116,18 @@ boolean isRefreshable() return refreshExpiresAt > clock.currentTimeMillis(); } + public synchronized void invalidate() + { + this.authentication = null; + this.expiresAt = -1; + this.refreshExpiresAt = -1; + } + private synchronized String getAccessTokenInternal() { this.authentication = api.authenticate(new AuthenticationRequest(userIdentifier, userSecret)); this.expiresAt = authentication.getExpiresIn() * 1000 + System.currentTimeMillis(); - this.refreshExpiresAt = authentication.getRefreshExpiresIn() * 100 + System.currentTimeMillis(); + this.refreshExpiresAt = authentication.getRefreshExpiresIn() * 1000 + System.currentTimeMillis(); return authentication.getAccessToken(); } @@ -128,7 +135,7 @@ private synchronized String refreshAccessToken() { this.authentication = api.refresh(new AuthenticationRefreshRequest(authentication.getRefreshToken())); this.expiresAt = authentication.getExpiresIn() * 1000 + System.currentTimeMillis(); - this.refreshExpiresAt = authentication.getRefreshExpiresIn() * 100 + System.currentTimeMillis(); + this.refreshExpiresAt = authentication.getRefreshExpiresIn() * 1000 + System.currentTimeMillis(); return authentication.getAccessToken(); } } diff --git a/smartling-api-commons/src/test/java/com/smartling/api/v2/client/auth/AuthenticatorTest.java b/smartling-api-commons/src/test/java/com/smartling/api/v2/client/auth/AuthenticatorTest.java index 8c6b4449..e559c555 100644 --- a/smartling-api-commons/src/test/java/com/smartling/api/v2/client/auth/AuthenticatorTest.java +++ b/smartling-api-commons/src/test/java/com/smartling/api/v2/client/auth/AuthenticatorTest.java @@ -141,4 +141,90 @@ public void isRefreshableExpired() when(clock.currentTimeMillis()).thenReturn(REFRESH_TOKEN_TTL * 1000 + System.currentTimeMillis()); assertFalse(authenticator.isRefreshable()); } + + @Test + public void invalidateClearsToken() + { + when(clock.currentTimeMillis()).thenReturn(System.currentTimeMillis()); + authenticator.getAccessToken(); + assertTrue(authenticator.isValid()); + + authenticator.invalidate(); + + assertFalse(authenticator.isValid()); + assertFalse(authenticator.isRefreshable()); + } + + @Test + public void getAccessTokenAfterInvalidateReAuthenticates() + { + when(clock.currentTimeMillis()).thenReturn(System.currentTimeMillis()); + authenticator.getAccessToken(); + authenticator.invalidate(); + authenticator.getAccessToken(); + + verify(authenticationApi, times(2)).authenticate(any(AuthenticationRequest.class)); + verify(authenticationApi, never()).refresh(any(AuthenticationRefreshRequest.class)); + } + + @Test + public void getAccessTokenRefreshesAtExactExpiryBoundary() + { + Authentication shortLivedAuth = new Authentication("accessToken", "refreshToken", 480, 21600, "bearer"); + Authentication refreshedAuth = new Authentication("newAccessToken", "newRefreshToken", 480, 21600, "bearer"); + + when(authenticationApi.authenticate(any(AuthenticationRequest.class))).thenReturn(shortLivedAuth); + when(authenticationApi.refresh(any(AuthenticationRefreshRequest.class))).thenReturn(refreshedAuth); + + authenticator.getAccessToken(); + + when(clock.currentTimeMillis()).thenReturn(System.currentTimeMillis() + 480_000L); + + String token = authenticator.getAccessToken(); + + assertEquals(refreshedAuth.getAccessToken(), token); + verify(authenticationApi, times(1)).authenticate(any(AuthenticationRequest.class)); + verify(authenticationApi, times(1)).refresh(any(AuthenticationRefreshRequest.class)); + } + + @Test + public void getAccessTokenRefreshesJustBeforeRefreshTokenExpiry() + { + Authentication shortLivedAuth = new Authentication("accessToken", "refreshToken", 480, 21600, "bearer"); + Authentication refreshedAuth = new Authentication("newAccessToken", "newRefreshToken", 480, 21600, "bearer"); + + when(authenticationApi.authenticate(any(AuthenticationRequest.class))).thenReturn(shortLivedAuth); + when(authenticationApi.refresh(any(AuthenticationRefreshRequest.class))).thenReturn(refreshedAuth); + + authenticator.getAccessToken(); + + // Access token expired, refresh token still valid + when(clock.currentTimeMillis()).thenReturn(System.currentTimeMillis() + 21_510_000L); + + String token = authenticator.getAccessToken(); + + assertEquals(refreshedAuth.getAccessToken(), token); + verify(authenticationApi, times(1)).authenticate(any(AuthenticationRequest.class)); + verify(authenticationApi, times(1)).refresh(any(AuthenticationRefreshRequest.class)); + } + + @Test + public void getAccessTokenRefreshesWhenAccessTokenExpiredButRefreshTokenValid() + { + Authentication shortLivedAuth = new Authentication("accessToken", "refreshToken", 480, 21600, "bearer"); + Authentication refreshedAuth = new Authentication("newAccessToken", "newRefreshToken", 480, 21600, "bearer"); + + when(authenticationApi.authenticate(any(AuthenticationRequest.class))).thenReturn(shortLivedAuth); + when(authenticationApi.refresh(any(AuthenticationRefreshRequest.class))).thenReturn(refreshedAuth); + + authenticator.getAccessToken(); + + when(clock.currentTimeMillis()).thenReturn(System.currentTimeMillis() + 600_000L); + + String token = authenticator.getAccessToken(); + + assertEquals(refreshedAuth.getAccessToken(), token); + verify(authenticationApi, times(1)).authenticate(any(AuthenticationRequest.class)); + verify(authenticationApi, times(1)).refresh(any(AuthenticationRefreshRequest.class)); + } }