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

🤸🏽 Create side panel for meeting using 8x8 jaas #1031

Merged
merged 10 commits into from
May 20, 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
2 changes: 2 additions & 0 deletions backend/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ def create_app():
masters,
translation,
events,
meeting,
)

# why blueprints http://flask.pocoo.org/docs/1.0/blueprints/
Expand All @@ -118,6 +119,7 @@ def create_app():
app.register_blueprint(mentee.mentee, url_prefix="/api/mentee")
app.register_blueprint(messages.messages, url_prefix="/api/messages")
app.register_blueprint(notifications.notifications, url_prefix="/api/notifications")
app.register_blueprint(meeting.meeting, url_prefix="/api/meeting")
app.register_blueprint(masters.masters, url_prefix="/api/masters")
app.register_blueprint(translation.translation, url_prefix="/api/translation")

Expand Down
169 changes: 169 additions & 0 deletions backend/api/utils/jaas_jwt_builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import time, uuid
from authlib.jose import jwt

class JaaSJwtBuilder:
"""
The JaaSJwtBuilder class helps with the generation of the JaaS JWT.
"""

EXP_TIME_DELAY_SEC = 7200
# Used as a delay for the exp claim value.

NBF_TIME_DELAY_SEC = 10
# Used as a delay for the nbf claim value.

def __init__(self) -> None:
self.header = { 'alg' : 'RS256' }
self.userClaims = {}
self.featureClaims = {}
self.payloadClaims = {}

def withDefaults(self):
"""Returns the JaaSJwtBuilder with default valued claims."""
return self.withExpTime(int(time.time() + JaaSJwtBuilder.EXP_TIME_DELAY_SEC)) \
.withNbfTime(int(time.time() - JaaSJwtBuilder.NBF_TIME_DELAY_SEC)) \
.withLiveStreamingEnabled(True) \
.withRecordingEnabled(True) \
.withOutboundCallEnabled(True) \
.withTranscriptionEnabled(True) \
.withModerator(True) \
.withRoomName('*') \
.withUserId(str(uuid.uuid4()))

def withApiKey(self, apiKey):
"""
Returns the JaaSJwtBuilder with the kid claim(apiKey) set.

:param apiKey A string as the API Key https://jaas.8x8.vc/#/apikeys
"""
self.header['kid'] = apiKey
return self

def withUserAvatar(self, avatarUrl):
"""
Returns the JaaSJwtBuilder with the avatar claim set.

:param avatarUrl A string representing the url to get the user avatar.
"""
self.userClaims['avatar'] = avatarUrl
return self

def withModerator(self, isModerator):
"""
Returns the JaaSJwtBuilder with the moderator claim set.

:param isModerator A boolean if set to True, user is moderator and False otherwise.
"""
self.userClaims['moderator'] = 'true' if isModerator == True else 'false'
return self

def withUserName(self, userName):
"""
Returns the JaaSJwtBuilder with the name claim set.

:param userName A string representing the user's name.
"""
self.userClaims['name'] = userName
return self

def withUserEmail(self, userEmail):
"""
Returns the JaaSJwtBuilder with the email claim set.

:param userEmail A string representing the user's email address.
"""
self.userClaims['email'] = userEmail
return self

def withLiveStreamingEnabled(self, isEnabled):
"""
Returns the JaaSJwtBuilder with the livestreaming claim set.

:param isEnabled A boolean if set to True, live streaming is enabled and False otherwise.
"""
self.featureClaims['livestreaming'] = 'true' if isEnabled == True else 'false'
return self

def withRecordingEnabled(self, isEnabled):
"""
Returns the JaaSJwtBuilder with the recording claim set.

:param isEnabled A boolean if set to True, recording is enabled and False otherwise.
"""
self.featureClaims['recording'] = 'true' if isEnabled == True else 'false'
return self

def withTranscriptionEnabled(self, isEnabled):
"""
Returns the JaaSJwtBuilder with the transcription claim set.

:param isEnabled A boolean if set to True, transcription is enabled and False otherwise.
"""
self.featureClaims['transcription'] = 'true' if isEnabled == True else 'false'
return self

def withOutboundCallEnabled(self, isEnabled):
"""
Returns the JaaSJwtBuilder with the outbound-call claim set.

:param isEnabled A boolean if set to True, outbound calls are enabled and False otherwise.
"""
self.featureClaims['outbound-call'] = 'true' if isEnabled == True else 'false'
return self

def withExpTime(self, expTime):
"""
Returns the JaaSJwtBuilder with exp claim set. Use the defaults, you won't have to change this value too much.

:param expTime Unix time in seconds since epochs plus a delay. Expiration time of the JWT.
"""
self.payloadClaims['exp'] = expTime
return self

def withNbfTime(self, nbfTime):
"""
Returns the JaaSJwtBuilder with nbf claim set. Use the defaults, you won't have to change this value too much.

:param nbfTime Unix time in seconds since epochs.
"""
self.payloadClaims['nbfTime'] = nbfTime
return self

def withRoomName(self, roomName):
"""
Returns the JaaSJwtBuilder with room claim set.

:param roomName A string representing the room to join.
"""
self.payloadClaims['room'] = roomName
return self

def withAppID(self, AppId):
"""
Returns the JaaSJwtBuilder with the sub claim set.

:param AppId A string representing the unique AppID (previously tenant).
"""
self.payloadClaims['sub'] = AppId
return self

def withUserId(self, userId):
"""
Returns the JaaSJwtBuilder with the id claim set.

:param A string representing the user, should be unique from your side.
"""
self.userClaims['id'] = userId
return self

def signWith(self, key):
"""
Returns a signed JWT.

:param key A string representing the private key in PEM format.
"""
context = { 'user': self.userClaims, 'features': self.featureClaims }
self.payloadClaims['context'] = context
self.payloadClaims['iss'] = 'chat'
self.payloadClaims['aud'] = 'jitsi'
return jwt.encode(self.header, self.payloadClaims, key)
39 changes: 39 additions & 0 deletions backend/api/views/meeting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from __future__ import print_function

import os.path, json
from datetime import datetime, timedelta
from flask import Blueprint
from api.core import create_response
import sys, os

from api.utils.jaas_jwt_builder import JaaSJwtBuilder


meeting = Blueprint("meeting", __name__)

API_KEY = os.environ.get("EIGHT_X_EIGHT_API_KEY")
APP_ID = os.environ.get("REACT_APP_EIGHT_X_EIGHT_APP_ID")

@meeting.route("/generateToken", methods=["GET"])
def generateToken():
try:
scriptDir = os.path.dirname('/')
fp = os.path.join(scriptDir, 'rsa-private.pem')

with open(fp, 'r') as reader:
PRIVATE_KEY = reader.read()
jaasJwt = JaaSJwtBuilder()
token = jaasJwt.withDefaults() \
.withApiKey(API_KEY) \
.withUserName("User Name") \
.withUserEmail("[email protected]") \
.withModerator(False) \
.withAppID(APP_ID) \
.withUserAvatar("https://asda.com/avatar") \
.signWith(PRIVATE_KEY)

return create_response(data={"token": token.decode('utf-8'), "appID": APP_ID})

except Exception as error:
print(error)
return create_response(status=422, message=f'Failed to generate token for meeting')
3 changes: 3 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"dependencies": {
"@ant-design/icons": "^4.8.0",
"@emotion/css": "^11.11.2",
"@jitsi/react-sdk": "^1.4.0",
"@reduxjs/toolkit": "^1.9.3",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
Expand All @@ -24,10 +25,12 @@
"pretty-quick": "^3.1.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-draggable": "^4.4.6",
"react-i18next": "^12.3.1",
"react-json-to-table": "^0.1.7",
"react-player": "^2.11.2",
"react-redux": "^8.1.1",
"react-resizable": "^3.0.5",
"react-responsive": "^9.0.2",
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
Expand Down
23 changes: 14 additions & 9 deletions frontend/public/locales/ar/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
"mentor": "مُرشِد",
"message": "رسالة",
"messages": "رسائل",
"group_message":"رسائل المجموعة",
"group_message":"الدردشة الجماعية",
"name": "الاسم",
"no": "لا",
"other": "آخر",
Expand Down Expand Up @@ -593,13 +593,18 @@
"resend": "إعادة إرسال"
},
"meeting": {
"title": "توليد رابط الاجتماع",
"generateButton": "توليد",
"title": "إنشاء/الانضمام إلى الاجتماع",
"generateButton": "إنشاء غرفة",
"cancelButton": "إلغاء",
"generatedURL": "الرابط المُولَد",
"errorGenerating": "فشل في توليد رابط الاجتماع.",
"errorCopy": "فشل في نسخ الرابط إلى الحافظة.",
"copyMessage": "تم نسخ الرابط إلى الحافظة!",
"limitWarning": "يرجى ملاحظة أن الاجتماع سيكون له حد أقصى من 100 مشارك، ولا يوجد حد زمني."
}
"generatedURL": "اسم الغرفة",
"errorGenerating": "فشل في إنشاء الاجتماع.",
"errorCopy": "فشل في نسخ اسم الغرفة إلى الحافظة.",
"copyMessage": "تم نسخ اسم الغرفة إلى الحافظة!",
"limitWarning": "يرجى ملاحظة أن الاجتماع سيقتصر على 100 مشارك، ولن يكون هناك حد زمني.",
"placeHolder": "إنشاء أو لصق اسم الغرفة للانضمام",
"roomName": "يرجى تقديم اسم الغرفة للانضمام.",
"getToken": "غير قادر على الانضمام إلى الاجتماع",
"generateToken": "غير قادر على توليد رمز",
"joinMeeting": "الانضمام إلى الاجتماع"
}
}
21 changes: 13 additions & 8 deletions frontend/public/locales/en-US/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
"mentor": "Mentor",
"message": "Message",
"messages": "Messages",
"group_message":"Group Messages",
"group_message":"Group Chat",
"name": "Name",
"no": "No",
"confirm": "Confirm",
Expand Down Expand Up @@ -594,13 +594,18 @@
"resend": "Resend"
},
"meeting": {
"title": "Generate Meeting Link",
"generateButton": "Generate",
"title": "Create/Join Meeting",
"generateButton": "Create Room",
"cancelButton": "Cancel",
"generatedURL": "Generated URL",
"errorGenerating": "Failed to generate meeting link.",
"errorCopy": "Failed to copy URL to clipboard.",
"copyMessage": "URL copied to clipboard!",
"limitWarning": "Please note that the meeting will have a limit of 100 attendees, and no time limit."
"generatedURL": "Room Name",
"errorGenerating": "Failed to generate meeting.",
"errorCopy": "Failed to copy room name to clipboard.",
"copyMessage": "Room name copied to clipboard!",
"limitWarning": "Please note that the meeting will have a limit of 100 attendees, and no time limit.",
"placeHolder": "Generate or Paste Room Name to Join",
"roomName": "Please provide a room name to join.",
"getToken": "Unable to join meeting",
"generateToken": "Unable to generate token",
"joinMeeting": "Join Meeting"
}
}
21 changes: 13 additions & 8 deletions frontend/public/locales/es-US/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
"mentor": "Mentor",
"message": "Mensaje",
"messages": "Mensajes",
"group_message":"Mensajes grupales",
"group_message":"Chat de Grupo",
"name": "Nombre",
"no": "No",
"other": "Otro",
Expand Down Expand Up @@ -593,13 +593,18 @@
"resend": "Reenviar correo electrónico"
},
"meeting": {
"title": "Generar Enlace de Reunión",
"generateButton": "Generar",
"title": "Crear/Unirse a la Reunión",
"generateButton": "Crear Sala",
"cancelButton": "Cancelar",
"generatedURL": "URL Generada",
"errorGenerating": "Error al generar el enlace de reunión.",
"errorCopy": "Error al copiar la URL al portapapeles.",
"copyMessage": "¡URL copiada al portapapeles!",
"limitWarning": "Por favor, tenga en cuenta que la reunión tendrá un límite de 100 asistentes, y sin límite de tiempo."
"generatedURL": "Nombre de la Sala",
"errorGenerating": "Error al generar la reunión.",
"errorCopy": "Error al copiar el nombre de la sala al portapapeles.",
"copyMessage": "¡Nombre de la sala copiado al portapapeles!",
"limitWarning": "Tenga en cuenta que la reunión tendrá un límite de 100 asistentes y no habrá límite de tiempo.",
"placeHolder": "Genere o pegue el nombre de la sala para unirse",
"roomName": "Por favor proporcione un nombre de sala para unirse.",
"getToken": "No se puede unir a la reunión",
"generateToken": "No se puede generar el token",
"joinMeeting": "Unirse a la Reunión"
}
}
Loading
Loading