Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 2 additions & 2 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export class Commands {
if (this.deploymentManager.isAuthenticated()) {
return;
}
this.logger.info("Logging in");
this.logger.debug("Logging in");

const currentDeployment = await this.secretsManager.getCurrentDeployment();
const url = await maybeAskUrl(
Expand Down Expand Up @@ -205,7 +205,7 @@ export class Commands {
return;
}

this.logger.info("Logging out");
this.logger.debug("Logging out");

await this.deploymentManager.clearDeployment();

Expand Down
17 changes: 14 additions & 3 deletions src/deployment/deploymentManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import { type Logger } from "../logging/logger";
import { type OAuthSessionManager } from "../oauth/sessionManager";
import { type WorkspaceProvider } from "../workspace/workspacesProvider";

import { type Deployment, type DeploymentWithAuth } from "./types";
import {
DeploymentSchema,
type Deployment,
type DeploymentWithAuth,
} from "./types";

import type { User } from "coder/site/src/api/typesGenerated";
import type * as vscode from "vscode";
Expand Down Expand Up @@ -115,6 +119,10 @@ export class DeploymentManager implements vscode.Disposable {
public async setDeployment(
deployment: DeploymentWithAuth & { user: User },
): Promise<void> {
this.logger.debug("Setting deployment", {
hostname: deployment.safeHostname,
user: deployment.user.username,
});
this.#deployment = { ...deployment };

// Updates client credentials
Expand All @@ -131,14 +139,17 @@ export class DeploymentManager implements vscode.Disposable {
this.updateAuthContexts(deployment.user);
this.refreshWorkspaces();

await this.oauthSessionManager.setDeployment(deployment);
await this.persistDeployment(deployment);
const deploymentWithoutAuth: Deployment =
DeploymentSchema.parse(deployment);
await this.oauthSessionManager.setDeployment(deploymentWithoutAuth);
await this.persistDeployment(deploymentWithoutAuth);
}

/**
* Clears the current deployment.
*/
public async clearDeployment(): Promise<void> {
this.logger.debug("Clearing deployment", this.#deployment?.safeHostname);
this.suspendSession();
this.#authListenerDisposable?.dispose();
this.#authListenerDisposable = undefined;
Expand Down
2 changes: 1 addition & 1 deletion src/login/loginCoordinator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ export class LoginCoordinator implements vscode.Disposable {
*/
private async loginWithOAuth(deployment: Deployment): Promise<LoginResult> {
try {
this.logger.info("Starting OAuth authentication");
this.logger.debug("Starting OAuth authentication");

const { tokenResponse, user } = await vscode.window.withProgress(
{
Expand Down
4 changes: 2 additions & 2 deletions src/oauth/authorizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export class OAuthAuthorizer implements vscode.Disposable {
reportProgress("fetching user...", 20);
const user = await client.getAuthenticatedUser();

this.logger.info("OAuth login flow completed successfully");
this.logger.debug("OAuth login flow completed successfully");

return {
tokenResponse,
Expand Down Expand Up @@ -157,7 +157,7 @@ export class OAuthAuthorizer implements vscode.Disposable {
deployment.safeHostname,
response.data,
);
this.logger.info(
this.logger.debug(
"Saved OAuth client registration:",
response.data.client_id,
);
Expand Down
19 changes: 11 additions & 8 deletions src/oauth/sessionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,12 +311,15 @@ export class OAuthSessionManager implements vscode.Disposable {
) {
return;
}
this.logger.debug("Switching OAuth deployment", deployment);
this.deployment = deployment;
this.clearRefreshState();

// Block on refresh if token is expired to ensure valid state for callers
const storedTokens = await this.getStoredTokens();
if (storedTokens) {
this.logger.debug("Switching OAuth deployment", deployment);
}

// Block on refresh if token is expired to ensure valid state for callers
if (storedTokens && Date.now() >= storedTokens.expiry_timestamp) {
try {
await this.refreshToken();
Expand All @@ -331,7 +334,6 @@ export class OAuthSessionManager implements vscode.Disposable {
}

public clearDeployment(): void {
this.logger.debug("Clearing OAuth deployment state");
this.deployment = null;
this.clearRefreshState();
}
Expand Down Expand Up @@ -422,7 +424,7 @@ export class OAuthSessionManager implements vscode.Disposable {
public async revokeRefreshToken(): Promise<void> {
const storedTokens = await this.getStoredTokens();
if (!storedTokens?.refresh_token) {
this.logger.info("No refresh token to revoke");
this.logger.debug("No refresh token to revoke");
return;
}

Expand All @@ -449,11 +451,13 @@ export class OAuthSessionManager implements vscode.Disposable {
await this.prepareOAuthOperation(authToken);

if (!metadata.revocation_endpoint) {
this.logger.info("No revocation endpoint available, skipping revocation");
this.logger.debug(
"No revocation endpoint available, skipping revocation",
);
return;
}

this.logger.info("Revoking refresh token");
this.logger.debug("Revoking refresh token");

const params: OAuth2TokenRevocationRequest = {
token: tokenToRevoke,
Expand All @@ -475,7 +479,7 @@ export class OAuthSessionManager implements vscode.Disposable {
},
);

this.logger.info("Token revocation successful");
this.logger.debug("Token revocation successful");
} catch (error) {
this.logger.error("Token revocation failed:", error);
throw error;
Expand Down Expand Up @@ -503,6 +507,5 @@ export class OAuthSessionManager implements vscode.Disposable {
public dispose(): void {
this.disposed = true;
this.clearDeployment();
this.logger.debug("OAuth session manager disposed");
}
}
21 changes: 21 additions & 0 deletions src/remote/remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ export class Remote {
return;
}

this.logger.debug("Setting up remote connection", {
hostname: parts.safeHostname,
workspace: `${parts.username}/${parts.workspace}`,
agent: parts.agent || "(default)",
});

const workspaceName = `${parts.username}/${parts.workspace}`;

// Migrate existing legacy file-based auth to secrets storage.
Expand All @@ -110,6 +116,11 @@ export class Remote {
const auth = await this.secretsManager.getSessionAuth(parts.safeHostname);
const baseUrlRaw = auth?.url ?? "";
const token = auth?.token;
this.logger.debug("Retrieved auth for hostname", {
hostname: parts.safeHostname,
hasUrl: Boolean(baseUrlRaw),
hasToken: token !== undefined,
});
// Empty token is valid for mTLS
if (baseUrlRaw && token !== undefined) {
await this.cliManager.configure(parts.safeHostname, baseUrlRaw, token);
Expand Down Expand Up @@ -343,6 +354,10 @@ export class Remote {
);

// Wait for workspace to be running and agent to be ready
this.logger.debug("Starting workspace state machine", {
workspace: workspaceName,
initialStatus: workspace.latest_build.status,
});
const stateMachine = new WorkspaceStateMachine(
parts,
workspaceClient,
Expand Down Expand Up @@ -423,6 +438,12 @@ export class Remote {
throw new Error("Failed to get workspace or agent from state machine");
}

this.logger.info("Workspace ready", {
workspace: workspaceName,
agent: agent.name,
status: workspace.latest_build.status,
});

this.commands.workspace = workspace;

// Watch coder inbox for messages
Expand Down
8 changes: 3 additions & 5 deletions src/workspace/workspacesProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,9 @@ export class WorkspaceProvider
* logged in or the query fails.
*/
private async fetch(): Promise<WorkspaceTreeItem[]> {
if (vscode.env.logLevel <= vscode.LogLevel.Debug) {
this.logger.info(
`Fetching workspaces: ${this.getWorkspacesQuery || "no filter"}...`,
);
}
this.logger.debug(
`Fetching workspaces: ${this.getWorkspacesQuery || "no filter"}...`,
);

// If there is no URL configured, assume we are logged out.
const url = this.client.getAxiosInstance().defaults.baseURL;
Expand Down