Skip to content

Commit 6090afa

Browse files
committed
fix(android): reset auth on new setup codes
1 parent 11bd40f commit 6090afa

7 files changed

Lines changed: 106 additions & 6 deletions

File tree

apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,10 @@ class MainViewModel(app: Application) : AndroidViewModel(app) {
204204
prefs.setGatewayPassword(value)
205205
}
206206

207+
fun resetGatewaySetupAuth() {
208+
ensureRuntime().resetGatewaySetupAuth()
209+
}
210+
207211
fun setOnboardingCompleted(value: Boolean) {
208212
if (value) {
209213
ensureRuntime()

apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,12 @@ class NodeRuntime(
556556
fun setGatewayToken(value: String) = prefs.setGatewayToken(value)
557557
fun setGatewayBootstrapToken(value: String) = prefs.setGatewayBootstrapToken(value)
558558
fun setGatewayPassword(value: String) = prefs.setGatewayPassword(value)
559+
fun resetGatewaySetupAuth() {
560+
prefs.clearGatewaySetupAuth()
561+
val deviceId = identityStore.loadOrCreate().deviceId
562+
deviceAuthStore.clearToken(deviceId, "node")
563+
deviceAuthStore.clearToken(deviceId, "operator")
564+
}
559565
fun setOnboardingCompleted(value: Boolean) = prefs.setOnboardingCompleted(value)
560566
val lastDiscoveredStableId: StateFlow<String> = prefs.lastDiscoveredStableId
561567
val canvasDebugStatusEnabled: StateFlow<Boolean> = prefs.canvasDebugStatusEnabled
@@ -1325,15 +1331,22 @@ internal fun resolveOperatorSessionConnectAuth(
13251331

13261332
val storedToken = storedOperatorToken?.trim()?.takeIf { it.isNotEmpty() }
13271333
if (storedToken != null) {
1328-
// Bootstrap can seed the operator token, but operator should reconnect
1329-
// through the stored device-token path rather than bootstrap auth itself.
13301334
return NodeRuntime.GatewayConnectAuth(
13311335
token = null,
13321336
bootstrapToken = null,
13331337
password = null,
13341338
)
13351339
}
13361340

1341+
val explicitBootstrapToken = auth.bootstrapToken?.trim()?.takeIf { it.isNotEmpty() }
1342+
if (explicitBootstrapToken != null) {
1343+
return NodeRuntime.GatewayConnectAuth(
1344+
token = null,
1345+
bootstrapToken = explicitBootstrapToken,
1346+
password = null,
1347+
)
1348+
}
1349+
13371350
return null
13381351
}
13391352

apps/android/app/src/main/java/ai/openclaw/app/SecurePrefs.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,18 @@ class SecurePrefs(
402402
securePrefs.edit { putString(key, password.trim()) }
403403
}
404404

405+
fun clearGatewaySetupAuth() {
406+
val instanceId = _instanceId.value
407+
securePrefs.edit {
408+
remove("gateway.manual.token")
409+
remove("gateway.token.$instanceId")
410+
remove("gateway.bootstrapToken.$instanceId")
411+
remove("gateway.password.$instanceId")
412+
}
413+
_gatewayToken.value = ""
414+
_gatewayBootstrapToken.value = ""
415+
}
416+
405417
fun loadGatewayTlsFingerprint(stableId: String): String? {
406418
val key = "gateway.tls.$stableId"
407419
return plainPrefs.getString(key, null)?.trim()?.takeIf { it.isNotEmpty() }

apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,9 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
293293
}
294294

295295
validationText = null
296+
if (inputMode == ConnectInputMode.SetupCode) {
297+
viewModel.resetGatewaySetupAuth()
298+
}
296299
viewModel.setManualEnabled(true)
297300
viewModel.setManualHost(config.host)
298301
viewModel.setManualPort(config.port)

apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,7 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
580580
return@addOnSuccessListener
581581
}
582582
setupCode = scannedSetupCode.setupCode
583+
viewModel.resetGatewaySetupAuth()
583584
gatewayInputMode = GatewayInputMode.SetupCode
584585
gatewayError = null
585586
attemptedConnect = false
@@ -817,6 +818,7 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
817818
)
818819
return@Button
819820
}
821+
viewModel.resetGatewaySetupAuth()
820822
gatewayUrl = parsedSetup.url
821823
viewModel.setGatewayBootstrapToken(parsedSetup.bootstrapToken.orEmpty())
822824
val sharedToken = parsedSetup.token.orEmpty().trim()
@@ -899,6 +901,8 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
899901
viewModel.setManualTls(parsed.config.tls)
900902
if (gatewayInputMode == GatewayInputMode.Manual) {
901903
viewModel.setGatewayBootstrapToken("")
904+
} else {
905+
viewModel.resetGatewaySetupAuth()
902906
}
903907
if (token.isNotEmpty()) {
904908
viewModel.setGatewayToken(token)

apps/android/app/src/test/java/ai/openclaw/app/GatewayBootstrapAuthTest.kt

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package ai.openclaw.app
22

33
import ai.openclaw.app.gateway.GatewayEndpoint
4+
import ai.openclaw.app.gateway.DeviceAuthStore
5+
import ai.openclaw.app.gateway.DeviceIdentityStore
46
import ai.openclaw.app.gateway.GatewaySession
57
import ai.openclaw.app.gateway.GatewayTlsProbeFailure
68
import ai.openclaw.app.gateway.GatewayTlsProbeResult
@@ -21,14 +23,14 @@ import java.util.UUID
2123
@Config(sdk = [34])
2224
class GatewayBootstrapAuthTest {
2325
@Test
24-
fun skipsOperatorSessionWhenOnlyBootstrapAuthExists() {
25-
assertFalse(
26+
fun connectsOperatorSessionWhenOnlyBootstrapAuthExists() {
27+
assertTrue(
2628
shouldConnectOperatorSession(
2729
NodeRuntime.GatewayConnectAuth(token = "", bootstrapToken = "bootstrap-1", password = ""),
2830
storedOperatorToken = "",
2931
),
3032
)
31-
assertFalse(
33+
assertTrue(
3234
shouldConnectOperatorSession(
3335
NodeRuntime.GatewayConnectAuth(token = null, bootstrapToken = "bootstrap-1", password = null),
3436
storedOperatorToken = null,
@@ -75,6 +77,20 @@ class GatewayBootstrapAuthTest {
7577
assertEquals(NodeRuntime.GatewayConnectAuth(token = null, bootstrapToken = null, password = null), resolved)
7678
}
7779

80+
@Test
81+
fun resolveOperatorSessionConnectAuthUsesBootstrapWhenNoStoredOperatorTokenExists() {
82+
val resolved =
83+
resolveOperatorSessionConnectAuth(
84+
auth = NodeRuntime.GatewayConnectAuth(token = null, bootstrapToken = "bootstrap-1", password = null),
85+
storedOperatorToken = null,
86+
)
87+
88+
assertEquals(
89+
NodeRuntime.GatewayConnectAuth(token = null, bootstrapToken = "bootstrap-1", password = null),
90+
resolved,
91+
)
92+
}
93+
7894
@Test
7995
fun resolveOperatorSessionConnectAuthPrefersExplicitSharedAuth() {
8096
val resolved =
@@ -152,7 +168,7 @@ class GatewayBootstrapAuthTest {
152168

153169
assertEquals("fp-1", prefs.loadGatewayTlsFingerprint(endpoint.stableId))
154170
assertEquals("setup-bootstrap-token", desiredBootstrapToken(runtime, "nodeSession"))
155-
assertNull(desiredBootstrapToken(runtime, "operatorSession"))
171+
assertEquals("setup-bootstrap-token", desiredBootstrapToken(runtime, "operatorSession"))
156172
}
157173

158174
@Test
@@ -178,6 +194,33 @@ class GatewayBootstrapAuthTest {
178194
assertNull(runtime.pendingGatewayTrust.value)
179195
}
180196

197+
@Test
198+
fun resetGatewaySetupAuth_clearsStoredGatewayAndDeviceTokens() {
199+
val app = RuntimeEnvironment.getApplication()
200+
val securePrefs =
201+
app.getSharedPreferences(
202+
"openclaw.node.secure.test.${UUID.randomUUID()}",
203+
android.content.Context.MODE_PRIVATE,
204+
)
205+
val prefs = SecurePrefs(app, securePrefsOverride = securePrefs)
206+
val runtime = NodeRuntime(app, prefs)
207+
val deviceId = DeviceIdentityStore(app).loadOrCreate().deviceId
208+
val authStore = DeviceAuthStore(prefs)
209+
prefs.setGatewayToken("stale-shared-token")
210+
prefs.setGatewayBootstrapToken("stale-bootstrap-token")
211+
prefs.setGatewayPassword("stale-password")
212+
authStore.saveToken(deviceId, "node", "stale-node-token")
213+
authStore.saveToken(deviceId, "operator", "stale-operator-token")
214+
215+
runtime.resetGatewaySetupAuth()
216+
217+
assertNull(prefs.loadGatewayToken())
218+
assertNull(prefs.loadGatewayBootstrapToken())
219+
assertNull(prefs.loadGatewayPassword())
220+
assertNull(authStore.loadToken(deviceId, "node"))
221+
assertNull(authStore.loadToken(deviceId, "operator"))
222+
}
223+
181224
private fun waitForGatewayTrustPrompt(runtime: NodeRuntime): NodeRuntime.GatewayTrustPrompt {
182225
repeat(50) {
183226
runtime.pendingGatewayTrust.value?.let { return it }

apps/android/app/src/test/java/ai/openclaw/app/SecurePrefsTest.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package ai.openclaw.app
22

33
import android.content.Context
44
import org.junit.Assert.assertEquals
5+
import org.junit.Assert.assertNull
56
import org.junit.Test
67
import org.junit.runner.RunWith
78
import org.robolectric.RobolectricTestRunner
@@ -35,4 +36,24 @@ class SecurePrefsTest {
3536
assertEquals("bootstrap-token", prefs.loadGatewayBootstrapToken())
3637
assertEquals("bootstrap-token", prefs.gatewayBootstrapToken.value)
3738
}
39+
40+
@Test
41+
fun clearGatewaySetupAuth_removesStoredGatewayAuth() {
42+
val context = RuntimeEnvironment.getApplication()
43+
val securePrefs = context.getSharedPreferences("openclaw.node.secure.test.clear", Context.MODE_PRIVATE)
44+
securePrefs.edit().clear().commit()
45+
val prefs = SecurePrefs(context, securePrefsOverride = securePrefs)
46+
47+
prefs.setGatewayToken("shared-token")
48+
prefs.setGatewayBootstrapToken("bootstrap-token")
49+
prefs.setGatewayPassword("password-token")
50+
51+
prefs.clearGatewaySetupAuth()
52+
53+
assertEquals("", prefs.gatewayToken.value)
54+
assertEquals("", prefs.gatewayBootstrapToken.value)
55+
assertNull(prefs.loadGatewayToken())
56+
assertNull(prefs.loadGatewayBootstrapToken())
57+
assertNull(prefs.loadGatewayPassword())
58+
}
3859
}

0 commit comments

Comments
 (0)