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
16 changes: 16 additions & 0 deletions extensions/vscode/src/stubs/SecretStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ export class SecretStorage {
const key = await this.getOrCreateEncryptionKey();
const data = fs.readFileSync(filePath);

// Validate minimum data size to detect corruption early
const minSize = this.saltLength + this.ivLength + this.tagLength;
if (data.length < minSize) {
throw new Error(
`Corrupted cache file: insufficient data (${data.length} bytes, expected at least ${minSize})`,
);
}

const salt = data.subarray(0, this.saltLength);
const iv = data.subarray(this.saltLength, this.saltLength + this.ivLength);
const tag = data.subarray(
Expand Down Expand Up @@ -105,4 +113,12 @@ export class SecretStorage {
}
return undefined;
}

async delete(key: string): Promise<void> {
const filePath = this.keyToFilepath(key);
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
console.log(`Successfully deleted cache file: ${filePath}`);
}
}
}
30 changes: 21 additions & 9 deletions extensions/vscode/src/stubs/WorkOsAuthProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,22 +161,34 @@ export class WorkOsAuthProvider implements AuthenticationProvider, Disposable {
scopes?: string[],
): Promise<ContinueAuthenticationSession[]> {
// await this.hasAttemptedRefresh;
const data = await this.secretStorage.get(SESSIONS_SECRET_KEY);
if (!data) {
return [];
}

try {
const data = await this.secretStorage.get(SESSIONS_SECRET_KEY);
if (!data) {
return [];
}

const value = JSON.parse(data) as ContinueAuthenticationSession[];
return value;
} catch (e: any) {
// Capture session file parsing errors to Sentry
// Capture session decrypt and parsing errors to Sentry
Logger.error(e, {
context: "workOS_sessions_json_parse",
dataLength: data.length,
context: "workOS_sessions_retrieval",
errorMessage: e.message,
});

console.warn(`Error parsing sessions.json: ${e}`);
console.warn(`Error retrieving or parsing sessions: ${e.message}`);

// Delete the corrupted cache file to allow fresh start on next attempt
// This handles cases where decryption succeeded but JSON parsing failed
try {
await this.secretStorage.delete(SESSIONS_SECRET_KEY);
} catch (deleteError: any) {
console.error(
`Failed to delete corrupted sessions cache:`,
deleteError.message,
);
}

return [];
}
}
Expand Down
36 changes: 19 additions & 17 deletions gui/src/context/Auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,25 +50,27 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
const currentOrg = useAppSelector(selectCurrentOrg);
const selectedProfile = useAppSelector(selectSelectedProfile);

const login: AuthContextType["login"] = (useOnboarding: boolean) => {
return new Promise(async (resolve) => {
await ideMessenger
.request("getControlPlaneSessionInfo", {
silent: false,
useOnboarding,
})
.then((result) => {
if (result.status === "error") {
resolve(false);
return;
}
const login: AuthContextType["login"] = async (useOnboarding: boolean) => {
try {
const result = await ideMessenger.request("getControlPlaneSessionInfo", {
silent: false,
useOnboarding,
});

if (result.status === "error") {
console.error("Login failed:", result.error);
return false;
}

const session = result.content;
setSession(session);
const session = result.content;
setSession(session);

resolve(true);
});
});
return true;
} catch (error: any) {
console.error("Login request failed:", error);
// Let the error propagate so the caller can handle it
throw error;
}
};

const logout = () => {
Expand Down
Loading