From eb7a1055d99f5c3881620ccabd772e11c87ed7e0 Mon Sep 17 00:00:00 2001 From: Guillaume De Saint Martin <guillaumemdsm@gmail.com> Date: Thu, 22 Aug 2024 21:41:25 +0200 Subject: [PATCH 1/7] [Requirements] bump --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a79ea10ae..2f9d0c4dd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ # Drakkar-Software requirements OctoBot-Commons==1.9.54 -OctoBot-Trading==2.4.100 +OctoBot-Trading==2.4.101 OctoBot-Evaluators==1.9.5 OctoBot-Tentacles-Manager==2.9.16 OctoBot-Services==1.6.17 From 39d5635218039889b2a4db9d9e4f1f8561efd832 Mon Sep 17 00:00:00 2001 From: Guillaume De Saint Martin <guillaumemdsm@gmail.com> Date: Thu, 22 Aug 2024 23:08:23 +0200 Subject: [PATCH 2/7] [Tracker] update error tracker log --- octobot/community/errors_upload/sentry_tracker.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/octobot/community/errors_upload/sentry_tracker.py b/octobot/community/errors_upload/sentry_tracker.py index 0f289fe60..13348b95f 100644 --- a/octobot/community/errors_upload/sentry_tracker.py +++ b/octobot/community/errors_upload/sentry_tracker.py @@ -26,9 +26,9 @@ def init_sentry_tracker(): """ Will upload errors to octobot.constants.ERROR_TRACKER_DSN if its value is set """ - logger = octobot_commons.logging.get_logger(__name__) + logger = octobot_commons.logging.get_logger("sentry_tracker") if not octobot.constants.ERROR_TRACKER_DSN: - logger.debug(f"Skipping error tracker: error tracker dsn is '{octobot.constants.ERROR_TRACKER_DSN}'") + logger.debug(f"Error tracker disabled") return environment = "cloud" if octobot.constants.IS_CLOUD_ENV else "self hosted" app_name = f"{octobot.constants.PROJECT_NAME} open source" @@ -72,7 +72,7 @@ def init_sentry_tracker(): def flush_tracker(): if octobot.constants.ERROR_TRACKER_DSN: delay = 2 - octobot_commons.logging.get_logger(__name__).info(f"Flushing trackers: shutting down in {delay} seconds ...") + octobot_commons.logging.get_logger("sentry_tracker").info(f"Flushing trackers: shutting down in {delay} seconds ...") sentry_sdk.flush() # let trackers upload errors time.sleep(delay) From 6d1770b22431e2e791ad866b6e8e4e4413efb84b Mon Sep 17 00:00:00 2001 From: Guillaume De Saint Martin <guillaumemdsm@gmail.com> Date: Sun, 25 Aug 2024 00:33:12 +0200 Subject: [PATCH 3/7] [Requirements] bump --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 2f9d0c4dd..dfc0d46d9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ # Drakkar-Software requirements OctoBot-Commons==1.9.54 -OctoBot-Trading==2.4.101 +OctoBot-Trading==2.4.102 OctoBot-Evaluators==1.9.5 OctoBot-Tentacles-Manager==2.9.16 OctoBot-Services==1.6.17 From c02ab0d88f662a32df743ce40e02f2b217703348 Mon Sep 17 00:00:00 2001 From: Guillaume De Saint Martin <guillaumemdsm@gmail.com> Date: Sun, 25 Aug 2024 12:05:32 +0200 Subject: [PATCH 4/7] [Requirements] bump --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index dfc0d46d9..9029ec652 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ # Drakkar-Software requirements OctoBot-Commons==1.9.54 -OctoBot-Trading==2.4.102 +OctoBot-Trading==2.4.103 OctoBot-Evaluators==1.9.5 OctoBot-Tentacles-Manager==2.9.16 OctoBot-Services==1.6.17 From 85b4ad52a40449f7fd9d81aa60843f8bc8aed545 Mon Sep 17 00:00:00 2001 From: Guillaume De Saint Martin <guillaumemdsm@gmail.com> Date: Sun, 25 Aug 2024 17:29:18 +0200 Subject: [PATCH 5/7] [Auth] add auth key authentication --- .../supabase_backend_tests/.env.template | 4 ++ .../supabase_backend_tests/__init__.py | 4 ++ .../test_user_elements.py | 50 ++++++++++++++++++- octobot/cli.py | 25 +++++++--- octobot/community/authentication.py | 18 +++++-- .../community_supabase_client.py | 19 ++++++- octobot/constants.py | 1 + 7 files changed, 107 insertions(+), 14 deletions(-) diff --git a/additional_tests/supabase_backend_tests/.env.template b/additional_tests/supabase_backend_tests/.env.template index a523992ef..c2c065c3e 100644 --- a/additional_tests/supabase_backend_tests/.env.template +++ b/additional_tests/supabase_backend_tests/.env.template @@ -3,8 +3,12 @@ SUPABASE_BACKEND_KEY= SUPABASE_BACKEND_CLIENT_1_EMAIL= SUPABASE_BACKEND_CLIENT_1_PASSWORD= +SUPABASE_BACKEND_CLIENT_1_AUTH_KEY= SUPABASE_BACKEND_CLIENT_2_EMAIL= SUPABASE_BACKEND_CLIENT_2_PASSWORD= +SUPABASE_BACKEND_CLIENT_3_EMAIL= +SUPABASE_BACKEND_CLIENT_3_PASSWORD= + SUPABASE_BACKEND_SERVICE_KEY= diff --git a/additional_tests/supabase_backend_tests/__init__.py b/additional_tests/supabase_backend_tests/__init__.py index 11a439045..6c085f833 100644 --- a/additional_tests/supabase_backend_tests/__init__.py +++ b/additional_tests/supabase_backend_tests/__init__.py @@ -217,6 +217,10 @@ def get_backend_client_creds(identifier): os.getenv(f"SUPABASE_BACKEND_CLIENT_{identifier}_PASSWORD") +def get_backend_client_auth_key(identifier): + return os.getenv(f"SUPABASE_BACKEND_CLIENT_{identifier}_AUTH_KEY") + + def _get_backend_service_key(): return os.getenv(f"SUPABASE_BACKEND_SERVICE_KEY") diff --git a/additional_tests/supabase_backend_tests/test_user_elements.py b/additional_tests/supabase_backend_tests/test_user_elements.py index 35c5c076a..2bd687af2 100644 --- a/additional_tests/supabase_backend_tests/test_user_elements.py +++ b/additional_tests/supabase_backend_tests/test_user_elements.py @@ -21,7 +21,8 @@ import octobot.community as community import octobot.community.supabase_backend.enums as supabase_backend_enums from additional_tests.supabase_backend_tests import authenticated_client_1, authenticated_client_2, \ - admin_client, anon_client, get_backend_api_creds, skip_if_no_service_key + admin_client, anon_client, get_backend_api_creds, skip_if_no_service_key, get_backend_client_creds, \ + get_backend_client_auth_key # All test coroutines will be treated as marked. @@ -114,3 +115,50 @@ async def test_sign_in_with_otp_token(authenticated_client_1, skip_if_no_service finally: if supabase_client: await supabase_client.aclose() + + +async def test_sign_in_with_auth_token(): + # create new client + backend_url, backend_key = get_backend_api_creds() + email, _ = get_backend_client_creds(1) + + config = commons_configuration.Configuration("", "") + config.config = {} + supabase_client = None + try: + supabase_client = community.CommunitySupabaseClient( + backend_url, + backend_key, + community.ASyncConfigurationStorage(config) + ) + saved_session = "saved_session" + await supabase_client.auth._storage.set_item(supabase_client.auth._storage_key, saved_session) + # wrong configs + with pytest.raises(authentication.AuthenticationError): + await supabase_client.get_otp_with_auth_key("", "") + with pytest.raises(authentication.AuthenticationError): + await supabase_client.get_otp_with_auth_key(None, "") + with pytest.raises(authentication.AuthenticationError): + await supabase_client.get_otp_with_auth_key(email, None) + with pytest.raises(authentication.AuthenticationError): + await supabase_client.get_otp_with_auth_key(email, "1234") + assert await supabase_client.auth._storage.get_item(supabase_client.auth._storage_key) == saved_session + token = await supabase_client.get_otp_with_auth_key(email, get_backend_client_auth_key(1)) + # ensure token is valid + + await supabase_client.sign_in_with_otp_token(token) + # save session has been updated + updated_session = await supabase_client.auth._storage.get_item(supabase_client.auth._storage_key) + assert updated_session != saved_session + + # ensure new supabase_client is bound to the same user as the previous client + user = await supabase_client.get_user() + assert user[supabase_backend_enums.UserKeys.EMAIL.value] == email + + # already consumed token + with pytest.raises(authentication.AuthenticationError): + await supabase_client.sign_in_with_otp_token(token) + assert await supabase_client.auth._storage.get_item(supabase_client.auth._storage_key) == updated_session + finally: + if supabase_client: + await supabase_client.aclose() diff --git a/octobot/cli.py b/octobot/cli.py index f9500ee1e..7062a667a 100644 --- a/octobot/cli.py +++ b/octobot/cli.py @@ -13,7 +13,6 @@ # # You should have received a copy of the GNU General Public # License along with OctoBot. If not, see <https://www.gnu.org/licenses/>. -import time import argparse import os import sys @@ -189,13 +188,23 @@ async def _get_authenticated_community_if_possible(config, logger): community_auth = octobot_community.CommunityAuthentication.create(config) try: if not community_auth.is_initialized(): - if constants.IS_CLOUD_ENV and constants.USER_ACCOUNT_EMAIL and constants.USER_PASSWORD_TOKEN: - try: - await community_auth.login( - constants.USER_ACCOUNT_EMAIL, None, password_token=constants.USER_PASSWORD_TOKEN - ) - except authentication.AuthenticationError as err: - logger.debug(f"Password token auth failure ({err}). Trying with saved session.") + if constants.IS_CLOUD_ENV: + if constants.USER_ACCOUNT_EMAIL and constants.USER_AUTH_KEY: + try: + logger.debug("Attempting auth key authentication") + await community_auth.login( + constants.USER_ACCOUNT_EMAIL, None, auth_key=constants.USER_AUTH_KEY + ) + except authentication.AuthenticationError as err: + logger.debug(f"Auth key auth failure ({err}). Trying other methods if available.") + if constants.USER_ACCOUNT_EMAIL and constants.USER_PASSWORD_TOKEN: + try: + logger.debug("Attempting password token authentication") + await community_auth.login( + constants.USER_ACCOUNT_EMAIL, None, password_token=constants.USER_PASSWORD_TOKEN + ) + except authentication.AuthenticationError as err: + logger.debug(f"Password token auth failure ({err}). Trying with saved session.") if not community_auth.is_initialized(): # try with saved credentials if any has_tentacles = tentacles_manager_api.is_tentacles_architecture_valid() diff --git a/octobot/community/authentication.py b/octobot/community/authentication.py index 3d699564a..b06cbb984 100644 --- a/octobot/community/authentication.py +++ b/octobot/community/authentication.py @@ -81,6 +81,7 @@ def __init__(self, config=None, backend_url=None, backend_key=None, use_as_singl self.user_account = community_user_account.CommunityUserAccount() self.public_data = community_public_data.CommunityPublicData() self.successfully_fetched_tentacles_package_urls = False + self.silent_auth = False self._community_feed = None self.initialized_event = None @@ -305,11 +306,20 @@ def can_authenticate(self): def must_be_authenticated_through_authenticator(self): return constants.IS_CLOUD_ENV - async def login(self, email, password, password_token=None, minimal=False): + async def login( + self, + email: str, + password: typing.Optional[str], + password_token: typing.Optional[str] = None, + auth_key: typing.Optional[str] = None, + minimal: bool = False + ): self._ensure_email(email) self._ensure_community_url() self._reset_tokens() with self._login_process(): + if auth_key and not password_token: + password_token = await self.supabase_client.get_otp_with_auth_key(email, auth_key) if password_token: await self.supabase_client.sign_in_with_otp_token(password_token) else: @@ -331,7 +341,8 @@ async def register(self, email, password): await self.on_signed_in() async def on_signed_in(self, minimal=False): - self.logger.info(f"Signed in as {self.get_logged_in_email()}") + if not self.silent_auth: + self.logger.info(f"Signed in as {self.get_logged_in_email()}") await self._initialize_account(minimal=minimal) async def _update_account_metadata(self, metadata_update): @@ -669,7 +680,8 @@ async def _restore_previous_session(self): # will raise on failure await self.supabase_client.restore_session() await self._on_account_updated() - self.logger.info(f"Signed in as {self.get_logged_in_email()}") + if not self.silent_auth: + self.logger.info(f"Signed in as {self.get_logged_in_email()}") return self.is_logged_in() @contextlib.asynccontextmanager diff --git a/octobot/community/supabase_backend/community_supabase_client.py b/octobot/community/supabase_backend/community_supabase_client.py index df49152f1..ec2984dbd 100644 --- a/octobot/community/supabase_backend/community_supabase_client.py +++ b/octobot/community/supabase_backend/community_supabase_client.py @@ -21,6 +21,7 @@ import logging import httpx import uuid +import json import aiohttp import gotrue.errors @@ -211,8 +212,22 @@ async def get_user(self) -> dict: raise errors.EmailValidationRequiredError(err) from err raise authentication.AuthenticationError(f"Please re-login to your OctoBot account: {err}") from err - def sync_get_user(self) -> dict: - return self.auth.get_user().user.model_dump() + async def get_otp_with_auth_key(self, user_email: str, auth_key: str) -> str: + try: + resp = await self.functions.invoke( + "create-auth-token", + { + "headers": { + "User-Auth-Token": auth_key + }, + "body": { + "user_email": user_email + }, + } + ) + return json.loads(resp)["token"] + except Exception: + raise authentication.AuthenticationError(f"Invalid auth key authentication details") async def fetch_bot(self, bot_id) -> dict: try: diff --git a/octobot/constants.py b/octobot/constants.py index 84ae1e7f6..f240d6188 100644 --- a/octobot/constants.py +++ b/octobot/constants.py @@ -107,6 +107,7 @@ USE_BETA_EARLY_ACCESS = os_util.parse_boolean_environment_var("USE_BETA_EARLY_ACCESS", "false") USER_ACCOUNT_EMAIL = os.getenv("USER_ACCOUNT_EMAIL", "") USER_PASSWORD_TOKEN = os.getenv("USER_PASSWORD_TOKEN", None) +USER_AUTH_KEY = os.getenv("USER_AUTH_KEY", None) COMMUNITY_BOT_ID = os.getenv("COMMUNITY_BOT_ID", "") IS_DEMO = os_util.parse_boolean_environment_var("IS_DEMO", "False") IS_CLOUD_ENV = os_util.parse_boolean_environment_var("IS_CLOUD_ENV", "false") From 1244896485a22ea60d8e8e4693d6bcdeedd1803c Mon Sep 17 00:00:00 2001 From: Guillaume De Saint Martin <guillaumemdsm@gmail.com> Date: Sun, 25 Aug 2024 18:07:24 +0200 Subject: [PATCH 6/7] [Requirements] bump --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9029ec652..a178de88f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ # Drakkar-Software requirements -OctoBot-Commons==1.9.54 +OctoBot-Commons==1.9.55 OctoBot-Trading==2.4.103 OctoBot-Evaluators==1.9.5 OctoBot-Tentacles-Manager==2.9.16 From 2bb3c49ae178bb5386a13d2985ce574f90c276f0 Mon Sep 17 00:00:00 2001 From: Guillaume De Saint Martin <guillaumemdsm@gmail.com> Date: Sun, 25 Aug 2024 18:36:15 +0200 Subject: [PATCH 7/7] [Version] v2.0.4 --- CHANGELOG.md | 16 ++++++++++++++++ README.md | 2 +- octobot/__init__.py | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 400d12492..e1cc79586 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 *It is strongly advised to perform an update of your tentacles after updating OctoBot. (start.py tentacles --install --all)* +## [2.0.4] - 2024-08-25 +### Added +- [BitMart] The BitMart exchange is now officially supported +- [GridTrading] Fund redispatch delay config +### Updated +- [WebInterface] Improve portfolio history display and make it more flexible +- [Webhook] Make error messages easier to understand +- [CCXT] update to ccxt 4.3.85 +- [Community] Fix community authentication related issues +### Fixed +- [GridTrading] Fixed grid reset issues when funds redispatch is enabled +- [MEXC] Fixed MEXC traded pairs fetching issues +- [OKX] Fixed leveraging parsing issues +- [WebInterface] Fixed order cancel UI issues +- [Configuration] Fixed recovery file related iss + ## [2.0.3] - 2024-08-03 ### Added - [IndexTradingMode]: Default profile, intra-day, real time update option and custom content diff --git a/README.md b/README.md index f60a82512..bd11ab65b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# OctoBot [2.0.3](https://github.com/Drakkar-Software/OctoBot/blob/master/CHANGELOG.md) +# OctoBot [2.0.4](https://github.com/Drakkar-Software/OctoBot/blob/master/CHANGELOG.md) [![PyPI](https://img.shields.io/pypi/v/OctoBot.svg?logo=pypi)](https://pypi.org/project/OctoBot) [![Downloads](https://pepy.tech/badge/octobot/month)](https://pepy.tech/project/octobot) [![Dockerhub](https://img.shields.io/docker/pulls/drakkarsoftware/octobot.svg?logo=docker)](https://hub.docker.com/r/drakkarsoftware/octobot) diff --git a/octobot/__init__.py b/octobot/__init__.py index bd2f79797..edb057f5d 100644 --- a/octobot/__init__.py +++ b/octobot/__init__.py @@ -16,5 +16,5 @@ PROJECT_NAME = "OctoBot" AUTHOR = "Drakkar-Software" -VERSION = "2.0.3" # major.minor.revision +VERSION = "2.0.4" # major.minor.revision LONG_VERSION = f"{VERSION}"