Skip to content

Commit ff93532

Browse files
committed
JaaS JWT creation and retrieval
1 parent 35a59f1 commit ff93532

File tree

4 files changed

+214
-2
lines changed

4 files changed

+214
-2
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,6 @@ local_settings.py
126126

127127
.env
128128
.venv/
129-
db.sqlite3
129+
db.sqlite3
130+
jitsi_key.pem
131+
jitsi-key.pem

authentication/jaasjwt.py

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
# https://github.com/8x8/jaas_demo/blob/main/jaas-jwt-samples/python/jass-jwt.py
2+
3+
import sys
4+
import os
5+
import time
6+
import uuid
7+
from authlib.jose import jwt
8+
9+
10+
class JaaSJwtBuilder:
11+
"""
12+
The JaaSJwtBuilder class helps with the generation of the JaaS JWT.
13+
"""
14+
15+
EXP_TIME_DELAY_SEC = 7200
16+
# Used as a delay for the exp claim value.
17+
18+
NBF_TIME_DELAY_SEC = 10
19+
# Used as a delay for the nbf claim value.
20+
21+
def __init__(self) -> None:
22+
self.header = {'alg': 'RS256'}
23+
self.userClaims = {}
24+
self.featureClaims = {}
25+
self.payloadClaims = {}
26+
27+
def withDefaults(self):
28+
"""Returns the JaaSJwtBuilder with default valued claims."""
29+
return self.withExpTime(int(time.time() + JaaSJwtBuilder.EXP_TIME_DELAY_SEC)) \
30+
.withNbfTime(int(time.time() - JaaSJwtBuilder.NBF_TIME_DELAY_SEC)) \
31+
.withLiveStreamingEnabled(False) \
32+
.withRecordingEnabled(False) \
33+
.withOutboundCallEnabled(False) \
34+
.withTranscriptionEnabled(False) \
35+
.withModerator(True) \
36+
.withRoomName('*') \
37+
.withUserId(str(uuid.uuid4()))
38+
39+
def withApiKey(self, apiKey):
40+
"""
41+
Returns the JaaSJwtBuilder with the kid claim(apiKey) set.
42+
43+
:param apiKey A string as the API Key https://jaas.8x8.vc/#/apikeys
44+
"""
45+
self.header['kid'] = apiKey
46+
return self
47+
48+
def withUserAvatar(self, avatarUrl):
49+
"""
50+
Returns the JaaSJwtBuilder with the avatar claim set.
51+
52+
:param avatarUrl A string representing the url to get the user avatar.
53+
"""
54+
self.userClaims['avatar'] = avatarUrl
55+
return self
56+
57+
def withModerator(self, isModerator):
58+
"""
59+
Returns the JaaSJwtBuilder with the moderator claim set.
60+
61+
:param isModerator A boolean if set to True, user is moderator and False otherwise.
62+
"""
63+
self.userClaims['moderator'] = 'true' if isModerator == True else 'false'
64+
return self
65+
66+
def withUserName(self, userName):
67+
"""
68+
Returns the JaaSJwtBuilder with the name claim set.
69+
70+
:param userName A string representing the user's name.
71+
"""
72+
self.userClaims['name'] = userName
73+
return self
74+
75+
def withUserEmail(self, userEmail):
76+
"""
77+
Returns the JaaSJwtBuilder with the email claim set.
78+
79+
:param userEmail A string representing the user's email address.
80+
"""
81+
self.userClaims['email'] = userEmail
82+
return self
83+
84+
def withLiveStreamingEnabled(self, isEnabled):
85+
"""
86+
Returns the JaaSJwtBuilder with the livestreaming claim set.
87+
88+
:param isEnabled A boolean if set to True, live streaming is enabled and False otherwise.
89+
"""
90+
self.featureClaims['livestreaming'] = 'true' if isEnabled == True else 'false'
91+
return self
92+
93+
def withRecordingEnabled(self, isEnabled):
94+
"""
95+
Returns the JaaSJwtBuilder with the recording claim set.
96+
97+
:param isEnabled A boolean if set to True, recording is enabled and False otherwise.
98+
"""
99+
self.featureClaims['recording'] = 'true' if isEnabled == True else 'false'
100+
return self
101+
102+
def withTranscriptionEnabled(self, isEnabled):
103+
"""
104+
Returns the JaaSJwtBuilder with the transcription claim set.
105+
106+
:param isEnabled A boolean if set to True, transcription is enabled and False otherwise.
107+
"""
108+
self.featureClaims['transcription'] = 'true' if isEnabled == True else 'false'
109+
return self
110+
111+
def withOutboundCallEnabled(self, isEnabled):
112+
"""
113+
Returns the JaaSJwtBuilder with the outbound-call claim set.
114+
115+
:param isEnabled A boolean if set to True, outbound calls are enabled and False otherwise.
116+
"""
117+
self.featureClaims['outbound-call'] = 'true' if isEnabled == True else 'false'
118+
return self
119+
120+
def withExpTime(self, expTime):
121+
"""
122+
Returns the JaaSJwtBuilder with exp claim set. Use the defaults, you won't have to change this value too much.
123+
124+
:param expTime Unix time in seconds since epochs plus a delay. Expiration time of the JWT.
125+
"""
126+
self.payloadClaims['exp'] = expTime
127+
return self
128+
129+
def withNbfTime(self, nbfTime):
130+
"""
131+
Returns the JaaSJwtBuilder with nbf claim set. Use the defaults, you won't have to change this value too much.
132+
133+
:param nbfTime Unix time in seconds since epochs.
134+
"""
135+
self.payloadClaims['nbfTime'] = nbfTime
136+
return self
137+
138+
def withRoomName(self, roomName):
139+
"""
140+
Returns the JaaSJwtBuilder with room claim set.
141+
142+
:param roomName A string representing the room to join.
143+
"""
144+
self.payloadClaims['room'] = roomName
145+
return self
146+
147+
def withAppID(self, AppId):
148+
"""
149+
Returns the JaaSJwtBuilder with the sub claim set.
150+
151+
:param AppId A string representing the unique AppID (previously tenant).
152+
"""
153+
self.payloadClaims['sub'] = AppId
154+
return self
155+
156+
def withUserId(self, userId):
157+
"""
158+
Returns the JaaSJwtBuilder with the id claim set.
159+
160+
:param A string representing the user, should be unique from your side.
161+
"""
162+
self.userClaims['id'] = userId
163+
return self
164+
165+
def signWith(self, key):
166+
"""
167+
Returns a signed JWT.
168+
169+
:param key A string representing the private key in PEM format.
170+
"""
171+
context = {'user': self.userClaims, 'features': self.featureClaims}
172+
self.payloadClaims['context'] = context
173+
self.payloadClaims['iss'] = 'chat'
174+
self.payloadClaims['aud'] = 'jitsi'
175+
return jwt.encode(self.header, self.payloadClaims, key)

authentication/urls.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
urlpatterns = [
99
path("register/", views.RegisterView.as_view()),
1010
path("token/", views.MyTokenObtainPairView.as_view(), name="token_obtain_pair"),
11+
path("token/jaas", views.RetrieveJaasToken.as_view(), name="jaas_jwt"),
1112
path("token/refresh", TokenRefreshView.as_view(), name="token_refresh"),
1213
path("token/check", TokenVerifyView.as_view(), name="token_check"),
13-
path("me/", views.UserInformation.as_view(), name="my_profile"),
14+
path("me/", views.UserInformation.as_view(), name="my_profile"),
15+
1416
]

authentication/views.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
import os
12
from rest_framework import views, permissions, status, generics
23
from rest_framework.response import Response
34
from rest_framework.views import APIView
45
from rest_framework_simplejwt.views import TokenObtainPairView
56
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
67

8+
from pathlib import Path
9+
710
from authentication.models import ManthanoUser
11+
from .jaasjwt import JaaSJwtBuilder
812
from . import serializers
913

1014

@@ -32,3 +36,32 @@ class UserInformation(APIView):
3236
def get(self, request):
3337
serializer = serializers.UserSerializer(request.user)
3438
return Response(serializer.data)
39+
40+
41+
class RetrieveJaasToken(APIView):
42+
permission_classes = [permissions.IsAuthenticated]
43+
44+
def get(self, request):
45+
from decouple import config
46+
47+
try:
48+
BASE_DIR = Path(__file__).resolve().parent.parent
49+
fp = os.path.join(BASE_DIR, 'jitsi-key.pem')
50+
private_key = open(fp, 'r')
51+
except Exception as e:
52+
print(e)
53+
return Response('There was an error while reading Jitsi private API key.', status=status.HTTP_500_INTERNAL_SERVER_ERROR)
54+
55+
user = request.user
56+
57+
jaasJWT = JaaSJwtBuilder()
58+
59+
token = jaasJWT.withDefaults() \
60+
.withUserName(user.username) \
61+
.withUserEmail(user.email) \
62+
.withUserId(user.id) \
63+
.withApiKey(config('JITSI_API_KEY')) \
64+
.withAppID(config('JITSI_APP_ID')) \
65+
.signWith(private_key.read())
66+
67+
return Response(token)

0 commit comments

Comments
 (0)