@@ -2,7 +2,7 @@ import { Api } from "coder/site/src/api/api"
2
2
import { getErrorMessage } from "coder/site/src/api/errors"
3
3
import { User , Workspace , WorkspaceAgent } from "coder/site/src/api/typesGenerated"
4
4
import * as vscode from "vscode"
5
- import { makeCoderSdk } from "./api"
5
+ import { makeCoderSdk , needToken } from "./api"
6
6
import { extractAgents } from "./api-helper"
7
7
import { CertificateError } from "./error"
8
8
import { Storage } from "./storage"
@@ -147,78 +147,33 @@ export class Commands {
147
147
// a host label.
148
148
const label = typeof args [ 2 ] === "undefined" ? toSafeHost ( url ) : args [ 2 ]
149
149
150
- // Use a temporary client to avoid messing with the global one while trying
151
- // to log in.
152
- const restClient = await makeCoderSdk ( url , undefined , this . storage )
153
-
154
- let user : User | undefined
155
- let token : string | undefined = args [ 1 ]
156
- if ( ! token ) {
157
- const opened = await vscode . env . openExternal ( vscode . Uri . parse ( `${ url } /cli-auth` ) )
158
- if ( ! opened ) {
159
- vscode . window . showWarningMessage ( "You must accept the URL prompt to generate an API key." )
160
- return
161
- }
162
-
163
- token = await vscode . window . showInputBox ( {
164
- title : "Coder API Key" ,
165
- password : true ,
166
- placeHolder : "Copy your API key from the opened browser page." ,
167
- value : await this . storage . getSessionToken ( ) ,
168
- ignoreFocusOut : true ,
169
- validateInput : async ( value ) => {
170
- restClient . setSessionToken ( value )
171
- try {
172
- user = await restClient . getAuthenticatedUser ( )
173
- if ( ! user ) {
174
- throw new Error ( "Failed to get authenticated user" )
175
- }
176
- } catch ( err ) {
177
- // For certificate errors show both a notification and add to the
178
- // text under the input box, since users sometimes miss the
179
- // notification.
180
- if ( err instanceof CertificateError ) {
181
- err . showNotification ( )
182
-
183
- return {
184
- message : err . x509Err || err . message ,
185
- severity : vscode . InputBoxValidationSeverity . Error ,
186
- }
187
- }
188
- // This could be something like the header command erroring or an
189
- // invalid session token.
190
- const message = getErrorMessage ( err , "no response from the server" )
191
- return {
192
- message : "Failed to authenticate: " + message ,
193
- severity : vscode . InputBoxValidationSeverity . Error ,
194
- }
195
- }
196
- } ,
197
- } )
198
- }
199
- if ( ! token || ! user ) {
200
- return
150
+ // Try to get a token from the user, if we need one, and their user.
151
+ const res = await this . maybeAskToken ( url , args [ 1 ] )
152
+ if ( ! res ) {
153
+ return // The user aborted.
201
154
}
202
155
203
- // The URL and token are good; authenticate the global client.
156
+ // The URL is good and the token is either good or not required; authorize
157
+ // the global client.
204
158
this . restClient . setHost ( url )
205
- this . restClient . setSessionToken ( token )
159
+ this . restClient . setSessionToken ( res . token )
206
160
207
161
// Store these to be used in later sessions.
208
162
await this . storage . setUrl ( url )
209
- await this . storage . setSessionToken ( token )
163
+ await this . storage . setSessionToken ( res . token )
210
164
211
165
// Store on disk to be used by the cli.
212
- await this . storage . configureCli ( label , url , token )
166
+ await this . storage . configureCli ( label , url , res . token )
213
167
168
+ // These contexts control various menu items and the sidebar.
214
169
await vscode . commands . executeCommand ( "setContext" , "coder.authenticated" , true )
215
- if ( user . roles . find ( ( role ) => role . name === "owner" ) ) {
170
+ if ( res . user . roles . find ( ( role ) => role . name === "owner" ) ) {
216
171
await vscode . commands . executeCommand ( "setContext" , "coder.isOwner" , true )
217
172
}
218
173
219
174
vscode . window
220
175
. showInformationMessage (
221
- `Welcome to Coder, ${ user . username } !` ,
176
+ `Welcome to Coder, ${ res . user . username } !` ,
222
177
{
223
178
detail : "You can now use the Coder extension to manage your Coder instance." ,
224
179
} ,
@@ -234,6 +189,69 @@ export class Commands {
234
189
vscode . commands . executeCommand ( "coder.refreshWorkspaces" )
235
190
}
236
191
192
+ /**
193
+ * If necessary, ask for a token, and keep asking until the token has been
194
+ * validated. Return the token and user that was fetched to validate the
195
+ * token.
196
+ */
197
+ private async maybeAskToken ( url : string , token : string ) : Promise < { user : User ; token : string } | null > {
198
+ const restClient = await makeCoderSdk ( url , token , this . storage )
199
+ if ( ! needToken ( ) ) {
200
+ return {
201
+ // For non-token auth, we write a blank token since the `vscodessh`
202
+ // command currently always requires a token file.
203
+ token : "" ,
204
+ user : await restClient . getAuthenticatedUser ( ) ,
205
+ }
206
+ }
207
+
208
+ // This prompt is for convenience; do not error if they close it since
209
+ // they may already have a token or already have the page opened.
210
+ await vscode . env . openExternal ( vscode . Uri . parse ( `${ url } /cli-auth` ) )
211
+
212
+ // For token auth, start with the existing token in the prompt or the last
213
+ // used token. Once submitted, if there is a failure we will keep asking
214
+ // the user for a new token until they quit.
215
+ let user : User | undefined
216
+ const validatedToken = await vscode . window . showInputBox ( {
217
+ title : "Coder API Key" ,
218
+ password : true ,
219
+ placeHolder : "Paste your API key." ,
220
+ value : token || ( await this . storage . getSessionToken ( ) ) ,
221
+ ignoreFocusOut : true ,
222
+ validateInput : async ( value ) => {
223
+ restClient . setSessionToken ( value )
224
+ try {
225
+ user = await restClient . getAuthenticatedUser ( )
226
+ } catch ( err ) {
227
+ // For certificate errors show both a notification and add to the
228
+ // text under the input box, since users sometimes miss the
229
+ // notification.
230
+ if ( err instanceof CertificateError ) {
231
+ err . showNotification ( )
232
+
233
+ return {
234
+ message : err . x509Err || err . message ,
235
+ severity : vscode . InputBoxValidationSeverity . Error ,
236
+ }
237
+ }
238
+ // This could be something like the header command erroring or an
239
+ // invalid session token.
240
+ const message = getErrorMessage ( err , "no response from the server" )
241
+ return {
242
+ message : "Failed to authenticate: " + message ,
243
+ severity : vscode . InputBoxValidationSeverity . Error ,
244
+ }
245
+ }
246
+ } ,
247
+ } )
248
+
249
+ if ( validatedToken && user ) {
250
+ return { token : validatedToken , user }
251
+ }
252
+ return null
253
+ }
254
+
237
255
/**
238
256
* View the logs for the currently connected workspace.
239
257
*/
0 commit comments