diff --git a/app/routers/api.py b/app/routers/api.py index 61b19a0..3e4474f 100644 --- a/app/routers/api.py +++ b/app/routers/api.py @@ -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 # For this example, we'll hardcode them, but you should use your actual values # from your .env file JWT_APP_ID = "my_jitsi_app_id" 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 class JitsiTokenRequest(BaseModel): + """Defines the expected request body from your frontend.""" room: str = Field(..., description="The name of the Jitsi room.") name: str = Field(..., description="The display name of the user.") email: EmailStr = Field(..., description="The email of the user.") is_moderator: bool = Field(..., description="Whether the user should be a moderator.") - features: Optional[Dict[str, bool]] = Field( - None, - description="Optional features to enable for the Jitsi meeting, such as livestreaming, recording, etc." - ) - # Optional settings for the Jitsi meeting - # settings: Optional[Dict[str, Union[bool, int]]] = Field( - # None, - # description="Optional settings for the Jitsi meeting, such as startAudioMuted, startVideoMuted, etc." - # ) + + # Clearly separated override categories + 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.") + config: Optional[Dict[str, any]] = Field(None, description="Overrides for config.js properties.") # A simple endpoint to generate the Jitsi-specific JWT @router.post("/jitsi_token") 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. 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.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: + # Build the payload with the correct structure accepted by Jitsi # Define the JWT payload with all the required claims for Jitsi. # This is where we securely set the moderator and user info. payload = { "aud": "jitsi", "iss": JWT_APP_ID, - "sub": "jitsi.dgrzone.com", # Your Jitsi base domain + "sub": JITSI_DOMAIN, # Your Jitsi base domain "room": request_data.room, "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": { "user": { "name": request_data.name, "email": request_data.email, - "moderator": "true" if request_data.is_moderator else "false" - }, - "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 + # CRITICAL: 'moderator' must be a boolean, not a string + "moderator": request_data.is_moderator, }, + # '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 # 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} except Exception as e: + log.exception("Failed to create JWT") raise HTTPException(status_code=500, detail=f"Failed to create JWT: {str(e)}") - - @router.post('', response_model=Resp_Body_Base) async def post_api_obj( obj: Api_Base,