89 lines
2.8 KiB
Go
89 lines
2.8 KiB
Go
package rtc
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"crypto/hmac"
|
||
|
|
"crypto/sha256"
|
||
|
|
"encoding/base64"
|
||
|
|
"encoding/binary"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestVolcengineTokenIssuerProducesSignedScopedDeterministicToken(t *testing.T) {
|
||
|
|
const appID = "661e7ae5543cf00000000000"
|
||
|
|
appKey := "unit-test-" + strings.Repeat("x", 23)
|
||
|
|
issuer, err := NewVolcengineTokenIssuer(appID, appKey, 5*time.Minute)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
expiresAt := time.Unix(1_800_000_000, 0).UTC()
|
||
|
|
first, err := issuer.Issue("room-1", "device-1", expiresAt)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
second, err := issuer.Issue("room-1", "device-1", expiresAt)
|
||
|
|
if err != nil || second != first {
|
||
|
|
t.Fatalf("idempotent issue mismatch: err=%v equal=%v", err, second == first)
|
||
|
|
}
|
||
|
|
if len(first) <= 27 || first[:27] != "001"+appID {
|
||
|
|
t.Fatalf("unexpected token prefix: %q", first)
|
||
|
|
}
|
||
|
|
|
||
|
|
content, err := base64.StdEncoding.DecodeString(first[27:])
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
message, rest := readTokenBytes(t, content)
|
||
|
|
signature, rest := readTokenBytes(t, rest)
|
||
|
|
if len(rest) != 0 {
|
||
|
|
t.Fatalf("unexpected trailing token bytes: %d", len(rest))
|
||
|
|
}
|
||
|
|
mac := hmac.New(sha256.New, []byte(appKey))
|
||
|
|
_, _ = mac.Write(message)
|
||
|
|
if !hmac.Equal(signature, mac.Sum(nil)) {
|
||
|
|
t.Fatal("token signature is invalid")
|
||
|
|
}
|
||
|
|
|
||
|
|
offset := 4 // deterministic nonce
|
||
|
|
issuedAt := binary.LittleEndian.Uint32(message[offset : offset+4])
|
||
|
|
offset += 4
|
||
|
|
expires := binary.LittleEndian.Uint32(message[offset : offset+4])
|
||
|
|
offset += 4
|
||
|
|
room, remaining := readTokenBytes(t, message[offset:])
|
||
|
|
user, remaining := readTokenBytes(t, remaining)
|
||
|
|
if string(room) != "room-1" || string(user) != "device-1" || issuedAt != uint32(expiresAt.Add(-5*time.Minute).Unix()) || expires != uint32(expiresAt.Unix()) {
|
||
|
|
t.Fatalf("unexpected token scope room=%s user=%s issued=%d expires=%d", room, user, issuedAt, expires)
|
||
|
|
}
|
||
|
|
if len(remaining) != 2+5*6 || binary.LittleEndian.Uint16(remaining[:2]) != 5 {
|
||
|
|
t.Fatalf("unexpected privileges payload: %x", remaining)
|
||
|
|
}
|
||
|
|
for index, want := range []uint16{0, 1, 2, 3, 4} {
|
||
|
|
position := 2 + index*6
|
||
|
|
if got := binary.LittleEndian.Uint16(remaining[position : position+2]); got != want {
|
||
|
|
t.Fatalf("privilege[%d]=%d want=%d", index, got, want)
|
||
|
|
}
|
||
|
|
if got := binary.LittleEndian.Uint32(remaining[position+2 : position+6]); got != uint32(expiresAt.Unix()) {
|
||
|
|
t.Fatalf("privilege[%d] expiry=%d", index, got)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestVolcengineTokenIssuerValidatesApplicationIdentity(t *testing.T) {
|
||
|
|
if _, err := NewVolcengineTokenIssuer("short", "0123456789abcdef", time.Minute); err == nil {
|
||
|
|
t.Fatal("expected invalid app ID to be rejected")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func readTokenBytes(t *testing.T, input []byte) ([]byte, []byte) {
|
||
|
|
t.Helper()
|
||
|
|
if len(input) < 2 {
|
||
|
|
t.Fatal("truncated token field length")
|
||
|
|
}
|
||
|
|
length := int(binary.LittleEndian.Uint16(input[:2]))
|
||
|
|
if len(input) < 2+length {
|
||
|
|
t.Fatal("truncated token field")
|
||
|
|
}
|
||
|
|
return input[2 : 2+length], input[2+length:]
|
||
|
|
}
|