github.com/livekit/protocol@v1.39.3/auth/accesstoken_test.go (about)

     1  // Copyright 2023 LiveKit, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package auth
    16  
    17  import (
    18  	"encoding/base64"
    19  	"encoding/json"
    20  	"strings"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/go-jose/go-jose/v3/jwt"
    25  	"github.com/stretchr/testify/require"
    26  
    27  	"github.com/livekit/protocol/livekit"
    28  	"github.com/livekit/protocol/utils"
    29  	"github.com/livekit/protocol/utils/guid"
    30  )
    31  
    32  func TestAccessToken(t *testing.T) {
    33  	t.Parallel()
    34  
    35  	t.Run("keys must be set", func(t *testing.T) {
    36  		token := NewAccessToken("", "")
    37  		_, err := token.ToJWT()
    38  		require.Equal(t, ErrKeysMissing, err)
    39  	})
    40  
    41  	t.Run("generates a decode-able key", func(t *testing.T) {
    42  		apiKey, secret := apiKeypair()
    43  		videoGrant := &VideoGrant{RoomJoin: true, Room: "myroom"}
    44  		sipGrant := &SIPGrant{Admin: true}
    45  		at := NewAccessToken(apiKey, secret).
    46  			SetVideoGrant(videoGrant).
    47  			SetSIPGrant(sipGrant).
    48  			SetValidFor(time.Minute * 5).
    49  			SetKind(livekit.ParticipantInfo_AGENT).
    50  			SetIdentity("user")
    51  		value, err := at.ToJWT()
    52  		//fmt.Println(raw)
    53  		require.NoError(t, err)
    54  
    55  		require.Len(t, strings.Split(value, "."), 3)
    56  
    57  		// ensure it's a valid JWT
    58  		token, err := jwt.ParseSigned(value)
    59  		require.NoError(t, err)
    60  
    61  		decodedGrant := ClaimGrants{}
    62  		err = token.UnsafeClaimsWithoutVerification(&decodedGrant)
    63  		require.NoError(t, err)
    64  
    65  		require.EqualValues(t, livekit.ParticipantInfo_AGENT, decodedGrant.GetParticipantKind())
    66  		require.EqualValues(t, videoGrant, decodedGrant.Video)
    67  		require.EqualValues(t, sipGrant, decodedGrant.SIP)
    68  	})
    69  
    70  	t.Run("missing kind should be interpreted as standard", func(t *testing.T) {
    71  		apiKey, secret := apiKeypair()
    72  		value, err := NewAccessToken(apiKey, secret).
    73  			SetVideoGrant(&VideoGrant{RoomJoin: true, Room: "myroom"}).
    74  			ToJWT()
    75  		require.NoError(t, err)
    76  		token, err := jwt.ParseSigned(value)
    77  		require.NoError(t, err)
    78  
    79  		decodedGrant := ClaimGrants{}
    80  		err = token.UnsafeClaimsWithoutVerification(&decodedGrant)
    81  		require.NoError(t, err)
    82  
    83  		// default validity
    84  		require.EqualValues(t, livekit.ParticipantInfo_STANDARD, decodedGrant.GetParticipantKind())
    85  	})
    86  
    87  	t.Run("default validity should be more than a minute", func(t *testing.T) {
    88  		apiKey, secret := apiKeypair()
    89  		videoGrant := &VideoGrant{RoomJoin: true, Room: "myroom"}
    90  		at := NewAccessToken(apiKey, secret).
    91  			SetVideoGrant(videoGrant)
    92  		value, err := at.ToJWT()
    93  		require.NoError(t, err)
    94  		token, err := jwt.ParseSigned(value)
    95  		require.NoError(t, err)
    96  
    97  		claim := jwt.Claims{}
    98  		decodedGrant := ClaimGrants{}
    99  		err = token.UnsafeClaimsWithoutVerification(&claim, &decodedGrant)
   100  		require.NoError(t, err)
   101  		require.EqualValues(t, videoGrant, decodedGrant.Video)
   102  
   103  		// default validity
   104  		require.True(t, claim.Expiry.Time().Sub(claim.IssuedAt.Time()) > time.Minute)
   105  	})
   106  
   107  	t.Run("room configuration serialization and deserialization", func(t *testing.T) {
   108  		apiKey, secret := apiKeypair()
   109  		roomConfig := &livekit.RoomConfiguration{
   110  			Agents: []*livekit.RoomAgentDispatch{{
   111  				AgentName: "agent1",
   112  				Metadata:  "metadata1",
   113  			}},
   114  			SyncStreams: true,
   115  			Egress: &livekit.RoomEgress{
   116  				Room: &livekit.RoomCompositeEgressRequest{
   117  					FileOutputs: []*livekit.EncodedFileOutput{{
   118  						DisableManifest: true,
   119  					}},
   120  				},
   121  			},
   122  		}
   123  		videoGrant := &VideoGrant{RoomJoin: true, Room: "test-room"}
   124  		at := NewAccessToken(apiKey, secret).
   125  			SetVideoGrant(videoGrant).
   126  			SetRoomConfig(roomConfig)
   127  
   128  		value, err := at.ToJWT()
   129  		require.NoError(t, err)
   130  
   131  		// Parse and verify the token
   132  		token, err := jwt.ParseSigned(value)
   133  		require.NoError(t, err)
   134  
   135  		decodedGrant := ClaimGrants{}
   136  		err = token.UnsafeClaimsWithoutVerification(&decodedGrant)
   137  		require.NoError(t, err)
   138  
   139  		// Check if the room configuration was correctly serialized and deserialized
   140  		roomDecoded := (*livekit.RoomConfiguration)(decodedGrant.RoomConfig)
   141  		require.NotNil(t, roomDecoded)
   142  		agents := roomDecoded.Agents
   143  		require.NotNil(t, agents)
   144  		require.Len(t, agents, 1)
   145  		require.Equal(t, "agent1", agents[0].AgentName)
   146  		require.Equal(t, "metadata1", agents[0].Metadata)
   147  		egress := roomDecoded.Egress
   148  		require.NotNil(t, egress)
   149  		require.Equal(t, true, egress.Room.FileOutputs[0].DisableManifest)
   150  
   151  		// Ensure that we are encoding room configuration with camelCase
   152  		parts := strings.Split(value, ".")
   153  		require.Equal(t, 3, len(parts), "JWT should have three parts")
   154  		payload, err := base64.RawURLEncoding.DecodeString(parts[1])
   155  		require.NoError(t, err)
   156  
   157  		// Parse the JSON
   158  		var jsonPayload map[string]interface{}
   159  		err = json.Unmarshal(payload, &jsonPayload)
   160  		require.NoError(t, err)
   161  
   162  		// Navigate to the agents array
   163  		room, ok := jsonPayload["roomConfig"].(map[string]interface{})
   164  		require.True(t, ok, "room should be a map")
   165  		agentsJSON, ok := room["agents"].([]interface{})
   166  		require.True(t, ok, "agents should be an array")
   167  		require.Len(t, agents, 1, "there should be one agent")
   168  
   169  		// Check if agentName is in camelCase
   170  		agent, ok := agentsJSON[0].(map[string]interface{})
   171  		require.True(t, ok, "agent should be a map")
   172  		_, hasAgentName := agent["agentName"]
   173  		require.True(t, hasAgentName, "agentName should be present in camelCase")
   174  		_, hasAgentNameSnakeCase := agent["agent_name"]
   175  		require.False(t, hasAgentNameSnakeCase, "agent_name should not be present in snake_case")
   176  	})
   177  }
   178  
   179  func apiKeypair() (string, string) {
   180  	return guid.New(utils.APIKeyPrefix), utils.RandomSecret()
   181  }