98 lines
2.7 KiB
Go
98 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))
|
||
|
|
}
|