Skip to content

Commit f7ae51f

Browse files
committed
chore: Release version 1.8.0 with OAuth 2.0 support and code improvements
1 parent f688b3d commit f7ae51f

File tree

7 files changed

+624
-221
lines changed

7 files changed

+624
-221
lines changed

changelog.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## v1.8.0
4+
5+
### Sep 15, 2025
6+
7+
- Feature : OAuth 2.0 support with PKCE flow
8+
- Improved code organization and removed redundant methods
9+
310
## v1.7.1
411

512
### Jul 21, 2025

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<artifactId>cms</artifactId>
88
<packaging>jar</packaging>
99
<name>contentstack-management-java</name>
10-
<version>1.7.1-SNAPSHOT</version>
10+
<version>1.8.0</version>
1111
<description>Contentstack Java Management SDK for Content Management API, Contentstack is a headless CMS with an
1212
API-first approach
1313
</description>

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

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
@Getter
1515
@Builder
1616
public class OAuthConfig {
17+
1718
private final String appId;
1819
private final String clientId;
1920
private final String clientSecret;
@@ -25,7 +26,9 @@ public class OAuthConfig {
2526

2627
/**
2728
* Validates the configuration
28-
* @throws IllegalArgumentException if required fields are missing or invalid
29+
*
30+
* @throws IllegalArgumentException if required fields are missing or
31+
* invalid
2932
*/
3033
public void validate() {
3134
if (appId == null || appId.trim().isEmpty()) {
@@ -47,6 +50,7 @@ public void validate() {
4750

4851
/**
4952
* Checks if PKCE flow should be used (when clientSecret is not provided)
53+
*
5054
* @return true if PKCE should be used
5155
*/
5256
public boolean isPkceEnabled() {
@@ -55,6 +59,7 @@ public boolean isPkceEnabled() {
5559

5660
/**
5761
* Gets the formatted authorization endpoint URL
62+
*
5863
* @return The authorization endpoint URL
5964
*/
6065
public String getFormattedAuthorizationEndpoint() {
@@ -63,19 +68,20 @@ public String getFormattedAuthorizationEndpoint() {
6368
}
6469

6570
String hostname = "app.contentstack.com";
66-
71+
6772
// Transform hostname if needed
6873
if (hostname.contains("contentstack")) {
6974
hostname = hostname
70-
.replaceAll("^api\\.", "app.") // api.contentstack -> app.contentstack
71-
.replaceAll("\\.io$", ".com"); // *.io -> *.com
75+
.replaceAll("^api\\.", "app.") // api.contentstack -> app.contentstack
76+
.replaceAll("\\.io$", ".com"); // *.io -> *.com
7277
}
73-
78+
7479
return "https://" + hostname + "/#!/apps/" + appId + "/authorize";
7580
}
7681

7782
/**
7883
* Gets the formatted token endpoint URL
84+
*
7985
* @return The token endpoint URL
8086
*/
8187
public String getTokenEndpoint() {
@@ -84,19 +90,20 @@ public String getTokenEndpoint() {
8490
}
8591

8692
String hostname = "developerhub-api.contentstack.com";
87-
93+
8894
// Transform hostname if needed
8995
if (hostname.contains("contentstack")) {
9096
hostname = hostname
91-
.replaceAll("^dev\\d+\\.", "dev.") // dev1.* -> dev.*
92-
.replaceAll("\\.io$", ".com"); // *.io -> *.com
97+
.replaceAll("^dev\\d+\\.", "dev.") // dev1.* -> dev.*
98+
.replaceAll("\\.io$", ".com"); // *.io -> *.com
9399
}
94-
100+
95101
return "https://" + hostname + "/token";
96102
}
97103

98104
/**
99105
* Gets the response type, defaulting to "code"
106+
*
100107
* @return The response type
101108
*/
102109
public String getResponseType() {
@@ -105,6 +112,7 @@ public String getResponseType() {
105112

106113
/**
107114
* Gets the scopes as a list
115+
*
108116
* @return List of scope strings or empty list if no scopes
109117
*/
110118
public List<String> getScopesList() {
@@ -116,6 +124,7 @@ public List<String> getScopesList() {
116124

117125
/**
118126
* Gets the scope string
127+
*
119128
* @return The space-delimited scope string or null
120129
*/
121130
public String getScope() {
@@ -126,8 +135,10 @@ public String getScope() {
126135
* Builder class for OAuthConfig
127136
*/
128137
public static class OAuthConfigBuilder {
138+
129139
/**
130140
* Sets scopes from a list
141+
*
131142
* @param scopes List of scope strings
132143
* @return Builder instance
133144
*/
@@ -142,6 +153,7 @@ public OAuthConfigBuilder scopes(List<String> scopes) {
142153

143154
/**
144155
* Sets scopes from varargs
156+
*
145157
* @param scopes Scope strings
146158
* @return Builder instance
147159
*/
@@ -154,4 +166,4 @@ public OAuthConfigBuilder scopes(String... scopes) {
154166
return this;
155167
}
156168
}
157-
}
169+
}

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

Lines changed: 29 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
@Getter
1515
@Setter
1616
public class OAuthTokens {
17+
1718
private static final String BEARER_TOKEN_TYPE = "Bearer";
1819
@SerializedName("access_token")
1920
private String accessToken;
@@ -50,6 +51,7 @@ public OAuthTokens() {
5051

5152
/**
5253
* Sets the expiration time in seconds and calculates the expiry date
54+
*
5355
* @param expiresIn Expiration time in seconds
5456
*/
5557
public void setExpiresIn(Long expiresIn) {
@@ -76,6 +78,7 @@ public synchronized Date getIssuedAt() {
7678

7779
/**
7880
* Gets the scopes as a list
81+
*
7982
* @return List of scope strings or empty list if no scopes
8083
*/
8184
public List<String> getScopesList() {
@@ -87,6 +90,7 @@ public List<String> getScopesList() {
8790

8891
/**
8992
* Sets scopes from a list
93+
*
9094
* @param scopes List of scope strings
9195
*/
9296
public void setScopesList(List<String> scopes) {
@@ -99,6 +103,7 @@ public void setScopesList(List<String> scopes) {
99103

100104
/**
101105
* Checks if the token has a specific scope
106+
*
102107
* @param scopeToCheck The scope to check for
103108
* @return true if the token has the scope
104109
*/
@@ -108,39 +113,42 @@ public boolean hasScope(String scopeToCheck) {
108113

109114
/**
110115
* Checks if the token is expired, including a buffer time
116+
*
111117
* @return true if token is expired or will expire soon
112118
*/
113119
public boolean isExpired() {
114120
// No expiry time means token is considered expired
115121
if (expiresAt == null) {
116122
return true;
117123
}
118-
124+
119125
// No access token means token is considered expired
120126
if (!hasAccessToken()) {
121127
return true;
122128
}
123-
129+
124130
// Check if current time + buffer is past expiry
125131
long currentTime = System.currentTimeMillis();
126132
long expiryTime = expiresAt.getTime();
127133
long timeUntilExpiry = expiryTime - currentTime;
128-
134+
129135
// Consider expired if within buffer window
130136
return timeUntilExpiry <= EXPIRY_BUFFER_MS;
131137
}
132138

133139
/**
134140
* Checks if the token is valid (has access token and not expired)
141+
*
135142
* @return true if token is valid
136143
*/
137144
public synchronized boolean isValid() {
138-
return hasAccessToken() && !isExpired() &&
139-
BEARER_TOKEN_TYPE.equalsIgnoreCase(tokenType);
145+
return hasAccessToken() && !isExpired()
146+
&& BEARER_TOKEN_TYPE.equalsIgnoreCase(tokenType);
140147
}
141148

142149
/**
143150
* Checks if access token is present
151+
*
144152
* @return true if access token exists
145153
*/
146154
public boolean hasAccessToken() {
@@ -149,6 +157,7 @@ public boolean hasAccessToken() {
149157

150158
/**
151159
* Checks if refresh token is present
160+
*
152161
* @return true if refresh token exists
153162
*/
154163
public boolean hasRefreshToken() {
@@ -157,6 +166,7 @@ public boolean hasRefreshToken() {
157166

158167
/**
159168
* Gets time until token expiration in milliseconds
169+
*
160170
* @return milliseconds until expiration or 0 if expired/invalid
161171
*/
162172
public long getTimeUntilExpiration() {
@@ -167,46 +177,20 @@ public long getTimeUntilExpiration() {
167177
return Math.max(0, timeLeft);
168178
}
169179

170-
/**
171-
* Creates a copy of this token object
172-
* @return A new OAuthTokens instance with copied values
173-
*/
174-
public OAuthTokens copy() {
175-
OAuthTokens copy = new OAuthTokens();
176-
copy.accessToken = this.accessToken;
177-
copy.refreshToken = this.refreshToken;
178-
copy.tokenType = this.tokenType;
179-
copy.expiresIn = this.expiresIn;
180-
copy.scope = this.scope;
181-
copy.organizationUid = this.organizationUid;
182-
copy.userUid = this.userUid;
183-
copy.stackApiKey = this.stackApiKey;
184-
copy.issuedAt = this.issuedAt != null ? new Date(this.issuedAt.getTime()) : null;
185-
copy.expiresAt = this.expiresAt != null ? new Date(this.expiresAt.getTime()) : null;
186-
return copy;
187-
}
188-
189-
/**
190-
* Gets the stack API key if available
191-
* @return The stack API key or null if not available
192-
*/
193-
public String getStackApiKey() {
194-
return stackApiKey != null && !stackApiKey.trim().isEmpty() ? stackApiKey : null;
195-
}
196180

197181
@Override
198182
public String toString() {
199-
return "OAuthTokens{" +
200-
"accessToken='" + (accessToken != null ? "[REDACTED]" : "null") + '\'' +
201-
", refreshToken='" + (refreshToken != null ? "[REDACTED]" : "null") + '\'' +
202-
", tokenType='" + tokenType + '\'' +
203-
", expiresIn=" + expiresIn +
204-
", scope='" + scope + '\'' +
205-
", organizationUid='" + organizationUid + '\'' +
206-
", userUid='" + userUid + '\'' +
207-
", stackApiKey='" + stackApiKey + '\'' +
208-
", issuedAt=" + issuedAt +
209-
", expiresAt=" + expiresAt +
210-
'}';
211-
}
212-
}
183+
return "OAuthTokens{"
184+
+ "accessToken='" + (accessToken != null ? "[REDACTED]" : "null") + '\''
185+
+ ", refreshToken='" + (refreshToken != null ? "[REDACTED]" : "null") + '\''
186+
+ ", tokenType='" + tokenType + '\''
187+
+ ", expiresIn=" + expiresIn
188+
+ ", scope='" + scope + '\''
189+
+ ", organizationUid='" + organizationUid + '\''
190+
+ ", userUid='" + userUid + '\''
191+
+ ", stackApiKey='" + stackApiKey + '\''
192+
+ ", issuedAt=" + issuedAt
193+
+ ", expiresAt=" + expiresAt
194+
+ '}';
195+
}
196+
}

0 commit comments

Comments
 (0)