77import java .security .NoSuchAlgorithmException ;
88import java .security .SecureRandom ;
99import java .util .Base64 ;
10+ import java .util .Date ;
1011import java .util .concurrent .CompletableFuture ;
1112
1213import com .contentstack .cms .models .OAuthConfig ;
@@ -31,6 +32,7 @@ public class OAuthHandler {
3132 private OkHttpClient httpClient ;
3233 private final OAuthConfig config ;
3334 private final Gson gson ;
35+ private final Object tokenLock = new Object ();
3436
3537 private String codeVerifier ;
3638 private String codeChallenge ;
@@ -118,7 +120,8 @@ public String authorize() {
118120 StringBuilder urlBuilder = new StringBuilder (baseUrl );
119121 urlBuilder .append ("?response_type=" ).append (config .getResponseType ())
120122 .append ("&client_id=" ).append (URLEncoder .encode (config .getClientId (), "UTF-8" ))
121- .append ("&redirect_uri=" ).append (URLEncoder .encode (config .getRedirectUri (), "UTF-8" ));
123+ .append ("&redirect_uri=" ).append (URLEncoder .encode (config .getRedirectUri (), "UTF-8" ))
124+ .append ("&app_id=" ).append (URLEncoder .encode (config .getAppId (), "UTF-8" ));
122125
123126 // Add scope if provided
124127 if (config .getScope () != null && !config .getScope ().trim ().isEmpty ()) {
@@ -145,11 +148,16 @@ public String authorize() {
145148 * @return Future containing the tokens
146149 */
147150 public CompletableFuture <OAuthTokens > exchangeCodeForToken (String code ) {
151+ if (code == null || code .trim ().isEmpty ()) {
152+ return CompletableFuture .failedFuture (new IllegalArgumentException ("Authorization code cannot be null or empty" ));
153+ }
154+
155+ System .out .println ("\n Exchanging authorization code for tokens..." );
148156 return CompletableFuture .supplyAsync (() -> {
149157 try {
150158 FormBody .Builder formBuilder = new FormBody .Builder ()
151159 .add ("grant_type" , "authorization_code" )
152- .add ("code" , code )
160+ .add ("code" , code . trim () )
153161 .add ("redirect_uri" , config .getRedirectUri ())
154162 .add ("client_id" , config .getClientId ())
155163 .add ("app_id" , config .getAppId ());
@@ -178,26 +186,49 @@ public CompletableFuture<OAuthTokens> exchangeCodeForToken(String code) {
178186 * @param tokens The tokens to save
179187 */
180188 private void _saveTokens (OAuthTokens tokens ) {
181- this .tokens = tokens ;
189+ synchronized (tokenLock ) {
190+ this .tokens = tokens ;
191+ }
192+ }
193+
194+ private OAuthTokens _getTokens () {
195+ synchronized (tokenLock ) {
196+ return this .tokens ;
197+ }
182198 }
183199
184200 /**
185201 * Refreshes the access token using the refresh token
186202 * @return Future containing the new tokens
187203 */
188204 public CompletableFuture <OAuthTokens > refreshAccessToken () {
189- if (tokens == null || !tokens .hasRefreshToken ()) {
205+ // Check if we have tokens and refresh token
206+ if (tokens == null ) {
207+ return CompletableFuture .failedFuture (
208+ new IllegalStateException ("No tokens available" ));
209+ }
210+ if (!tokens .hasRefreshToken ()) {
190211 return CompletableFuture .failedFuture (
191212 new IllegalStateException ("No refresh token available" ));
192213 }
214+
215+ // Check if token is actually expired
216+ if (!tokens .isExpired ()) {
217+ return CompletableFuture .completedFuture (tokens );
218+ }
193219
194220 return CompletableFuture .supplyAsync (() -> {
195221 try {
222+ System .out .println ("\n Refreshing access token..." );
223+ System .out .println ("Current token expired: " + tokens .isExpired ());
224+ System .out .println ("Has refresh token: " + tokens .hasRefreshToken ());
225+ System .out .println ("Time until expiry: " + tokens .getTimeUntilExpiration () + "ms" );
226+
196227 FormBody .Builder formBuilder = new FormBody .Builder ()
197- .add ("app_id" , config .getAppId ())
198228 .add ("grant_type" , "refresh_token" )
199229 .add ("refresh_token" , tokens .getRefreshToken ())
200- .add ("client_id" , config .getClientId ());
230+ .add ("client_id" , config .getClientId ())
231+ .add ("app_id" , config .getAppId ());
201232
202233 // Add client_secret if available, otherwise add code_verifier
203234 if (config .getClientSecret () != null && !config .getClientSecret ().trim ().isEmpty ()) {
@@ -206,13 +237,18 @@ public CompletableFuture<OAuthTokens> refreshAccessToken() {
206237 formBuilder .add ("code_verifier" , this .codeVerifier );
207238 }
208239
209- Request request = _getHeaders ()
240+ Request request = new Request . Builder ()
210241 .url (config .getTokenEndpoint ())
242+ .header ("Content-Type" , "application/x-www-form-urlencoded" )
211243 .post (formBuilder .build ())
212244 .build ();
213245
214- return executeTokenRequest (request );
246+ OAuthTokens newTokens = executeTokenRequest (request );
247+ System .out .println ("Token refresh successful!" );
248+ System .out .println ("New token expires in: " + newTokens .getExpiresIn () + " seconds" );
249+ return newTokens ;
215250 } catch (IOException | RuntimeException e ) {
251+ System .err .println ("Token refresh failed: " + e .getMessage ());
216252 throw new RuntimeException ("Failed to refresh tokens" , e );
217253 }
218254 });
@@ -258,8 +294,13 @@ private OAuthTokens executeTokenRequest(Request request) throws IOException {
258294
259295 OAuthTokens newTokens = gson .fromJson (body , OAuthTokens .class );
260296
261- // Keep old refresh token if new one not provided
262- if (this .tokens != null && newTokens .getRefreshToken () == null ) {
297+ // Set token expiry time
298+ if (newTokens .getExpiresIn () != null ) {
299+ newTokens .setExpiresAt (new Date (System .currentTimeMillis () + (newTokens .getExpiresIn () * 1000 )));
300+ }
301+
302+ // Keep refresh token if new one not provided
303+ if (newTokens .getRefreshToken () == null && this .tokens != null && this .tokens .hasRefreshToken ()) {
263304 newTokens .setRefreshToken (this .tokens .getRefreshToken ());
264305 }
265306
@@ -370,17 +411,37 @@ public CompletableFuture<Void> revokeOauthAppAuthorization() {
370411 }
371412
372413 // Convenience methods for token access
373- public String getAccessToken () { return tokens != null ? tokens .getAccessToken () : null ; }
374- public String getRefreshToken () { return tokens != null ? tokens .getRefreshToken () : null ; }
375- public String getOrganizationUID () { return tokens != null ? tokens .getOrganizationUid () : null ; }
376- public String getUserUID () { return tokens != null ? tokens .getUserUid () : null ; }
377- public Long getTokenExpiryTime () { return tokens != null ? tokens .getExpiresIn () : null ; }
414+ public String getAccessToken () {
415+ OAuthTokens t = _getTokens ();
416+ return t != null ? t .getAccessToken () : null ;
417+ }
418+
419+ public String getRefreshToken () {
420+ OAuthTokens t = _getTokens ();
421+ return t != null ? t .getRefreshToken () : null ;
422+ }
423+
424+ public String getOrganizationUID () {
425+ OAuthTokens t = _getTokens ();
426+ return t != null ? t .getOrganizationUid () : null ;
427+ }
428+
429+ public String getUserUID () {
430+ OAuthTokens t = _getTokens ();
431+ return t != null ? t .getUserUid () : null ;
432+ }
433+
434+ public Long getTokenExpiryTime () {
435+ OAuthTokens t = _getTokens ();
436+ return t != null ? t .getExpiresIn () : null ;
437+ }
378438
379439 /**
380440 * Checks if we have a valid access token
381441 * @return true if we have a non-expired access token
382442 */
383443 public boolean hasValidAccessToken () {
384- return tokens != null && tokens .hasAccessToken () && !tokens .isExpired ();
444+ OAuthTokens t = _getTokens ();
445+ return t != null && t .hasAccessToken () && !t .isExpired ();
385446 }
386447}
0 commit comments