diff --git a/.vscodeignore b/.vscodeignore index 1db9423f..8d41cd70 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -11,6 +11,11 @@ node_modules server test/ img/ +docs/ +CLAUDE.md +build-api.js +qcumber.q +secure-processes.md qcumber.sh esbuild.mjs sonar-project.properties diff --git a/docs/index.md b/docs/index.md index ec25778c..af2fe894 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,23 +1,27 @@ --- type: Index title: kdb VS Code Extension Docs -description: In-tree documentation for the kdb VS Code extension, split into user notes and developer notes. +description: + In-tree documentation for the kdb VS Code extension, split into user notes and + developer notes. tags: [kdb, vscode, documentation] timestamp: 2026-07-10 --- -# kdb VS Code Extension Docs +# kdb VS Code Extension Wiki -Welcome to the developer and user documentation for the -[kdb VS Code extension](https://marketplace.visualstudio.com/items?itemName=KX.kdb). +This is the in-repo wiki for the +[kdb VS Code extension](https://marketplace.visualstudio.com/items?itemName=KX.kdb), +covering developer and user notes. -These docs follow the [Open Knowledge Format](https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing/): +These pages follow the +[Open Knowledge Format](https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing/): plain markdown files with YAML frontmatter, `index.md` files for navigation, and relative cross-links. They were ported from the project's [GitHub wiki](https://github.com/KxSystems/kx-vscode/wiki). > The end-user product documentation is hosted separately at -> . +> . ## User Notes diff --git a/src/commands/serverCommand.ts b/src/commands/serverCommand.ts index 5a3ead24..febe8202 100644 --- a/src/commands/serverCommand.ts +++ b/src/commands/serverCommand.ts @@ -88,6 +88,7 @@ import { workspaceHas, } from "../utils/workspace"; import { + validateInsightsServerUrl, validateServerAlias, validateServerName, validateServerPort, @@ -120,8 +121,14 @@ export async function addInsightsConnection( notify(aliasValidation, MessageKind.ERROR, { logger }); return; } + const serverValidation = validateInsightsServerUrl(insightsData.server); + if (serverValidation) { + notify(serverValidation, MessageKind.ERROR, { logger }); + return; + } + const server = insightsData.server!.trim(); if (insightsData.alias === undefined || insightsData.alias === "") { - const host = new url.URL(insightsData.server!); + const host = new url.URL(server); insightsData.alias = host.host; } @@ -138,10 +145,6 @@ export async function addInsightsConnection( return; } else { const key = insightsData.alias; - let server = insightsData.server || ""; - if (!/^https?:\/\//i.exec(server)) { - server = "https://" + server; - } if (insights === undefined) { insights = { key: { @@ -201,6 +204,12 @@ export async function editInsightsConnection( notify(aliasValidation, MessageKind.ERROR, { logger }); return; } + const serverValidation = validateInsightsServerUrl(insightsData.server); + if (serverValidation) { + notify(serverValidation, MessageKind.ERROR, { logger }); + return; + } + insightsData.server = insightsData.server.trim(); const isConnectedConn = isConnected(oldAlias); await disconnect(oldAlias); if (insightsData.alias === undefined || insightsData.alias === "") { diff --git a/src/services/dataSourceEditorProvider.ts b/src/services/dataSourceEditorProvider.ts index 86a51d66..dd6f61ff 100644 --- a/src/services/dataSourceEditorProvider.ts +++ b/src/services/dataSourceEditorProvider.ts @@ -105,7 +105,7 @@ export class DataSourceEditorProvider implements CustomTextEditorProvider { } catch { notify( "No database running in this Insights connection.", - MessageKind.ERROR, + MessageKind.WARNING, { logger }, ); meta = Promise.resolve({}); diff --git a/src/services/kdbInsights/codeFlowLogin.ts b/src/services/kdbInsights/codeFlowLogin.ts index 9736d929..0a187fa3 100644 --- a/src/services/kdbInsights/codeFlowLogin.ts +++ b/src/services/kdbInsights/codeFlowLogin.ts @@ -48,7 +48,7 @@ async function getAuthPrefix( insightsUrl, ).toString(), { - maxRedirects: 0, + maxRedirects: 5, validateStatus: () => true, transformResponse: (res) => res, responseType: "json", diff --git a/src/validators/kdbValidator.ts b/src/validators/kdbValidator.ts index f66d1f7c..304dba7c 100644 --- a/src/validators/kdbValidator.ts +++ b/src/validators/kdbValidator.ts @@ -96,6 +96,18 @@ export function validateServerPassword( return undefined; } +export function validateInsightsServerUrl( + input: string | undefined, +): string | undefined { + if (input === undefined || input.trim() === "") { + return "Insights connection address is required."; + } + if (!/^https:\/\//i.exec(input.trim())) { + return "Insights connections require a secure https:// URL (for example, https://myinsights.example.com)."; + } + return undefined; +} + export function validateTls(input: string | undefined): string | undefined { if (input !== undefined && input !== "") { if (!validateUtils.isBoolean(input)) { diff --git a/src/webview/components/kdbNewConnectionView.ts b/src/webview/components/kdbNewConnectionView.ts index f0f30576..182ac612 100644 --- a/src/webview/components/kdbNewConnectionView.ts +++ b/src/webview/components/kdbNewConnectionView.ts @@ -231,7 +231,7 @@ export class KdbNewConnectionView extends LitElement { class="text-field larger option-title" placeholder="${serverType === ServerType.KDB ? "127.0.0.1 or localhost" - : `myinsights.clouddeploy.com`}" + : `https://myinsights.example.com`}" value="${live( serverType === ServerType.KDB ? this.kdbServer.serverName diff --git a/test/suite/commands/serverCommand.test.ts b/test/suite/commands/serverCommand.test.ts index 2ffd2063..9033b807 100644 --- a/test/suite/commands/serverCommand.test.ts +++ b/test/suite/commands/serverCommand.test.ts @@ -195,6 +195,45 @@ describe("serverCommand", () => { sinon.match.any, ); }); + + it("should reject an insecure http:// Insights URL", async () => { + getInsightsStub.returns({}); + insightsData.server = "http://insightsservername.com/"; + await serverCommand.addInsightsConnection(insightsData); + sinon.assert.notCalled(updateInsightsStub); + sinon.assert.calledWithExactly( + notifyStub, + sinon.match(/require a secure https:\/\/ URL/), + sinon.match.any, + sinon.match.any, + ); + }); + + it("should reject an Insights URL with no protocol", async () => { + getInsightsStub.returns({}); + insightsData.server = "insightsservername.com"; + await serverCommand.addInsightsConnection(insightsData); + sinon.assert.notCalled(updateInsightsStub); + sinon.assert.calledWithExactly( + notifyStub, + sinon.match(/require a secure https:\/\/ URL/), + sinon.match.any, + sinon.match.any, + ); + }); + + it("should reject an empty Insights URL", async () => { + getInsightsStub.returns({}); + insightsData.server = ""; + await serverCommand.addInsightsConnection(insightsData); + sinon.assert.notCalled(updateInsightsStub); + sinon.assert.calledWithExactly( + notifyStub, + "Insights connection address is required.", + sinon.match.any, + sinon.match.any, + ); + }); }); describe("addKdbConnection", () => { diff --git a/test/suite/webPanels/webViews/kdbNewConnectionView.test.ts b/test/suite/webPanels/webViews/kdbNewConnectionView.test.ts index 0ce2db09..4474b1d4 100644 --- a/test/suite/webPanels/webViews/kdbNewConnectionView.test.ts +++ b/test/suite/webPanels/webViews/kdbNewConnectionView.test.ts @@ -181,7 +181,7 @@ describe("KdbNewConnectionView", () => { it("should render connection address for Insights", () => { const result = view.renderConnAddress(ServerType.INSIGHTS); assert.strictEqual( - JSON.stringify(result).includes("myinsights.clouddeploy.com"), + JSON.stringify(result).includes("https://myinsights.example.com"), true, ); });