package jitsi import ( "crypto/rand" "encoding/hex" "fmt" "time" "github.com/golang-jwt/jwt/v5" ) type Config struct { // Secret is the HMAC-SHA256 shared secret (at least 32 bytes hex-encoded). Secret string // AppID is the Jitsi App ID (the "iss" and "aud" claim value). AppID string // AppName is the friendly name shown in the Jitsi client (optional). AppName string // Domain is the Jitsi Meet domain (e.g. "meet.jit.si"). Domain string // SiteDomain is the public domain of this portal (e.g. "portal.example.com"). // When set, avatar URLs embedded in the JWT are absolute (https://...) so // Jitsi, running on another domain, can load them. When empty, relative // URLs are used. SiteDomain string } type JitsiClaims struct { jwt.RegisteredClaims Room string `json:"room,omitempty"` Moderator bool `json:"moderator"` Context *JitsiContext `json:"context,omitempty"` } type JitsiContext struct { User *JitsiUser `json:"user,omitempty"` } type JitsiUser struct { ID string `json:"id"` Name string `json:"name"` Email string `json:"email,omitempty"` Avatar string `json:"avatar,omitempty"` } // GenerateSecret creates a new random hex-encoded 32-byte secret. func GenerateSecret() string { b := make([]byte, 32) rand.Read(b) return hex.EncodeToString(b) } // MeetURL returns the full Jitsi Meet URL for a room with a JWT token. // If Domain is empty it defaults to "meet.jit.si". func (c *Config) MeetURL(room, jwtToken string) string { domain := c.Domain if domain == "" { domain = "meet.jit.si" } return fmt.Sprintf("https://%s/%s?jwt=%s", domain, room, jwtToken) } // GenerateToken creates a JWT for the given room and user. // moderator grants Jitsi moderator privileges (mute, kick, etc.) and avatar // is the user's avatar URL (may be empty). func (c *Config) GenerateToken(userID, userName, room string, moderator bool, avatar string) (string, error) { if c.Secret == "" { return "", fmt.Errorf("jitsi secret is not configured") } if c.AppID == "" { return "", fmt.Errorf("jitsi app id is not configured") } now := time.Now().UTC() claims := JitsiClaims{ RegisteredClaims: jwt.RegisteredClaims{ Issuer: c.AppID, Subject: userID, Audience: jwt.ClaimStrings{c.AppID}, IssuedAt: jwt.NewNumericDate(now), NotBefore: jwt.NewNumericDate(now), ExpiresAt: jwt.NewNumericDate(now.Add(1 * time.Hour)), ID: fmt.Sprintf("%s-%d", userID, now.Unix()), }, Room: room, Moderator: moderator, Context: &JitsiContext{ User: &JitsiUser{ ID: userID, Name: userName, Avatar: avatar, }, }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString([]byte(c.Secret)) }