11from  enum  import  Enum 
2+ import  os 
3+ import  pyotp 
24from  ._api_client  import  _APIClient 
35from  contentstack_management .organizations  import  organization 
46from  contentstack_management .stack  import  stack 
57from  contentstack_management .user_session  import  user_session 
68from  contentstack_management .users  import  user 
9+ from  contentstack_management .oauth .oauth_handler  import  OAuthHandler 
710
811version  =  '0.0.1' 
912
@@ -33,14 +36,16 @@ class Client:
3336    def  __init__ (self , host : str  =  'api.contentstack.io' , scheme : str  =  'https://' ,
3437                 authtoken : str  =  None  , management_token = None , headers : dict  =  None ,
3538                 region : Region  =  Region .US .value , version = 'v3' , timeout = 2 , max_retries : int  =  18 , early_access : list  =  None ,
36-                  ** kwargs ):
39+                  oauth_config :  dict   =   None ,  ** kwargs ):
3740        self .endpoint  =  'https://api.contentstack.io/v3/' 
38-         if  region  is  not None  and  host  is  not None  and  region  is  not Region .US .value :
39-             self .endpoint  =  f'{ scheme } { region } { host } { version }  
40-         if  region  is  not None  and  host  is  None  and  region  is  not Region .US .value :
41-             host  =  'api.contentstack.com' 
42-             self .endpoint  =  f'{ scheme } { region } { host } { version }  
43-         if  host  is  not None  and  region  is  None :
41+         
42+         if  region  is  not None  and  region  is  not Region .US .value :
43+             if  host  is  not None  and  host  !=  'api.contentstack.io' :
44+                 self .endpoint  =  f'{ scheme } { region } { host } { version }  
45+             else :
46+                 host  =  'api.contentstack.com' 
47+                 self .endpoint  =  f'{ scheme } { region } { host } { version }  
48+         elif  host  is  not None  and  host  !=  'api.contentstack.io' :
4449            self .endpoint  =  f'{ scheme } { host } { version }  
4550        if  headers  is  None :
4651            headers  =  {}
@@ -55,6 +60,19 @@ def __init__(self, host: str = 'api.contentstack.io', scheme: str = 'https://',
5560            headers ['authorization' ] =  management_token 
5661        headers  =  user_agents (headers )
5762        self .client  =  _APIClient (endpoint = self .endpoint , headers = headers , timeout = timeout , max_retries = max_retries )
63+         
64+         # Initialize OAuth if configuration is provided 
65+         self .oauth_handler  =  None 
66+         if  oauth_config :
67+             self .oauth_handler  =  OAuthHandler (
68+                 app_id = oauth_config .get ('app_id' ),
69+                 client_id = oauth_config .get ('client_id' ),
70+                 redirect_uri = oauth_config .get ('redirect_uri' ),
71+                 response_type = oauth_config .get ('response_type' , 'code' ),
72+                 client_secret = oauth_config .get ('client_secret' ),
73+                 scope = oauth_config .get ('scope' ),
74+                 api_client = self .client 
75+             )
5876
5977        """ 
6078        :param host: Optional hostname for the API endpoint. 
@@ -77,9 +95,36 @@ def __init__(self, host: str = 'api.contentstack.io', scheme: str = 'https://',
7795        ------------------------------- 
7896        """ 
7997
80-     def  login (self , email : str , password : str , tfa_token : str  =  None ):
81-         return  user_session .UserSession (self .client ).login (email , password , tfa_token )
82-         pass 
98+     def  login (self , email : str , password : str , tfa_token : str  =  None , mfa_secret : str  =  None ):
99+         """ 
100+         Login to Contentstack with optional TOTP support. 
101+          
102+         :param email: User's email address 
103+         :param password: User's password 
104+         :param tfa_token: Optional two-factor authentication token 
105+         :param mfa_secret: Optional MFA secret for automatic TOTP generation.  
106+                           If not provided, will check MFA_SECRET environment variable 
107+         :return: Response object from the login request 
108+         """ 
109+         final_tfa_token  =  tfa_token 
110+         
111+         if  not  mfa_secret :
112+             mfa_secret  =  os .getenv ('MFA_SECRET' )
113+         
114+         if  mfa_secret  and  not  tfa_token :
115+             final_tfa_token  =  self ._generate_totp (mfa_secret )
116+         
117+         return  user_session .UserSession (self .client ).login (email , password , final_tfa_token )
118+ 
119+     def  _generate_totp (self , secret : str ) ->  str :
120+         """ 
121+         Generate a Time-Based One-Time Password (TOTP) from the provided secret. 
122+          
123+         :param secret: The MFA secret key for TOTP generation 
124+         :return: The current TOTP code as a string 
125+         """ 
126+         totp  =  pyotp .TOTP (secret )
127+         return  totp .now ()
83128
84129    def  logout (self ):
85130        return  user_session .UserSession (client = self .client ).logout ()
@@ -96,3 +141,41 @@ def organizations(self, organization_uid: str = None):
96141
97142    def  stack (self , api_key : str  =  None ):
98143        return  stack .Stack (self .client , api_key )
144+     
145+     def  oauth (self , app_id : str , client_id : str , redirect_uri : str , 
146+               response_type : str  =  "code" , client_secret : str  =  None , 
147+               scope : list  =  None ):
148+         """ 
149+         Create an OAuth handler for OAuth 2.0 authentication. 
150+          
151+         Args: 
152+             app_id: Your registered App ID 
153+             client_id: Your OAuth Client ID 
154+             redirect_uri: The URL where the user is redirected after login and consent 
155+             response_type: OAuth response type (default: "code") 
156+             client_secret: Client secret for standard OAuth flows (optional for PKCE) 
157+             scope: Permissions requested (optional) 
158+              
159+         Returns: 
160+             OAuthHandler instance 
161+              
162+         Example: 
163+             >>> import contentstack_management 
164+             >>> client = contentstack_management.Client() 
165+             >>> oauth_handler = client.oauth( 
166+             ...     app_id='your-app-id', 
167+             ...     client_id='your-client-id', 
168+             ...     redirect_uri='http://localhost:3000/callback' 
169+             ... ) 
170+             >>> auth_url = oauth_handler.authorize() 
171+             >>> print(f"Visit this URL to authorize: {auth_url}") 
172+         """ 
173+         return  OAuthHandler (
174+             app_id = app_id ,
175+             client_id = client_id ,
176+             redirect_uri = redirect_uri ,
177+             response_type = response_type ,
178+             client_secret = client_secret ,
179+             scope = scope ,
180+             api_client = self .client 
181+         )
0 commit comments