@@ -179,17 +179,28 @@ export class LoginCoordinator {
179179 isAutoLogin: boolean,
180180 oauthSessionManager: OAuthSessionManager,
181181 ): Promise<LoginResult> {
182- const token = await this.secretsManager.getSessionToken(deployment.label);
183- const client = CoderApi.create(deployment.url, token, this.logger);
184182 const needsToken = needToken(vscode.workspace.getConfiguration());
185- if (!needsToken || token) {
186- try {
187- const user = await client.getAuthenticatedUser();
188- // For non-token auth, we write a blank token since the `vscodessh`
189- // command currently always requires a token file.
190- // For token auth, we have valid access so we can just return the user here
191- return { success: true, token: needsToken && token ? token : "", user };
192- } catch (err) {
183+ const client = CoderApi.create(deployment.url, "", this.logger);
184+
185+ let storedToken: string | undefined;
186+ if (needsToken) {
187+ storedToken = await this.secretsManager.getSessionToken(deployment.label);
188+ if (storedToken) {
189+ client.setSessionToken(storedToken);
190+ }
191+ }
192+
193+ // Attempt authentication with current credentials (token or mTLS)
194+ try {
195+ const user = await client.getAuthenticatedUser();
196+ // Return the token that was used (empty string for mTLS since
197+ // the `vscodessh` command currently always requires a token file)
198+ return { success: true, token: storedToken ?? "", user };
199+ } catch (err) {
200+ if (needsToken) {
201+ // For token auth: silently continue to prompt for new credentials
202+ } else {
203+ // For mTLS: show error and abort (no credentials to prompt for)
193204 const message = getErrorMessage(err, "no response from the server");
194205 if (isAutoLogin) {
195206 this.logger.warn("Failed to log in to Coder server:", message);
@@ -203,7 +214,6 @@ export class LoginCoordinator {
203214 },
204215 );
205216 }
206- // Invalid certificate, most likely.
207217 return { success: false };
208218 }
209219 }
@@ -212,12 +222,8 @@ export class LoginCoordinator {
212222 switch (authMethod) {
213223 case "oauth":
214224 return this.loginWithOAuth(client, oauthSessionManager, deployment);
215- case "legacy": {
216- const initialToken =
217- token ||
218- (await this.secretsManager.getSessionToken(deployment.label));
219- return this.loginWithToken(client, initialToken);
220- }
225+ case "legacy":
226+ return this.loginWithToken(client);
221227 case undefined:
222228 return { success: false }; // User aborted
223229 }
@@ -226,10 +232,7 @@ export class LoginCoordinator {
226232 /**
227233 * Session token authentication flow.
228234 */
229- private async loginWithToken(
230- client: CoderApi,
231- initialToken: string | undefined,
232- ): Promise<LoginResult> {
235+ private async loginWithToken(client: CoderApi): Promise<LoginResult> {
233236 const url = client.getAxiosInstance().defaults.baseURL;
234237 if (!url) {
235238 throw new Error("No base URL set on REST client");
@@ -246,7 +249,6 @@ export class LoginCoordinator {
246249 title: "Coder API Key",
247250 password: true,
248251 placeHolder: "Paste your API key.",
249- value: initialToken,
250252 ignoreFocusOut: true,
251253 validateInput: async (value) => {
252254 if (!value) {
@@ -315,10 +317,15 @@ export class LoginCoordinator {
315317 user,
316318 };
317319 } catch (error) {
318- this.logger.error("OAuth authentication failed:", error);
319- vscode.window.showErrorMessage(
320- `OAuth authentication failed: ${getErrorMessage(error, "Unknown error")}`,
321- );
320+ const title = "OAuth authentication failed";
321+ this.logger.error(title, error);
322+ if (error instanceof CertificateError) {
323+ error.showNotification(title);
324+ } else {
325+ vscode.window.showErrorMessage(
326+ `${title}: ${getErrorMessage(error, "Unknown error")}`,
327+ );
328+ }
322329 return { success: false };
323330 }
324331 }
0 commit comments