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 }