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