Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit d706582

Browse files
committedMay 23, 2025
fix: auto-login was no longer loading the last url and token
On top of that the shared auth context was reset each time the TBX window was hided and then made visible again. The auth context is now a singleton object shared between the wizard steps
1 parent eb5ab02 commit d706582

File tree

7 files changed

+85
-42
lines changed

7 files changed

+85
-42
lines changed
 

‎src/main/kotlin/com/coder/toolbox/browser/BrowserUtil.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package com.coder.toolbox.browser
22

3+
import com.coder.toolbox.util.toURL
34
import com.jetbrains.toolbox.api.core.os.LocalDesktopManager
4-
import java.net.URI
55

66

77
suspend fun LocalDesktopManager.browse(rawUrl: String, errorHandler: suspend (BrowserException) -> Unit) {
88
try {
9-
val url = URI.create(rawUrl).toURL()
9+
val url = rawUrl.toURL()
1010
this.openUrl(url)
1111
} catch (e: Exception) {
1212
errorHandler(

‎src/main/kotlin/com/coder/toolbox/views/AuthWizardPage.kt

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.coder.toolbox.views
33
import com.coder.toolbox.CoderToolboxContext
44
import com.coder.toolbox.cli.CoderCLIManager
55
import com.coder.toolbox.sdk.CoderRestClient
6+
import com.coder.toolbox.util.toURL
67
import com.coder.toolbox.views.state.AuthContext
78
import com.coder.toolbox.views.state.AuthWizardState
89
import com.coder.toolbox.views.state.WizardStep
@@ -25,24 +26,29 @@ class AuthWizardPage(
2526
context.ui.showUiPage(settingsPage)
2627
})
2728

28-
private val authContext: AuthContext = AuthContext()
29-
private val signInStep = SignInStep(context, authContext, this::notify)
30-
private val tokenStep = TokenStep(context, authContext)
29+
private val signInStep = SignInStep(context, this::notify)
30+
private val tokenStep = TokenStep(context)
3131
private val connectStep = ConnectStep(
3232
context,
33-
authContext,
3433
shouldAutoLogin,
3534
this::notify,
36-
this::displaySteps, onConnect
35+
this::displaySteps,
36+
onConnect
3737
)
3838

39-
4039
/**
4140
* Fields for this page, displayed in order.
4241
*/
4342
override val fields: MutableStateFlow<List<UiField>> = MutableStateFlow(emptyList())
4443
override val actionButtons: MutableStateFlow<List<RunnableActionDescription>> = MutableStateFlow(emptyList())
4544

45+
init {
46+
if (shouldAutoLogin.value) {
47+
AuthContext.url = context.secrets.lastDeploymentURL.toURL()
48+
AuthContext.token = context.secrets.lastToken
49+
}
50+
}
51+
4652
override fun beforeShow() {
4753
displaySteps()
4854
}

‎src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ private const val USER_HIT_THE_BACK_BUTTON = "User hit the back button"
2525
*/
2626
class ConnectStep(
2727
private val context: CoderToolboxContext,
28-
private val authContext: AuthContext,
2928
private val shouldAutoLogin: StateFlow<Boolean>,
3029
private val notify: (String, Throwable) -> Unit,
3130
private val refreshWizard: () -> Unit,
@@ -50,35 +49,39 @@ class ConnectStep(
5049
errorField.textState.update {
5150
context.i18n.pnotr("")
5251
}
53-
if (authContext.isNotReadyForAuth()) return
5452

55-
statusField.textState.update { context.i18n.pnotr("Connecting to ${authContext.url!!.host}...") }
53+
if (AuthContext.isNotReadyForAuth()) {
54+
errorField.textState.update {
55+
context.i18n.pnotr("URL and token were not properly configured. Please go back and provide a proper URL and token!")
56+
}
57+
return
58+
}
59+
60+
statusField.textState.update { context.i18n.pnotr("Connecting to ${AuthContext.url!!.host}...") }
5661
connect()
5762
}
5863

5964
/**
6065
* Try connecting to Coder with the provided URL and token.
6166
*/
6267
private fun connect() {
63-
val url = authContext.url
64-
val token = authContext.token
65-
if (url == null) {
68+
if (!AuthContext.hasUrl()) {
6669
errorField.textState.update { context.i18n.ptrl("URL is required") }
6770
return
6871
}
6972

70-
if (token.isNullOrBlank()) {
73+
if (!AuthContext.hasToken()) {
7174
errorField.textState.update { context.i18n.ptrl("Token is required") }
7275
return
7376
}
7477
signInJob?.cancel()
7578
signInJob = context.cs.launch {
7679
try {
77-
statusField.textState.update { (context.i18n.ptrl("Authenticating to ${url.host}...")) }
80+
statusField.textState.update { (context.i18n.ptrl("Authenticating to ${AuthContext.url!!.host}...")) }
7881
val client = CoderRestClient(
7982
context,
80-
url,
81-
token,
83+
AuthContext.url!!,
84+
AuthContext.token!!,
8285
PluginManager.pluginInfo.version,
8386
)
8487
// allows interleaving with the back/cancel action
@@ -93,21 +96,20 @@ class ConnectStep(
9396
yield()
9497
cli.login(client.token)
9598
}
96-
statusField.textState.update { (context.i18n.ptrl("Successfully configured ${url.host}...")) }
99+
statusField.textState.update { (context.i18n.ptrl("Successfully configured ${AuthContext.url!!.host}...")) }
97100
// allows interleaving with the back/cancel action
98101
yield()
99-
onConnect(client, cli)
100-
101-
authContext.reset()
102+
AuthContext.reset()
102103
AuthWizardState.resetSteps()
104+
onConnect(client, cli)
103105
} catch (ex: CancellationException) {
104106
if (ex.message != USER_HIT_THE_BACK_BUTTON) {
105-
notify("Connection to ${url.host} was configured", ex)
107+
notify("Connection to ${AuthContext.url!!.host} was configured", ex)
106108
onBack()
107109
refreshWizard()
108110
}
109111
} catch (ex: Exception) {
110-
notify("Failed to configure ${url.host}", ex)
112+
notify("Failed to configure ${AuthContext.url!!.host}", ex)
111113
onBack()
112114
refreshWizard()
113115
}
@@ -123,6 +125,7 @@ class ConnectStep(
123125
signInJob?.cancel(CancellationException(USER_HIT_THE_BACK_BUTTON))
124126
} finally {
125127
if (shouldAutoLogin.value) {
128+
AuthContext.reset()
126129
AuthWizardState.resetSteps()
127130
context.secrets.rememberMe = false
128131
} else {

‎src/main/kotlin/com/coder/toolbox/views/SignInStep.kt

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import com.jetbrains.toolbox.api.ui.components.TextType
1111
import com.jetbrains.toolbox.api.ui.components.ValidationErrorField
1212
import kotlinx.coroutines.flow.update
1313
import java.net.MalformedURLException
14-
import java.net.URI
14+
import java.net.URL
1515

1616
/**
1717
* A page with a field for providing the Coder deployment URL.
@@ -21,7 +21,6 @@ import java.net.URI
2121
*/
2222
class SignInStep(
2323
private val context: CoderToolboxContext,
24-
private val authContext: AuthContext,
2524
private val notify: (String, Throwable) -> Unit
2625
) :
2726
WizardStep {
@@ -56,22 +55,21 @@ class SignInStep(
5655
url
5756
}
5857
try {
59-
validateRawUrl(url)
58+
AuthContext.url = validateRawUrl(url)
6059
} catch (e: MalformedURLException) {
6160
notify("URL is invalid", e)
6261
return false
6362
}
64-
authContext.url = URI.create(url).toURL()
6563
AuthWizardState.goToNextStep()
6664
return true
6765
}
6866

6967
/**
7068
* Throws [MalformedURLException] if the given string violates RFC-2396
7169
*/
72-
private fun validateRawUrl(url: String) {
70+
private fun validateRawUrl(url: String): URL {
7371
try {
74-
url.toURL()
72+
return url.toURL()
7573
} catch (e: Exception) {
7674
throw MalformedURLException(e.message)
7775
}

‎src/main/kotlin/com/coder/toolbox/views/TokenStep.kt

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.coder.toolbox.views
22

33
import com.coder.toolbox.CoderToolboxContext
4-
import com.coder.toolbox.util.toURL
54
import com.coder.toolbox.util.withPath
65
import com.coder.toolbox.views.state.AuthContext
76
import com.coder.toolbox.views.state.AuthWizardState
@@ -22,7 +21,6 @@ import kotlinx.coroutines.flow.update
2221
*/
2322
class TokenStep(
2423
private val context: CoderToolboxContext,
25-
private val authContext: AuthContext
2624
) : WizardStep {
2725
private val tokenField = TextField(context.i18n.ptrl("Token"), "", TextType.Password)
2826
private val linkField = LinkField(context.i18n.ptrl("Get a token"), "")
@@ -39,15 +37,18 @@ class TokenStep(
3937
errorField.textState.update {
4038
context.i18n.pnotr("")
4139
}
42-
tokenField.textState.update {
43-
if (authContext.hasUrl()) {
44-
context.secrets.tokenFor(authContext.url!!) ?: ""
45-
} else {
46-
""
40+
if (AuthContext.hasUrl()) {
41+
tokenField.textState.update {
42+
context.secrets.tokenFor(AuthContext.url!!) ?: ""
43+
}
44+
} else {
45+
errorField.textState.update {
46+
context.i18n.pnotr("URL not configure in the previous step. Please go back and provide a proper URL.")
47+
return
4748
}
4849
}
4950
(linkField.urlState as MutableStateFlow).update {
50-
context.deploymentUrl?.first?.toURL()?.withPath("/login?redirect=%2Fcli-auth")?.toString() ?: ""
51+
AuthContext.url!!.withPath("/login?redirect=%2Fcli-auth")?.toString() ?: ""
5152
}
5253
}
5354

@@ -58,7 +59,7 @@ class TokenStep(
5859
return false
5960
}
6061

61-
authContext.token = token
62+
AuthContext.token = token
6263
AuthWizardState.goToNextStep()
6364
return true
6465
}

‎src/main/kotlin/com/coder/toolbox/views/state/AuthContext.kt

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,42 @@ package com.coder.toolbox.views.state
22

33
import java.net.URL
44

5-
data class AuthContext(
6-
var url: URL? = null,
5+
/**
6+
* Singleton that holds authentication context (URL and token) across multiple
7+
* Toolbox window lifecycle events.
8+
*
9+
* This ensures that user input (URL and token) is not lost when the Toolbox
10+
* window is temporarily closed or recreated.
11+
*/
12+
object AuthContext {
13+
/**
14+
* The currently entered URL.
15+
*/
16+
var url: URL? = null
17+
18+
/**
19+
* The token associated with the URL.
20+
*/
721
var token: String? = null
8-
) {
22+
23+
/**
24+
* Returns true if a URL is currently set.
25+
*/
926
fun hasUrl(): Boolean = url != null
1027

28+
/**
29+
* Returns true if a token is currently set.
30+
*/
31+
fun hasToken(): Boolean = !token.isNullOrBlank()
32+
33+
/**
34+
* Returns true if URL or token is missing and auth is not yet possible.
35+
*/
1136
fun isNotReadyForAuth(): Boolean = !(hasUrl() && token != null)
1237

38+
/**
39+
* Resets both URL and token to null.
40+
*/
1341
fun reset() {
1442
url = null
1543
token = null

‎src/main/kotlin/com/coder/toolbox/views/state/AuthWizardState.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
package com.coder.toolbox.views.state
22

33

4+
/**
5+
* A singleton that maintains the state of the authorization wizard across Toolbox window lifecycle events.
6+
*
7+
* This is used to persist the wizard's progress (i.e., current step) between visibility changes
8+
* of the Toolbox window. Without this object, closing and reopening the window would reset the wizard
9+
* to its initial state by creating a new instance.
10+
*/
411
object AuthWizardState {
512
private var currentStep = WizardStep.URL_REQUEST
613

0 commit comments

Comments
 (0)
Please sign in to comment.