Skip to content

Commit bd5dad0

Browse files
authored
Support array values in roomConfiguration in token (#121)
* Support array values in roomConfiguration in token * Create nasty-dolphins-train.md
1 parent d61ebca commit bd5dad0

File tree

4 files changed

+93
-56
lines changed

4 files changed

+93
-56
lines changed

.changeset/nasty-dolphins-train.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"server-sdk-kotlin": patch
3+
---
4+
5+
Support array values in AccessToken's roomConfiguration

.github/workflows/CI.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ jobs:
5757
- name: Build with Gradle
5858
run: ./gradlew assemble
5959

60+
- name: Run unit tests (no livekit-server integration tests)
61+
run: ./gradlew test --tests "io.livekit.server.AccessTokenTest"
62+
6063
- name: get version name
6164
if: github.event_name == 'push'
6265
run: echo "::set-output name=version_name::$(cat gradle.properties | grep VERSION_NAME | cut -d "=" -f2)"

src/main/kotlin/io/livekit/server/AccessToken.kt

Lines changed: 43 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,7 @@ import java.util.concurrent.TimeUnit
3333
* https://docs.livekit.io/home/get-started/authentication/
3434
*/
3535
@Suppress("MemberVisibilityCanBePrivate", "unused")
36-
class AccessToken(
37-
private val apiKey: String,
38-
private val secret: String
39-
) {
36+
class AccessToken(private val apiKey: String, private val secret: String) {
4037
private val videoGrants = mutableSetOf<VideoGrant>()
4138
private val sipGrants = mutableSetOf<SIPGrant>()
4239

@@ -57,33 +54,25 @@ class AccessToken(
5754
var expiration: Date? = null
5855

5956
/**
60-
* Date specifying the time [before which this token is invalid](https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-4.1.5).
57+
* Date specifying the time
58+
* [before which this token is invalid](https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-4.1.5)
59+
* .
6160
*/
6261
var notBefore: Date? = null
6362

64-
/**
65-
* Display name for the participant, available as `Participant.name`
66-
*/
63+
/** Display name for the participant, available as `Participant.name` */
6764
var name: String? = null
6865

69-
/**
70-
* Unique identity of the user, required for room join tokens
71-
*/
66+
/** Unique identity of the user, required for room join tokens */
7267
var identity: String? = null
7368

74-
/**
75-
* Custom metadata to be passed to participants
76-
*/
69+
/** Custom metadata to be passed to participants */
7770
var metadata: String? = null
7871

79-
/**
80-
* For verifying integrity of message body
81-
*/
72+
/** For verifying integrity of message body */
8273
var sha256: String? = null
8374

84-
/**
85-
* Key/value attributes to attach to the participant
86-
*/
75+
/** Key/value attributes to attach to the participant */
8776
val attributes = mutableMapOf<String, String>()
8877

8978
/**
@@ -93,57 +82,43 @@ class AccessToken(
9382
*/
9483
var roomPreset: String? = null
9584

96-
/**
97-
* Configuration for when creating a room.
98-
*/
85+
/** Configuration for when creating a room. */
9986
var roomConfiguration: RoomConfiguration? = null
10087

101-
/**
102-
* Add [VideoGrant] to this token.
103-
*/
88+
/** Add [VideoGrant] to this token. */
10489
fun addGrants(vararg grants: VideoGrant) {
10590
for (grant in grants) {
10691
videoGrants.add(grant)
10792
}
10893
}
10994

110-
/**
111-
* Add [VideoGrant] to this token.
112-
*/
95+
/** Add [VideoGrant] to this token. */
11396
fun addGrants(grants: Iterable<VideoGrant>) {
11497
for (grant in grants) {
11598
videoGrants.add(grant)
11699
}
117100
}
118101

119-
/**
120-
* Clear all previously added [VideoGrant]s.
121-
*/
102+
/** Clear all previously added [VideoGrant]s. */
122103
fun clearGrants() {
123104
videoGrants.clear()
124105
}
125106

126-
/**
127-
* Add [VideoGrant] to this token.
128-
*/
107+
/** Add [VideoGrant] to this token. */
129108
fun addSIPGrants(vararg grants: SIPGrant) {
130109
for (grant in grants) {
131110
sipGrants.add(grant)
132111
}
133112
}
134113

135-
/**
136-
* Add [VideoGrant] to this token.
137-
*/
114+
/** Add [VideoGrant] to this token. */
138115
fun addSIPGrants(grants: Iterable<SIPGrant>) {
139116
for (grant in grants) {
140117
sipGrants.add(grant)
141118
}
142119
}
143120

144-
/**
145-
* Clear all previously added [SIPGrant]s.
146-
*/
121+
/** Clear all previously added [SIPGrant]s. */
147122
fun clearSIPGrants() {
148123
sipGrants.clear()
149124
}
@@ -191,9 +166,7 @@ class AccessToken(
191166
claimsMap["video"] = videoGrantsMap
192167
claimsMap["sip"] = sipGrantsMap
193168

194-
claimsMap.forEach { (key, value) ->
195-
withClaimAny(key, value)
196-
}
169+
claimsMap.forEach { (key, value) -> withClaimAny(key, value) }
197170

198171
val alg = Algorithm.HMAC256(secret)
199172

@@ -214,22 +187,36 @@ internal fun JWTCreator.Builder.withClaimAny(name: String, value: Any) {
214187
is Instant -> withClaim(name, value)
215188
is List<*> -> withClaim(name, value)
216189
is Map<*, *> -> {
217-
@Suppress("UNCHECKED_CAST")
218-
withClaim(name, value as Map<String, *>)
190+
@Suppress("UNCHECKED_CAST") withClaim(name, value as Map<String, *>)
219191
}
220192
}
221193
}
222194

223-
internal fun MessageOrBuilder.toMap(): Map<String, *> {
224-
val map = mutableMapOf<String, Any>()
225-
195+
internal fun MessageOrBuilder.toMap(): Map<String, *> = buildMap {
226196
for ((field, value) in allFields) {
227-
if (value is MessageOrBuilder) {
228-
map[field.name] = value.toMap()
229-
} else {
230-
map[field.name] = value
231-
}
197+
put(
198+
field.name,
199+
when (value) {
200+
is MessageOrBuilder -> value.toMap()
201+
is List<*> ->
202+
value.map { item ->
203+
when (item) {
204+
is MessageOrBuilder -> item.toMap()
205+
else -> if (isSupportedType(item)) item else item.toString()
206+
}
207+
}
208+
else -> if (isSupportedType(value)) value else value.toString()
209+
}
210+
)
232211
}
233-
234-
return map
235212
}
213+
214+
private fun isSupportedType(value: Any?) =
215+
value == null ||
216+
value is Boolean ||
217+
value is Int ||
218+
value is Long ||
219+
value is Double ||
220+
value is String ||
221+
value is Map<*, *> ||
222+
value is List<*>

src/test/kotlin/io/livekit/server/AccessTokenTest.kt

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package io.livekit.server
1818

1919
import com.auth0.jwt.JWT
2020
import com.auth0.jwt.algorithms.Algorithm
21+
import livekit.LivekitAgentDispatch
2122
import livekit.LivekitRoom.RoomConfiguration
2223
import org.junit.jupiter.api.Test
2324
import java.util.Date
@@ -121,4 +122,45 @@ class AccessTokenTest {
121122
assertEquals(roomConfig.emptyTimeout, map["empty_timeout"])
122123
assertEquals(roomConfig.egress.room.roomName, ((map["egress"] as Map<*, *>)["room"] as Map<*, *>)["room_name"])
123124
}
125+
126+
@Test
127+
fun testArraysInRoomConfiguration() {
128+
val roomConfig = with(RoomConfiguration.newBuilder()) {
129+
name = "test_room"
130+
addAgents(
131+
LivekitAgentDispatch.RoomAgentDispatch.newBuilder()
132+
.setAgentName("agent_name")
133+
.setMetadata("metadata")
134+
.build()
135+
)
136+
build()
137+
}
138+
139+
val token = AccessToken(KEY, SECRET)
140+
token.roomConfiguration = roomConfig
141+
142+
// This should not throw an exception
143+
val jwt = token.toJwt()
144+
145+
// Verify the JWT can be decoded
146+
val alg = Algorithm.HMAC256(SECRET)
147+
val decodedJWT = JWT.require(alg)
148+
.withIssuer(KEY)
149+
.build()
150+
.verify(jwt)
151+
152+
// Verify the room configuration was properly encoded
153+
val claims = decodedJWT.claims
154+
val roomConfigMap = claims["roomConfig"]?.asMap()
155+
assertNotNull(roomConfigMap)
156+
157+
val agentsMap = roomConfigMap.get("agents") as? List<*>
158+
assertNotNull(agentsMap)
159+
assertEquals(1, agentsMap.size)
160+
161+
val agentMap = agentsMap.first() as? Map<*, *>
162+
assertNotNull(agentMap)
163+
assertEquals("agent_name", agentMap.get("agent_name"))
164+
assertEquals("metadata", agentMap.get("metadata"))
165+
}
124166
}

0 commit comments

Comments
 (0)