diff --git a/server/venueless/api/task.py b/server/venueless/api/task.py new file mode 100644 index 00000000..88d46fa0 --- /dev/null +++ b/server/venueless/api/task.py @@ -0,0 +1,139 @@ +import datetime +import datetime as dt +import logging +import uuid + +import jwt +import requests +from celery import shared_task +from django.conf import settings + +from venueless.core.models.auth import ShortToken +from venueless.core.models.world import World + +logger = logging.getLogger(__name__) + + +def generate_video_token(world, days, number, traits, long=False): + """ + Generate video token + :param world: World object + :param days: A integer representing the number of days the token is valid + :param number: A integer representing the number of tokens to generate + :param traits: A dictionary representing the traits of the token + :param long: A boolean representing if the token is long or short + :return: A list of tokens + """ + jwt_secrets = world.config.get("JWT_secrets", []) + if not jwt_secrets: + logger.error("JWT_secrets is missing or empty in the configuration") + return + jwt_config = jwt_secrets[0] + secret = jwt_config.get("secret") + audience = jwt_config.get("audience") + issuer = jwt_config.get("issuer") + iat = datetime.datetime.utcnow() + exp = iat + datetime.timedelta(days=days) + result = [] + bulk_create = [] + for _ in range(number): + payload = { + "iss": issuer, + "aud": audience, + "exp": exp, + "iat": iat, + "uid": str(uuid.uuid4()), + "traits": traits, + } + token = jwt.encode(payload, secret, algorithm="HS256") + if long: + result.append(token) + else: + st = ShortToken(world=world, long_token=token, expires=exp) + result.append(st.short_token) + bulk_create.append(st) + + if not long: + ShortToken.objects.bulk_create(bulk_create) + return result + + +def generate_talk_token(video_settings, video_tokens, event_slug): + """ + Generate talk token + :param video_settings: A dictionary representing the video settings + :param video_tokens: A list of video tokens + :param event_slug: A string representing the event slug + :return: A token + """ + iat = dt.datetime.utcnow() + exp = iat + dt.timedelta(days=30) + payload = { + "exp": exp, + "iat": iat, + "video_tokens": video_tokens, + "slug": event_slug, + } + token = jwt.encode(payload, video_settings.get("secret"), algorithm="HS256") + return token + + +@shared_task(bind=True, max_retries=5, default_retry_delay=60) +def configure_video_settings_for_talks( + self, world_id, days, number, traits, long=False +): + """ + Configure video settings for talks + :param self: instance of the task + :param world_id: A integer representing the world id + :param days: A integer representing the number of days the token is valid + :param number: A integer representing the number of tokens to generate + :param traits: A dictionary representing the traits of the token + :param long: A boolean representing if the token is long or short + """ + try: + if not isinstance(world_id, str) or not world_id.isalnum(): + raise ValueError("Invalid world_id format") + world = World.objects.get(id=world_id) + event_slug = world_id + jwt_secrets = world.config.get("JWT_secrets", []) + if not jwt_secrets: + logger.error("JWT_secrets is missing or empty in the configuration") + return + jwt_config = jwt_secrets[0] + video_tokens = generate_video_token(world, days, number, traits, long) + talk_token = generate_talk_token(jwt_config, video_tokens, event_slug) + header = { + "Content-Type": "application/json", + "Authorization": f"Bearer {talk_token}", + } + payload = { + "video_settings": { + "audience": jwt_config.get("audience"), + "issuer": jwt_config.get("issuer"), + "secret": jwt_config.get("secret"), + } + } + requests.post( + "{}/api/configure-video-settings/".format(settings.EVENTYAY_TALK_BASE_PATH), + json=payload, + headers=header, + ) + world.config["pretalx"] = { + "event": event_slug, + "domain": "{}".format(settings.EVENTYAY_TALK_BASE_PATH), + "pushed": datetime.datetime.now().isoformat(), + "connected": True, + } + world.save() + except requests.exceptions.ConnectionError as e: + logger.error("Connection error: %s", str(e)) + self.retry(exc=e) + except requests.exceptions.HTTPError as e: + if e.response.status_code in (401, 403, 404): + logger.error("Non-retryable error: %s", str(e)) + raise + logger.error("HTTP error: %s", str(e)) + self.retry(exc=e) + except ValueError as e: + logger.error("Error configuring video settings: %s", e) diff --git a/server/venueless/api/views.py b/server/venueless/api/views.py index 1652eb99..1ca1d1cb 100644 --- a/server/venueless/api/views.py +++ b/server/venueless/api/views.py @@ -31,6 +31,7 @@ from venueless.core.services.world import notify_schedule_change, notify_world_change from ..core.models import Room, World +from .task import configure_video_settings_for_talks from .utils import get_protocol logger = logging.getLogger(__name__) @@ -149,6 +150,15 @@ def post(request, *args, **kwargs) -> JsonResponse: title = titles.get(locale) or titles.get("en") or title_default + attendee_trait_grants = request.data.get("traits", {}).get("attendee", "") + trait_grants = { + "admin": ["admin"], + "attendee": ( + [attendee_trait_grants] if attendee_trait_grants else ["attendee"] + ), + "scheduleuser": ["schedule-update"], + } + # if world already exists, update it, otherwise create a new world world_id = request.data.get("id") domain_path = "{}{}/{}".format( @@ -165,6 +175,7 @@ def post(request, *args, **kwargs) -> JsonResponse: world.domain = domain_path world.locale = request.data.get("locale") or "en" world.timezone = request.data.get("timezone") or "UTC" + world.trait_grants = trait_grants world.save() else: world = World.objects.create( @@ -174,8 +185,11 @@ def post(request, *args, **kwargs) -> JsonResponse: locale=request.data.get("locale") or "en", timezone=request.data.get("timezone") or "UTC", config=config, + trait_grants=trait_grants, ) - + configure_video_settings_for_talks.delay( + world_id, days=30, number=1, traits=["schedule-update"], long=True + ) site_url = settings.SITE_URL protocol = get_protocol(site_url) world.domain = "{}://{}".format(protocol, domain_path) diff --git a/server/venueless/settings.py b/server/venueless/settings.py index 648942d3..d2fe5f17 100644 --- a/server/venueless/settings.py +++ b/server/venueless/settings.py @@ -202,6 +202,9 @@ MEDIA_URL = os.getenv( "VENUELESS_MEDIA_URL", config.get("urls", "media", fallback="/media/") ) +EVENTYAY_TALK_BASE_PATH = config.get( + "urls", "eventyay-talk", fallback="https://app-test.eventyay.com/talk" +) WEBSOCKET_PROTOCOL = os.getenv( "VENUELESS_WEBSOCKET_PROTOCOL",