diff --git a/auth/accesstoken.go b/auth/accesstoken.go index f9584367..69d59aba 100644 --- a/auth/accesstoken.go +++ b/auth/accesstoken.go @@ -19,6 +19,8 @@ import ( "github.com/go-jose/go-jose/v3" "github.com/go-jose/go-jose/v3/jwt" + + "github.com/livekit/protocol/livekit" ) const ( @@ -55,6 +57,11 @@ func (t *AccessToken) SetName(name string) *AccessToken { return t } +func (t *AccessToken) SetKind(kind livekit.ParticipantInfo_Kind) *AccessToken { + t.grant.SetParticipantKind(kind) + return t +} + func (t *AccessToken) AddGrant(grant *VideoGrant) *AccessToken { t.grant.Video = grant return t diff --git a/auth/accesstoken_test.go b/auth/accesstoken_test.go index f6531507..e368d86b 100644 --- a/auth/accesstoken_test.go +++ b/auth/accesstoken_test.go @@ -22,6 +22,7 @@ import ( "github.com/go-jose/go-jose/v3/jwt" "github.com/stretchr/testify/require" + "github.com/livekit/protocol/livekit" "github.com/livekit/protocol/utils" ) @@ -40,6 +41,7 @@ func TestAccessToken(t *testing.T) { at := NewAccessToken(apiKey, secret). AddGrant(videoGrant). SetValidFor(time.Minute * 5). + SetKind(livekit.ParticipantInfo_AGENT). SetIdentity("user") value, err := at.ToJWT() //fmt.Println(raw) @@ -55,9 +57,27 @@ func TestAccessToken(t *testing.T) { err = token.UnsafeClaimsWithoutVerification(&decodedGrant) require.NoError(t, err) + require.EqualValues(t, livekit.ParticipantInfo_AGENT, decodedGrant.GetParticipantKind()) require.EqualValues(t, videoGrant, decodedGrant.Video) }) + t.Run("missing kind should be interpreted as standard", func(t *testing.T) { + apiKey, secret := apiKeypair() + value, err := NewAccessToken(apiKey, secret). + AddGrant(&VideoGrant{RoomJoin: true, Room: "myroom"}). + ToJWT() + require.NoError(t, err) + token, err := jwt.ParseSigned(value) + require.NoError(t, err) + + decodedGrant := ClaimGrants{} + err = token.UnsafeClaimsWithoutVerification(&decodedGrant) + require.NoError(t, err) + + // default validity + require.EqualValues(t, livekit.ParticipantInfo_STANDARD, decodedGrant.GetParticipantKind()) + }) + t.Run("default validity should be more than a minute", func(t *testing.T) { apiKey, secret := apiKeypair() videoGrant := &VideoGrant{RoomJoin: true, Room: "myroom"} diff --git a/auth/grants.go b/auth/grants.go index 839d3b6a..6c5f4e49 100644 --- a/auth/grants.go +++ b/auth/grants.go @@ -59,12 +59,21 @@ type VideoGrant struct { type ClaimGrants struct { Identity string `json:"-"` Name string `json:"name,omitempty"` + Kind string `json:"kind,omitempty"` Video *VideoGrant `json:"video,omitempty"` // for verifying integrity of the message body Sha256 string `json:"sha256,omitempty"` Metadata string `json:"metadata,omitempty"` } +func (c *ClaimGrants) SetParticipantKind(kind livekit.ParticipantInfo_Kind) { + c.Kind = kindFromProto(kind) +} + +func (c *ClaimGrants) GetParticipantKind() livekit.ParticipantInfo_Kind { + return kindToProto(c.Kind) +} + func (c *ClaimGrants) Clone() *ClaimGrants { if c == nil { return nil @@ -271,3 +280,24 @@ func sourceToProto(sourceStr string) livekit.TrackSource { return livekit.TrackSource_UNKNOWN } } + +func kindFromProto(source livekit.ParticipantInfo_Kind) string { + return strings.ToLower(source.String()) +} + +func kindToProto(sourceStr string) livekit.ParticipantInfo_Kind { + switch sourceStr { + case "", "standard": + return livekit.ParticipantInfo_STANDARD + case "ingress": + return livekit.ParticipantInfo_INGRESS + case "egress": + return livekit.ParticipantInfo_EGRESS + case "sip": + return livekit.ParticipantInfo_SIP + case "agent": + return livekit.ParticipantInfo_AGENT + default: + return livekit.ParticipantInfo_STANDARD + } +} diff --git a/auth/grants_test.go b/auth/grants_test.go index 868228b8..acc00998 100644 --- a/auth/grants_test.go +++ b/auth/grants_test.go @@ -16,9 +16,12 @@ package auth import ( "reflect" + "strconv" "testing" "github.com/stretchr/testify/require" + + "github.com/livekit/protocol/livekit" ) func TestGrants(t *testing.T) { @@ -66,6 +69,7 @@ func TestGrants(t *testing.T) { grants := &ClaimGrants{ Identity: "identity", Name: "name", + Kind: "kind", Video: video, Sha256: "sha256", Metadata: "metadata", @@ -80,3 +84,17 @@ func TestGrants(t *testing.T) { require.True(t, reflect.DeepEqual(grants.Video, clone.Video)) }) } + +func TestParticipantKind(t *testing.T) { + const kindMin, kindMax = livekit.ParticipantInfo_STANDARD, livekit.ParticipantInfo_AGENT + for k := kindMin; k <= kindMax; k++ { + k := k + t.Run(k.String(), func(t *testing.T) { + require.Equal(t, k, kindToProto(kindFromProto(k))) + }) + } + const kindNext = kindMax + 1 + if _, err := strconv.Atoi(kindNext.String()); err != nil { + t.Errorf("Please update kindMax to match protobuf. Missing value: %s", kindNext) + } +} diff --git a/egress/token.go b/egress/token.go index cbc0e534..cf20ca0f 100644 --- a/egress/token.go +++ b/egress/token.go @@ -18,6 +18,7 @@ import ( "time" "github.com/livekit/protocol/auth" + "github.com/livekit/protocol/livekit" ) func BuildEgressToken(egressID, apiKey, secret, roomName string) (string, error) { @@ -36,6 +37,7 @@ func BuildEgressToken(egressID, apiKey, secret, roomName string) (string, error) at := auth.NewAccessToken(apiKey, secret). AddGrant(grant). SetIdentity(egressID). + SetKind(livekit.ParticipantInfo_EGRESS). SetValidFor(24 * time.Hour) return at.ToJWT() diff --git a/ingress/token.go b/ingress/token.go index b82b28f8..49eccf43 100644 --- a/ingress/token.go +++ b/ingress/token.go @@ -18,6 +18,7 @@ import ( "time" "github.com/livekit/protocol/auth" + "github.com/livekit/protocol/livekit" ) func BuildIngressToken(apiKey, secret, roomName, participantIdentity, participantName string) (string, error) { @@ -34,6 +35,7 @@ func BuildIngressToken(apiKey, secret, roomName, participantIdentity, participan AddGrant(grant). SetIdentity(participantIdentity). SetName(participantName). + SetKind(livekit.ParticipantInfo_INGRESS). SetValidFor(24 * time.Hour) return at.ToJWT() diff --git a/sip/token.go b/sip/token.go new file mode 100644 index 00000000..743c1a6d --- /dev/null +++ b/sip/token.go @@ -0,0 +1,39 @@ +// Copyright 2023 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sip + +import ( + "time" + + "github.com/livekit/protocol/auth" + "github.com/livekit/protocol/livekit" +) + +func BuildSIPToken(apiKey, secret, roomName, participantIdentity, participantName string) (string, error) { + t := true + at := auth.NewAccessToken(apiKey, secret). + AddGrant(&auth.VideoGrant{ + RoomJoin: true, + Room: roomName, + CanSubscribe: &t, + CanPublish: &t, + }). + SetIdentity(participantIdentity). + SetName(participantName). + SetKind(livekit.ParticipantInfo_SIP). + SetValidFor(24 * time.Hour) + + return at.ToJWT() +}