Skip to content

Commit

Permalink
Support generating development token for a development environment (#888
Browse files Browse the repository at this point in the history
)

* new codegen

* Imlpement token utils and unit tests

* Implement devToken

---------

Co-authored-by: Tommaso Barbugli <[email protected]>
Co-authored-by: Thierry Schellenbach <[email protected]>
  • Loading branch information
3 people authored Oct 25, 2023
1 parent f5eac64 commit a6a6194
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ Video roadmap and changelog is available [here](https://github.com/GetStream/pro

### 0.5.0 milestone

- [X] Development token to support a development environment
- [ ] H264 workaround on Samsung 23? (see https://github.com/livekit/client-sdk-android/blob/main/livekit-android-sdk/src/main/java/io/livekit/android/webrtc/SimulcastVideoEncoderFactoryWrapper.kt#L34 and
- https://github.com/react-native-webrtc/react-native-webrtc/issues/983#issuecomment-975624906)
- [ ] Test coverage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ import io.getstream.video.android.model.User
*
* Based on the state within the [User], we either show an image or their initials.
*
* @param user The user whose avatar we want to show.
* @param userName The user name whose avatar we want to show.
* @param userImage The user image whose avatar we want to show.
* @param modifier Modifier for styling.
* @param shape The shape of the avatar.
* @param textStyle The [TextStyle] that will be used for the initials.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,7 @@ public abstract interface class io/getstream/video/android/core/StreamVideo : io
public abstract fun connectAsync (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun createDevice (Lio/getstream/android/push/PushDevice;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun deleteDevice (Lio/getstream/video/android/model/Device;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun devToken (Ljava/lang/String;)Ljava/lang/String;
public abstract fun getContext ()Landroid/content/Context;
public abstract fun getEdges (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun getState ()Lio/getstream/video/android/core/ClientState;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import io.getstream.video.android.core.model.QueriedCalls
import io.getstream.video.android.core.model.QueriedMembers
import io.getstream.video.android.core.model.SortField
import io.getstream.video.android.core.notifications.NotificationHandler
import io.getstream.video.android.core.utils.TokenUtils
import io.getstream.video.android.model.Device
import io.getstream.video.android.model.User
import kotlinx.coroutines.Deferred
Expand Down Expand Up @@ -216,6 +217,13 @@ public interface StreamVideo : NotificationHandler {
}
}

/**
* Generate a developer token that can be used to connect users while the app is using a development environment.
*
* @param userId the desired id of the user to be connected.
*/
public fun devToken(userId: String): String = TokenUtils.devToken(userId)

public fun cleanup()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright (c) 2014-2023 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-video-android/blob/main/LICENSE
*
* 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 io.getstream.video.android.core.utils

import android.util.Base64
import io.getstream.log.taggedLogger
import org.json.JSONException
import org.json.JSONObject
import java.nio.charset.StandardCharsets

internal object TokenUtils {

val logger by taggedLogger("Video:TokenUtils")

fun getUserId(token: String): String = try {
JSONObject(
token
.takeIf { it.contains(".") }
?.split(".")
?.getOrNull(1)
?.let {
String(
Base64.decode(
it.toByteArray(StandardCharsets.UTF_8),
Base64.NO_WRAP,
),
)
}
?: "",
).optString("user_id")
} catch (e: JSONException) {
logger.e(e) { "Unable to obtain userId from JWT Token Payload" }
""
} catch (e: IllegalArgumentException) {
logger.e(e) { "Unable to obtain userId from JWT Token Payload" }
""
}

/**
* Generate a developer token that can be used to connect users while the app is using a development environment.
*
* @param userId the desired id of the user to be connected.
*/
fun devToken(userId: String): String {
require(userId.isNotEmpty()) { "User id must not be empty" }
val header = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" // {"alg": "HS256", "typ": "JWT"}
val devSignature = "devtoken"
val payload: String =
Base64.encodeToString(
"{\"user_id\":\"$userId\"}".toByteArray(StandardCharsets.UTF_8),
Base64.NO_WRAP,
)
return "$header.$payload.$devSignature"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* Copyright (c) 2014-2023 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-video-android/blob/main/LICENSE
*
* 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 io.getstream.video.android.core.utils

import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.ParameterizedRobolectricTestRunner
import org.robolectric.annotation.Config
import kotlin.test.assertEquals

@RunWith(ParameterizedRobolectricTestRunner::class)
@Config(manifest = Config.NONE)
internal class TokenUtilsTest(
private val token: String,
private val expectedUserId: String,
) {

@Test
fun `Should return userId inside of the token`() {
assertEquals(TokenUtils.getUserId(token), expectedUserId)
}

companion object {

@Suppress("MaxLineLength")
@JvmStatic
@ParameterizedRobolectricTestRunner.Parameters(name = "{index}: {0} => {1}")
fun data(): Collection<Array<Any?>> = listOf(
arrayOf(
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiamMifQ==.devtoken",
"jc",
),
arrayOf(
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidmlzaGFsIn0=.devtoken",
"vishal",
),
arrayOf(
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiYW1pbiJ9.devtoken",
"amin",
),
arrayOf(
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiMWYzN2U1OGQtZDhiMC00NzZhLWE0ZjItZjg2MTFlMGQ4NWQ5In0.l3u9P1NKhJ91rI1tzOcABGh045Kj69-iVkC2yUtohVw",
"1f37e58d-d8b0-476a-a4f2-f8611e0d85d9",
),
arrayOf(
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiRnJhIn0.ENQGHEsAL3WjVhd_qTiJa_9ojGKi2ftJ8xlocT8SVX4",
"Fra",
),
arrayOf(
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiNmQ5NTI3M2ItMzNmMC00MGY1LWIwN2MtMGRhMjYxMDkyMDc0In0.lT5O4EmWzhRKPTau6dHP4F6M42EA2aN_8-iAPuiFPLc",
"6d95273b-33f0-40f5-b07c-0da261092074",
),
arrayOf(
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiMWUzMzAxMTEtNjcwZC00OWE3LThmMDgtZTY3MzQzMzhjNjQxIn0.YEFdEMWj5rurQKr0QMrvO72jGZHU-AlpUIbyY4jxYdU",
"1e330111-670d-49a7-8f08-e6734338c641",
),
arrayOf(
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiMjllNDZkZWYtODhmNC00YjZhLWExMGMtNTg0ZDEwYzRmZGM5In0.Mxr4Prnb1-EVM5NSSP2EugLApSChoKnVFwe7ZO15V_U",
"29e46def-88f4-4b6a-a10c-584d10c4fdc9",
),
arrayOf(
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiMWYwNTJjMDgtZjY4Mi00YTgzLTg5NmMtOWYxOWE2OGJkMmJiIn0.L-cQ-DYubOzFpsg94OEwlTRYjat9G4cqfAgzBPALW0g",
"1f052c08-f682-4a83-896c-9f19a68bd2bb",
),
arrayOf(
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiMGQzZTZlNjMtNjIwMC00ZGQxLWE4NDEtNDA1MDY2NDg5MWUyIn0.osFIgnle17f6yEkK7rPJguQaKhOiawAO3BylYaiRTqE",
"0d3e6e63-6200-4dd1-a841-4050664891e2",
),
arrayOf(
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiMTJmYjBlZDktOTNkOC00OGE1LTk4ODUtMjhlNDFmMmU0YzQzIn0.t_oc_DEwTav7ni0z4bi8Xla_5Zj5cI6l3rKxwoCvtB0",
"12fb0ed9-93d8-48a5-9885-28e41f2e4c43",
),
arrayOf(
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiNTUzMWE4Y2ItM2I4MS00YTU0LWI0MjQtN2FlNGUyN2JmOGJhIn0.PXkmukg3JU4igH_YUMr7WC7a1EcwKBr_C5V2ouBlmIs",
"5531a8cb-3b81-4a54-b424-7ae4e27bf8ba",
),
arrayOf(
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiMDYzNTY1NjQtMTQ5Zi00YjJjLTg1MjUtZDIyMDU2ZmVjNDA0In0.R3-HY9Cno62yIhCjLXDBR8LF7y1udwX8m4LLNP2dIZo",
"06356564-149f-4b2c-8525-d22056fec404",
),
arrayOf(
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiYWQ3ZDkzMTQtNTA3MS00ZDYxLTk4YTEtZmZhNjQzY2U4MjRhIn0.iF4UWGFtX0eTAIBTCum7fjD_TKn8wjEqb3PVxJrwbuM",
"ad7d9314-5071-4d61-98a1-ffa643ce824a",
),
arrayOf(
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY2ViZjU2MmEtNDgwNi00YzY0LWE4MjctNTlkNTBhYWM0MmJhIn0.kuXab7RhQRHdsErEW5tTN_mmuyLPNU4ZbprvuPXM4OY",
"cebf562a-4806-4c64-a827-59d50aac42ba",
),
arrayOf(
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicWF0ZXN0MCJ9.Vow00KvvhLvWRZIPKomXQOYpBL_P-_-eDeDKmBRvEj4",
"qatest0",
),
arrayOf(
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicWF0ZXN0MSJ9.H1nlYibjgp1HfaOd0sA_T4038tjsN61mJWxvUjmRQI0",
"qatest1",
),
arrayOf(
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicWF0ZXN0MiJ9.GYp9ikLtU2eG9Mq7tmHThzbV7C8W82j18sExuO7-ogc",
"qatest2",
),
arrayOf(
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicWF0ZXN0MyJ9.kLZJz5kl7e3Zw7i2T39Yp05_nAmh9RGG0rt6-5zOpfE",
"qatest3",
),
arrayOf(
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.kLZJz5kl7e3Zw7i2T39Yp05_nAmh9RGG0rt6-5zOpfE",
"",
),
arrayOf(
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.",
"",
),
arrayOf(
randomString(),
"",
),
arrayOf(
"${randomString()}.",
"",
),
arrayOf(
"${randomString()}.${randomString()}",
"",
),
arrayOf(
"",
"",
),
)
}
}

private val charPool: CharArray = (('a'..'z') + ('A'..'Z') + ('0'..'9')).toCharArray()

private fun randomString(size: Int = 20): String = buildString(capacity = size) {
repeat(size) {
append(charPool.random())
}
}

0 comments on commit a6a6194

Please sign in to comment.