Skip to content

Commit b29a4a5

Browse files
authored
Fix jwt expiration
1 parent ca4fda5 commit b29a4a5

2 files changed

Lines changed: 66 additions & 18 deletions

File tree

src/main/java/com/translated/lara/authentication/AuthToken.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
package com.translated.lara.authentication;
22

3+
import com.google.gson.JsonObject;
4+
import com.google.gson.JsonParser;
5+
import java.nio.charset.StandardCharsets;
6+
import java.util.Base64;
7+
38
public class AuthToken {
49
private final String token;
510
private final String refreshToken;
11+
private final long expiresAtMs;
612

713
public AuthToken(String token, String refreshToken) {
814
this.token = token;
915
this.refreshToken = refreshToken;
16+
this.expiresAtMs = parseExpiresAtMs(token);
1017
}
1118

1219
public String getToken() {
@@ -21,4 +28,23 @@ public String getRefreshToken() {
2128
public String toString() {
2229
return token;
2330
}
31+
32+
public boolean isTokenExpired() {
33+
return expiresAtMs <= System.currentTimeMillis() + 5000L;
34+
}
35+
36+
private static long parseExpiresAtMs(String token) {
37+
String[] parts = token.split("\\.");
38+
if (parts.length != 3)
39+
throw new IllegalArgumentException("Invalid JWT format");
40+
41+
byte[] decodedBytes = Base64.getUrlDecoder().decode(parts[1]);
42+
String decodedPayload = new String(decodedBytes, StandardCharsets.UTF_8);
43+
JsonObject jsonObject = JsonParser.parseString(decodedPayload).getAsJsonObject();
44+
45+
if (!jsonObject.has("exp") || !jsonObject.get("exp").isJsonPrimitive())
46+
throw new IllegalArgumentException("JWT missing 'exp' claim");
47+
48+
return jsonObject.get("exp").getAsLong() * 1000;
49+
}
2450
}

src/main/java/com/translated/lara/net/LaraClient.java

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,11 @@ private Stream<ClientResponse> requestLineStream(String method, String path, Req
135135
path = normalizePath(path);
136136
headers = prune(headers);
137137

138-
HttpURLConnection connection = connect(baseUrl + path);
138+
if (this.authToken == null || this.authToken.isTokenExpired()) {
139+
this.refreshOrReauthenticate();
140+
}
139141

140-
if (this.authToken == null) authToken = this.authenticate();
142+
HttpURLConnection connection = connect(baseUrl + path);
141143

142144
try {
143145
if (connectionTimeout > 0) connection.setConnectTimeout(connectionTimeout);
@@ -181,8 +183,10 @@ private Stream<ClientResponse> requestLineStream(String method, String path, Req
181183
boolean isSuccessful = responseCode >= 200 && responseCode < 300;
182184
if (!isSuccessful) {
183185
String errorBody = readErrorStream(connection);
186+
connection.disconnect();
187+
184188
if (responseCode == 401 && !isRetry && errorBody != null && errorBody.contains("jwt expired")) {
185-
this.refreshToken();
189+
this.refreshOrReauthenticate();
186190
return requestLineStream(method, path, body, headers, true);
187191
}
188192
if (errorBody != null) {
@@ -209,6 +213,7 @@ private Stream<ClientResponse> requestLineStream(String method, String path, Req
209213
});
210214

211215
} catch (IOException e) {
216+
connection.disconnect();
212217
throw new LaraApiConnectionException("Streaming request failed: " + e.getMessage(), e);
213218
}
214219
}
@@ -225,9 +230,11 @@ private ClientResponse request(String method, String path, RequestBody body, Map
225230
path = normalizePath(path);
226231
headers = prune(headers);
227232

228-
HttpURLConnection connection = connect(baseUrl + path);
233+
if (this.authToken == null || this.authToken.isTokenExpired()) {
234+
this.refreshOrReauthenticate();
235+
}
229236

230-
if (this.authToken == null) authToken = this.authenticate();
237+
HttpURLConnection connection = connect(baseUrl + path);
231238

232239
try {
233240
if (connectionTimeout > 0) connection.setConnectTimeout(connectionTimeout);
@@ -263,9 +270,8 @@ private ClientResponse request(String method, String path, RequestBody body, Map
263270
if (responseCode == 401) {
264271
String errorBody = readErrorStream(connection);
265272

266-
// TODO: improve check for expired token
267273
if (errorBody != null && errorBody.contains("jwt expired") && !isRetry) {
268-
this.refreshToken();
274+
this.refreshOrReauthenticate();
269275
return this.request(method, path, body, headers, true);
270276
}
271277
}
@@ -287,9 +293,11 @@ private InputStream requestStream(String method, String path, RequestBody body,
287293
path = normalizePath(path);
288294
headers = prune(headers);
289295

290-
HttpURLConnection connection = connect(baseUrl + path);
296+
if (this.authToken == null || this.authToken.isTokenExpired()) {
297+
this.refreshOrReauthenticate();
298+
}
291299

292-
if (this.authToken == null) authToken = this.authenticate();
300+
HttpURLConnection connection = connect(baseUrl + path);
293301

294302
try {
295303
if (connectionTimeout > 0) connection.setConnectTimeout(connectionTimeout);
@@ -333,8 +341,10 @@ private InputStream requestStream(String method, String path, RequestBody body,
333341
boolean isSuccessful = responseCode >= 200 && responseCode < 300;
334342
if (!isSuccessful) {
335343
String errorBody = readErrorStream(connection);
344+
connection.disconnect();
345+
336346
if (responseCode == 401 && !isRetry && errorBody != null && errorBody.contains("jwt expired")) {
337-
this.refreshToken();
347+
this.refreshOrReauthenticate();
338348
return requestStream(method, path, body, headers, true);
339349
}
340350
if (errorBody != null) {
@@ -343,19 +353,32 @@ private InputStream requestStream(String method, String path, RequestBody body,
343353
throw new LaraApiConnectionException("HTTP error code: " + responseCode);
344354
}
345355

346-
// String contentType = connection.getContentType();
347356
return connection.getInputStream();
348-
// connection.disconnect();
349357
} catch (IOException e) {
358+
connection.disconnect();
350359
throw new LaraApiConnectionException("Streaming request failed: " + e.getMessage(), e);
351360
}
352361
}
353362

354-
private AuthToken authenticate() throws LaraException {
355-
if (this.authToken != null) return this.authToken;
356-
if (this.accessKey != null) return this.authenticate(this.accessKey);
357-
throw new Error("No authentication method available");
363+
private void refreshOrReauthenticate() throws LaraException {
364+
if (this.authToken != null && this.authToken.getRefreshToken() != null
365+
&& !this.authToken.getRefreshToken().isEmpty()) {
366+
try {
367+
this.refreshToken();
368+
return;
369+
} catch (LaraException e) {
370+
if (this.accessKey == null) throw e;
371+
}
372+
}
373+
374+
if (this.accessKey != null) {
375+
this.authToken = this.authenticate(this.accessKey);
376+
return;
377+
}
378+
379+
throw new LaraApiConnectionException("No authentication method available for token renewal");
358380
}
381+
359382
private AuthToken authenticate(AccessKey accessKey) throws LaraException {
360383
String path = "/v2/auth";
361384
String method = "POST";
@@ -406,7 +429,6 @@ private AuthToken authenticate(AccessKey accessKey) throws LaraException {
406429
private void refreshToken() throws LaraException {
407430
HttpURLConnection connection = connect(baseUrl + "/v2/auth/refresh");
408431

409-
410432
connection.setUseCaches(false);
411433
connection.setRequestProperty("Date", date());
412434
connection.setRequestProperty("X-Lara-SDK-Name", "lara-java");
@@ -425,7 +447,7 @@ private void refreshToken() throws LaraException {
425447

426448
String refreshToken = connection.getHeaderField("x-lara-refresh-token");
427449
if (refreshToken == null || refreshToken.isEmpty()) {
428-
throw new LaraApiConnectionException("Missing refresh token in authentication response");
450+
throw new LaraApiConnectionException("Missing refresh token in refresh response");
429451
}
430452
this.authToken = new AuthToken(authResponse.getToken(), refreshToken);
431453
} catch (IOException e) {

0 commit comments

Comments
 (0)