Skip to content

poc: install plugins before IDE is launched#283

Open
fioan89 wants to merge 55 commits intomainfrom
poc-installing-plugins-before-launch
Open

poc: install plugins before IDE is launched#283
fioan89 wants to merge 55 commits intomainfrom
poc-installing-plugins-before-launch

Conversation

@fioan89
Copy link
Copy Markdown
Collaborator

@fioan89 fioan89 commented Mar 18, 2026

This PR leverages the new ToolLifetimeListener callbacks in order to pre-configure the IDE with a set of plugins. Of course the same callbacks can be leveraged for much more than plugin setup, from running custom scripts to setting up the IDE available memory.

fioan89 added 30 commits October 9, 2025 22:52
Toolbox API comes with a basic oauth2 client. This commit
sets-up details about two important oauth flows:

- authorization flow, in which the user is sent to web page
  where an authorization code is generated which is exchanged
  for an access token.
- details about token refresh endpoint where users can obtain
  a new access token and a new refresh token.

A couple of important aspects:
- the client app id is resolved in upstream
- as well as the actual endpoints for authorization and token refresh
- S256 is the only code challenge supported
…ation url

OAuth endpoint `.well-known/oauth-authorization-server` provides metadata about
the endpoint for dynamic client registration and supported response types.
This commit adds support for deserializing these values.
OAuth allows programatic client registration for apps like Coder Toolbox
via the DCR endpoint which requires a name for the client app, the requested
scopes, redirect URI, etc... DCR replies back with a similar structure but
in addition it returs two very important properties: client_id - a unique
client identifier string and also a client_secret - a secret string value
used by clients to authenticate to the token endpoint.
Code Toolbox plugin should protect against authorization code interception
attacks by making use of the PKCE security extension which involves
a cryptographically random string (128 characters) known as code verifier
and a code challenge - derived from code verifier using the S256 challenge method.
The OAuth2-compatible authentication manager provided by Toolbox
- authentication and token endpoints are now passed via the login configuration object
- similar for client_id and client_secret
- PCKE is now enabled
…injection

- remove ServiceLocator dependency from CoderToolboxContext
- move OAuth manager creation to CoderToolboxExtension for cleaner separation
- Refactor CoderOAuthManager to use configuration-based approach instead of constructor injection

The idea behind these changes is that createRefreshConfig API does not receive a configuration
object that can provide the client id and secret and even the refresh url. So initially
we worked around the issue by passing the necessary data via the constructor. However this approach
means a couple of things:

- the actual auth manager can be created only at a very late stage, when a URL is provided by users
- can't easily pass arround the auth manager without coupling the components
- have to recreate a new auth manager instance if the user logs out and logs in to a different URL
- service locator needs to be passed around because this is the actual factory of oauth managers in Toolbox

Instead, we went with a differet approach, COderOAuthManager will derive and store the refresh configs once
the authorization config is received. If the user logs out and logs in to a different URL the refresh data is
also guaranteed to be updated. And on top of that - this approach allows us to get rid of all of the issues
mentioned above.
Toolbox can handle automatically the exchange of an authorization code with a token
by handling the custom URI for oauth. This commit calls the necessary API
in the Coder Toolbox URI handling.
POST /api/v2/oauth2-provider/apps is actually for manual admin
registration for admin created apps. Programmatic Dynamic Client
Registration is done via `POST /oauth2/register`.

At the same time I included `registration_access_token` and `registration_client_uri`
to use it later in order to refresh the client secret without re-registering the client app.
A bunch of code thrown around to launch the OAuth flow.
Still needs a couple of things:
- persist the client id and registration uri and token
- re-use client id instead of re-register every time
- properly handle scenarios where OAuth is not available
- the OAuth right now can be enabled if we log out and then
hit next in the deployment screen
A new config `preferAuthViaApiToken` allows users to continue to use
API tokens for authentication when OAuth2 is available on the Coder deployment.
Account implementation with logic to resolve the account once the token
is retrieved. Marshalling logic for the account is also added.

There is a limitation in the Toolbox API where createRefreshConfig
is not receiving the auth params. We worked around by capturing and
storing these params in the createAuthConfig but this is unreliable.
Instead we use the account to pass the missing info around.
OAuth2 should be launched if user prefers is over any other method of auth
and if only the server supports it.
Fallback on client_secret_basic or None depending on what the Coder
server supports.
…n endpoint

Based on the auth method type we need to send client id and client secret as a basic
auth header or part of the body as an encoded url form
We encountered a couple of issues with the Toolbox API which is inflexible:
- we don't have complete control over which parameters are sent as query&body
- we don't have fully basic + headers + body logging for debugging purposes
- doesn't integrate that well with our existing http client used for polling
- spent more than a couple of hours trying to understand why Coder rejects the
  authorization call with:
 ```
  {"error":"invalid_request","error_description":"The request is missing required parameters or is otherwise malformed"} from Coder server.
 ```
 Instead we will slowly discard the existing logic and rely on enhancements to our existing http client.
 Basically, the login screen will try to first determine if mTLS auth is configured and use that, otherwise
 it will check if the user wants to use OAuth over API token, if available. When the flag is
 true then the login screen will query the Coder server to see if OAuth2 is supported.
 If that is true then browser is launched pointing to the authentication URL. If not we will default to
 the API token authentication.
Same pattern for instantiating the same http client is used in many places.
Refactored the setup into a simple factory method.
Implements the callback for the authentication step. It basically exchanges the auth code for
a token response. Supports client_secret_basic as preferred auth method by Coder for the
token endpoint, but also client_secret_post if nothing is available.
Coder server effectively rejected the request because the
challenge was executed twice
Include utility for fallbacks of auth method.
Initiates the REST API client and the CLI with the resolved token.
There is no support for refreshing the token if it expires, for now.
Rough implementation for detecting expired tokens, and calling the token endpoint
to refresh and retrieve a new access token but also a new refresh token.

If the refresh is successful then we retry the failed call again, but also
execute a callback that in subsequent commits should allow the new token to be
passed to the cli, and also stored.

This is a crude implementation, I think a refactor is needed as there are so many params
that are cloned and passed around between the CoderProvider, login steps and rest api client
and then back again to the coder remote provider.
Which was improperly named, it had nothing to do with the CLI.
It is used only for the auth setup wizard.
…tion instead of ProcessInitException

We observed some UTs fails to a difference in the JDK version used by Gradle (CLI) versus the one used by IntelliJ to run the tests.
The zt-exec library attempts to wrap the standard java.io.IOException (thrown when a process fails to start) into a ProcessInitException.
It does this by parsing the exception message to extract the error code (e.g., looking for error=2).
However, different JDK versions (and distributions) format this error message differently.
If the JDK used by IntelliJ produces an error message that zt-exec doesn't recognize (e.g., missing the error= part or using a different format),
zt-exec fails to wrap the exception and instead propagates the original IOException.
To fix the test so it passes in both environments, we should catch the more general IOException (which is the parent of ProcessInitException) and
assert on the essential part of the error message ("No such file or directory") rather than the specific format zt-exec produces.
We need the client id, client secret, auth method a refresh token to be persisted
so that at the next app restart we can avoid going again through client registration,
and authentication and authorization steps.
Save the oauth session details as soon as the http client and cli
are initialized. Up until know it happened only on token refresh.
Load previous oauth session details, and resolve the access token
before initializing the cli and rest client.
A flow like logging out from an existing OAuth2 session and then logging
back in fails due to issues with handleUri API which is not able to properly
handle UI pages, especially if an existing page was already pushed into the
display stack - after log out we have the DeploymentUrl step visible. Once the user
hits next the browser is launched where the user authorizes the client app. After
which the callback URL pops up the plugin screen which is still showing the deployment
url. Normally, in the URI handler we would now push a new page - the ConnectStep. But
that doesn't work, the plugin is still stuck with the initial page. After extensive
research I found way around by making the login wizard for "listening" to step updates.

In other words, once the authorization callback launches the URI, in the handler instead
of pushing a new page to the display stack we actually push a new "step" in the existing
wizard page. See https://youtrack.jetbrains.com/issue/TBX-16622 for more
details.
There were a couple of issues with the existing implementation.
ConnectStep was emiting the URL_REQUEST both at the end of the connect
but also part of the close() call. This caused the screen to flicker
during setup. As a fix close no longer resets the setup global state
while a final new step was introduced to allow the ConnectStep to
properly close the entire wizard.
When switching between Coder deployments using the URI.
…ization

mTLS always takes precedence over OAuth and API token authorization, while API Token
is the default method if mTLS is not used. But now user can prefer OAuth authorization
over API tokens, if OAuth is available. The setting is now available in the Settings page,
no longer in the main login screen as it was the case in the initial draft of the PR.
For easy navigation as there were too many of them, mixed
with no logical differentiation. Now we cluster on a couple of topics:
- General
- Security & Authentication
- CLI
- SSH
Initially the OAuth implementation was trying to use
the existing OAuth API/Client provided by Toolbox which is not very flexible.
The OAuth2 server implementation needs to provide an authorization code
that can be exchanged for an access token. But in order to make sure the
authorization code is for the "our" login request, the client provides a
state value when launching the authorization URL which the OAuth2 server has
to send back when with the auth code. This fix makes sure the authorization
code is actually sent, and that the state value is the same as in our initial
request.
This fix reports an error to the user when token exchange
request is failing, or returning an empty body or a body that does not contain the token.
The logic for exchanging auth code to tokens, refreshing tokens was used in
multiple places without any code reuse strategy. Extracted an OAuth service that
handles the basic operations.
The metadata endpoint provide an absolute URL for the client registration endpoint
which we should use instead of hardcoding the path relative to the base url.
This PR leverages the new ToolLifetimeListener callbacks
in order to pre-configure the IDE with a set of plugins.
Of course the same callbacks can be leveraged for much more
than plugin setup, from running custom scripts to setting up
the IDE available memory.
Base automatically changed from impl-support-for-oauth to main April 17, 2026 21:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant