Try try again... Jitsi JWT....

This commit is contained in:
Scott Idem
2025-12-02 18:36:56 -05:00
parent 412277b3a7
commit 5535b1af34

View File

@@ -216,76 +216,87 @@ async def get_api_temp_token(
# Updated 2025-09-18 # Updated 2025-12-02
# It's best practice to import settings from a config file or environment variables # It's best practice to import settings from a config file or environment variables
# For this example, we'll hardcode them, but you should use your actual values # For this example, we'll hardcode them, but you should use your actual values
# from your .env file # from your .env file
JWT_APP_ID = "my_jitsi_app_id" JWT_APP_ID = "my_jitsi_app_id"
JWT_APP_SECRET = "my_jitsi_app_secret-9876543210" JWT_APP_SECRET = "my_jitsi_app_secret-9876543210"
JITSI_DOMAIN = "jitsi.dgrzone.com"
# Define the data model for the incoming request body from the client # Define the data model for the incoming request body from the client
class JitsiTokenRequest(BaseModel): class JitsiTokenRequest(BaseModel):
"""Defines the expected request body from your frontend."""
room: str = Field(..., description="The name of the Jitsi room.") room: str = Field(..., description="The name of the Jitsi room.")
name: str = Field(..., description="The display name of the user.") name: str = Field(..., description="The display name of the user.")
email: EmailStr = Field(..., description="The email of the user.") email: EmailStr = Field(..., description="The email of the user.")
is_moderator: bool = Field(..., description="Whether the user should be a moderator.") is_moderator: bool = Field(..., description="Whether the user should be a moderator.")
features: Optional[Dict[str, bool]] = Field(
None, # Clearly separated override categories
description="Optional features to enable for the Jitsi meeting, such as livestreaming, recording, etc." features: Optional[Dict[str, bool]] = Field(None, description="Feature flags like recording, livestreaming.")
) settings: Optional[Dict[str, bool]] = Field(None, description="User profile settings like startMuted, reactionsMuted.")
# Optional settings for the Jitsi meeting config: Optional[Dict[str, any]] = Field(None, description="Overrides for config.js properties.")
# settings: Optional[Dict[str, Union[bool, int]]] = Field(
# None,
# description="Optional settings for the Jitsi meeting, such as startAudioMuted, startVideoMuted, etc."
# )
# A simple endpoint to generate the Jitsi-specific JWT # A simple endpoint to generate the Jitsi-specific JWT
@router.post("/jitsi_token") @router.post("/jitsi_token")
async def create_jitsi_jwt( async def create_jitsi_jwt(
request_data: JitsiTokenRequest = Body(...), request_data: JitsiTokenRequest = Body(...),
# commons: Common_Route_Params_Min = Depends(common_route_params_min), # commons: Common_Route_Params_Min = Depends(common_route_params_min),
): ):
""" """
Generates a Jitsi-specific JWT token for authentication. Generates a Jitsi-specific JWT token for authentication.
The token includes claims to set the user's name, email, and moderator status. The token includes claims to set the user's name, email, and moderator status.
""" """
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals()) log.debug(locals())
log.info("Generating Jitsi JWT...") log.debug(f"Received Jitsi token request: {request_data.model_dump_json(indent=2)}")
if not request_data.is_moderator:
raise HTTPException(
status_code=403,
detail="JWT generation is only permitted for moderators."
)
try: try:
# Build the payload with the correct structure accepted by Jitsi
# Define the JWT payload with all the required claims for Jitsi. # Define the JWT payload with all the required claims for Jitsi.
# This is where we securely set the moderator and user info. # This is where we securely set the moderator and user info.
payload = { payload = {
"aud": "jitsi", "aud": "jitsi",
"iss": JWT_APP_ID, "iss": JWT_APP_ID,
"sub": "jitsi.dgrzone.com", # Your Jitsi base domain "sub": JITSI_DOMAIN, # Your Jitsi base domain
"room": request_data.room, "room": request_data.room,
"exp": int(time.time()) + 3600, # Token expires in 1 hour "exp": int(time.time()) + 3600, # Token expires in 1 hour
# 1. Top-level 'config' for config.js overrides
"config": request_data.config or {},
# 2. 'context' for user data, features, and moderator settings
"context": { "context": {
"user": { "user": {
"name": request_data.name, "name": request_data.name,
"email": request_data.email, "email": request_data.email,
"moderator": "true" if request_data.is_moderator else "false" # CRITICAL: 'moderator' must be a boolean, not a string
}, "moderator": request_data.is_moderator,
"features": request_data.features if request_data.features else {
"livestreaming": False,
"recording": False,
"transcription": False,
# "outbound-call": False,
# "sip-outbound-call": False,
# "disableAudioLevels": False,
"startAudioMuted": True,
"startVideoMuted": True,
"startMuted": True,
"startHidden": True,
"followMe": False,
"reactionsMuted": True
}, },
# 'features' enables/disables major Jitsi functionalities
"features": request_data.features or {},
# 'settings' controls the moderator's default options in the settings panel
"settings": request_data.settings or {},
} }
} }
log.debug(payload)
# Clean up empty objects to keep the final JWT tidy
if not payload["config"]:
del payload["config"]
if not payload["context"]["features"]:
del payload["context"]["features"]
if not payload["context"]["settings"]:
del payload["context"]["settings"]
log.debug(f"Constructed JWT payload: {payload}")
# Sign the JWT with your secret key # Sign the JWT with your secret key
# The algorithm must be the same as configured in your Prosody setup (HS256) # The algorithm must be the same as configured in your Prosody setup (HS256)
@@ -296,11 +307,10 @@ async def create_jitsi_jwt(
return {"token": token} return {"token": token}
except Exception as e: except Exception as e:
log.exception("Failed to create JWT")
raise HTTPException(status_code=500, detail=f"Failed to create JWT: {str(e)}") raise HTTPException(status_code=500, detail=f"Failed to create JWT: {str(e)}")
@router.post('', response_model=Resp_Body_Base) @router.post('', response_model=Resp_Body_Base)
async def post_api_obj( async def post_api_obj(
obj: Api_Base, obj: Api_Base,