From 9e5ce7f892aad0be6f8b00b2a6f613fdacc0443f Mon Sep 17 00:00:00 2001 From: Aleksandar Apostolov Date: Thu, 22 Oct 2026 10:10:00 +0200 Subject: [PATCH] fix(tutorial): fetch ringing tutorial credentials from demo token generator The API key hardcoded in the ringing tutorial was revoked, and the bundled user tokens were signed with its secret. Fetch the API key and token from the demo token generator at login instead, so the tutorial no longer hardcodes keys. --- .../android/tutorial/ringing/MainActivity.kt | 23 ++++++++++--- .../android/tutorial/ringing/RingingApp.kt | 34 ++++++++++++++----- 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/tutorials/tutorial-ringing/src/main/kotlin/io/getstream/video/android/tutorial/ringing/MainActivity.kt b/tutorials/tutorial-ringing/src/main/kotlin/io/getstream/video/android/tutorial/ringing/MainActivity.kt index ad368704593..9c79b03c189 100644 --- a/tutorials/tutorial-ringing/src/main/kotlin/io/getstream/video/android/tutorial/ringing/MainActivity.kt +++ b/tutorials/tutorial-ringing/src/main/kotlin/io/getstream/video/android/tutorial/ringing/MainActivity.kt @@ -21,6 +21,7 @@ import android.content.Context import android.content.pm.PackageManager import android.os.Build import android.os.Bundle +import android.util.Log import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.setContent @@ -73,6 +74,8 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.launch +import org.json.JSONException +import java.io.IOException import java.util.UUID /** @@ -91,11 +94,21 @@ class MainActivity : ComponentActivity() { var selectedUser by remember { mutableStateOf(RingingApp.currentUser) } when (selectedUser) { null -> LoginScreen(onUserSelected = { user -> - RingingApp.login(applicationContext, user) - // For the simplicity of the tutorial, we are requesting the required permissions here. - // In a real app, you should request the permissions when needed. - resultLauncher.requestDefaultPermissions() - selectedUser = user + lifecycleScope.launch { + try { + RingingApp.login(applicationContext, user) + } catch (e: IOException) { + Log.e("MainActivity", "Login failed", e) + return@launch + } catch (e: JSONException) { + Log.e("MainActivity", "Login failed", e) + return@launch + } + // For the simplicity of the tutorial, we are requesting the required permissions here. + // In a real app, you should request the permissions when needed. + resultLauncher.requestDefaultPermissions() + selectedUser = user + } }) else -> HomeScreen( diff --git a/tutorials/tutorial-ringing/src/main/kotlin/io/getstream/video/android/tutorial/ringing/RingingApp.kt b/tutorials/tutorial-ringing/src/main/kotlin/io/getstream/video/android/tutorial/ringing/RingingApp.kt index c7d3888f98e..82e462caac7 100644 --- a/tutorials/tutorial-ringing/src/main/kotlin/io/getstream/video/android/tutorial/ringing/RingingApp.kt +++ b/tutorials/tutorial-ringing/src/main/kotlin/io/getstream/video/android/tutorial/ringing/RingingApp.kt @@ -28,8 +28,13 @@ import io.getstream.video.android.core.StreamVideoBuilder import io.getstream.video.android.core.notifications.NotificationConfig import io.getstream.video.android.model.Device import io.getstream.video.android.model.User +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import kotlinx.coroutines.tasks.await +import kotlinx.coroutines.withContext +import org.json.JSONObject +import java.net.HttpURLConnection +import java.net.URL class RingingApp : Application() { @@ -42,6 +47,8 @@ class RingingApp : Application() { companion object { + private const val TOKEN_URL = "https://pronto.getstream.io/api/auth/create-token" + var currentUser: TutorialUser? = null val caller get() = currentUser ?: error("#caller; user not logged in") @@ -61,16 +68,16 @@ class RingingApp : Application() { } } - fun login(context: Context, user: TutorialUser) { + suspend fun login(context: Context, user: TutorialUser) { if (!StreamVideo.isInstalled) { - currentUser = user + val (apiKey, token) = fetchCredentials(user.id) // Initialize StreamVideo. For a production app we recommend adding // the client to your Application class or di module. StreamVideoBuilder( context = context.applicationContext, - apiKey = "mmhfdzb5evj2", + apiKey = apiKey, user = user.delegate, - token = user.token, + token = token, notificationConfig = NotificationConfig( // Make the notification low priority if the app is in foreground, so its not visible as a popup, since we want to handle // the incoming call in full screen when app is running. @@ -87,9 +94,24 @@ class RingingApp : Application() { ), ), ).build() + currentUser = user } } + // Credentials come from Stream's demo token generator; use your own backend in production. + private suspend fun fetchCredentials(userId: String): Pair = + withContext(Dispatchers.IO) { + val url = URL("$TOKEN_URL?environment=demo&user_id=$userId") + val connection = url.openConnection() as HttpURLConnection + try { + val body = connection.inputStream.bufferedReader().use { it.readText() } + val json = JSONObject(body) + json.getString("apiKey") to json.getString("token") + } finally { + connection.disconnect() + } + } + suspend fun logout() { if (StreamVideo.isInstalled) { with(StreamVideo.instance()) { @@ -114,7 +136,6 @@ class RingingApp : Application() { data class TutorialUser( val delegate: User, - val token: String, val checked: Boolean = false, ) { val id: String get() = delegate.id @@ -128,21 +149,18 @@ data class TutorialUser( id = "android-tutorial-1", name = "User 1", ), - token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiYW5kcm9pZC10dXRvcmlhbC0xIn0.3qB-FI6OAqf5ZEETtgs0XhaMmiaRF2jDJOqCVRsqqbc", ), TutorialUser( User( id = "android-tutorial-2", name = "User 2", ), - token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiYW5kcm9pZC10dXRvcmlhbC0yIn0.ksOq5ahC0745oZdPDKr2hyLp0j9exfwLE-AQITc9ZSc", ), TutorialUser( User( id = "android-tutorial-3", name = "User 3", ), - token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiYW5kcm9pZC10dXRvcmlhbC0zIn0.g5h8coX8J1XUNHagPFoGBI0D7bN6P0w2Sd2rui89puE", ), ) }