11package com .contentstack .cms ;
22
3+ import java .io .IOException ;
4+ import java .net .Proxy ;
5+ import java .time .Duration ;
6+ import java .util .HashMap ;
7+ import java .util .Map ;
8+ import java .util .Objects ;
9+ import java .util .concurrent .CompletableFuture ;
10+ import java .util .concurrent .TimeUnit ;
11+ import java .util .logging .Logger ;
12+
13+ import org .jetbrains .annotations .NotNull ;
14+
315import com .contentstack .cms .core .AuthInterceptor ;
416import com .contentstack .cms .core .Util ;
17+ import static com .contentstack .cms .core .Util .API_KEY ;
18+ import static com .contentstack .cms .core .Util .AUTHORIZATION ;
19+ import static com .contentstack .cms .core .Util .BRANCH ;
20+ import static com .contentstack .cms .core .Util .ILLEGAL_USER ;
21+ import static com .contentstack .cms .core .Util .PLEASE_LOGIN ;
522import com .contentstack .cms .models .Error ;
623import com .contentstack .cms .models .LoginDetails ;
24+ import com .contentstack .cms .models .OAuthConfig ;
25+ import com .contentstack .cms .models .OAuthTokens ;
26+ import com .contentstack .cms .oauth .OAuthHandler ;
27+ import com .contentstack .cms .oauth .OAuthInterceptor ;
728import com .contentstack .cms .organization .Organization ;
829import com .contentstack .cms .stack .Stack ;
930import com .contentstack .cms .user .User ;
1031import com .google .gson .Gson ;
32+
1133import okhttp3 .ConnectionPool ;
1234import okhttp3 .OkHttpClient ;
1335import okhttp3 .ResponseBody ;
1436import okhttp3 .logging .HttpLoggingInterceptor ;
15- import org .jetbrains .annotations .NotNull ;
1637import retrofit2 .Response ;
1738import retrofit2 .Retrofit ;
1839import retrofit2 .converter .gson .GsonConverterFactory ;
1940
20- import java .io .IOException ;
21- import java .net .Proxy ;
22- import java .time .Duration ;
23- import java .util .HashMap ;
24- import java .util .Map ;
25- import java .util .Objects ;
26- import java .util .concurrent .TimeUnit ;
27- import java .util .logging .Logger ;
28-
29- import static com .contentstack .cms .core .Util .*;
30-
3141/**
3242 * <b>Contentstack Java Management SDK</b>
3343 * <br>
4151 */
4252public class Contentstack {
4353
44- public final Logger logger = Logger .getLogger (Contentstack .class .getName ());
54+ public static final Logger logger = Logger .getLogger (Contentstack .class .getName ());
4555 protected final String host ;
4656 protected final String port ;
4757 protected final String version ;
@@ -51,6 +61,8 @@ public class Contentstack {
5161 protected final Boolean retryOnFailure ;
5262 protected final Proxy proxy ;
5363 protected AuthInterceptor interceptor ;
64+ protected OAuthInterceptor oauthInterceptor ;
65+ protected OAuthHandler oauthHandler ;
5466 protected String [] earlyAccess ;
5567 protected User user ;
5668
@@ -201,15 +213,20 @@ public Response<LoginDetails> login(String emailId, String password, String tfaT
201213
202214 private void setupLoginCredentials (Response <LoginDetails > loginResponse ) throws IOException {
203215 if (loginResponse .isSuccessful ()) {
204- assert loginResponse .body () != null ;
205- // logger.info(loginResponse.body().getNotice());
206- this .authtoken = loginResponse .body ().getUser ().getAuthtoken ();
207- this .interceptor .setAuthtoken (this .authtoken );
216+ LoginDetails loginDetails = loginResponse .body ();
217+ if (loginDetails != null && loginDetails .getUser () != null ) {
218+ this .authtoken = loginDetails .getUser ().getAuthtoken ();
219+ if (this .interceptor != null ) {
220+ this .interceptor .setAuthtoken (this .authtoken );
221+ }
222+ }
208223 } else {
209- assert loginResponse .errorBody () != null ;
210- String errorJsonString = loginResponse .errorBody ().string ();
211- logger .info (errorJsonString );
212- new Gson ().fromJson (errorJsonString , Error .class );
224+ ResponseBody errorBody = loginResponse .errorBody ();
225+ if (errorBody != null ) {
226+ String errorJsonString = errorBody .string ();
227+ logger .info (errorJsonString );
228+ new Gson ().fromJson (errorJsonString , Error .class );
229+ }
213230 }
214231 }
215232
@@ -435,10 +452,94 @@ public Stack stack(@NotNull String apiKey, @NotNull String managementToken, @Not
435452 }
436453
437454 /**
438- * Instantiates a new Contentstack.
439- *
440- * @param builder the builder
455+ * Get the OAuth authorization URL for the user to visit
456+ * @return Authorization URL string
457+ * @throws IllegalStateException if OAuth is not configured
458+ */
459+ public String getOAuthAuthorizationUrl () {
460+ if (!isOAuthConfigured ()) {
461+ throw new IllegalStateException ("OAuth is not configured. Use Builder.setOAuth() or Builder.setOAuthWithPKCE()" );
462+ }
463+ return oauthHandler .authorize ();
464+ }
465+
466+ /**
467+ * Exchange OAuth authorization code for tokens
468+ * @param code Authorization code from OAuth callback
469+ * @return CompletableFuture containing OAuth tokens
470+ * @throws IllegalStateException if OAuth is not configured
471+ */
472+ public CompletableFuture <OAuthTokens > exchangeOAuthCode (String code ) {
473+ if (!isOAuthConfigured ()) {
474+ throw new IllegalStateException ("OAuth is not configured. Use Builder.setOAuth() or Builder.setOAuthWithPKCE()" );
475+ }
476+ return oauthHandler .exchangeCodeForToken (code );
477+ }
478+
479+ /**
480+ * Refresh the OAuth access token
481+ * @return CompletableFuture containing new OAuth tokens
482+ * @throws IllegalStateException if OAuth is not configured or no refresh token available
483+ */
484+ public CompletableFuture <OAuthTokens > refreshOAuthToken () {
485+ if (!isOAuthConfigured ()) {
486+ throw new IllegalStateException ("OAuth is not configured. Use Builder.setOAuth() or Builder.setOAuthWithPKCE()" );
487+ }
488+ return oauthHandler .refreshAccessToken ();
489+ }
490+
491+ /**
492+ * Get the current OAuth tokens
493+ * @return Current OAuth tokens or null if not available
494+ */
495+ public OAuthTokens getOAuthTokens () {
496+ return oauthHandler != null ? oauthHandler .getTokens () : null ;
497+ }
498+
499+ /**
500+ * Check if we have valid OAuth tokens
501+ * @return true if we have valid tokens
502+ */
503+ public boolean hasValidOAuthTokens () {
504+ return oauthInterceptor != null && oauthInterceptor .hasValidTokens ();
505+ }
506+
507+ /**
508+ * Check if OAuth is configured
509+ * @return true if OAuth is configured
510+ */
511+ public boolean isOAuthConfigured () {
512+ return oauthInterceptor != null && oauthInterceptor .isOAuthConfigured ();
513+ }
514+
515+ /**
516+ * Get the OAuth handler instance
517+ * @return OAuth handler or null if not configured
518+ */
519+ public OAuthHandler getOAuthHandler () {
520+ return oauthHandler ;
521+ }
522+
523+ /**
524+ * Logout from OAuth session and optionally revoke authorization
525+ * @param revokeAuthorization If true, revokes the OAuth authorization
526+ * @return CompletableFuture that completes when logout is done
527+ */
528+ public CompletableFuture <Void > oauthLogout (boolean revokeAuthorization ) {
529+ if (!isOAuthConfigured ()) {
530+ throw new IllegalStateException ("OAuth is not configured. Use Builder.setOAuth() or Builder.setOAuthWithPKCE()" );
531+ }
532+ return oauthHandler .logout (revokeAuthorization );
533+ }
534+
535+ /**
536+ * Logout from OAuth session without revoking authorization
537+ * @return CompletableFuture that completes when logout is done
441538 */
539+ public CompletableFuture <Void > oauthLogout () {
540+ return oauthLogout (false );
541+ }
542+
442543 public Contentstack (Builder builder ) {
443544 this .host = builder .hostname ;
444545 this .port = builder .port ;
@@ -449,6 +550,8 @@ public Contentstack(Builder builder) {
449550 this .retryOnFailure = builder .retry ;
450551 this .proxy = builder .proxy ;
451552 this .interceptor = builder .authInterceptor ;
553+ this .oauthInterceptor = builder .oauthInterceptor ;
554+ this .oauthHandler = builder .oauthHandler ;
452555 this .earlyAccess = builder .earlyAccess ;
453556 }
454557
@@ -462,6 +565,9 @@ public static class Builder {
462565 */
463566 protected Proxy proxy ;
464567 private AuthInterceptor authInterceptor ;
568+ private OAuthInterceptor oauthInterceptor ;
569+ private OAuthConfig oauthConfig ;
570+ private OAuthHandler oauthHandler ;
465571
466572 private String authtoken ; // authtoken for client
467573 private String [] earlyAccess ;
@@ -608,6 +714,49 @@ public Builder setAuthtoken(String authtoken) {
608714 return this ;
609715 }
610716
717+ /**
718+ * Sets OAuth configuration for the client
719+ * @param config OAuth configuration
720+ * @return Builder instance
721+ */
722+ public Builder setOAuthConfig (OAuthConfig config ) {
723+ this .oauthConfig = config ;
724+ return this ;
725+ }
726+
727+ /**
728+ * Configures OAuth with client credentials (traditional flow)
729+ * @param appId Application ID
730+ * @param clientId Client ID
731+ * @param clientSecret Client secret
732+ * @param redirectUri Redirect URI
733+ * @return Builder instance
734+ */
735+ public Builder setOAuth (String appId , String clientId , String clientSecret , String redirectUri ) {
736+ this .oauthConfig = OAuthConfig .builder ()
737+ .appId (appId )
738+ .clientId (clientId )
739+ .clientSecret (clientSecret )
740+ .redirectUri (redirectUri )
741+ .build ();
742+ return this ;
743+ }
744+
745+ /**
746+ * Configures OAuth with PKCE (no client secret)
747+ * @param appId Application ID
748+ * @param clientId Client ID
749+ * @param redirectUri Redirect URI
750+ * @return Builder instance
751+ */
752+ public Builder setOAuthWithPKCE (String appId , String clientId , String redirectUri ) {
753+ this .oauthConfig = OAuthConfig .builder ()
754+ .appId (appId )
755+ .clientId (clientId )
756+ .redirectUri (redirectUri )
757+ .build ();
758+ return this ;
759+ }
611760
612761 public Builder earlyAccess (String [] earlyAccess ) {
613762 this .earlyAccess = earlyAccess ;
@@ -631,18 +780,41 @@ private void validateClient(Contentstack contentstack) {
631780 .addConverterFactory (GsonConverterFactory .create ())
632781 .client (httpClient (contentstack , this .retry )).build ();
633782 contentstack .instance = this .instance ;
783+
784+ // Initialize OAuth if configured
785+ if (this .oauthConfig != null ) {
786+ this .oauthHandler = contentstack .oauthHandler = new OAuthHandler (httpClient (contentstack , this .retry ), this .oauthConfig );
787+ this .oauthInterceptor = contentstack .oauthInterceptor = new OAuthInterceptor (this .oauthHandler );
788+ if (this .earlyAccess != null ) {
789+ this .oauthInterceptor .setEarlyAccess (this .earlyAccess );
790+ }
791+ }
634792 }
635793
636794 private OkHttpClient httpClient (Contentstack contentstack , Boolean retryOnFailure ) {
637- this .authInterceptor = contentstack .interceptor = new AuthInterceptor ();
638- return new OkHttpClient .Builder ()
795+ OkHttpClient .Builder builder = new OkHttpClient .Builder ()
639796 .connectionPool (this .connectionPool )
640- .addInterceptor (this .authInterceptor )
641797 .addInterceptor (logger ())
642798 .proxy (this .proxy )
643799 .connectTimeout (Duration .ofSeconds (this .timeout ))
644- .retryOnConnectionFailure (retryOnFailure )
645- .build ();
800+ .retryOnConnectionFailure (retryOnFailure );
801+
802+ // Add either OAuth or traditional auth interceptor
803+ if (this .oauthConfig != null ) {
804+ if (this .oauthInterceptor == null ) {
805+ this .oauthHandler = new OAuthHandler (builder .build (), this .oauthConfig );
806+ this .oauthInterceptor = new OAuthInterceptor (this .oauthHandler );
807+ if (this .earlyAccess != null ) {
808+ this .oauthInterceptor .setEarlyAccess (this .earlyAccess );
809+ }
810+ }
811+ builder .addInterceptor (this .oauthInterceptor );
812+ } else {
813+ this .authInterceptor = contentstack .interceptor = new AuthInterceptor ();
814+ builder .addInterceptor (this .authInterceptor );
815+ }
816+
817+ return builder .build ();
646818 }
647819
648820 private HttpLoggingInterceptor logger () {
0 commit comments