136 lines
3.2 KiB
Go
136 lines
3.2 KiB
Go
package ultidraw
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"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 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)
|
|
}
|
|
|
|
func hashPath(p string) string {
|
|
h := sha256Hex([]byte(normalizePath(p)))
|
|
if len(h) > 16 {
|
|
return h[:16]
|
|
}
|
|
return h
|
|
}
|
|
|
|
func verifyPublicDocAccess(token, filePath, password, sig, secret string) bool {
|
|
if secret == "" {
|
|
return true
|
|
}
|
|
payload, err := verifyJWT(sig, secret)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
if payload["token"] != strings.TrimSpace(token) || payload["path"] != normalizePath(filePath) {
|
|
return false
|
|
}
|
|
if pw, _ := payload["password"].(string); pw != password {
|
|
return false
|
|
}
|
|
if exp, ok := payload["exp"].(float64); ok && int64(exp) < time.Now().Unix() {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func signPublicDocAccess(token, filePath, password, secret string) (string, error) {
|
|
payload := map[string]any{
|
|
"token": strings.TrimSpace(token),
|
|
"path": normalizePath(filePath),
|
|
"password": password,
|
|
"exp": time.Now().Add(2 * time.Hour).Unix(),
|
|
}
|
|
return signJWT(payload, secret)
|
|
}
|