Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev merge #2720

Merged
merged 7 commits into from
Aug 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
4 changes: 4 additions & 0 deletions additional_tests/supabase_backend_tests/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -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=
4 changes: 4 additions & 0 deletions additional_tests/supabase_backend_tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
50 changes: 49 additions & 1 deletion additional_tests/supabase_backend_tests/test_user_elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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()
2 changes: 1 addition & 1 deletion octobot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
25 changes: 17 additions & 8 deletions octobot/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down
18 changes: 15 additions & 3 deletions octobot/community/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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):
Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions octobot/community/errors_upload/sentry_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down
19 changes: 17 additions & 2 deletions octobot/community/supabase_backend/community_supabase_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import logging
import httpx
import uuid
import json

import aiohttp
import gotrue.errors
Expand Down Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions octobot/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Drakkar-Software requirements
OctoBot-Commons==1.9.54
OctoBot-Trading==2.4.100
OctoBot-Commons==1.9.55
OctoBot-Trading==2.4.103
OctoBot-Evaluators==1.9.5
OctoBot-Tentacles-Manager==2.9.16
OctoBot-Services==1.6.17
Expand Down
Loading