ultisuite-backend/internal/api/richtext/jwt.go
R3D347HR4Y d4ccf7eb6e
Some checks are pending
CI / Go tests (push) Waiting to run
CI / Integration tests (push) Waiting to run
CI / DB migrations (push) Waiting to run
hocuspocus lol 2
2026-06-09 14:30:34 +02:00

114 lines
2.7 KiB
Go

package richtext
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"time"
)
type roomTokenPayload struct {
Room string `json:"room"`
Path string `json:"path"`
User string `json:"user"`
Sub string `json:"sub"`
Name string `json:"name"`
Mode string `json:"mode"`
Expires int64 `json:"exp"`
}
func signRoomToken(payload roomTokenPayload, secret string) (string, error) {
if secret == "" {
return "", nil
}
return signJWT(payload, secret)
}
func VerifyRoomToken(token, secret string) (roomTokenPayload, error) {
var out roomTokenPayload
if secret == "" {
return out, fmt.Errorf("missing secret")
}
raw, err := verifyJWT(token, secret)
if err != nil {
return out, err
}
if exp, ok := raw["exp"].(float64); ok && int64(exp) < time.Now().Unix() {
return out, fmt.Errorf("token expired")
}
b, _ := json.Marshal(raw)
_ = json.Unmarshal(b, &out)
return out, nil
}
func signJWT(payload any, secret string) (string, error) {
if secret == "" {
return "", nil
}
header := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"HS256","typ":"JWT"}`))
bodyBytes, err := json.Marshal(payload)
if err != nil {
return "", err
}
body := base64.RawURLEncoding.EncodeToString(bodyBytes)
mac := hmac.New(sha256.New, []byte(secret))
_, _ = mac.Write([]byte(header + "." + body))
sig := base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
return header + "." + body + "." + sig, nil
}
func verifyJWT(token, secret string) (map[string]any, error) {
if secret == "" || token == "" {
return nil, fmt.Errorf("missing token or secret")
}
parts := splitJWT(token)
if len(parts) != 3 {
return nil, fmt.Errorf("invalid token")
}
mac := hmac.New(sha256.New, []byte(secret))
_, _ = mac.Write([]byte(parts[0] + "." + parts[1]))
expected := base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(expected), []byte(parts[2])) {
return nil, fmt.Errorf("invalid signature")
}
raw, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
return nil, err
}
var payload map[string]any
if err := json.Unmarshal(raw, &payload); err != nil {
return nil, err
}
return payload, nil
}
func splitJWT(token string) []string {
var parts []string
start := 0
for i := 0; i < len(token); i++ {
if token[i] == '.' {
parts = append(parts, token[start:i])
start = i + 1
}
}
parts = append(parts, token[start:])
return parts
}
func sha256Hex(b []byte) string {
sum := sha256.Sum256(b)
return hexEncode(sum[:])
}
func hexEncode(b []byte) string {
const hexdigits = "0123456789abcdef"
out := make([]byte, len(b)*2)
for i, v := range b {
out[i*2] = hexdigits[v>>4]
out[i*2+1] = hexdigits[v&0x0f]
}
return string(out)
}