From 0c2d7816f44933a74ffc2d9c0b4e6f6d23290b07 Mon Sep 17 00:00:00 2001 From: khushal Date: Sat, 4 Jan 2025 15:22:51 +0530 Subject: [PATCH] (20250104) Breeze authorization will be accepted now. --- .../finstitutions/trading/oauth/callback.py | 75 +++++++---- .../finstitutions/trading/oauth/request.py | 26 +++- .../finstitutions/trading/all_trading.py | 9 +- controllers_v2/finstitutions/trading/base.py | 9 +- .../finstitutions/trading/icici_breeze.py | 13 +- .../finstitutions/trading/paper_trading.py | 9 +- .../finstitutions/trading/zerodha_kite.py | 83 ++++++++---- .../api/finstitutions/trading/auth/oauth.py | 36 +++-- models/finstitutions/trading/oauth.py | 127 ++++++++++++++++++ playground/trading/__init__.py | 0 playground/trading/icici_breeze.py | 0 .../trading/oauth/oauth_failure_v2.html | 6 +- .../trading/oauth/oauth_success_v2.html | 6 +- 13 files changed, 316 insertions(+), 83 deletions(-) create mode 100644 models/finstitutions/trading/oauth.py create mode 100644 playground/trading/__init__.py create mode 100644 playground/trading/icici_breeze.py diff --git a/api/blueprints/finstitutions/trading/oauth/callback.py b/api/blueprints/finstitutions/trading/oauth/callback.py index bc4055a..c57c92e 100644 --- a/api/blueprints/finstitutions/trading/oauth/callback.py +++ b/api/blueprints/finstitutions/trading/oauth/callback.py @@ -61,9 +61,6 @@ from utils_v2.api.async_quart import ( handle_failed_request ) -# GMail-related utils: -from utils_v2.goog.controllers.gmail.gmail_client import SCOPES_GMAIL_MAIL_MANAGEMENT - # Data Models: from models.core.auth_token import CoreAuthTokenModel @@ -124,17 +121,19 @@ async def handle_auth_exception(): return await render_template( "/finstitutions/trading/oauth/oauth_failure_v2.html", client = g.client_label, - failure_hint = ( - f"Something went wrong (E). " + failure_hint = " ".join([ + f"Something went wrong (E).", + g.client_response.message, f"Please use log-id '{g.log_id}' to check with the support team." - ) + ]) ) # --------------------------------------------------------------------------------------------------------------------- -@trading_oauth_callback_bp.route("/callback/", methods = ["POST", "GET"]) +@trading_oauth_callback_bp.route("/callback/", methods = ["GET", "POST"]) +@trading_oauth_callback_bp.route("/callback//", methods = ["GET", "POST"]) @set_api_version(api_version = "1.0.0") @read_input(sanitize_headers = False, sanitize_data = False) @log_request_to_mongo( @@ -152,6 +151,7 @@ async def handle_auth_exception(): @handle_failed_request(cleanup_coro = handle_auth_exception) async def trading_oauth_callback( trading_client: str = None, + client_user_id: str = None, inbound_headers: dict = None, inbound_data: dict = None, inbound_files: dict = None, @@ -162,6 +162,7 @@ async def trading_oauth_callback( This endpoint gets triggered by the stockbroker's servers to let you know when a user accepted or rejected an authorization request. :param trading_client: The name of the stockbroker that you have received the callback from. + :param client_user_id: How the trading client identifies this user. :param inbound_headers: auto-extracted by the decorators. :param inbound_data: auto-extracted by the decorators. :param inbound_files: auto-extracted by the decorators. @@ -174,20 +175,31 @@ async def trading_oauth_callback( # Store needed values in 'g': g.log_id = kwargs.get("log_id") - g.client_label = "Zerodha (Kite)" + g.client_label_map = { + "zerodhaKite": "Zerodha (Kite)", + "iciciBreeze": "ICICI (Breeze)" + } # Start by assuming failure: - success = None + g.client_response = None - # ┏┓ ┏┓ ┓┓ ┓┏┓• - # ┣ ┏┓┏┓ ┏┛┏┓┏┓┏┓┏┫┣┓┏┓ ┃┫ ┓╋┏┓ - # ┻ ┗┛┛ ┗┛┗ ┛ ┗┛┗┻┛┗┗┻ ┛┗┛┗┗┗ + # ┳┓ ┓ ┏┓ ┓ • + # ┣┫┏┓┏┓┃┏┏┓┏┓ ┗┓┏┓┃┏┓┏╋┓┏┓┏┓ + # ┻┛┛ ┗┛┛┗┗ ┛ ┗┛┗ ┗┗ ┗┗┗┗┛┛┗ - if trading_client == "zerodha": - success = await current_app.zerodha_kite_controller.handle_authorization_callback( + g.client_label = g.client_label_map.get(trading_client, trading_client) + + match trading_client: + case "zerodhaKite": client_controller = current_app.zerodha_kite_controller + case "iciciBreeze": client_controller = current_app.icici_breeze_controller + case _: client_controller = None + + if client_controller is not None: + g.client_response = await client_controller.handle_authorization_callback( sql_conn = current_app.sql_writer, mongo_data_conn = current_app.data_mongo, - inbound_data = inbound_data + inbound_data = inbound_data, + client_user_id = client_user_id ) # ┳┓ @@ -195,30 +207,35 @@ async def trading_oauth_callback( # ┛┗┗ ┛┣┛┗┛┛┗┛┗ # ┛ + # Prepare a set of warning messages: + callback_url_upgrade_warning = ( + "

WARNING: You are using the old callback system which could be discontinued at any time. " + "Please update the callback URL on your broker's portal to include your user id in it." + ) + # No valid client: - if success is None: return await render_template( + if client_controller is None: return await render_template( "/finstitutions/trading/oauth/oauth_failure_v2.html", - client = g.client_label, - failure_hint = ( - f"Invalid client '{g.client_label}' selected. " - f"Please use log-id '{g.log_id}' to check with the support team." - ) + failure_hint = " ".join([ + f"Invalid client '{g.client_label}' selected.", + f"Please use log-id '{g.log_id}' to check with the support team." + ]) ) # Successful auth: - if success: return await render_template( + if g.client_response.success: return await render_template( "/finstitutions/trading/oauth/oauth_success_v2.html", - client = g.client_label + client = g.client_label, + extra_message = callback_url_upgrade_warning if client_user_id is None else "" ) # Failed auth: - if success is None: return await render_template( + if g.client_response.success is False: return await render_template( "/finstitutions/trading/oauth/oauth_failure_v2.html", - client = g.client_label, - failure_hint = ( - f"Something went wrong (NE). " - f"Please use log-id '{g.log_id}' to check with the support team." - ) + failure_hint = " ".join([ + g.client_response.message, + f"Please use log-id '{g.log_id}' to check with the support team." + ]) ) diff --git a/api/blueprints/finstitutions/trading/oauth/request.py b/api/blueprints/finstitutions/trading/oauth/request.py index d079f9d..6711927 100644 --- a/api/blueprints/finstitutions/trading/oauth/request.py +++ b/api/blueprints/finstitutions/trading/oauth/request.py @@ -171,6 +171,9 @@ async def request_oauth_authorization_url( # ┻ ┗┛┛ ┣┛┗┻┣┛┗ ┛ ┻ ┛ ┗┻┗┻┗┛┗┗┫ # ┛ ┛ + # This is an internal paper-trading account. + # It won't need daily logins for usage. + if inbound_data.client == "paperTrading": # Immediately save the details against that token id: @@ -187,7 +190,7 @@ async def request_oauth_authorization_url( }, auth = inbound_data.auth.model_dump(), status = "active", - syncFreq = 1500 + syncFreq = None ), token_notes = { "username": inbound_data.auth.username @@ -205,7 +208,7 @@ async def request_oauth_authorization_url( # ┻ ┗┛┛ ┗┛┗ ┛ ┗┛┗┻┛┗┗┻ ┛┗┛┗┗┗ # PLANNED FLOW FOR ZERODHA-KITE: - # Step 01.: (One time) The user will go to the integrations page and add his API Key and SPI Secret there. We store + # Step 01.: (One time) The user will go to the integrations page and add his API Key and API Secret there. We store # these values without verification. # Step 02.: (Daily) The user will go to the investments tab and click on his Zerodha account, which will give him # a URL that will take him to Zerodha's official site to log in. When he logs in, Zerodha will hit our @@ -226,17 +229,19 @@ async def request_oauth_authorization_url( authType = "oauth", user = kwargs["session_info"], clientUserId = { + "userId": inbound_data.auth.userId, "apiKey": inbound_data.auth.apiKey }, auth = inbound_data.auth.model_dump(), status = "active", - syncFreq = 1500 + syncFreq = None ), token_notes = { + "userId": inbound_data.auth.userId, "apiKey": inbound_data.auth.apiKey, "authUrl": auth_url }, - display_name = None, + display_name = inbound_data.auth.userId, display_picture = None, session_token = inbound_headers["X-Session-Token"] ) @@ -248,6 +253,13 @@ async def request_oauth_authorization_url( # ┣ ┏┓┏┓ ┃┃ ┃┃ ┃ ┣┫┏┓┏┓┏┓┓┏┓ # ┻ ┗┛┛ ┻┗┛┻┗┛┻ ┻┛┛ ┗ ┗ ┗┗ + # PLANNED FLOW FOR ICICI-BREEZE: + # Step 01.: (One time) The user will go to the integrations page and add his Client User ID, API Key, and API Secret + # there. We store these values without verification. + # Step 02.: (Daily) The user will go to the investments tab and click on his Breeze account, which will give him a + # URL that will take him to ICICI's official site to log in. When he logs in, ICICI will hit our callback + # URL and give us the authentication details. + if inbound_data.client == "iciciBreeze": # Prepare the inputs: @@ -263,17 +275,19 @@ async def request_oauth_authorization_url( authType = "oauth", user = kwargs["session_info"], clientUserId = { + "userId": inbound_data.auth.userId, "apiKey": inbound_data.auth.apiKey }, auth = inbound_data.auth.model_dump(), status = "active", - syncFreq = 1500 + syncFreq = None ), token_notes = { + "userId": inbound_data.auth.userId, "apiKey": inbound_data.auth.apiKey, "authUrl": auth_url }, - display_name = None, + display_name = inbound_data.auth.userId, display_picture = None, session_token = inbound_headers["X-Session-Token"] ) diff --git a/controllers_v2/finstitutions/trading/all_trading.py b/controllers_v2/finstitutions/trading/all_trading.py index 0d88927..557a36a 100644 --- a/controllers_v2/finstitutions/trading/all_trading.py +++ b/controllers_v2/finstitutions/trading/all_trading.py @@ -46,6 +46,7 @@ from controllers_v2.finstitutions.trading.base import TradingController # Models: from models.core.auth_token import CoreAuthTokenModel from models.api.finstitutions.trading.symbols.list import TradingSymbolListRequestData, TradingSymbolListBrokerResponse +from models.finstitutions.trading.oauth import TradingOAuthCallbackResponse # To work with datatypes: from typing import List, Any @@ -150,8 +151,9 @@ class AllTradingController(TradingController): self, sql_conn: AsyncMySQL, mongo_data_conn: AsyncMongo, - inbound_data: dict - ) -> bool: + inbound_data: dict, + client_user_id: str + ) -> TradingOAuthCallbackResponse: """ When the end user interacts with their broker's APIs, the broker's servers would usually issue a callback. We've seen this in the case of Zerodha Kite and ICICI Breeze. Use this method to handle the callback loop to @@ -159,7 +161,8 @@ class AllTradingController(TradingController): :param sql_conn: The database connection to use to perform this activity. :param mongo_data_conn: The database connection to use to perform this activity. :param inbound_data: The data that came in from the broker. This could be in the JSON body, query params, etc. - :return: True if the callback loop was completed successfully, else False. + :param client_user_id: How the trading client identifies this user. + :return: A structured response to capture the process of callback handling. """ raise NotImplementedError diff --git a/controllers_v2/finstitutions/trading/base.py b/controllers_v2/finstitutions/trading/base.py index 0f674ca..109b838 100644 --- a/controllers_v2/finstitutions/trading/base.py +++ b/controllers_v2/finstitutions/trading/base.py @@ -46,6 +46,7 @@ from controllers_v2.core.auth_token import CoreAuthTokenController # Models: from models.core.auth_token import CoreAuthTokenModel from models.api.finstitutions.trading.symbols.list import TradingSymbolListRequestData, TradingSymbolListBrokerResponse +from models.finstitutions.trading.oauth import TradingOAuthCallbackResponse # To work with datatypes: from typing import List, Any @@ -170,8 +171,9 @@ class TradingController(CoreAuthTokenController, ABC): self, sql_conn: AsyncMySQL, mongo_data_conn: AsyncMongo, - inbound_data: dict - ) -> bool: + inbound_data: dict, + client_user_id: str + ) -> TradingOAuthCallbackResponse: """ When the end user interacts with their broker's APIs, the broker's servers would usually issue a callback. @@ -180,7 +182,8 @@ class TradingController(CoreAuthTokenController, ABC): :param sql_conn: The database connection to use to perform this activity. :param mongo_data_conn: The database connection to use to perform this activity. :param inbound_data: The data that came in from the broker. This could be in the JSON body, query params, etc. - :return: True if the callback loop was completed successfully, else False. + :param client_user_id: How the trading client identifies this user. + :return: A structured response to capture the process of callback handling. """ pass diff --git a/controllers_v2/finstitutions/trading/icici_breeze.py b/controllers_v2/finstitutions/trading/icici_breeze.py index 3baf46e..fdfb912 100644 --- a/controllers_v2/finstitutions/trading/icici_breeze.py +++ b/controllers_v2/finstitutions/trading/icici_breeze.py @@ -51,6 +51,7 @@ from models.api.finstitutions.trading.symbols.list import ( TradingSymbolListBrokerResponse, TradingSymbol ) +from models.finstitutions.trading.oauth import TradingOAuthCallbackResponse # To work with MongoDB: from bson.objectid import ObjectId @@ -62,8 +63,8 @@ from typing import List, Any import httpx import urllib -# To work with Zerodha's Kite platform: -from kiteconnect import KiteConnect +# To work with ICICI Breeze's platform: +from breeze_connect import BreezeConnect # To handle exceptions: from pydantic import ValidationError @@ -179,8 +180,9 @@ class ICICIBreezeTradingController(TradingController): self, sql_conn: AsyncMySQL, mongo_data_conn: AsyncMongo, - inbound_data: dict - ) -> bool: + inbound_data: dict, + client_user_id: str + ) -> TradingOAuthCallbackResponse: """ To capture the callback from ICICI Breeze's authorization loop. This happens when the user successfully logs in @@ -188,7 +190,8 @@ class ICICIBreezeTradingController(TradingController): :param sql_conn: The database connection to use to perform this activity. :param mongo_data_conn: The database connection to use to perform this activity. :param inbound_data: The data that came in from the broker. This could be in the JSON body, query params, etc. - :return: True if the callback loop was completed successfully, else False.. + :param client_user_id: How the trading client identifies this user. + :return: A structured response to capture the process of callback handling. """ raise NotImplementedError diff --git a/controllers_v2/finstitutions/trading/paper_trading.py b/controllers_v2/finstitutions/trading/paper_trading.py index e5d3a87..68117f8 100644 --- a/controllers_v2/finstitutions/trading/paper_trading.py +++ b/controllers_v2/finstitutions/trading/paper_trading.py @@ -52,6 +52,7 @@ from models.api.finstitutions.trading.symbols.list import ( TradingSymbolListBrokerResponse, TradingSymbol ) +from models.finstitutions.trading.oauth import TradingOAuthCallbackResponse # To work with MongoDB: from bson.objectid import ObjectId @@ -179,15 +180,17 @@ class PaperTradingController(TradingController): self, sql_conn: AsyncMySQL, mongo_data_conn: AsyncMongo, - inbound_data: dict - ) -> bool: + inbound_data: dict, + client_user_id: str + ) -> TradingOAuthCallbackResponse: """ Not needed for paper trading. :param sql_conn: The database connection to use to perform this activity. :param mongo_data_conn: The database connection to use to perform this activity. :param inbound_data: The data that came in from the broker. This could be in the JSON body, query params, etc. - :return: True if the callback loop was completed successfully, else False.. + :param client_user_id: How the trading client identifies this user. + :return: A structured response to capture the process of callback handling. """ raise NotImplementedError diff --git a/controllers_v2/finstitutions/trading/zerodha_kite.py b/controllers_v2/finstitutions/trading/zerodha_kite.py index 2221513..4513961 100644 --- a/controllers_v2/finstitutions/trading/zerodha_kite.py +++ b/controllers_v2/finstitutions/trading/zerodha_kite.py @@ -52,6 +52,7 @@ from models.api.finstitutions.trading.symbols.list import ( TradingSymbolListBrokerResponse, TradingSymbol ) +from models.finstitutions.trading.oauth import TradingOAuthCallbackResponse # To work with MongoDB: from bson.objectid import ObjectId @@ -179,50 +180,79 @@ class ZerodhaKiteTradingController(TradingController): self, sql_conn: AsyncMySQL, mongo_data_conn: AsyncMongo, - inbound_data: dict - ) -> bool: + inbound_data: dict, + client_user_id: str + ) -> TradingOAuthCallbackResponse: """ When the end user interacts with Zerodha's APIs, Zerodha's servers issue a callback like this: http://127.0.0.1:5999/auth/callback?action=login&type=login&status=success&request_token=the-request-token We must use the request token to get the access token. The access token is the thing that we must hold onto for executing actual actions like subscribing to live market feed, placing trades, etc. - NOTE: Please ensure that you set the 'Redirect URL' such that is passes back Kite's 'api_key' back through the - callback URL. This can be one by setting the value manually as a query param on the app's configuration - page. E.g.: http://127.0.0.1:5999/auth/callback?api_key=user_api_key + NOTE: Please ensure that you set the 'Redirect URL' in the format as shown below: + 01. http://127.0.0.1:5106/converse/finstitutions/trading/oauth/callback/zerodhaKite/ + 02. https://api.thecaoffice.com/converse/finstitutions/trading/oauth/callback/zerodhaKite/ + BACKWARD COMPATIBILITY: + Earlier, we used to set the same API key in the callback URL as a query param like shown below: + E.g.: http://127.0.0.1:5106/converse/finstitutions/trading/oauth/callback/zerodhaKite?api_key=user_api_key :param sql_conn: The database connection to use to perform this activity. :param mongo_data_conn: The database connection to use to perform this activity. :param inbound_data: The data that came in from the broker. This could be in the JSON body, query params, etc. - :return: True if the callback loop was completed successfully, else False.. + :param client_user_id: How the trading client identifies this user. + :return: A structured response to capture the process of callback handling. """ # Start by assuming failure: - success = False + response = TradingOAuthCallbackResponse() zerodha_auth_token = None + # Check if either the new system or the old system is being followed. + # At least one is needed: + api_key = inbound_data.get("api_key") + if not api_key and not client_user_id: + response.message = "Your callback URL hasn't been configured properly." + return response + # Get the token from the database: + old_condition = mongo_data_conn.dict_to_dot_notation({"auth": {"apiKey": api_key}}) + condition = mongo_data_conn.dict_to_dot_notation({"auth": {"userId": client_user_id}}) auth_token = await self.get_token_from_filter( mongo_data_conn = mongo_data_conn, - filter_json = mongo_data_conn.dict_to_dot_notation({ - "auth": { - "apiKey": inbound_data.get( - "api_key", - "Hint: Put the user's app's key in the query params of the 'Redirect URL'" - ) - } - }) + filter_json = {"$or": [old_condition, condition]} ) # If not such auth token exists: - if not auth_token: return success + if not auth_token: + response.message = ( + f"No such integration found in our system. " + "Please add this integration first and then try again." + ) + return response # Get the final access tokens set from Zerodha Kite: - kite = KiteConnect(api_key = auth_token.auth["apiKey"]) - session_data = kite.generate_session( - request_token = inbound_data["request_token"], - api_secret = auth_token.auth["apiSecret"] - ) - zerodha_auth_token = ZerodhaKiteAuthTokens(**session_data) + try: + kite = KiteConnect(api_key = auth_token.auth["apiKey"]) + session_data = kite.generate_session( + request_token = inbound_data["request_token"], + api_secret = auth_token.auth["apiSecret"] + ) + zerodha_auth_token = ZerodhaKiteAuthTokens(**session_data) + except Exception as exception: + response.exception = exception + response.message = str(exception) + return response + + # Ensure that the client user id of the incoming callback and the one given in Zerodha's session data match: + if ( + client_user_id is not None and # ................ For backward compatibility. + zerodha_auth_token.userId != client_user_id # ... New mechanism that verifies account match. + ): + response.message = ( + f"We were expecting authorization for the account '{client_user_id}', " + f"but Zerodha says the authorization was granted for the account '{zerodha_auth_token.userId}'. " + "This could be because of a misconfigured callback URL." + ) + return response # Prepare the inputs to save to the database: auth_url = await self.get_authorization_url(api_key = auth_token.auth["apiKey"]) @@ -242,8 +272,15 @@ class ZerodhaKiteTradingController(TradingController): display_picture = zerodha_auth_token.displayPictureUrl ) + # If saving the token fails: + if not success: + response.message = "Something went wrong towards the end of the authorization cycle." + return response + # Done here: - return success + response.success = True + response.message = "Authorization cycle successfully completed." + return response # ┏┳┓ ┓• ┏┓ ┓ ┓ # ┃ ┏┓┏┓┏┫┓┏┓┏┓ ┗┓┓┏┏┳┓┣┓┏┓┃┏ diff --git a/models/api/finstitutions/trading/auth/oauth.py b/models/api/finstitutions/trading/auth/oauth.py index d5b67b3..5113d87 100644 --- a/models/api/finstitutions/trading/auth/oauth.py +++ b/models/api/finstitutions/trading/auth/oauth.py @@ -101,18 +101,24 @@ class PaperTradingAuth(BaseModel): class ZerodhaKiteAuth(BaseModel): + userId: str = Field( + description = "How Zerodha's Kite platform identifies this user.", + frozen = True, + alias = "clientId" + ) + apiKey: str = Field( description = ( - "the api key of your kite connect app; " - "this remains constant throughout the life of the app" + "The API key of your Kite app. " + "This remains constant throughout the life of the app." ), frozen = True ) apiSecret: str = Field( description = ( - "the api secret of your kite connect app; " - "this can change if you think the security of your app has been compromised" + "The API secret of your Kite app. " + "This can be changed if you think the security of your app has been compromised." ), frozen = True ) @@ -124,6 +130,7 @@ class ZerodhaKiteAuth(BaseModel): class Config: extra = "forbid" + populate_by_name = True # --------------------------------------------------------------------------------------------------------------------- @@ -131,13 +138,25 @@ class ZerodhaKiteAuth(BaseModel): class ICICIBreezeAuth(BaseModel): + userId: str = Field( + description = "How ICICI's Breeze platform identifies this user.", + frozen = True, + alias = "clientId" + ) + apiKey: str = Field( - description = "??", + description = ( + "The API key of your Breeze app. " + "This remains constant throughout the life of the app." + ), frozen = True ) apiSecret: str = Field( - description = "??", + description = ( + "The API secret of your Breeze app. " + "This can be changed if you think the security of your app has been compromised." + ), frozen = True ) @@ -148,6 +167,7 @@ class ICICIBreezeAuth(BaseModel): class Config: extra = "forbid" + populate_by_name = True # --------------------------------------------------------------------------------------------------------------------- @@ -179,8 +199,8 @@ class TradingAuthRequestHeaders(BaseModel): class TradingAuthRequestData(BaseModel): - client: Literal["zerodhaKite", "iciciBreeze", "paperTrading"] = Field(alias = "client") - auth: Union[ZerodhaKiteAuth, ICICIBreezeAuth, PaperTradingAuth] + client: Literal["paperTrading", "zerodhaKite", "iciciBreeze"] = Field(alias = "client") + auth: Union[PaperTradingAuth, ZerodhaKiteAuth, ICICIBreezeAuth] # ┏┓ ┏• # ┃ ┏┓┏┓╋┓┏┓ diff --git a/models/finstitutions/trading/oauth.py b/models/finstitutions/trading/oauth.py new file mode 100644 index 0000000..f212457 --- /dev/null +++ b/models/finstitutions/trading/oauth.py @@ -0,0 +1,127 @@ +""" + + AUTHOR: + + Khushal P Soonderji + + DATE: + + Saturday, 4th Jan., 2025. + + OBJECTIVE: + + To provide a structure to represent OAuth callback responses. + + REFERENCES: + + N/A + + DOWNLOADS: + + N/A + +""" + + +# ***************************************************************************************************************** +# ***** **** +# *** IMPORT *** +# ***** **** +# ***************************************************************************************************************** + + +# To make sibling directories accessible for imports: +import sys +sys.path.append(".") +sys.path.append("..") + +# For making data behaviour_models: +from pydantic import BaseModel, Field, field_validator, PastDatetime, model_validator, AwareDatetime +from typing import Optional, Literal, Union, List, Any + +# My utils: +from utils_v2.string import regex +from utils_v2.date_time import date_time + +# To work with date and time: +import datetime + + +# ***************************************************************************************************************** +# ***** **** +# *** MACROS / ONE-TIME INIT *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** VARIABLES *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** FUNCTIONS *** +# ***** **** +# ***************************************************************************************************************** + + +class TradingOAuthCallbackResponse(BaseModel): + + success: bool = Field( + description = "To indicate whether or not, the action was a success", + frozen = False, + default = False + ) + + message: str = Field( + description = "To explain what happened in the process of handling the OAuth callback.", + frozen = False, + default = "ERR: Message not captured." + ) + + exception: Any = Field( + description = "To pass on any exception that occurred in the process.", + frozen = False, + default = None + ) + + # ┏┓ ┏• + # ┃ ┏┓┏┓╋┓┏┓ + # ┗┛┗┛┛┗┛┗┗┫ + # ┛ + + class Config: + extra = "forbid" + + # ┏┓ ┏┓ + # ┃ ┓┏┏╋┏┓┏┳┓ ┣ ┓┏┏┓┏┏ + # ┗┛┗┻┛┗┗┛┛┗┗ ┻ ┗┻┛┗┗┛ + + pass + + # ┓┏ ┓• ┓ • + # ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓ + # ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗ + + pass + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + pass diff --git a/playground/trading/__init__.py b/playground/trading/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/playground/trading/icici_breeze.py b/playground/trading/icici_breeze.py new file mode 100644 index 0000000..e69de29 diff --git a/views_v2/finstitutions/trading/oauth/oauth_failure_v2.html b/views_v2/finstitutions/trading/oauth/oauth_failure_v2.html index e396f42..c7beccd 100644 --- a/views_v2/finstitutions/trading/oauth/oauth_failure_v2.html +++ b/views_v2/finstitutions/trading/oauth/oauth_failure_v2.html @@ -101,9 +101,11 @@

Authorization Failed

-

Something went wrong in getting authorization from your {{ client }} account. +

+ Something went wrong in getting authorization from your {{ client }} account.

Hint: {{ failure_hint|safe }}

- Please feel free to try the same steps again. You can close this tab at any time.

+ Please feel free to try the same steps again. You can close this tab at any time. +

diff --git a/views_v2/finstitutions/trading/oauth/oauth_success_v2.html b/views_v2/finstitutions/trading/oauth/oauth_success_v2.html index 33ee0b1..471a352 100644 --- a/views_v2/finstitutions/trading/oauth/oauth_success_v2.html +++ b/views_v2/finstitutions/trading/oauth/oauth_success_v2.html @@ -101,7 +101,11 @@

Authorization Successful

-

We have received authorization from your {{ client }} account. You can close this tab at any time.

+

+ We have received authorization from your {{ client }} account. + {{ extra_message|safe }} +

You can close this tab at any time. +