Skip to content

Show errors more prominently and show token source #228

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 25, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Surface token source and error
Now it will say whether the token was from the config or was the last
known token and if it fails there will be an error message.  You could
always check the error in the bottom right but this way it is more
obvious why the token dialog has reappeared.

Also if the URL has changed there is no point trying to use the token
we had stored for the previous URL.
  • Loading branch information
code-asher committed Apr 24, 2023
commit e3bf09269a794972f4a301435b4fc542ed2bae61
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
package com.coder.gateway.models

enum class TokenSource {
CONFIG, // Pulled from the Coder CLI config.
USER, // Input by the user.
LAST_USED, // Last used token, either from storage or current run.
}

data class CoderWorkspacesWizardModel(
var coderURL: String = "https://coder.example.com",
var token: String = "",
var token: Pair<String, TokenSource>? = null,
var selectedWorkspace: WorkspaceAgentModel? = null,
var useExistingToken: Boolean = false,
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.coder.gateway.views.steps
import com.coder.gateway.CoderGatewayBundle
import com.coder.gateway.icons.CoderIcons
import com.coder.gateway.models.CoderWorkspacesWizardModel
import com.coder.gateway.models.TokenSource
import com.coder.gateway.models.WorkspaceAgentModel
import com.coder.gateway.models.WorkspaceAgentStatus
import com.coder.gateway.models.WorkspaceAgentStatus.FAILED
Expand Down Expand Up @@ -56,10 +57,12 @@ import com.intellij.ui.dsl.builder.bindSelected
import com.intellij.ui.dsl.builder.bindText
import com.intellij.ui.dsl.builder.panel
import com.intellij.ui.table.TableView
import com.intellij.util.applyIf
import com.intellij.util.ui.ColumnInfo
import com.intellij.util.ui.JBFont
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.ListTableModel
import com.intellij.util.ui.UIUtil
import com.intellij.util.ui.table.IconTableCellRenderer
import com.jetbrains.rd.util.lifetime.LifetimeDefinition
import kotlinx.coroutines.CoroutineScope
Expand Down Expand Up @@ -348,7 +351,7 @@ class CoderWorkspacesStepView(val setNextButtonEnabled: (Boolean) -> Unit) : Cod

override fun onInit(wizardModel: CoderWorkspacesWizardModel) {
listTableModelOfWorkspaces.items = emptyList()
if (localWizardModel.coderURL.isNotBlank() && localWizardModel.token.isNotBlank()) {
if (localWizardModel.coderURL.isNotBlank() && localWizardModel.token != null) {
triggerWorkspacePolling(true)
} else {
val (url, token) = readStorageOrConfig()
Expand All @@ -357,10 +360,10 @@ class CoderWorkspacesStepView(val setNextButtonEnabled: (Boolean) -> Unit) : Cod
tfUrl?.text = url
}
if (!token.isNullOrBlank()) {
localWizardModel.token = token
localWizardModel.token = Pair(token, TokenSource.CONFIG)
}
if (!url.isNullOrBlank() && !token.isNullOrBlank()) {
connect(url.toURL(), token)
connect(url.toURL(), Pair(token, TokenSource.CONFIG))
}
}
updateWorkspaceActions()
Expand Down Expand Up @@ -417,20 +420,21 @@ class CoderWorkspacesStepView(val setNextButtonEnabled: (Boolean) -> Unit) : Cod
* If the token is invalid abort and start over from askTokenAndConnect()
* unless retry is false.
*/
private fun askTokenAndConnect(openBrowser: Boolean = true) {
private fun askTokenAndConnect(isRetry: Boolean = false) {
val oldURL = localWizardModel.coderURL.toURL()
component.apply() // Force bindings to be filled.
val newURL = localWizardModel.coderURL.toURL()
val pastedToken = askToken(
localWizardModel.coderURL.toURL(),
localWizardModel.token,
openBrowser,
newURL,
// If this is a new URL there is no point in trying to use the same
// token.
if (oldURL == newURL) localWizardModel.token else null,
isRetry,
localWizardModel.useExistingToken,
)
if (pastedToken.isNullOrBlank()) {
return // User aborted.
}
) ?: return // User aborted.
localWizardModel.token = pastedToken
connect(localWizardModel.coderURL.toURL(), localWizardModel.token) {
askTokenAndConnect(false)
connect(newURL, pastedToken) {
askTokenAndConnect(true)
}
}

Expand All @@ -444,7 +448,11 @@ class CoderWorkspacesStepView(val setNextButtonEnabled: (Boolean) -> Unit) : Cod
*
* If the token is invalid invoke onAuthFailure.
*/
private fun connect(deploymentURL: URL, token: String, onAuthFailure: (() -> Unit)? = null): Job {
private fun connect(
deploymentURL: URL,
token: Pair<String, TokenSource>,
onAuthFailure: (() -> Unit)? = null,
): Job {
// Clear out old deployment details.
poller?.cancel()
tableOfWorkspaces.setEmptyState("Connecting to $deploymentURL...")
Expand All @@ -465,16 +473,16 @@ class CoderWorkspacesStepView(val setNextButtonEnabled: (Boolean) -> Unit) : Cod
)
try {
this.indicator.text = "Authenticating client..."
authenticate(deploymentURL, token)
authenticate(deploymentURL, token.first)
// Remember these in order to default to them for future attempts.
appPropertiesService.setValue(CODER_URL_KEY, deploymentURL.toString())
appPropertiesService.setValue(SESSION_TOKEN, token)
appPropertiesService.setValue(SESSION_TOKEN, token.first)

this.indicator.text = "Downloading Coder CLI..."
cliManager.downloadCLI()

this.indicator.text = "Authenticating Coder CLI..."
cliManager.login(token)
cliManager.login(token.first)

this.indicator.text = "Retrieving workspaces..."
loadWorkspaces()
Expand Down Expand Up @@ -521,22 +529,29 @@ class CoderWorkspacesStepView(val setNextButtonEnabled: (Boolean) -> Unit) : Cod
}

/**
* Open a dialog for providing the token. Show the existing token so the
* user can validate it if a previous connection failed. Open a browser to
* the auth page if openBrowser is true and useExisting is false. If
* useExisting is true then populate the dialog with the token on disk if
* there is one and it matches the url (this will overwrite the provided
* token). Return the token submitted by the user.
* Open a dialog for providing the token. Show any existing token so the
* user can validate it if a previous connection failed. If we are not
* retrying and the user has not checked the existing token box then open a
* browser to the auth page. If the user has checked the existing token box
* then populate the dialog with the token on disk (this will overwrite any
* other existing token) unless this is a retry to avoid clobbering the
* token that just failed. Return the token submitted by the user.
*/
private fun askToken(url: URL, token: String, openBrowser: Boolean, useExisting: Boolean): String? {
var existingToken = token
private fun askToken(
url: URL,
token: Pair<String, TokenSource>?,
isRetry: Boolean,
useExisting: Boolean,
): Pair<String, TokenSource>? {
var (existingToken, tokenSource) = token ?: Pair("", TokenSource.USER)
val getTokenUrl = url.withPath("/login?redirect=%2Fcli-auth")
if (openBrowser && !useExisting) {
if (!isRetry && !useExisting) {
BrowserUtil.browse(getTokenUrl)
} else if (useExisting) {
} else if (!isRetry && useExisting) {
val (u, t) = CoderCLIManager.readConfig()
if (url == u?.toURL() && !t.isNullOrBlank()) {
logger.info("Injecting valid token from CLI config")
if (url == u?.toURL() && !t.isNullOrBlank() && t != existingToken) {
logger.info("Injecting token from CLI config")
tokenSource = TokenSource.CONFIG
existingToken = t
}
}
Expand All @@ -549,11 +564,32 @@ class CoderWorkspacesStepView(val setNextButtonEnabled: (Boolean) -> Unit) : Cod
CoderGatewayBundle.message("gateway.connector.view.login.token.label"),
getTokenUrl.toString()
)
sessionTokenTextField = textField().applyToComponent {
text = existingToken
minimumSize = Dimension(520, -1)
}.component
}
sessionTokenTextField = textField()
.applyToComponent {
text = existingToken
minimumSize = Dimension(520, -1)
}.component
}.layout(RowLayout.PARENT_GRID)
row {
cell() // To align with the text box.
cell(
ComponentPanelBuilder.createCommentComponent(
CoderGatewayBundle.message(
if (isRetry) "gateway.connector.view.workspaces.token.rejected"
else if (tokenSource == TokenSource.CONFIG) "gateway.connector.view.workspaces.token.injected"
else if (existingToken.isNotBlank()) "gateway.connector.view.workspaces.token.comment"
else "gateway.connector.view.workspaces.token.none"
),
false,
-1,
true
).applyIf(isRetry) {
apply {
foreground = UIUtil.getErrorForeground()
}
}
)
}.layout(RowLayout.PARENT_GRID)
}
AppIcon.getInstance().requestAttention(null, true)
if (!dialog(
Expand All @@ -566,7 +602,13 @@ class CoderWorkspacesStepView(val setNextButtonEnabled: (Boolean) -> Unit) : Cod
}
tokenFromUser = sessionTokenTextField.text
}, ModalityState.any())
return tokenFromUser
if (tokenFromUser.isNullOrBlank()) {
return null
}
if (tokenFromUser != existingToken) {
tokenSource = TokenSource.USER
}
return Pair(tokenFromUser!!, tokenSource)
}

private fun triggerWorkspacePolling(fetchNow: Boolean) {
Expand Down
4 changes: 4 additions & 0 deletions src/main/resources/messages/CoderGatewayBundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ gateway.connector.view.workspaces.connect.unauthorized=Token was rejected by {0}
gateway.connector.view.workspaces.connect.timeout=Unable to connect to {0}; is it up?
gateway.connector.view.workspaces.connect.download-failed=Failed to download Coder CLI from {0}: {1}
gateway.connector.view.workspaces.connect.failed=Failed to configure connection to {0}: {1}
gateway.connector.view.workspaces.token.comment=The last used token is shown above.
gateway.connector.view.workspaces.token.rejected=This token was rejected.
gateway.connector.view.workspaces.token.injected=This token was pulled from your CLI config.
gateway.connector.view.workspaces.token.none=No existing token found.
gateway.connector.view.coder.remoteproject.loading.text=Retrieving products...
gateway.connector.view.coder.remoteproject.ide.error.text=Could not retrieve any IDE because an error was encountered. Please check the logs for more details!
gateway.connector.view.coder.remoteproject.ssh.error.text=Can't connect to the workspace. Please make sure Coder Agent is running!
Expand Down