73 lines
2.2 KiB
Go
73 lines
2.2 KiB
Go
package rtc
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"crypto/hmac"
|
||
|
|
"crypto/sha256"
|
||
|
|
"encoding/base64"
|
||
|
|
"encoding/json"
|
||
|
|
"errors"
|
||
|
|
"strings"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
type TokenClaims struct {
|
||
|
|
Version int `json:"v"`
|
||
|
|
RoomID string `json:"room_id"`
|
||
|
|
UserID string `json:"user_id"`
|
||
|
|
ExpiresAt int64 `json:"exp"`
|
||
|
|
}
|
||
|
|
|
||
|
|
type TokenIssuer interface {
|
||
|
|
Issue(roomID, userID string, expiresAt time.Time) (string, error)
|
||
|
|
}
|
||
|
|
|
||
|
|
type LocalTokenIssuer struct{ secret []byte }
|
||
|
|
|
||
|
|
func NewLocalTokenIssuer(secret []byte) (*LocalTokenIssuer, error) {
|
||
|
|
if len(secret) < 32 {
|
||
|
|
return nil, errors.New("RTC token signing secret must contain at least 32 bytes")
|
||
|
|
}
|
||
|
|
return &LocalTokenIssuer{secret: append([]byte(nil), secret...)}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (i *LocalTokenIssuer) Issue(roomID, userID string, expiresAt time.Time) (string, error) {
|
||
|
|
if roomID == "" || userID == "" || expiresAt.IsZero() {
|
||
|
|
return "", errors.New("room, user and expiry are required")
|
||
|
|
}
|
||
|
|
payload, err := json.Marshal(TokenClaims{Version: 1, RoomID: roomID, UserID: userID, ExpiresAt: expiresAt.Unix()})
|
||
|
|
if err != nil {
|
||
|
|
return "", err
|
||
|
|
}
|
||
|
|
encoded := base64.RawURLEncoding.EncodeToString(payload)
|
||
|
|
return "dev1." + encoded + "." + base64.RawURLEncoding.EncodeToString(i.sign(encoded)), nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (i *LocalTokenIssuer) Verify(token string, now time.Time) (TokenClaims, error) {
|
||
|
|
parts := strings.Split(token, ".")
|
||
|
|
if len(parts) != 3 || parts[0] != "dev1" {
|
||
|
|
return TokenClaims{}, errors.New("invalid RTC token")
|
||
|
|
}
|
||
|
|
signature, err := base64.RawURLEncoding.DecodeString(parts[2])
|
||
|
|
if err != nil || !hmac.Equal(signature, i.sign(parts[1])) {
|
||
|
|
return TokenClaims{}, errors.New("invalid RTC token")
|
||
|
|
}
|
||
|
|
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
|
||
|
|
if err != nil {
|
||
|
|
return TokenClaims{}, errors.New("invalid RTC token")
|
||
|
|
}
|
||
|
|
var claims TokenClaims
|
||
|
|
if err := json.Unmarshal(payload, &claims); err != nil || claims.Version != 1 || claims.RoomID == "" || claims.UserID == "" {
|
||
|
|
return TokenClaims{}, errors.New("invalid RTC token")
|
||
|
|
}
|
||
|
|
if now.Unix() >= claims.ExpiresAt {
|
||
|
|
return TokenClaims{}, errors.New("RTC token expired")
|
||
|
|
}
|
||
|
|
return claims, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (i *LocalTokenIssuer) sign(payload string) []byte {
|
||
|
|
mac := hmac.New(sha256.New, i.secret)
|
||
|
|
_, _ = mac.Write([]byte(payload))
|
||
|
|
return mac.Sum(nil)
|
||
|
|
}
|