Skip to content

Commit 8d60046

Browse files
committed
Break out askToken dialog
So it can be reused in the link flow.
1 parent bc0ce3f commit 8d60046

File tree

2 files changed

+102
-93
lines changed

2 files changed

+102
-93
lines changed

src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,39 @@
22

33
package com.coder.gateway
44

5+
import com.coder.gateway.models.TokenSource
6+
import com.coder.gateway.sdk.CoderCLIManager
57
import com.coder.gateway.sdk.humanizeDuration
68
import com.coder.gateway.sdk.isCancellation
79
import com.coder.gateway.sdk.isWorkerTimeout
810
import com.coder.gateway.sdk.suspendingRetryWithExponentialBackOff
11+
import com.coder.gateway.sdk.toURL
12+
import com.coder.gateway.sdk.withPath
913
import com.coder.gateway.services.CoderRecentWorkspaceConnectionsService
14+
import com.intellij.ide.BrowserUtil
1015
import com.intellij.openapi.application.ApplicationManager
16+
import com.intellij.openapi.application.ModalityState
1117
import com.intellij.openapi.components.service
1218
import com.intellij.openapi.diagnostic.Logger
1319
import com.intellij.openapi.rd.util.launchUnderBackgroundProgress
1420
import com.intellij.openapi.ui.Messages
21+
import com.intellij.openapi.ui.panel.ComponentPanelBuilder
22+
import com.intellij.ui.AppIcon
23+
import com.intellij.ui.components.JBTextField
24+
import com.intellij.ui.components.dialog
25+
import com.intellij.ui.dsl.builder.RowLayout
26+
import com.intellij.ui.dsl.builder.panel
27+
import com.intellij.util.applyIf
28+
import com.intellij.util.ui.UIUtil
1529
import com.jetbrains.gateway.ssh.SshDeployFlowUtil
1630
import com.jetbrains.gateway.ssh.SshMultistagePanelContext
1731
import com.jetbrains.gateway.ssh.deploy.DeployException
1832
import com.jetbrains.rd.util.lifetime.LifetimeDefinition
1933
import kotlinx.coroutines.launch
2034
import net.schmizz.sshj.common.SSHException
2135
import net.schmizz.sshj.connection.ConnectionException
36+
import java.awt.Dimension
37+
import java.net.URL
2238
import java.time.Duration
2339
import java.util.concurrent.TimeoutException
2440

@@ -31,8 +47,6 @@ class CoderRemoteConnectionHandle {
3147
suspend fun connect(parameters: Map<String, String>) {
3248
logger.debug("Creating connection handle", parameters)
3349
val clientLifetime = LifetimeDefinition()
34-
// TODO: If this fails determine if it is an auth error and if so prompt
35-
// for a new token, configure the CLI, then try again.
3650
clientLifetime.launchUnderBackgroundProgress(CoderGatewayBundle.message("gateway.connector.coder.connection.provider.title"), canBeCancelled = true, isIndeterminate = true, project = null) {
3751
try {
3852
indicator.text = CoderGatewayBundle.message("gateway.connector.coder.connecting")
@@ -88,5 +102,89 @@ class CoderRemoteConnectionHandle {
88102

89103
companion object {
90104
val logger = Logger.getInstance(CoderRemoteConnectionHandle::class.java.simpleName)
105+
106+
/**
107+
* Open a dialog for providing the token. Show any existing token so the
108+
* user can validate it if a previous connection failed. If we are not
109+
* retrying and the user has not checked the existing token box then open a
110+
* browser to the auth page. If the user has checked the existing token box
111+
* then populate the dialog with the token on disk (this will overwrite any
112+
* other existing token) unless this is a retry to avoid clobbering the
113+
* token that just failed. Return the token submitted by the user.
114+
*/
115+
@JvmStatic
116+
fun askToken(
117+
url: URL,
118+
token: Pair<String, TokenSource>?,
119+
isRetry: Boolean,
120+
useExisting: Boolean,
121+
): Pair<String, TokenSource>? {
122+
var (existingToken, tokenSource) = token ?: Pair("", TokenSource.USER)
123+
val getTokenUrl = url.withPath("/login?redirect=%2Fcli-auth")
124+
if (!isRetry && !useExisting) {
125+
BrowserUtil.browse(getTokenUrl)
126+
} else if (!isRetry && useExisting) {
127+
val (u, t) = CoderCLIManager.readConfig()
128+
if (url == u?.toURL() && !t.isNullOrBlank() && t != existingToken) {
129+
logger.info("Injecting token from CLI config")
130+
tokenSource = TokenSource.CONFIG
131+
existingToken = t
132+
}
133+
}
134+
var tokenFromUser: String? = null
135+
ApplicationManager.getApplication().invokeAndWait({
136+
lateinit var sessionTokenTextField: JBTextField
137+
val panel = panel {
138+
row {
139+
browserLink(
140+
CoderGatewayBundle.message("gateway.connector.view.login.token.label"),
141+
getTokenUrl.toString()
142+
)
143+
sessionTokenTextField = textField()
144+
.applyToComponent {
145+
text = existingToken
146+
minimumSize = Dimension(520, -1)
147+
}.component
148+
}.layout(RowLayout.PARENT_GRID)
149+
row {
150+
cell() // To align with the text box.
151+
cell(
152+
ComponentPanelBuilder.createCommentComponent(
153+
CoderGatewayBundle.message(
154+
if (isRetry) "gateway.connector.view.workspaces.token.rejected"
155+
else if (tokenSource == TokenSource.CONFIG) "gateway.connector.view.workspaces.token.injected"
156+
else if (existingToken.isNotBlank()) "gateway.connector.view.workspaces.token.comment"
157+
else "gateway.connector.view.workspaces.token.none"
158+
),
159+
false,
160+
-1,
161+
true
162+
).applyIf(isRetry) {
163+
apply {
164+
foreground = UIUtil.getErrorForeground()
165+
}
166+
}
167+
)
168+
}.layout(RowLayout.PARENT_GRID)
169+
}
170+
AppIcon.getInstance().requestAttention(null, true)
171+
if (!dialog(
172+
CoderGatewayBundle.message("gateway.connector.view.login.token.dialog"),
173+
panel = panel,
174+
focusedComponent = sessionTokenTextField
175+
).showAndGet()
176+
) {
177+
return@invokeAndWait
178+
}
179+
tokenFromUser = sessionTokenTextField.text
180+
}, ModalityState.any())
181+
if (tokenFromUser.isNullOrBlank()) {
182+
return null
183+
}
184+
if (tokenFromUser != existingToken) {
185+
tokenSource = TokenSource.USER
186+
}
187+
return Pair(tokenFromUser!!, tokenSource)
188+
}
91189
}
92190
}

src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt

Lines changed: 2 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.coder.gateway.views.steps
22

33
import com.coder.gateway.CoderGatewayBundle
4+
import com.coder.gateway.CoderRemoteConnectionHandle
45
import com.coder.gateway.icons.CoderIcons
56
import com.coder.gateway.models.CoderWorkspacesWizardModel
67
import com.coder.gateway.models.TokenSource
@@ -20,7 +21,6 @@ import com.coder.gateway.sdk.ex.WorkspaceResponseException
2021
import com.coder.gateway.sdk.toURL
2122
import com.coder.gateway.sdk.v2.models.WorkspaceStatus
2223
import com.coder.gateway.sdk.v2.models.toAgentModels
23-
import com.coder.gateway.sdk.withPath
2424
import com.coder.gateway.services.CoderSettingsState
2525
import com.intellij.icons.AllIcons
2626
import com.intellij.ide.ActivityTracker
@@ -30,20 +30,15 @@ import com.intellij.ide.util.PropertiesComponent
3030
import com.intellij.openapi.Disposable
3131
import com.intellij.openapi.actionSystem.AnAction
3232
import com.intellij.openapi.actionSystem.AnActionEvent
33-
import com.intellij.openapi.application.ApplicationManager
34-
import com.intellij.openapi.application.ModalityState
3533
import com.intellij.openapi.components.service
3634
import com.intellij.openapi.diagnostic.Logger
3735
import com.intellij.openapi.rd.util.launchUnderBackgroundProgress
3836
import com.intellij.openapi.ui.panel.ComponentPanelBuilder
3937
import com.intellij.openapi.ui.setEmptyState
4038
import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeScreenUIManager
4139
import com.intellij.ui.AnActionButton
42-
import com.intellij.ui.AppIcon
4340
import com.intellij.ui.RelativeFont
4441
import com.intellij.ui.ToolbarDecorator
45-
import com.intellij.ui.components.JBTextField
46-
import com.intellij.ui.components.dialog
4742
import com.intellij.ui.dsl.builder.AlignX
4843
import com.intellij.ui.dsl.builder.AlignY
4944
import com.intellij.ui.dsl.builder.BottomGap
@@ -54,7 +49,6 @@ import com.intellij.ui.dsl.builder.bindSelected
5449
import com.intellij.ui.dsl.builder.bindText
5550
import com.intellij.ui.dsl.builder.panel
5651
import com.intellij.ui.table.TableView
57-
import com.intellij.util.applyIf
5852
import com.intellij.util.ui.ColumnInfo
5953
import com.intellij.util.ui.JBFont
6054
import com.intellij.util.ui.JBUI
@@ -391,7 +385,7 @@ class CoderWorkspacesStepView(val setNextButtonEnabled: (Boolean) -> Unit) : Cod
391385
val oldURL = localWizardModel.coderURL.toURL()
392386
component.apply() // Force bindings to be filled.
393387
val newURL = localWizardModel.coderURL.toURL()
394-
val pastedToken = askToken(
388+
val pastedToken = CoderRemoteConnectionHandle.askToken(
395389
newURL,
396390
// If this is a new URL there is no point in trying to use the same
397391
// token.
@@ -512,89 +506,6 @@ class CoderWorkspacesStepView(val setNextButtonEnabled: (Boolean) -> Unit) : Cod
512506
}
513507
}
514508

515-
/**
516-
* Open a dialog for providing the token. Show any existing token so the
517-
* user can validate it if a previous connection failed. If we are not
518-
* retrying and the user has not checked the existing token box then open a
519-
* browser to the auth page. If the user has checked the existing token box
520-
* then populate the dialog with the token on disk (this will overwrite any
521-
* other existing token) unless this is a retry to avoid clobbering the
522-
* token that just failed. Return the token submitted by the user.
523-
*/
524-
private fun askToken(
525-
url: URL,
526-
token: Pair<String, TokenSource>?,
527-
isRetry: Boolean,
528-
useExisting: Boolean,
529-
): Pair<String, TokenSource>? {
530-
var (existingToken, tokenSource) = token ?: Pair("", TokenSource.USER)
531-
val getTokenUrl = url.withPath("/login?redirect=%2Fcli-auth")
532-
if (!isRetry && !useExisting) {
533-
BrowserUtil.browse(getTokenUrl)
534-
} else if (!isRetry && useExisting) {
535-
val (u, t) = CoderCLIManager.readConfig()
536-
if (url == u?.toURL() && !t.isNullOrBlank() && t != existingToken) {
537-
logger.info("Injecting token from CLI config")
538-
tokenSource = TokenSource.CONFIG
539-
existingToken = t
540-
}
541-
}
542-
var tokenFromUser: String? = null
543-
ApplicationManager.getApplication().invokeAndWait({
544-
lateinit var sessionTokenTextField: JBTextField
545-
val panel = panel {
546-
row {
547-
browserLink(
548-
CoderGatewayBundle.message("gateway.connector.view.login.token.label"),
549-
getTokenUrl.toString()
550-
)
551-
sessionTokenTextField = textField()
552-
.applyToComponent {
553-
text = existingToken
554-
minimumSize = Dimension(520, -1)
555-
}.component
556-
}.layout(RowLayout.PARENT_GRID)
557-
row {
558-
cell() // To align with the text box.
559-
cell(
560-
ComponentPanelBuilder.createCommentComponent(
561-
CoderGatewayBundle.message(
562-
if (isRetry) "gateway.connector.view.workspaces.token.rejected"
563-
else if (tokenSource == TokenSource.CONFIG) "gateway.connector.view.workspaces.token.injected"
564-
else if (existingToken.isNotBlank()) "gateway.connector.view.workspaces.token.comment"
565-
else "gateway.connector.view.workspaces.token.none"
566-
),
567-
false,
568-
-1,
569-
true
570-
).applyIf(isRetry) {
571-
apply {
572-
foreground = UIUtil.getErrorForeground()
573-
}
574-
}
575-
)
576-
}.layout(RowLayout.PARENT_GRID)
577-
}
578-
AppIcon.getInstance().requestAttention(null, true)
579-
if (!dialog(
580-
CoderGatewayBundle.message("gateway.connector.view.login.token.dialog"),
581-
panel = panel,
582-
focusedComponent = sessionTokenTextField
583-
).showAndGet()
584-
) {
585-
return@invokeAndWait
586-
}
587-
tokenFromUser = sessionTokenTextField.text
588-
}, ModalityState.any())
589-
if (tokenFromUser.isNullOrBlank()) {
590-
return null
591-
}
592-
if (tokenFromUser != existingToken) {
593-
tokenSource = TokenSource.USER
594-
}
595-
return Pair(tokenFromUser!!, tokenSource)
596-
}
597-
598509
private fun triggerWorkspacePolling(fetchNow: Boolean) {
599510
poller?.cancel()
600511

0 commit comments

Comments
 (0)