initial reenable jwt
This commit is contained in:
parent
a3ffd2cd82
commit
5536451299
57
mode/jwt.sh
57
mode/jwt.sh
|
@ -20,10 +20,12 @@ 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))"
|
||||||
|
|
||||||
|
@ -37,6 +39,15 @@ if command -v prosodyctl >/dev/null 2>&1; then
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Custom 5.4 lua workaround for prosody 0.12
|
||||||
|
echo "Warning: Ubuntu 22.04/24.04 don't ship the required lua inspect module 5.4,"
|
||||||
|
echo " so, we work arround it, be careful on further upgrades or changes."
|
||||||
|
install -d -m 755 /usr/share/lua/5.4
|
||||||
|
ln -sf /usr/share/lua/5.3/inspect.lua /usr/share/lua/5.4/inspect.lua
|
||||||
|
systemctl restart prosody jicofo jitsi-videobridge2
|
||||||
|
|
||||||
|
sleep .1
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
apt-get -y install python3-jwt
|
apt-get -y install python3-jwt
|
||||||
|
|
||||||
|
@ -45,41 +56,36 @@ echo "set jitsi-meet-tokens/appsecret password $SECRET_APP" | debconf-set-select
|
||||||
|
|
||||||
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"
|
||||||
|
## 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 = false" "$PROSODY_FILE"
|
sed -i "/app_secret/a \ \ \ \ allow_empty_token = false" "$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 = false|" "$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
|
||||||
## JWT via Prosody: don't touch Jicofo
|
## Harden JWT auth, preventing "free" moderator by racing into room,
|
||||||
#sed -i "s|#org.jitsi.jicofo.auth.URL=EXT_JWT:|org.jitsi.jicofo.auth.URL=EXT_JWT:|" "$JICOFO_SIP"
|
## 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
|
|
||||||
cat << REC-JIBRI >> "$PROSODY_FILE"
|
|
||||||
|
|
||||||
VirtualHost "recorder.$DOMAIN"
|
|
||||||
modules_enabled = {
|
|
||||||
"ping";
|
|
||||||
}
|
|
||||||
authentication = "internal_hashed"
|
|
||||||
|
|
||||||
REC-JIBRI
|
|
||||||
|
|
||||||
# Setup guests and lobby
|
# Setup guests and lobby
|
||||||
cat << P_SR >> "$PROSODY_FILE"
|
cat << P_SR >> "$PROSODY_FILE"
|
||||||
VirtualHost "guest.$DOMAIN"
|
VirtualHost "guest.$DOMAIN"
|
||||||
authentication = "jitsi-anonymous"
|
authentication = "anonymous"
|
||||||
c2s_require_encryption = false
|
c2s_require_encryption = false
|
||||||
speakerstats_component = "speakerstats.$DOMAIN"
|
speakerstats_component = "speakerstats.$DOMAIN"
|
||||||
|
|
||||||
|
@ -93,15 +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 for the next hour:\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-file - \
|
||||||
--alg HS256 \
|
--domain "$DOMAIN" --room "$ROOM" \
|
||||||
group="Rocket.Chat" \
|
--moderator --features-all \
|
||||||
aud="$APP_ID" \
|
--minutes 120 --nbf-offset 300 --include-iat \
|
||||||
iss="$APP_ID" \
|
--url "https://$DOMAIN/" <<<"$APP_SECRET"
|
||||||
sub="$DOMAIN" \
|
|
||||||
room="*" \
|
|
||||||
exp="$(($(date +%s)+3600))"
|
|
||||||
|
|
||||||
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'
|
||||||
|
|
|
@ -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