Skip to content

Commit 2644266

Browse files
committed
fix: Update OAuth implementation 9
1 parent a02f161 commit 2644266

File tree

5 files changed

+90
-29
lines changed

5 files changed

+90
-29
lines changed

src/main/java/com/contentstack/cms/Contentstack.java

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,15 @@ public Organization organization() {
295295
if (!isOAuthConfigured() && this.authtoken == null) {
296296
throw new IllegalStateException("Please login or configure OAuth to access organization");
297297
}
298+
299+
// If using OAuth, get organization from tokens
300+
if (isOAuthConfigured() && oauthHandler.getTokens() != null) {
301+
String orgUid = oauthHandler.getTokens().getOrganizationUid();
302+
if (orgUid != null && !orgUid.isEmpty()) {
303+
return organization(orgUid);
304+
}
305+
}
306+
298307
return new Organization(this.instance);
299308
}
300309

@@ -810,13 +819,15 @@ private OkHttpClient httpClient(Contentstack contentstack, Boolean retryOnFailur
810819

811820
// Add either OAuth or traditional auth interceptor
812821
if (this.oauthConfig != null) {
813-
if (this.oauthInterceptor == null) {
814-
this.oauthHandler = new OAuthHandler(builder.build(), this.oauthConfig);
815-
this.oauthInterceptor = new OAuthInterceptor(this.oauthHandler);
816-
if (this.earlyAccess != null) {
817-
this.oauthInterceptor.setEarlyAccess(this.earlyAccess);
818-
}
822+
// Create OAuth handler and interceptor first
823+
OkHttpClient tempClient = builder.build();
824+
this.oauthHandler = new OAuthHandler(tempClient, this.oauthConfig);
825+
this.oauthInterceptor = new OAuthInterceptor(this.oauthHandler);
826+
if (this.earlyAccess != null) {
827+
this.oauthInterceptor.setEarlyAccess(this.earlyAccess);
819828
}
829+
830+
// Add interceptor to final client
820831
builder.addInterceptor(this.oauthInterceptor);
821832
} else {
822833
this.authInterceptor = contentstack.interceptor = new AuthInterceptor();

src/main/java/com/contentstack/cms/models/OAuthConfig.java

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,11 @@ public String getFormattedAuthorizationEndpoint() {
6464

6565
String hostname = "app.contentstack.com";
6666

67-
if (hostname.endsWith("io")) {
68-
hostname = hostname.replace("io", "com");
69-
}
70-
if (hostname.startsWith("api")) {
71-
hostname = hostname.replace("api", "app");
67+
// Transform hostname if needed
68+
if (hostname.contains("contentstack")) {
69+
hostname = hostname
70+
.replaceAll("^api\\.", "app.") // api.contentstack -> app.contentstack
71+
.replaceAll("\\.io$", ".com"); // *.io -> *.com
7272
}
7373

7474
return "https://" + hostname + "/#!/apps/" + appId + "/authorize";
@@ -84,9 +84,13 @@ public String getTokenEndpoint() {
8484
}
8585

8686
String hostname = "developerhub-api.contentstack.com";
87-
hostname = hostname
88-
.replaceAll("^dev\\d+", "dev")
89-
.replace("io", "com");
87+
88+
// Transform hostname if needed
89+
if (hostname.contains("contentstack")) {
90+
hostname = hostname
91+
.replaceAll("^dev\\d+\\.", "dev.") // dev1.* -> dev.*
92+
.replaceAll("\\.io$", ".com"); // *.io -> *.com
93+
}
9094

9195
return "https://" + hostname + "/token";
9296
}

src/main/java/com/contentstack/cms/models/OAuthTokens.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ public class OAuthTokens {
3535
@SerializedName("user_uid")
3636
private String userUid;
3737

38+
@SerializedName("stack_api_key")
39+
private String stackApiKey;
40+
3841
private Date issuedAt;
3942
private Date expiresAt;
4043

@@ -147,11 +150,20 @@ public OAuthTokens copy() {
147150
copy.scope = this.scope;
148151
copy.organizationUid = this.organizationUid;
149152
copy.userUid = this.userUid;
153+
copy.stackApiKey = this.stackApiKey;
150154
copy.issuedAt = this.issuedAt != null ? new Date(this.issuedAt.getTime()) : null;
151155
copy.expiresAt = this.expiresAt != null ? new Date(this.expiresAt.getTime()) : null;
152156
return copy;
153157
}
154158

159+
/**
160+
* Gets the stack API key if available
161+
* @return The stack API key or null if not available
162+
*/
163+
public String getStackApiKey() {
164+
return stackApiKey != null && !stackApiKey.trim().isEmpty() ? stackApiKey : null;
165+
}
166+
155167
@Override
156168
public String toString() {
157169
return "OAuthTokens{" +
@@ -162,6 +174,7 @@ public String toString() {
162174
", scope='" + scope + '\'' +
163175
", organizationUid='" + organizationUid + '\'' +
164176
", userUid='" + userUid + '\'' +
177+
", stackApiKey='" + stackApiKey + '\'' +
165178
", issuedAt=" + issuedAt +
166179
", expiresAt=" + expiresAt +
167180
'}';

src/main/java/com/contentstack/cms/oauth/OAuthHandler.java

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,14 @@ public OAuthHandler(OkHttpClient httpClient, OAuthConfig config) {
6161

6262

6363
private Request.Builder _getHeaders() {
64-
return new Request.Builder()
65-
.header("Content-Type", "application/x-www-form-urlencoded")
66-
.header("authorization", "Bearer " + (tokens != null ? tokens.getAccessToken() : ""));
64+
Request.Builder builder = new Request.Builder()
65+
.header("Content-Type", "application/x-www-form-urlencoded");
66+
67+
// Only add authorization header for non-token endpoints
68+
if (tokens != null && tokens.getAccessToken() != null) {
69+
builder.header("authorization", "Bearer " + tokens.getAccessToken());
70+
}
71+
return builder;
6772
}
6873

6974
/**
@@ -194,8 +199,11 @@ public CompletableFuture<OAuthTokens> refreshAccessToken() {
194199
.add("refresh_token", tokens.getRefreshToken())
195200
.add("client_id", config.getClientId());
196201

197-
if (!config.isPkceEnabled()) {
202+
// Add client_secret if available, otherwise add code_verifier
203+
if (config.getClientSecret() != null && !config.getClientSecret().trim().isEmpty()) {
198204
formBuilder.add("client_secret", config.getClientSecret());
205+
} else if (this.codeVerifier != null) {
206+
formBuilder.add("code_verifier", this.codeVerifier);
199207
}
200208

201209
Request request = _getHeaders()
@@ -233,10 +241,13 @@ private OAuthTokens executeTokenRequest(Request request) throws IOException {
233241
String error = responseBody != null ? responseBody.string() : "Unknown error";
234242
System.err.println("Error Response Body: " + error);
235243

236-
// Parse error response if possible
244+
// Try to parse error as JSON for better error message
237245
try {
238-
throw new RuntimeException("Token request failed: " + error);
246+
com.contentstack.cms.models.Error errorObj = gson.fromJson(error, com.contentstack.cms.models.Error.class);
247+
throw new RuntimeException("Token request failed: " +
248+
(errorObj != null ? errorObj.getErrorMessage() : error));
239249
} catch (JsonParseException e) {
250+
// If not JSON, use raw error string
240251
throw new RuntimeException("Token request failed with status " +
241252
response.code() + ": " + error);
242253
}
@@ -364,4 +375,12 @@ public CompletableFuture<Void> revokeOauthAppAuthorization() {
364375
public String getOrganizationUID() { return tokens != null ? tokens.getOrganizationUid() : null; }
365376
public String getUserUID() { return tokens != null ? tokens.getUserUid() : null; }
366377
public Long getTokenExpiryTime() { return tokens != null ? tokens.getExpiresIn() : null; }
378+
379+
/**
380+
* Checks if we have a valid access token
381+
* @return true if we have a non-expired access token
382+
*/
383+
public boolean hasValidAccessToken() {
384+
return tokens != null && tokens.hasAccessToken() && !tokens.isExpired();
385+
}
367386
}

src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,21 +41,35 @@ public boolean hasValidTokens() {
4141
@Override
4242
public Response intercept(Chain chain) throws IOException {
4343
Request originalRequest = chain.request();
44+
45+
System.out.println("\nOAuth Interceptor - Request Details:");
46+
System.out.println("URL: " + originalRequest.url());
47+
System.out.println("Method: " + originalRequest.method());
48+
System.out.println("Has Tokens: " + (oauthHandler.getTokens() != null));
49+
if (oauthHandler.getTokens() != null) {
50+
System.out.println("Access Token: " + oauthHandler.getTokens().getAccessToken());
51+
System.out.println("Token Expired: " + oauthHandler.getTokens().isExpired());
52+
}
53+
4454
Request.Builder requestBuilder = originalRequest.newBuilder()
4555
.header("X-User-Agent", Util.defaultUserAgent())
4656
.header("User-Agent", Util.defaultUserAgent())
4757
.header("Content-Type", originalRequest.url().toString().contains("/token") ? "application/x-www-form-urlencoded" : "application/json")
4858
.header("X-Header-EA", earlyAccess != null ? String.join(",", earlyAccess) : "true");
49-
if (oauthHandler.getTokens() != null) {
50-
if (oauthHandler.getTokens().isExpired() && oauthHandler.getTokens().hasRefreshToken()) {
51-
try {
52-
oauthHandler.refreshAccessToken().get(30, TimeUnit.SECONDS);
53-
} catch (InterruptedException | ExecutionException | TimeoutException e) {
54-
throw new IOException("Failed to refresh access token", e);
59+
// Skip auth header for token endpoints
60+
if (!originalRequest.url().toString().contains("/token")) {
61+
if (oauthHandler.getTokens() != null) {
62+
if (oauthHandler.getTokens().isExpired() && oauthHandler.getTokens().hasRefreshToken()) {
63+
try {
64+
oauthHandler.refreshAccessToken().get(30, TimeUnit.SECONDS);
65+
} catch (InterruptedException | ExecutionException | TimeoutException e) {
66+
throw new IOException("Failed to refresh access token", e);
67+
}
68+
}
69+
if (oauthHandler.getTokens().hasAccessToken()) {
70+
requestBuilder.header("Authorization", "Bearer " + oauthHandler.getAccessToken());
71+
System.out.println("Added Authorization header: Bearer " + oauthHandler.getAccessToken());
5572
}
56-
}
57-
if (oauthHandler.getTokens().hasAccessToken()) {
58-
requestBuilder.header("Authorization", "Bearer " + oauthHandler.getAccessToken());
5973
}
6074
}
6175

0 commit comments

Comments
 (0)