mode/jwt.sh: renable jwt setup on QJI #125
			
				
			
		
		
		
	
							
								
								
									
										5
									
								
								jm-bm.sh
								
								
								
								
							
							
						
						
									
										5
									
								
								jm-bm.sh
								
								
								
								
							| 
						 | 
					@ -23,6 +23,7 @@ if ! [ "$(id -u)" = 0 ]; then
 | 
				
			||||||
fi
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DOMAIN="$(find /etc/prosody/conf.d/ -name \*.lua|awk -F'.cfg' '!/localhost/{print $1}'|xargs basename)"
 | 
					DOMAIN="$(find /etc/prosody/conf.d/ -name \*.lua|awk -F'.cfg' '!/localhost/{print $1}'|xargs basename)"
 | 
				
			||||||
 | 
					MEET_CONF="/etc/jitsi/meet/$DOMAIN-config.js"
 | 
				
			||||||
CSS_FILE="/usr/share/jitsi-meet/css/all.css"
 | 
					CSS_FILE="/usr/share/jitsi-meet/css/all.css"
 | 
				
			||||||
TITLE_FILE="/usr/share/jitsi-meet/title.html"
 | 
					TITLE_FILE="/usr/share/jitsi-meet/title.html"
 | 
				
			||||||
INT_CONF="/usr/share/jitsi-meet/interface_config.js"
 | 
					INT_CONF="/usr/share/jitsi-meet/interface_config.js"
 | 
				
			||||||
| 
						 | 
					@ -87,10 +88,10 @@ sed -i "s| powered by the Jitsi Videobridge||g" "$TITLE_FILE"
 | 
				
			||||||
sed -i "/appNotInstalled/ s|{{app}}|$MOVILE_APP_NAME|g" /usr/share/jitsi-meet/lang/*
 | 
					sed -i "/appNotInstalled/ s|{{app}}|$MOVILE_APP_NAME|g" /usr/share/jitsi-meet/lang/*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#Custom UI changes
 | 
					#Custom UI changes
 | 
				
			||||||
if [ -f "$INT_CONF_ETC" ]; then
 | 
					if [ -f "$INT_CONF"] && [ -f "$INT_CONF_ETC" ]; then
 | 
				
			||||||
    echo "Static interface_config.js exists, skipping modification..."
 | 
					    echo "Static interface_config.js exists, skipping modification..."
 | 
				
			||||||
else
 | 
					else
 | 
				
			||||||
    echo "This setup doesn't have a static interface_config.js, checking changes..."
 | 
					    echo "This setup doesn't have a static interface_config.js, seting it up and applying changes..."
 | 
				
			||||||
    echo -e "\nPlease note that brandless mode will also overwrite support links.\n"
 | 
					    echo -e "\nPlease note that brandless mode will also overwrite support links.\n"
 | 
				
			||||||
    sed -i "21,32 s|Jitsi Meet|$APP_NAME|g" "$INT_CONF"
 | 
					    sed -i "21,32 s|Jitsi Meet|$APP_NAME|g" "$INT_CONF"
 | 
				
			||||||
    sed -i  "s|\([[:space:]]\)APP_NAME:.*| APP_NAME: \'$APP_NAME\',|" "$INT_CONF"
 | 
					    sed -i  "s|\([[:space:]]\)APP_NAME:.*| APP_NAME: \'$APP_NAME\',|" "$INT_CONF"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										91
									
								
								mode/jwt.sh
								
								
								
								
							
							
						
						
									
										91
									
								
								mode/jwt.sh
								
								
								
								
							| 
						 | 
					@ -14,83 +14,80 @@ done
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#DEBUG
 | 
					#DEBUG
 | 
				
			||||||
if [ "$MODE" = "debug" ]; then
 | 
					if [ "$MODE" = "debug" ]; then
 | 
				
			||||||
set -x
 | 
					    set -x
 | 
				
			||||||
fi
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DOMAIN="$(find /etc/prosody/conf.d/ -name \*.lua|awk -F'.cfg' '!/localhost/{print $1}'|xargs basename)"
 | 
					DOMAIN="$(find /etc/prosody/conf.d/ -name \*.lua|awk -F'.cfg' '!/localhost/{print $1}'|xargs basename)"
 | 
				
			||||||
MEET_CONF="/etc/jitsi/meet/$DOMAIN-config.js"
 | 
					MEET_CONF="/etc/jitsi/meet/$DOMAIN-config.js"
 | 
				
			||||||
JICOFO_SIP="/etc/jitsi/jicofo/sip-communicator.properties"
 | 
					JICOFO_SIP="/etc/jitsi/jicofo/sip-communicator.properties"
 | 
				
			||||||
 | 
					JICOFO_CONF="/etc/jitsi/jicofo/jicofo.conf"
 | 
				
			||||||
PROSODY_FILE="/etc/prosody/conf.d/$DOMAIN.cfg.lua"
 | 
					PROSODY_FILE="/etc/prosody/conf.d/$DOMAIN.cfg.lua"
 | 
				
			||||||
PROSODY_SYS="/etc/prosody/prosody.cfg.lua"
 | 
					PROSODY_SYS="/etc/prosody/prosody.cfg.lua"
 | 
				
			||||||
APP_ID="$(tr -dc "a-zA-Z0-9" < /dev/urandom | fold -w 16 | head -n1)"
 | 
					APP_ID="$(tr -dc "a-zA-Z0-9" < /dev/urandom | fold -w 16 | head -n1)"
 | 
				
			||||||
SECRET_APP="$(tr -dc "a-zA-Z0-9" < /dev/urandom | fold -w 64 | head -n1)"
 | 
					SECRET_APP="$(tr -dc "a-zA-Z0-9" < /dev/urandom | fold -w 64 | head -n1)"
 | 
				
			||||||
 | 
					ROOM="Two-Hour-Test-Room"
 | 
				
			||||||
SRP_STR="$(grep -n "VirtualHost \"$DOMAIN\"" "$PROSODY_FILE" | head -n1 | cut -d ":" -f1)"
 | 
					SRP_STR="$(grep -n "VirtualHost \"$DOMAIN\"" "$PROSODY_FILE" | head -n1 | cut -d ":" -f1)"
 | 
				
			||||||
SRP_END="$((SRP_STR + 10))"
 | 
					SRP_END="$((SRP_STR + 10))"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Required  openssl for Focal 20.04
 | 
					# Prosody 0.12 only
 | 
				
			||||||
if [ "$(lsb_release -sc)" = "focal" ]; then
 | 
					if command -v prosodyctl >/dev/null 2>&1; then
 | 
				
			||||||
echo "deb http://ppa.launchpad.net/rael-gc/rvm/ubuntu focal main" | \
 | 
					  PROSODY_VER="$(prosodyctl about 2>/dev/null | sed -n 's/^Prosody //p' | awk '{print $1}')"
 | 
				
			||||||
sudo tee /etc/apt/sources.list.d/rvm.list
 | 
					  case "$PROSODY_VER" in
 | 
				
			||||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys F4E3FBBE
 | 
					    0.12.*) : ;;
 | 
				
			||||||
apt-get update
 | 
					    *) echo "Prosody $PROSODY_VER NO supported for JWT mode (required 0.12.x)"
 | 
				
			||||||
 | 
					       exit 1 ;;
 | 
				
			||||||
 | 
					  esac
 | 
				
			||||||
fi
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
apt-get -y install \
 | 
					# Custom 5.4 lua workaround for prosody 0.12
 | 
				
			||||||
                    lua5.2 \
 | 
					echo "Warning: Ubuntu 22.04/24.04 don't ship the required lua inspect module 5.4,"
 | 
				
			||||||
                    liblua5.2 \
 | 
					echo "         so, we work arround it, be careful on further upgrades or changes."
 | 
				
			||||||
                    luarocks \
 | 
					install -d -m 755 /usr/share/lua/5.4
 | 
				
			||||||
                    libssl1.0-dev \
 | 
					ln -sf /usr/share/lua/5.3/inspect.lua /usr/share/lua/5.4/inspect.lua
 | 
				
			||||||
                    python3-jwt
 | 
					systemctl restart prosody jicofo jitsi-videobridge2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
luarocks install basexx
 | 
					sleep .1
 | 
				
			||||||
luarocks install luacrypto
 | 
					
 | 
				
			||||||
luarocks install lua-cjson 2.1.0-1
 | 
					# Install dependencies
 | 
				
			||||||
 | 
					apt-get -y install python3-jwt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
echo "set jitsi-meet-tokens/appid string $APP_ID" | debconf-set-selections
 | 
					echo "set jitsi-meet-tokens/appid string $APP_ID" | debconf-set-selections
 | 
				
			||||||
echo "set jitsi-meet-tokens/appsecret password $SECRET_APP" | debconf-set-selections
 | 
					echo "set jitsi-meet-tokens/appsecret password $SECRET_APP" | debconf-set-selections
 | 
				
			||||||
 | 
					
 | 
				
			||||||
apt-get install -y jitsi-meet-tokens
 | 
					apt-get install -y jitsi-meet-tokens
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#Setting up
 | 
					# Setting up prosody
 | 
				
			||||||
sed -i "s|c2s_require_encryption = true|c2s_require_encryption = false|" "$PROSODY_SYS"
 | 
					sed -i "s|c2s_require_encryption = true|c2s_require_encryption = false|" "$PROSODY_SYS"
 | 
				
			||||||
#-
 | 
					#-
 | 
				
			||||||
sed -i "$SRP_STR,$SRP_END{s|authentication = \"jitsi-anonymous\"|authentication = \"token\"|}" "$PROSODY_FILE"
 | 
					sed -i "$SRP_STR,$SRP_END{s|authentication = \"jitsi-anonymous\"|authentication = \"token\"|}" "$PROSODY_FILE"
 | 
				
			||||||
sed -i "s|--app_id=\"example_app_id\"|app_id=\"$APP_ID\"|" "$PROSODY_FILE"
 | 
					sed -i "s|--app_id=\"example_app_id\"|app_id=\"$APP_ID\"|" "$PROSODY_FILE"
 | 
				
			||||||
sed -i "s|--app_secret=\"example_app_secret\"|app_secret=\"$SECRET_APP\"|" "$PROSODY_FILE"
 | 
					sed -i "s|--app_secret=\"example_app_secret\"|app_secret=\"$SECRET_APP\"|" "$PROSODY_FILE"
 | 
				
			||||||
sed -i "/app_secret/a \\\\" "$PROSODY_FILE"
 | 
					sed -i "/app_secret/a \\\\" "$PROSODY_FILE"
 | 
				
			||||||
sed -i "/app_secret/a \ \ \ \ allow_empty_token = false" "$PROSODY_FILE"
 | 
					## Only token owners can create, open the room and become moderators: allow_empty_token = false
 | 
				
			||||||
 | 
					## other participants are redirected authentication to guest.
 | 
				
			||||||
 | 
					sed -i "/app_secret/a \ \ \ \ allow_empty_token = true" "$PROSODY_FILE"
 | 
				
			||||||
sed -i "/app_secret/a \\\\" "$PROSODY_FILE"
 | 
					sed -i "/app_secret/a \\\\" "$PROSODY_FILE"
 | 
				
			||||||
sed -i "/app_secret/a \ \ \ \ asap_accepted_issuers = { \"$APP_ID\" }" "$PROSODY_FILE"
 | 
					sed -i "/app_secret/a \ \ \ \ asap_accepted_issuers = { \"$APP_ID\" }" "$PROSODY_FILE"
 | 
				
			||||||
sed -i "/app_secret/a \ \ \ \ asap_accepted_audiences = { \"$APP_ID\", \"RocketChat\" }" "$PROSODY_FILE"
 | 
					sed -i "/app_secret/a \ \ \ \ asap_accepted_audiences = { \"$APP_ID\" }" "$PROSODY_FILE"
 | 
				
			||||||
sed -i "/app_secret/a \\\\" "$PROSODY_FILE"
 | 
					sed -i "/app_secret/a \\\\" "$PROSODY_FILE"
 | 
				
			||||||
sed -i "s|--allow_empty_token =.*|allow_empty_token = false|" "$PROSODY_FILE"
 | 
					sed -i "s|--allow_empty_token =.*|allow_empty_token = true|" "$PROSODY_FILE"
 | 
				
			||||||
sed -i 's|--"token_verification"|"token_verification"|' "$PROSODY_FILE"
 | 
					sed -i 's|--"token_verification"|"token_verification"|' "$PROSODY_FILE"
 | 
				
			||||||
 | 
					sed -i "/muc_lobby_rooms/a \ \ \ \ \ \ \ \ \"persistent_lobby\";" "$PROSODY_FILE"
 | 
				
			||||||
 | 
					sed -i "/token_verification/a \ \ \ \ \ \ \ \ \"muc_wait_for_host\";" "$PROSODY_FILE"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#Request auth
 | 
					# Set JWT and Guest settings
 | 
				
			||||||
sed -i "s|#org.jitsi.jicofo.auth.URL=EXT_JWT:|org.jitsi.jicofo.auth.URL=EXT_JWT:|" "$JICOFO_SIP"
 | 
					## Harden JWT auth, preventing "free" moderator by racing into room,
 | 
				
			||||||
 | 
					## only participants with token with moderator:true.
 | 
				
			||||||
 | 
					sed -i '1ijicofo.conference.enable-auto-owner = false' "$JICOFO_CONF"
 | 
				
			||||||
 | 
					## config.js
 | 
				
			||||||
sed -i "s|// anonymousdomain: 'guest.example.com'|anonymousdomain: \'guest.$DOMAIN\'|" "$MEET_CONF"
 | 
					sed -i "s|// anonymousdomain: 'guest.example.com'|anonymousdomain: \'guest.$DOMAIN\'|" "$MEET_CONF"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#Enable jibri recording
 | 
					# Setup guests and lobby
 | 
				
			||||||
cat  << REC-JIBRI >> "$PROSODY_FILE"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
VirtualHost "recorder.$DOMAIN"
 | 
					 | 
				
			||||||
  modules_enabled = {
 | 
					 | 
				
			||||||
    "ping";
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  authentication = "internal_hashed"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
REC-JIBRI
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#Setup guests and lobby
 | 
					 | 
				
			||||||
cat << P_SR >> "$PROSODY_FILE"
 | 
					cat << P_SR >> "$PROSODY_FILE"
 | 
				
			||||||
-- #Change back lobby - https://community.jitsi.org/t/64769/136
 | 
					 | 
				
			||||||
VirtualHost "guest.$DOMAIN"
 | 
					VirtualHost "guest.$DOMAIN"
 | 
				
			||||||
    authentication = "token"
 | 
					    authentication = "anonymous"
 | 
				
			||||||
    allow_empty_token = true
 | 
					 | 
				
			||||||
    c2s_require_encryption = false
 | 
					    c2s_require_encryption = false
 | 
				
			||||||
    speakerstats_component = "speakerstats.$DOMAIN"
 | 
					    speakerstats_component = "speakerstats.$DOMAIN"
 | 
				
			||||||
    app_id="$APP_ID";
 | 
					 | 
				
			||||||
    app_secret="$SECRET_APP";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    modules_enabled = {
 | 
					    modules_enabled = {
 | 
				
			||||||
      "speakerstats";
 | 
					      "speakerstats";
 | 
				
			||||||
| 
						 | 
					@ -102,14 +99,12 @@ echo -e "\nUse the following for your App (e.g. Rocket.Chat):\n"
 | 
				
			||||||
echo -e "\nAPP_ID: $APP_ID" && \
 | 
					echo -e "\nAPP_ID: $APP_ID" && \
 | 
				
			||||||
echo -e "SECRET_APP: $SECRET_APP\n"
 | 
					echo -e "SECRET_APP: $SECRET_APP\n"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
echo -e "You can test JWT authentication with the following token:\n"
 | 
					echo -e "You can test JWT authentication with the following token for the next 2 hours:\n"
 | 
				
			||||||
pyjwt3 --key="$SECRET_APP" \
 | 
					python3 tools/jitsi_token_maker_features.py \
 | 
				
			||||||
    encode \
 | 
					  --app-id "$APP_ID" --secret "$SECRET_APP" \
 | 
				
			||||||
    group="Rocket.Chat" \
 | 
					  --domain "$DOMAIN" --room "$ROOM" \
 | 
				
			||||||
    aud="$APP_ID" \
 | 
					  --moderator --features-all \
 | 
				
			||||||
    iss="$APP_ID" \
 | 
					  --minutes 120 --nbf-offset 300 --include-iat \
 | 
				
			||||||
    sub="$DOMAIN" \
 | 
					  --url "https://$DOMAIN/" <<<"$APP_SECRET"
 | 
				
			||||||
    room="*" \
 | 
					 | 
				
			||||||
    algorithm="HS256"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
read -n 1 -s -r -p $'\n'"Press any key to continue..."$'\n'
 | 
					read -n 1 -s -r -p $'\n'"Press any key to continue..."$'\n'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -819,9 +819,6 @@ restart_services() {
 | 
				
			||||||
    check_jibri
 | 
					    check_jibri
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Configure Jvb2
 | 
					 | 
				
			||||||
sed -i "/shard.HOSTNAME/s|localhost|$DOMAIN|" "$JVB2_SIP"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#--------------------------------------------------
 | 
					#--------------------------------------------------
 | 
				
			||||||
print_title "Configure Jibri"
 | 
					print_title "Configure Jibri"
 | 
				
			||||||
#--------------------------------------------------
 | 
					#--------------------------------------------------
 | 
				
			||||||
| 
						 | 
					@ -839,16 +836,6 @@ if [ "$ENABLE_SC" = "yes" ]; then
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fi
 | 
					fi
 | 
				
			||||||
sleep .1
 | 
					sleep .1
 | 
				
			||||||
#Enable jibri recording
 | 
					 | 
				
			||||||
cat  << REC-JIBRI >> "$PROSODY_FILE"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
VirtualHost "recorder.$DOMAIN"
 | 
					 | 
				
			||||||
  modules_enabled = {
 | 
					 | 
				
			||||||
    "ping";
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  authentication = "internal_hashed"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
REC-JIBRI
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#Enable Jibri withelist
 | 
					#Enable Jibri withelist
 | 
				
			||||||
sed -i "s|-- muc_lobby_whitelist|muc_lobby_whitelist|" "$PROSODY_FILE"
 | 
					sed -i "s|-- muc_lobby_whitelist|muc_lobby_whitelist|" "$PROSODY_FILE"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,158 @@
 | 
				
			||||||
 | 
					#!/usr/bin/env python3
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					JWT generator for self‑hosted Jitsi (also compatible with JAAS if desired)
 | 
				
			||||||
 | 
					- HS256 (HMAC) signing using only Python standard library (no external deps).
 | 
				
			||||||
 | 
					- Flags to omit exp/nbf (test tokens), include iat, and read secret from file/STDIN.
 | 
				
			||||||
 | 
					- Flags to populate context.features: recording, livestreaming, transcription, sip-in/out.
 | 
				
			||||||
 | 
					- Robust URL construction (escapes the room name).
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					import argparse, base64, hashlib, hmac, json, time, sys
 | 
				
			||||||
 | 
					from urllib.parse import quote
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def b64url(data: bytes) -> str:
 | 
				
			||||||
 | 
					    return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def sign_hs256(secret: str, signing_input: str) -> str:
 | 
				
			||||||
 | 
					    sig = hmac.new(secret.encode("utf-8"), signing_input.encode("ascii"), hashlib.sha256).digest()
 | 
				
			||||||
 | 
					    return b64url(sig)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def main():
 | 
				
			||||||
 | 
					    p = argparse.ArgumentParser(description="JWT generator for Jitsi (HS256)")
 | 
				
			||||||
 | 
					    # Identity / target
 | 
				
			||||||
 | 
					    p.add_argument("--app-id", required=True, help="app_id configured in Prosody/JAAS")
 | 
				
			||||||
 | 
					    p.add_argument("--secret", required=False, help="app_secret (HMAC/HS256)")
 | 
				
			||||||
 | 
					    p.add_argument("--secret-file", help="Read secret from file or '-' for STDIN")
 | 
				
			||||||
 | 
					    p.add_argument("--domain", help="Jitsi domain (e.g. meet.example.com) used as 'sub' in self-hosted")
 | 
				
			||||||
 | 
					    p.add_argument("--room", default="*", help="Target room (or '*' for all)")
 | 
				
			||||||
 | 
					    # Time
 | 
				
			||||||
 | 
					    p.add_argument("--minutes", type=int, default=60, help="Validity (minutes). Ignored if --no-exp")
 | 
				
			||||||
 | 
					    p.add_argument("--no-exp", action="store_true", help="Do not include 'exp' (tests only)")
 | 
				
			||||||
 | 
					    p.add_argument("--nbf-offset", type=int, default=10, help="Backdating seconds for 'nbf' (default: 10)")
 | 
				
			||||||
 | 
					    p.add_argument("--no-nbf", action="store_true", help="Do not include 'nbf' (tests only)")
 | 
				
			||||||
 | 
					    p.add_argument("--include-iat", action="store_true", help="Include 'iat'=now")
 | 
				
			||||||
 | 
					    # User
 | 
				
			||||||
 | 
					    p.add_argument("--user-name", default=None, help="User display name")
 | 
				
			||||||
 | 
					    p.add_argument("--user-email", default=None, help="User email")
 | 
				
			||||||
 | 
					    p.add_argument("--user-id", default=None, help="User unique ID")
 | 
				
			||||||
 | 
					    p.add_argument("--avatar", default=None, help="Avatar URL")
 | 
				
			||||||
 | 
					    p.add_argument("--moderator", action="store_true", help="Grant moderator role via token")
 | 
				
			||||||
 | 
					    p.add_argument("--moderator-as-string", action="store_true",
 | 
				
			||||||
 | 
					                   help="Use 'moderator': 'true'/'false' (string) instead of boolean")
 | 
				
			||||||
 | 
					    # Features (self-hosted with enableFeaturesBasedOnToken)
 | 
				
			||||||
 | 
					    p.add_argument("--feature-recording", action="store_true", help="Enable 'recording' in context.features")
 | 
				
			||||||
 | 
					    p.add_argument("--feature-livestreaming", action="store_true", help="Enable 'livestreaming' in context.features")
 | 
				
			||||||
 | 
					    p.add_argument("--feature-transcription", action="store_true", help="Enable 'transcription' in context.features")
 | 
				
			||||||
 | 
					    p.add_argument("--feature-sip-in", action="store_true", help="Enable 'sip-inbound-call' in context.features")
 | 
				
			||||||
 | 
					    p.add_argument("--feature-sip-out", action="store_true", help="Enable 'sip-outbound-call' in context.features")
 | 
				
			||||||
 | 
					    p.add_argument("--features-all", action="store_true", help="Enable all the features above")
 | 
				
			||||||
 | 
					    # Overrides / modes
 | 
				
			||||||
 | 
					    p.add_argument("--aud", default=None, help="Override 'aud' (default: app_id in self-hosted)")
 | 
				
			||||||
 | 
					    p.add_argument("--iss", default=None, help="Override 'iss' (default: app_id in self-hosted)")
 | 
				
			||||||
 | 
					    p.add_argument("--jaas", action="store_true",
 | 
				
			||||||
 | 
					                   help="JAAS mode: aud='jitsi', iss='chat', sub=app_id (ignores --domain for 'sub')")
 | 
				
			||||||
 | 
					    # Output
 | 
				
			||||||
 | 
					    p.add_argument("--url", default=None,
 | 
				
			||||||
 | 
					                   help="If provided (e.g. 'https://meet.example.com/'), prints full join URL with ?jwt=")
 | 
				
			||||||
 | 
					    p.add_argument("--print-json", action="store_true", help="Print payload JSON to STDERR (debug)")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    args = p.parse_args()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Secret: --secret-file takes precedence
 | 
				
			||||||
 | 
					    secret = args.secret
 | 
				
			||||||
 | 
					    if args.secret_file:
 | 
				
			||||||
 | 
					        if args.secret_file == "-":
 | 
				
			||||||
 | 
					            secret = sys.stdin.read().strip()
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            with open(args.secret_file, "r", encoding="utf-8") as fh:
 | 
				
			||||||
 | 
					                secret = fh.read().strip()
 | 
				
			||||||
 | 
					    if not secret:
 | 
				
			||||||
 | 
					        p.error("You must provide --secret or --secret-file (or --secret-file - for STDIN).")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    now = int(time.time())
 | 
				
			||||||
 | 
					    exp = None if args.no_exp else (now + args.minutes * 60)
 | 
				
			||||||
 | 
					    nbf = None if args.no_nbf else (now - max(args.nbf_offset, 0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Header
 | 
				
			||||||
 | 
					    header = {"typ": "JWT", "alg": "HS256"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Base claims by mode
 | 
				
			||||||
 | 
					    if args.jaas:
 | 
				
			||||||
 | 
					        aud = "jitsi"
 | 
				
			||||||
 | 
					        iss = "chat"
 | 
				
			||||||
 | 
					        sub = args.app_id
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        if not args.domain:
 | 
				
			||||||
 | 
					            p.error("--domain is required in self-hosted mode (without --jaas).")
 | 
				
			||||||
 | 
					        aud = args.aud or args.app_id
 | 
				
			||||||
 | 
					        iss = args.iss or args.app_id
 | 
				
			||||||
 | 
					        sub = args.domain
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # User / contexto
 | 
				
			||||||
 | 
					    user = {}
 | 
				
			||||||
 | 
					    if args.user_id: user["id"] = args.user_id
 | 
				
			||||||
 | 
					    if args.user_name: user["name"] = args.user_name
 | 
				
			||||||
 | 
					    if args.user_email: user["email"] = args.user_email
 | 
				
			||||||
 | 
					    if args.avatar: user["avatar"] = args.avatar
 | 
				
			||||||
 | 
					    if args.moderator:
 | 
				
			||||||
 | 
					        if args.moderator_as_string:
 | 
				
			||||||
 | 
					            user["moderator"] = "true"
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            user["moderator"] = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Features
 | 
				
			||||||
 | 
					    features = {}
 | 
				
			||||||
 | 
					    if args.features_all:
 | 
				
			||||||
 | 
					        features = {
 | 
				
			||||||
 | 
					            "recording": True,
 | 
				
			||||||
 | 
					            "livestreaming": True,
 | 
				
			||||||
 | 
					            "transcription": True,
 | 
				
			||||||
 | 
					            "sip-inbound-call": True,
 | 
				
			||||||
 | 
					            "sip-outbound-call": True
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        if args.feature_recording:     features["recording"] = True
 | 
				
			||||||
 | 
					        if args.feature_livestreaming: features["livestreaming"] = True
 | 
				
			||||||
 | 
					        if args.feature_transcription: features["transcription"] = True
 | 
				
			||||||
 | 
					        if args.feature_sip_in:        features["sip-inbound-call"] = True
 | 
				
			||||||
 | 
					        if args.feature_sip_out:       features["sip-outbound-call"] = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context = {}
 | 
				
			||||||
 | 
					    if user: context["user"] = user
 | 
				
			||||||
 | 
					    if features: context["features"] = features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    payload = {
 | 
				
			||||||
 | 
					        "aud": aud,
 | 
				
			||||||
 | 
					        "iss": iss,
 | 
				
			||||||
 | 
					        "sub": sub,
 | 
				
			||||||
 | 
					        "room": args.room,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if context:
 | 
				
			||||||
 | 
					        payload["context"] = context
 | 
				
			||||||
 | 
					    if exp is not None:
 | 
				
			||||||
 | 
					        payload["exp"] = exp
 | 
				
			||||||
 | 
					    if nbf is not None:
 | 
				
			||||||
 | 
					        payload["nbf"] = nbf
 | 
				
			||||||
 | 
					    if args.include_iat:
 | 
				
			||||||
 | 
					        payload["iat"] = now
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Build JWT manually
 | 
				
			||||||
 | 
					    signing_input = f"{b64url(json.dumps(header, separators=(',', ':'), ensure_ascii=False).encode())}." \
 | 
				
			||||||
 | 
					                    f"{b64url(json.dumps(payload, separators=(',', ':'), ensure_ascii=False).encode())}"
 | 
				
			||||||
 | 
					    signature = sign_hs256(secret, signing_input)
 | 
				
			||||||
 | 
					    token = f"{signing_input}.{signature}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if args.print_json:
 | 
				
			||||||
 | 
					        print(json.dumps(payload, indent=2, ensure_ascii=False), file=sys.stderr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if args.url:
 | 
				
			||||||
 | 
					        base = args.url if args.url.endswith("/") else args.url + "/"
 | 
				
			||||||
 | 
					        room_path = "" if args.room == "*" else quote(args.room, safe="")
 | 
				
			||||||
 | 
					        join_url = base + room_path
 | 
				
			||||||
 | 
					        sep = "&" if "?" in join_url else "?"
 | 
				
			||||||
 | 
					        print(f"{join_url}{sep}jwt={token}")
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        print(token)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__":
 | 
				
			||||||
 | 
					    main()
 | 
				
			||||||
		Loading…
	
		Reference in New Issue