fix(android): require gateway device auth store

This commit is contained in:
Ayaan Zaidi
2026-02-26 12:07:09 +05:30
committed by Ayaan Zaidi
parent f7865527af
commit a87d961ebc
3 changed files with 27 additions and 9 deletions

View File

@@ -2,13 +2,18 @@ package ai.openclaw.android.gateway
import ai.openclaw.android.SecurePrefs
class DeviceAuthStore(private val prefs: SecurePrefs) {
fun loadToken(deviceId: String, role: String): String? {
interface DeviceAuthTokenStore {
fun loadToken(deviceId: String, role: String): String?
fun saveToken(deviceId: String, role: String, token: String)
}
class DeviceAuthStore(private val prefs: SecurePrefs) : DeviceAuthTokenStore {
override fun loadToken(deviceId: String, role: String): String? {
val key = tokenKey(deviceId, role)
return prefs.getString(key)?.trim()?.takeIf { it.isNotEmpty() }
}
fun saveToken(deviceId: String, role: String, token: String) {
override fun saveToken(deviceId: String, role: String, token: String) {
val key = tokenKey(deviceId, role)
prefs.putString(key, token.trim())
}

View File

@@ -55,7 +55,7 @@ data class GatewayConnectOptions(
class GatewaySession(
private val scope: CoroutineScope,
private val identityStore: DeviceIdentityStore,
private val deviceAuthStore: DeviceAuthStore? = null,
private val deviceAuthStore: DeviceAuthTokenStore,
private val onConnected: (serverName: String?, remoteAddress: String?, mainSessionKey: String?) -> Unit,
private val onDisconnected: (message: String) -> Unit,
private val onEvent: (event: String, payloadJson: String?) -> Unit,
@@ -303,7 +303,7 @@ class GatewaySession(
private suspend fun sendConnect(connectNonce: String) {
val identity = identityStore.loadOrCreate()
val storedToken = deviceAuthStore?.loadToken(identity.deviceId, options.role)
val storedToken = deviceAuthStore.loadToken(identity.deviceId, options.role)
val trimmedToken = token?.trim().orEmpty()
// QR/setup/manual shared token must take precedence; stale role tokens can survive re-onboarding.
val authToken = if (trimmedToken.isNotBlank()) trimmedToken else storedToken.orEmpty()
@@ -325,7 +325,7 @@ class GatewaySession(
val deviceToken = authObj?.get("deviceToken").asStringOrNull()
val authRole = authObj?.get("role").asStringOrNull() ?: options.role
if (!deviceToken.isNullOrBlank()) {
deviceAuthStore?.saveToken(deviceId, authRole, deviceToken)
deviceAuthStore.saveToken(deviceId, authRole, deviceToken)
}
val rawCanvas = obj["canvasHostUrl"].asStringOrNull()
canvasHostUrl = normalizeCanvasHostUrl(rawCanvas, endpoint, isTlsConnection = tls != null)

View File

@@ -27,6 +27,16 @@ import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config
import java.util.concurrent.atomic.AtomicReference
private class InMemoryDeviceAuthStore : DeviceAuthTokenStore {
private val tokens = mutableMapOf<String, String>()
override fun loadToken(deviceId: String, role: String): String? = tokens["${deviceId.trim()}|${role.trim()}"]?.trim()?.takeIf { it.isNotEmpty() }
override fun saveToken(deviceId: String, role: String, token: String) {
tokens["${deviceId.trim()}|${role.trim()}"] = token.trim()
}
}
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [34])
class GatewaySessionInvokeTest {
@@ -84,11 +94,12 @@ class GatewaySessionInvokeTest {
val app = RuntimeEnvironment.getApplication()
val sessionJob = SupervisorJob()
val deviceAuthStore = InMemoryDeviceAuthStore()
val session =
GatewaySession(
scope = CoroutineScope(sessionJob + Dispatchers.Default),
identityStore = DeviceIdentityStore(app),
deviceAuthStore = null,
deviceAuthStore = deviceAuthStore,
onConnected = { _, _, _ ->
if (!connected.isCompleted) connected.complete(Unit)
},
@@ -218,11 +229,12 @@ class GatewaySessionInvokeTest {
val app = RuntimeEnvironment.getApplication()
val sessionJob = SupervisorJob()
val deviceAuthStore = InMemoryDeviceAuthStore()
val session =
GatewaySession(
scope = CoroutineScope(sessionJob + Dispatchers.Default),
identityStore = DeviceIdentityStore(app),
deviceAuthStore = null,
deviceAuthStore = deviceAuthStore,
onConnected = { _, _, _ ->
if (!connected.isCompleted) connected.complete(Unit)
},
@@ -347,11 +359,12 @@ class GatewaySessionInvokeTest {
val app = RuntimeEnvironment.getApplication()
val sessionJob = SupervisorJob()
val deviceAuthStore = InMemoryDeviceAuthStore()
val session =
GatewaySession(
scope = CoroutineScope(sessionJob + Dispatchers.Default),
identityStore = DeviceIdentityStore(app),
deviceAuthStore = null,
deviceAuthStore = deviceAuthStore,
onConnected = { _, _, _ ->
if (!connected.isCompleted) connected.complete(Unit)
},