Files
uPortal/internal/jitsi/jitsi.go
T
2026-10-15 23:25:46 +03:00

99 lines
2.7 KiB
Go

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))
}