From 461a3de983e7c70ea5fc72d1338361bfdbddbb29 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Mon, 13 Apr 2026 16:31:46 -0400 Subject: [PATCH 01/17] fix #1750 --- src/commands/compile.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/commands/compile.ts b/src/commands/compile.ts index e7af69e1..56c4e98a 100644 --- a/src/commands/compile.ts +++ b/src/commands/compile.ts @@ -283,11 +283,12 @@ function updateStorage(content: string[], storage: string[]): string[] { let contentString = content.join("\n"); contentString = contentString // update existing Storages - .replaceAll(/\n(\s*storage\s+(\w+)\s*{\s*)([^}]*?)(\s*})/gim, (_match, beforeXML, name, _oldXML, afterXML) => { - const newXML = storageMap.get(name); + .replaceAll(/\n(\s*storage\s+(\w+)\s*{\s*)(.*?)(>\s*})/gis, (_match, beforeXML, name, _oldXML, afterXML) => { + let newXML = storageMap.get(name); if (newXML === undefined) { return ""; } + newXML = newXML.slice(0, newXML.length - 1); storageMap.delete(name); return "\n" + beforeXML + newXML + afterXML; }); From 2d6e2489d07cbf65c30de66da298dbe69ab486bb Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Tue, 23 Jun 2026 09:33:00 -0400 Subject: [PATCH 02/17] working? --- src/api/index.ts | 22 ++++++++++++------- src/commands/restDebugPanel.ts | 17 ++++----------- src/extension.ts | 40 +++++++++++++++++++++++++++++----- 3 files changed, 52 insertions(+), 27 deletions(-) diff --git a/src/api/index.ts b/src/api/index.ts index 4e1d5dd2..f1b760da 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -12,6 +12,7 @@ import { schemas, checkingConnection, inactiveServerIds, + serverManagerApi, } from "../extension"; import { currentWorkspaceFolder, outputChannel, outputConsole } from "../utils"; @@ -19,6 +20,7 @@ const DEFAULT_API_VERSION = 1; const DEFAULT_SERVER_VERSION = "2016.2.0"; import * as Atelier from "./atelier"; import { isfsConfig } from "../utils/FileProviderUtil"; +import { IServerSpec } from "@intersystems-community/intersystems-servermanager"; // Map of the authRequest promises for each username@host:port/pathPrefix target to avoid concurrency issues const authRequestMap = new Map>(); @@ -37,6 +39,7 @@ interface ConnectionSettings { superserverPort?: number; pathPrefix: string; ns: string; + authMethod: "password" | "oauth2"; username: string; password: string; docker: boolean; @@ -59,7 +62,7 @@ export class AtelierAPI { } public get config(): ConnectionSettings { - const { serverName, active = false, https = false, pathPrefix = "", username } = this._config; + const { serverName, active = false, https = false, pathPrefix = "", authMethod, username } = this._config; const ns = this.namespace || this._config.ns; const wsKey = this.configName.toLowerCase(); const host = this.externalServer ? this._config.host : workspaceState.get(wsKey + ":host", this._config.host); @@ -83,6 +86,7 @@ export class AtelierAPI { superserverPort, pathPrefix, ns, + authMethod, username, password, docker, @@ -147,12 +151,14 @@ export class AtelierAPI { * Manually set the connection spec for this object, * where `connSpec` is the return value of `getResolvedConnectionSpec()`. */ - public setConnSpec(serverName: string, connSpec: any): void { + public setConnSpec(serverName: string, connSpec: IServerSpec): void { const { webServer: { scheme, host, port, pathPrefix = "" }, + authMethod = "password", username, password, } = connSpec; + this._config.authMethod = authMethod; this._config.username = username; this._config.password = password; this._config.https = scheme == "https"; @@ -239,6 +245,7 @@ export class AtelierAPI { if (serverName !== "") { const { webServer: { scheme, host, port, pathPrefix = "" }, + authMethod, username, password, superServer, @@ -253,6 +260,7 @@ export class AtelierAPI { host, port, superserverPort: superServer?.port, + authMethod, username, password, pathPrefix, @@ -264,6 +272,7 @@ export class AtelierAPI { if (resolvedSpec) { const { webServer: { scheme, host, port, pathPrefix = "" }, + authMethod, username, password, superServer, @@ -278,6 +287,7 @@ export class AtelierAPI { host, port, superserverPort: superServer?.port, + authMethod, username, password, pathPrefix, @@ -316,7 +326,7 @@ export class AtelierAPI { headers?: any, options?: any ): Promise { - const { active, apiVersion, host, port, username, password, https } = this.config; + const { active, apiVersion, host, port, password, https } = this.config; if (!active || !port || !host) { return Promise.reject(); } @@ -370,11 +380,7 @@ export class AtelierAPI { let authRequest = authRequestMap.get(mapKey); if (cookies.length || (method === "HEAD" && !originalPath)) { auth = Promise.resolve(cookies); - - // Only send basic authorization if username and password specified (including blank, for unauthenticated access) - if (typeof username === "string" && typeof password === "string") { - headers["Authorization"] = `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`; - } + headers["Authorization"] = serverManagerApi.getAuthorization(this.config); } else if (!cookies.length) { if (!authRequest) { // Recursion point diff --git a/src/commands/restDebugPanel.ts b/src/commands/restDebugPanel.ts index ae3344eb..c65bbc35 100644 --- a/src/commands/restDebugPanel.ts +++ b/src/commands/restDebugPanel.ts @@ -3,7 +3,7 @@ import * as httpsModule from "https"; import * as vscode from "vscode"; import { AtelierAPI } from "../api"; import { handleError, webviewCSS } from "../utils"; -import { iscIcon } from "../extension"; +import { iscIcon, serverManagerApi } from "../extension"; interface WebviewMessage { /** Whether this message was triggered by the user pressing the 'Start Debugging' button */ @@ -501,8 +501,8 @@ export class RESTDebugPanel { form.onchange = () => sendData(false); button.onclick = () => sendData(true); // Bubble change events up to the form - bodyContent.onchange = headersText.onchange = - paramsText.onchange = path.onchange = + bodyContent.onchange = headersText.onchange = + paramsText.onchange = path.onchange = () => form.dispatchEvent(new Event("change")); @@ -548,16 +548,7 @@ export class RESTDebugPanel { .trim(); } }); - if ( - headers["authorization"] == undefined && - typeof api.config.username === "string" && - typeof api.config.password === "string" - ) { - // Use the server connection's auth if the user didn't specify any - headers["authorization"] = `Basic ${Buffer.from(`${api.config.username}:${api.config.password}`).toString( - "base64" - )}`; - } + headers["authorization"] = headers["authorization"] ?? serverManagerApi.getAuthorization(api.config); const hasBody = typeof message.bodyContent == "string" && message.bodyContent != "" && message.bodyType != "No Body"; if (hasBody) { diff --git a/src/extension.ts b/src/extension.ts index a0a741e0..a4939009 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -209,7 +209,7 @@ export let checkingConnection = false; export let serverManagerApi: serverManager.ServerManagerAPI; /** Map of the intersystems.server connection specs we have resolved via the API to that extension */ -const resolvedConnSpecs = new Map(); +const resolvedConnSpecs = new Map(); /** * If servermanager extension is available, fetch the connection spec unless already cached. @@ -364,7 +364,7 @@ export async function checkConnection( _onDidChangeConnection.fire(); } let api = new AtelierAPI(apiTarget, false); - const { active, host = "", port = 0, superserverPort = 0, username, ns = "" } = api.config; + const { active, host = "", port = 0, superserverPort = 0, ns = "", username } = api.config; vscode.commands.executeCommand("setContext", "vscode-objectscript.connectActive", active); if (!panel.text) { panel.text = `${PANEL_LABEL}`; @@ -1945,9 +1945,35 @@ export async function activate(context: vscode.ExtensionContext): Promise { return extensionApi; } +type HttpsAndScheme = + | { + scheme: "https"; + https: true; + } + | { + scheme: "http"; + https?: false; + }; + +export interface GeneralServerForUri { + serverName: string; + active: boolean; + host: string; + port: number; + superserverPort: number; + pathPrefix: string; + namespace: string; + apiVersion: number; + serverVersion: string; +} + +export type ServerForUri = GeneralServerForUri & + HttpsAndScheme & + Required>; + // This function is exported as one of our API functions but is also used internally // for example to implement the async variant capable of resolving docker port number. -function serverForUri(uri: vscode.Uri): any { +function serverForUri(uri: vscode.Uri): ServerForUri { const { apiTarget, configName } = connectionTarget(uri); const configNameLower = configName.toLowerCase(); const api = new AtelierAPI(apiTarget); @@ -1958,25 +1984,27 @@ function serverForUri(uri: vscode.Uri): any { const { serverName, active, - host = "", + host, https, port, superserverPort, pathPrefix, + authMethod, username, password, - ns = "", + ns, apiVersion, serverVersion, } = api.config; return { serverName, active, - scheme: https ? "https" : "http", + ...(https ? ({ https: true, scheme: "https" } as const) : ({ scheme: "http" } as const)), host, port, superserverPort, pathPrefix, + authMethod, username, password: serverName === "" From d5c4b34155fb775f5cab4ae4130fe7790f1f3369 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Tue, 30 Jun 2026 11:03:12 -0400 Subject: [PATCH 03/17] with-authorization-class --- src/api/index.ts | 50 ++++------ .../connectFolderToServerNamespace.ts | 2 +- src/commands/restDebugPanel.ts | 6 +- src/commands/serverActions.ts | 3 +- src/commands/webSocketTerminal.ts | 2 +- src/extension.ts | 98 +++++++++---------- .../FileSystemProvider/FileSystemProvider.ts | 6 +- src/providers/LowCodeEditorProvider.ts | 4 +- src/utils/index.ts | 2 +- 9 files changed, 82 insertions(+), 91 deletions(-) diff --git a/src/api/index.ts b/src/api/index.ts index f1b760da..b5bab855 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -12,7 +12,6 @@ import { schemas, checkingConnection, inactiveServerIds, - serverManagerApi, } from "../extension"; import { currentWorkspaceFolder, outputChannel, outputConsole } from "../utils"; @@ -20,7 +19,7 @@ const DEFAULT_API_VERSION = 1; const DEFAULT_SERVER_VERSION = "2016.2.0"; import * as Atelier from "./atelier"; import { isfsConfig } from "../utils/FileProviderUtil"; -import { IServerSpec } from "@intersystems-community/intersystems-servermanager"; +import { Authorization, IServerSpec } from "@intersystems-community/intersystems-servermanager"; // Map of the authRequest promises for each username@host:port/pathPrefix target to avoid concurrency issues const authRequestMap = new Map>(); @@ -39,9 +38,8 @@ interface ConnectionSettings { superserverPort?: number; pathPrefix: string; ns: string; - authMethod: "password" | "oauth2"; - username: string; - password: string; + authorization: Authorization; + password?: string; docker: boolean; dockerService?: string; } @@ -62,7 +60,7 @@ export class AtelierAPI { } public get config(): ConnectionSettings { - const { serverName, active = false, https = false, pathPrefix = "", authMethod, username } = this._config; + const { serverName, active = false, https = false, pathPrefix = "", authorization } = this._config; const ns = this.namespace || this._config.ns; const wsKey = this.configName.toLowerCase(); const host = this.externalServer ? this._config.host : workspaceState.get(wsKey + ":host", this._config.host); @@ -70,7 +68,10 @@ export class AtelierAPI { const superserverPort = this.externalServer ? this._config.superserverPort : workspaceState.get(wsKey + ":superserverPort", this._config.superserverPort); - const password = workspaceState.get(wsKey + ":password", this._config.password); + const password = workspaceState.get(wsKey + ":password", undefined); + if (password !== undefined) { + authorization.resolve(password); + } const apiVersion = workspaceState.get(wsKey + ":apiVersion", DEFAULT_API_VERSION); const serverVersion = workspaceState.get(wsKey + ":serverVersion", DEFAULT_SERVER_VERSION); const docker = workspaceState.get(wsKey + ":docker", false); @@ -86,9 +87,7 @@ export class AtelierAPI { superserverPort, pathPrefix, ns, - authMethod, - username, - password, + authorization, docker, dockerService, }; @@ -154,13 +153,9 @@ export class AtelierAPI { public setConnSpec(serverName: string, connSpec: IServerSpec): void { const { webServer: { scheme, host, port, pathPrefix = "" }, - authMethod = "password", - username, - password, + authorization, } = connSpec; - this._config.authMethod = authMethod; - this._config.username = username; - this._config.password = password; + this._config.authorization = authorization; this._config.https = scheme == "https"; this._config.host = host; this._config.port = port; @@ -215,7 +210,8 @@ export class AtelierAPI { /** Return the key for getting values from connection-specific Maps for this connection */ private mapKey(): string { - const { host, port, username } = this.config; + const { host, port, authorization } = this.config; + const username = authorization.resolved() ? authorization.username : ""; let pathPrefix = this._config.pathPrefix || ""; if (pathPrefix.length && !pathPrefix.startsWith("/")) { pathPrefix = "/" + pathPrefix; @@ -245,9 +241,7 @@ export class AtelierAPI { if (serverName !== "") { const { webServer: { scheme, host, port, pathPrefix = "" }, - authMethod, - username, - password, + authorization, superServer, } = getResolvedConnectionSpec(serverName, config("intersystems.servers", workspaceFolderName).get(serverName)); this._config = { @@ -260,9 +254,7 @@ export class AtelierAPI { host, port, superserverPort: superServer?.port, - authMethod, - username, - password, + authorization, pathPrefix, docker: false, }; @@ -272,9 +264,7 @@ export class AtelierAPI { if (resolvedSpec) { const { webServer: { scheme, host, port, pathPrefix = "" }, - authMethod, - username, - password, + authorization, superServer, } = resolvedSpec; this._config = { @@ -287,9 +277,7 @@ export class AtelierAPI { host, port, superserverPort: superServer?.port, - authMethod, - username, - password, + authorization, pathPrefix, docker: true, dockerService: conn["docker-compose"].service, @@ -380,7 +368,9 @@ export class AtelierAPI { let authRequest = authRequestMap.get(mapKey); if (cookies.length || (method === "HEAD" && !originalPath)) { auth = Promise.resolve(cookies); - headers["Authorization"] = serverManagerApi.getAuthorization(this.config); + headers["Authorization"] = this.config.authorization.resolved() + ? this.config.authorization.httpAuthorizationHeader + : ""; } else if (!cookies.length) { if (!authRequest) { // Recursion point diff --git a/src/commands/connectFolderToServerNamespace.ts b/src/commands/connectFolderToServerNamespace.ts index 1cd26e16..3180c8cc 100644 --- a/src/commands/connectFolderToServerNamespace.ts +++ b/src/commands/connectFolderToServerNamespace.ts @@ -80,7 +80,7 @@ export async function connectFolderToServerNamespace(): Promise { .serverInfo(false) .then((data) => data.result.content.namespaces) .catch(async (error) => { - if (error?.statusCode == 401 && isUnauthenticated(api.config.username)) { + if (error?.statusCode == 401 && isUnauthenticated(api.config.authorization.username)) { // Attempt to resolve username and password and try again const newSpec = await resolveUsernameAndPassword(api.config.serverName, connSpec); if (newSpec) { diff --git a/src/commands/restDebugPanel.ts b/src/commands/restDebugPanel.ts index c65bbc35..772b3611 100644 --- a/src/commands/restDebugPanel.ts +++ b/src/commands/restDebugPanel.ts @@ -3,7 +3,7 @@ import * as httpsModule from "https"; import * as vscode from "vscode"; import { AtelierAPI } from "../api"; import { handleError, webviewCSS } from "../utils"; -import { iscIcon, serverManagerApi } from "../extension"; +import { iscIcon } from "../extension"; interface WebviewMessage { /** Whether this message was triggered by the user pressing the 'Start Debugging' button */ @@ -548,7 +548,9 @@ export class RESTDebugPanel { .trim(); } }); - headers["authorization"] = headers["authorization"] ?? serverManagerApi.getAuthorization(api.config); + headers["authorization"] = + headers["authorization"] ?? + (api.config.authorization.resolved() ? api.config.authorization.httpAuthorizationHeader : ""); const hasBody = typeof message.bodyContent == "string" && message.bodyContent != "" && message.bodyType != "No Body"; if (hasBody) { diff --git a/src/commands/serverActions.ts b/src/commands/serverActions.ts index a9a14fdb..d5002031 100644 --- a/src/commands/serverActions.ts +++ b/src/commands/serverActions.ts @@ -25,7 +25,8 @@ type ServerAction = { detail: string; id: string; label: string; rawLink?: strin export async function serverActions(): Promise { const { apiTarget, configName: workspaceFolder } = connectionTarget(); const api = new AtelierAPI(apiTarget); - const { active, host = "", ns = "", https, port = 0, pathPrefix, username, docker } = api.config; + const { active, host = "", ns = "", https, port = 0, pathPrefix, authorization, docker } = api.config; + const username = authorization.username; const explorerCount = (await explorerProvider.getChildren()).length; if (!explorerCount && (!docker || host === "")) { await vscode.commands.executeCommand("ObjectScriptExplorer.focus"); diff --git a/src/commands/webSocketTerminal.ts b/src/commands/webSocketTerminal.ts index 9b71df36..f2e40607 100644 --- a/src/commands/webSocketTerminal.ts +++ b/src/commands/webSocketTerminal.ts @@ -225,7 +225,7 @@ class WebSocketTerminal implements vscode.Pseudoterminal { this._hideCursorWrite("\x1b]633;P;HasRichCommandDetection=True\x07"); // Print the opening message this._hideCursorWrite( - `\x1b[32mConnected to \x1b[0m\x1b[4m${api.config.host}:${api.config.port}${api.config.pathPrefix}\x1b[0m\x1b[32m as \x1b[0m\x1b[3m${api.config.username}\x1b[0m\r\n` + `\x1b[32mConnected to \x1b[0m\x1b[4m${api.config.host}:${api.config.port}${api.config.pathPrefix}\x1b[0m\x1b[32m as \x1b[0m\x1b[3m${api.config.authorization.username}\x1b[0m\r\n` ); // Add event handlers to the socket this._socket diff --git a/src/extension.ts b/src/extension.ts index a4939009..71f958ee 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -209,7 +209,7 @@ export let checkingConnection = false; export let serverManagerApi: serverManager.ServerManagerAPI; /** Map of the intersystems.server connection specs we have resolved via the API to that extension */ -const resolvedConnSpecs = new Map(); +const resolvedConnSpecs = new Map(); /** * If servermanager extension is available, fetch the connection spec unless already cached. @@ -255,8 +255,7 @@ export async function resolveConnectionSpec( superServer: { port: serverForUri.superserverPort, }, - username: serverForUri.username, - password: serverForUri.password ? serverForUri.password : undefined, + authorization: serverManagerApi.makeAuthorization(serverForUri.username, serverForUri.password), description: `Server for workspace folder '${serverName}'`, }; } @@ -269,14 +268,12 @@ export async function resolveConnectionSpec( } } -async function resolvePassword(serverSpec, ignoreUnauthenticated = false): Promise { - if ( - // Connection isn't unauthenticated - (!isUnauthenticated(serverSpec.username) || ignoreUnauthenticated) && - // A password is missing - typeof serverSpec.password == "undefined" - ) { - const scopes = [serverSpec.name, serverSpec.username || ""]; +async function resolvePassword( + serverSpec: Pick, + ignoreUnauthenticated = false +): Promise { + if (!(serverSpec.authorization.resolved() || ignoreUnauthenticated)) { + const scopes = [serverSpec.name, serverSpec.authorization.username || ""]; // Handle Server Manager extension version < 3.8.0 const account = serverManagerApi.getAccount ? serverManagerApi.getAccount(serverSpec) : undefined; @@ -293,25 +290,28 @@ async function resolvePassword(serverSpec, ignoreUnauthenticated = false): Promi } if (session) { // If original spec lacked username use the one obtained from the user by the authprovider (exact case) - serverSpec.username = serverSpec.username || session.scopes[1]; - serverSpec.password = session.accessToken; + serverSpec.authorization.resolve(session.accessToken, session.scopes[1]); + return session.accessToken; } } } /** Resolve credentials for `serverName` and returned the complete connection spec if successful */ -export async function resolveUsernameAndPassword(serverName: string, oldSpec: any): Promise { - const newSpec: { name: string; username?: string; password?: string } = { - name: serverName, - username: oldSpec?.username, - }; - await resolvePassword(newSpec, true); - if (newSpec.password) { +export async function resolveUsernameAndPassword( + serverName: string, + oldSpec: serverManager.IServerSpec +): Promise<(serverManager.IServerSpec & { accessToken?: string }) | undefined> { + const { authorization: _authorization, ...newSpec } = oldSpec; + newSpec.name = serverName; + const authorization = _authorization.clone(); + + const accessToken = await resolvePassword({ ...newSpec, authorization }, true); + if (authorization.resolved()) { // Update the connection spec resolvedConnSpecs.set(serverName, { ...oldSpec, - username: newSpec.username, - password: newSpec.password, + authorization, + accessToken, }); return resolvedConnSpecs.get(serverName); } @@ -364,7 +364,8 @@ export async function checkConnection( _onDidChangeConnection.fire(); } let api = new AtelierAPI(apiTarget, false); - const { active, host = "", port = 0, superserverPort = 0, ns = "", username } = api.config; + const { active, host = "", port = 0, superserverPort = 0, ns = "", authorization } = api.config; + const username = authorization.resolved() ? authorization.username : ""; vscode.commands.executeCommand("setContext", "vscode-objectscript.connectActive", active); if (!panel.text) { panel.text = `${PANEL_LABEL}`; @@ -470,8 +471,7 @@ export async function checkConnection( let success = false; message = "Not Authorized."; errorMessage = `Authorization error: Check your credentials in Settings, and that you have sufficient privileges on the /api/atelier web application on ${connInfo}`; - const username = api.config.username; - if (isUnauthenticated(username)) { + if (isUnauthenticated(api.config.authorization.username)) { vscode.window.showErrorMessage( `Unauthenticated access rejected by '${api.serverId}'.${ !api.config.serverName ? " Connection has been disabled." : "" @@ -487,7 +487,7 @@ export async function checkConnection( const newSpec = await resolveUsernameAndPassword(api.config.serverName, oldSpec); if (newSpec) { // We were able to resolve credentials, so try again - await workspaceState.update(wsKey + ":password", newSpec.password); + await workspaceState.update(wsKey + ":password", newSpec.accessToken); api = new AtelierAPI(apiTarget, false); await api .serverInfo(true, serverInfoTimeout) @@ -515,7 +515,7 @@ export async function checkConnection( vscode.window .showInputBox({ password: true, - title: `Not Authorized. Enter password to connect as user '${username}' to ${connInfo}`, + title: `Not Authorized. Enter password to connect as user '${api.config.authorization.username}' to ${connInfo}`, prompt: !api.externalServer ? "If no password is entered the connection will be disabled." : "", ignoreFocusOut: true, }) @@ -1965,11 +1965,10 @@ export interface GeneralServerForUri { namespace: string; apiVersion: number; serverVersion: string; + authorization: serverManager.Authorization; } -export type ServerForUri = GeneralServerForUri & - HttpsAndScheme & - Required>; +export type ServerForUri = GeneralServerForUri & HttpsAndScheme; // This function is exported as one of our API functions but is also used internally // for example to implement the async variant capable of resolving docker port number. @@ -1989,13 +1988,28 @@ function serverForUri(uri: vscode.Uri): ServerForUri { port, superserverPort, pathPrefix, - authMethod, - username, - password, + authorization, ns, apiVersion, serverVersion, } = api.config; + if (serverName !== "") { + const password = vscode.workspace + .getConfiguration( + `intersystems.servers.${serverName.toLowerCase()}`, + // objectscript(xml):// URIs are not in any workspace folder, + // so make sure we resolve the server definition with the proper + // granularity. This is needed to prevent other extensions like + // Language Server prompting for a passwoord when it's not needed. + [OBJECTSCRIPT_FILE_SCHEMA, OBJECTSCRIPTXML_FILE_SCHEMA].includes(uri.scheme) + ? vscode.workspace.workspaceFolders?.find((f) => f.name.toLowerCase() == configNameLower)?.uri + : uri + ) + .get("password") as string | undefined; + if (password !== undefined) { + authorization.resolve(password); + } + } return { serverName, active, @@ -2004,23 +2018,7 @@ function serverForUri(uri: vscode.Uri): ServerForUri { port, superserverPort, pathPrefix, - authMethod, - username, - password: - serverName === "" - ? password - : vscode.workspace - .getConfiguration( - `intersystems.servers.${serverName.toLowerCase()}`, - // objectscript(xml):// URIs are not in any workspace folder, - // so make sure we resolve the server definition with the proper - // granularity. This is needed to prevent other extensions like - // Language Server prompting for a passwoord when it's not needed. - [OBJECTSCRIPT_FILE_SCHEMA, OBJECTSCRIPTXML_FILE_SCHEMA].includes(uri.scheme) - ? vscode.workspace.workspaceFolders?.find((f) => f.name.toLowerCase() == configNameLower)?.uri - : uri - ) - .get("password"), + authorization, namespace: ns, apiVersion: active ? apiVersion : undefined, serverVersion: active ? serverVersion : undefined, diff --git a/src/providers/FileSystemProvider/FileSystemProvider.ts b/src/providers/FileSystemProvider/FileSystemProvider.ts index 709d03f4..0d402e3a 100644 --- a/src/providers/FileSystemProvider/FileSystemProvider.ts +++ b/src/providers/FileSystemProvider/FileSystemProvider.ts @@ -476,10 +476,10 @@ export class FileSystemProvider implements vscode.FileSystemProvider { .catch((error) => { if (error) { if (error.errorText.includes(" #5540:")) { - const message = `User '${api.config.username}' cannot list ${ + const message = `User '${api.config.authorization.username}' cannot list ${ csp ? `web application '${uri.path}'` : "namespace" } contents. If they do not have READ permission on the default code database of the ${api.config.ns.toUpperCase()} namespace then grant it and retry. If the problem remains then execute the following SQL in that namespace:\n\t GRANT EXECUTE ON %Library.RoutineMgr_StudioOpenDialog TO ${ - api.config.username + api.config.authorization.username }`; handleError(message); } @@ -1006,7 +1006,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider { private async _lookup(uri: vscode.Uri, fillInPath?: boolean): Promise { const api = new AtelierAPI(uri); const config = api.config; - const rootName = `${config.username}@${config.host}:${config.port}${config.pathPrefix}/${config.ns.toUpperCase()}`; + const rootName = `${config.authorization.username}@${config.host}:${config.port}${config.pathPrefix}/${config.ns.toUpperCase()}`; let entry: Entry = this.superRoot.entries.get(rootName); if (!entry) { entry = new Directory(rootName, ""); diff --git a/src/providers/LowCodeEditorProvider.ts b/src/providers/LowCodeEditorProvider.ts index 46009619..66bf15a2 100644 --- a/src/providers/LowCodeEditorProvider.ts +++ b/src/providers/LowCodeEditorProvider.ts @@ -212,8 +212,8 @@ export class LowCodeEditorProvider implements vscode.CustomTextEditorProvider { webviewPanel.webview.postMessage({ direction: "editor", type: "auth", - username: api.config.username, - password: api.config.password, + username: api.config.authorization.username, + password: api.config.authorization.password, }); } return; diff --git a/src/utils/index.ts b/src/utils/index.ts index ba7ee6cc..79e21cfd 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -928,7 +928,7 @@ export async function getWsServerConnection(minVersion?: string): Promise Date: Tue, 30 Jun 2026 11:06:00 -0400 Subject: [PATCH 04/17] clean --- src/api/index.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/api/index.ts b/src/api/index.ts index b5bab855..a13f4adf 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -39,7 +39,6 @@ interface ConnectionSettings { pathPrefix: string; ns: string; authorization: Authorization; - password?: string; docker: boolean; dockerService?: string; } @@ -314,7 +313,7 @@ export class AtelierAPI { headers?: any, options?: any ): Promise { - const { active, apiVersion, host, port, password, https } = this.config; + const { active, apiVersion, host, port, https, authorization } = this.config; if (!active || !port || !host) { return Promise.reject(); } @@ -445,7 +444,7 @@ export class AtelierAPI { if (this.wsOrFile && !checkingConnection) { setTimeout(() => { checkConnection( - password ? true : false, + authorization.resolved(), typeof this.wsOrFile === "object" ? this.wsOrFile : undefined, true ); From c033875d075f6428d976c35b7ab6241ba5e6fa86 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Tue, 30 Jun 2026 11:07:30 -0400 Subject: [PATCH 05/17] simp --- src/api/index.ts | 3 +-- src/commands/serverActions.ts | 5 ++--- src/extension.ts | 7 ++++--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/api/index.ts b/src/api/index.ts index a13f4adf..efddd728 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -210,12 +210,11 @@ export class AtelierAPI { /** Return the key for getting values from connection-specific Maps for this connection */ private mapKey(): string { const { host, port, authorization } = this.config; - const username = authorization.resolved() ? authorization.username : ""; let pathPrefix = this._config.pathPrefix || ""; if (pathPrefix.length && !pathPrefix.startsWith("/")) { pathPrefix = "/" + pathPrefix; } - return `${username}@${host}:${port}${pathPrefix}`; + return `${authorization.username}@${host}:${port}${pathPrefix}`; } private setConnection(workspaceFolderName: string, namespace?: string): void { diff --git a/src/commands/serverActions.ts b/src/commands/serverActions.ts index d5002031..5e0dc5ef 100644 --- a/src/commands/serverActions.ts +++ b/src/commands/serverActions.ts @@ -26,7 +26,6 @@ export async function serverActions(): Promise { const { apiTarget, configName: workspaceFolder } = connectionTarget(); const api = new AtelierAPI(apiTarget); const { active, host = "", ns = "", https, port = 0, pathPrefix, authorization, docker } = api.config; - const username = authorization.username; const explorerCount = (await explorerProvider.getChildren()).length; if (!explorerCount && (!docker || host === "")) { await vscode.commands.executeCommand("ObjectScriptExplorer.focus"); @@ -153,7 +152,7 @@ export async function serverActions(): Promise { .replace("${serverAuth}", "") .replace("${ns}", nsEncoded) .replace("${namespace}", ns == "%SYS" ? "sys" : nsEncoded.toLowerCase()) - .replace("${username}", username) + .replace("${username}", authorization.username) .replace("${classname}", classname) .replace("${classnameEncoded}", classnameEncoded) .replace("${project}", project); @@ -249,7 +248,7 @@ export async function serverActions(): Promise { if (addin) { sendStudioAddinTelemetryEvent(addin.label); let params = `Namespace=${nsEncoded}`; - params += `&User=${encodeURIComponent(username)}`; + params += `&User=${encodeURIComponent(authorization.username)}`; if (project != "") { params += `&Project=${encodeURIComponent(project)}`; } diff --git a/src/extension.ts b/src/extension.ts index 71f958ee..3209776f 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -365,7 +365,6 @@ export async function checkConnection( } let api = new AtelierAPI(apiTarget, false); const { active, host = "", port = 0, superserverPort = 0, ns = "", authorization } = api.config; - const username = authorization.resolved() ? authorization.username : ""; vscode.commands.executeCommand("setContext", "vscode-objectscript.connectActive", active); if (!panel.text) { panel.text = `${PANEL_LABEL}`; @@ -450,9 +449,11 @@ export async function checkConnection( panel.text = api.connInfo; const { serverName, host, port, pathPrefix } = api.config; if (serverName) { - panel.tooltip = new vscode.MarkdownString(`Connected to \`${host}:${port}${pathPrefix}\` as \`${username}\``); + panel.tooltip = new vscode.MarkdownString( + `Connected to \`${host}:${port}${pathPrefix}\` as \`${authorization.username}\`` + ); } else { - panel.tooltip = new vscode.MarkdownString(`Connected as \`${username}\``); + panel.tooltip = new vscode.MarkdownString(`Connected as \`${authorization.username}\``); } inactiveServerIds.delete(api.serverId); if (!api.externalServer) await setConnectionState(configName, true); From 82b080092a5247e4422567f39ab38a9abffef04e Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Tue, 30 Jun 2026 11:08:35 -0400 Subject: [PATCH 06/17] simp --- src/api/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/index.ts b/src/api/index.ts index efddd728..1ab4c9f0 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -312,7 +312,7 @@ export class AtelierAPI { headers?: any, options?: any ): Promise { - const { active, apiVersion, host, port, https, authorization } = this.config; + const { active, apiVersion, host, port, authorization, https } = this.config; if (!active || !port || !host) { return Promise.reject(); } From 70d638db2516fe1c889d760e4ec07008bc4b2e5a Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Tue, 30 Jun 2026 11:11:32 -0400 Subject: [PATCH 07/17] simp --- src/commands/connectFolderToServerNamespace.ts | 4 ++-- src/extension.ts | 3 +-- src/utils/index.ts | 7 +------ 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/commands/connectFolderToServerNamespace.ts b/src/commands/connectFolderToServerNamespace.ts index 3180c8cc..edec16e5 100644 --- a/src/commands/connectFolderToServerNamespace.ts +++ b/src/commands/connectFolderToServerNamespace.ts @@ -7,7 +7,7 @@ import { serverManagerApi, resolveUsernameAndPassword, } from "../extension"; -import { handleError, isUnauthenticated, notIsfs, displayableUri } from "../utils"; +import { handleError, notIsfs, displayableUri } from "../utils"; interface ConnSettings { server: string; @@ -80,7 +80,7 @@ export async function connectFolderToServerNamespace(): Promise { .serverInfo(false) .then((data) => data.result.content.namespaces) .catch(async (error) => { - if (error?.statusCode == 401 && isUnauthenticated(api.config.authorization.username)) { + if (error?.statusCode == 401 && !api.config.authorization.resolved()) { // Attempt to resolve username and password and try again const newSpec = await resolveUsernameAndPassword(api.config.serverName, connSpec); if (newSpec) { diff --git a/src/extension.ts b/src/extension.ts index 3209776f..3b44cfbb 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -85,7 +85,6 @@ import { portFromDockerCompose, notNull, currentFile, - isUnauthenticated, notIsfs, handleError, cspApps, @@ -472,7 +471,7 @@ export async function checkConnection( let success = false; message = "Not Authorized."; errorMessage = `Authorization error: Check your credentials in Settings, and that you have sufficient privileges on the /api/atelier web application on ${connInfo}`; - if (isUnauthenticated(api.config.authorization.username)) { + if (!api.config.authorization.resolved()) { vscode.window.showErrorMessage( `Unauthenticated access rejected by '${api.serverId}'.${ !api.config.serverName ? " Connection has been disabled." : "" diff --git a/src/utils/index.ts b/src/utils/index.ts index 79e21cfd..e26de5a6 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -871,11 +871,6 @@ export function methodOffsetToLine( return line; } -/** Return `true` if this username signals unauthenticated access */ -export function isUnauthenticated(username: string): boolean { - return username == undefined || username == "" || username.toLowerCase() == "unknownuser"; -} - /** Returns `true` if `uri.scheme` is neither `isfs` nor `isfs-readonly` */ export function notIsfs(uri: vscode.Uri): boolean { return !filesystemSchemas.includes(uri.scheme); @@ -928,7 +923,7 @@ export async function getWsServerConnection(minVersion?: string): Promise Date: Tue, 30 Jun 2026 11:22:16 -0400 Subject: [PATCH 08/17] simp --- src/api/index.ts | 2 +- src/extension.ts | 24 +++++++----------------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/src/api/index.ts b/src/api/index.ts index 1ab4c9f0..74923995 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -27,7 +27,7 @@ const authRequestMap = new Map>(); /** Map of `username@host:port/pathPrefix` to cookies */ const cookiesMap = new Map(); -interface ConnectionSettings { +export interface ConnectionSettings { serverName: string; active: boolean; apiVersion: number; diff --git a/src/extension.ts b/src/extension.ts index 3b44cfbb..8ba74817 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -71,7 +71,7 @@ import { ObjectScriptRoutineSymbolProvider } from "./providers/ObjectScriptRouti import { ObjectScriptCodeLensProvider } from "./providers/ObjectScriptCodeLensProvider"; import { XmlContentProvider } from "./providers/XmlContentProvider"; -import { AtelierAPI } from "./api"; +import { AtelierAPI, ConnectionSettings } from "./api"; import { ObjectScriptDebugAdapterDescriptorFactory } from "./debug/debugAdapterFactory"; import { ObjectScriptConfigurationProvider } from "./debug/debugConfProvider"; import { ProjectsExplorerProvider } from "./explorer/projectsExplorer"; @@ -254,7 +254,7 @@ export async function resolveConnectionSpec( superServer: { port: serverForUri.superserverPort, }, - authorization: serverManagerApi.makeAuthorization(serverForUri.username, serverForUri.password), + authorization: serverForUri.authorization.clone(), description: `Server for workspace folder '${serverName}'`, }; } @@ -1955,20 +1955,10 @@ type HttpsAndScheme = https?: false; }; -export interface GeneralServerForUri { - serverName: string; - active: boolean; - host: string; - port: number; - superserverPort: number; - pathPrefix: string; - namespace: string; - apiVersion: number; - serverVersion: string; - authorization: serverManager.Authorization; -} - -export type ServerForUri = GeneralServerForUri & HttpsAndScheme; +export type ServerForUri = Omit & + HttpsAndScheme & { + namespace: ConnectionSettings["ns"]; + }; // This function is exported as one of our API functions but is also used internally // for example to implement the async variant capable of resolving docker port number. @@ -2027,7 +2017,7 @@ function serverForUri(uri: vscode.Uri): ServerForUri { // An async variant capable of resolving docker port number. // It is exported as one of our API functions but is also used internally. -async function asyncServerForUri(uri: vscode.Uri): Promise { +async function asyncServerForUri(uri: vscode.Uri): Promise { const server = serverForUri(uri); if (!server.port) { let { apiTarget } = connectionTarget(uri); From 3f812e26fe815e2a9e1c35a2d1b6debbefb52713 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Thu, 2 Jul 2026 08:54:12 -0400 Subject: [PATCH 09/17] draft (still buggy) --- src/api/index.ts | 22 ++++++------ .../connectFolderToServerNamespace.ts | 2 +- src/commands/restDebugPanel.ts | 3 +- src/commands/serverActions.ts | 2 +- src/commands/webSocketTerminal.ts | 2 +- src/extension.ts | 35 ++++++++++--------- .../FileSystemProvider/FileSystemProvider.ts | 6 ++-- src/providers/LowCodeEditorProvider.ts | 4 +-- src/utils/index.ts | 2 +- 9 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/api/index.ts b/src/api/index.ts index 74923995..4c321b24 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -38,7 +38,7 @@ export interface ConnectionSettings { superserverPort?: number; pathPrefix: string; ns: string; - authorization: Authorization; + auth: Authorization; docker: boolean; dockerService?: string; } @@ -59,7 +59,7 @@ export class AtelierAPI { } public get config(): ConnectionSettings { - const { serverName, active = false, https = false, pathPrefix = "", authorization } = this._config; + const { serverName, active = false, https = false, pathPrefix = "", auth: authorization } = this._config; const ns = this.namespace || this._config.ns; const wsKey = this.configName.toLowerCase(); const host = this.externalServer ? this._config.host : workspaceState.get(wsKey + ":host", this._config.host); @@ -86,7 +86,7 @@ export class AtelierAPI { superserverPort, pathPrefix, ns, - authorization, + auth: authorization, docker, dockerService, }; @@ -152,9 +152,9 @@ export class AtelierAPI { public setConnSpec(serverName: string, connSpec: IServerSpec): void { const { webServer: { scheme, host, port, pathPrefix = "" }, - authorization, + auth, } = connSpec; - this._config.authorization = authorization; + this._config.auth = auth; this._config.https = scheme == "https"; this._config.host = host; this._config.port = port; @@ -209,7 +209,7 @@ export class AtelierAPI { /** Return the key for getting values from connection-specific Maps for this connection */ private mapKey(): string { - const { host, port, authorization } = this.config; + const { host, port, auth: authorization } = this.config; let pathPrefix = this._config.pathPrefix || ""; if (pathPrefix.length && !pathPrefix.startsWith("/")) { pathPrefix = "/" + pathPrefix; @@ -252,7 +252,7 @@ export class AtelierAPI { host, port, superserverPort: superServer?.port, - authorization, + auth: authorization, pathPrefix, docker: false, }; @@ -275,7 +275,7 @@ export class AtelierAPI { host, port, superserverPort: superServer?.port, - authorization, + auth: authorization, pathPrefix, docker: true, dockerService: conn["docker-compose"].service, @@ -312,7 +312,7 @@ export class AtelierAPI { headers?: any, options?: any ): Promise { - const { active, apiVersion, host, port, authorization, https } = this.config; + const { active, apiVersion, host, port, auth: authorization, https } = this.config; if (!active || !port || !host) { return Promise.reject(); } @@ -366,9 +366,7 @@ export class AtelierAPI { let authRequest = authRequestMap.get(mapKey); if (cookies.length || (method === "HEAD" && !originalPath)) { auth = Promise.resolve(cookies); - headers["Authorization"] = this.config.authorization.resolved() - ? this.config.authorization.httpAuthorizationHeader - : ""; + headers["Authorization"] = this.config.auth.resolved() ? this.config.auth.httpAuthorizationHeader : ""; } else if (!cookies.length) { if (!authRequest) { // Recursion point diff --git a/src/commands/connectFolderToServerNamespace.ts b/src/commands/connectFolderToServerNamespace.ts index edec16e5..b4076888 100644 --- a/src/commands/connectFolderToServerNamespace.ts +++ b/src/commands/connectFolderToServerNamespace.ts @@ -80,7 +80,7 @@ export async function connectFolderToServerNamespace(): Promise { .serverInfo(false) .then((data) => data.result.content.namespaces) .catch(async (error) => { - if (error?.statusCode == 401 && !api.config.authorization.resolved()) { + if (error?.statusCode == 401 && !api.config.auth.resolved()) { // Attempt to resolve username and password and try again const newSpec = await resolveUsernameAndPassword(api.config.serverName, connSpec); if (newSpec) { diff --git a/src/commands/restDebugPanel.ts b/src/commands/restDebugPanel.ts index 772b3611..fc9bc2df 100644 --- a/src/commands/restDebugPanel.ts +++ b/src/commands/restDebugPanel.ts @@ -549,8 +549,7 @@ export class RESTDebugPanel { } }); headers["authorization"] = - headers["authorization"] ?? - (api.config.authorization.resolved() ? api.config.authorization.httpAuthorizationHeader : ""); + headers["authorization"] ?? (api.config.auth.resolved() ? api.config.auth.httpAuthorizationHeader : ""); const hasBody = typeof message.bodyContent == "string" && message.bodyContent != "" && message.bodyType != "No Body"; if (hasBody) { diff --git a/src/commands/serverActions.ts b/src/commands/serverActions.ts index 5e0dc5ef..de88070b 100644 --- a/src/commands/serverActions.ts +++ b/src/commands/serverActions.ts @@ -25,7 +25,7 @@ type ServerAction = { detail: string; id: string; label: string; rawLink?: strin export async function serverActions(): Promise { const { apiTarget, configName: workspaceFolder } = connectionTarget(); const api = new AtelierAPI(apiTarget); - const { active, host = "", ns = "", https, port = 0, pathPrefix, authorization, docker } = api.config; + const { active, host = "", ns = "", https, port = 0, pathPrefix, auth: authorization, docker } = api.config; const explorerCount = (await explorerProvider.getChildren()).length; if (!explorerCount && (!docker || host === "")) { await vscode.commands.executeCommand("ObjectScriptExplorer.focus"); diff --git a/src/commands/webSocketTerminal.ts b/src/commands/webSocketTerminal.ts index f2e40607..b77b9d2e 100644 --- a/src/commands/webSocketTerminal.ts +++ b/src/commands/webSocketTerminal.ts @@ -225,7 +225,7 @@ class WebSocketTerminal implements vscode.Pseudoterminal { this._hideCursorWrite("\x1b]633;P;HasRichCommandDetection=True\x07"); // Print the opening message this._hideCursorWrite( - `\x1b[32mConnected to \x1b[0m\x1b[4m${api.config.host}:${api.config.port}${api.config.pathPrefix}\x1b[0m\x1b[32m as \x1b[0m\x1b[3m${api.config.authorization.username}\x1b[0m\r\n` + `\x1b[32mConnected to \x1b[0m\x1b[4m${api.config.host}:${api.config.port}${api.config.pathPrefix}\x1b[0m\x1b[32m as \x1b[0m\x1b[3m${api.config.auth.username}\x1b[0m\r\n` ); // Add event handlers to the socket this._socket diff --git a/src/extension.ts b/src/extension.ts index 8ba74817..6e5a498d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -254,7 +254,7 @@ export async function resolveConnectionSpec( superServer: { port: serverForUri.superserverPort, }, - authorization: serverForUri.authorization.clone(), + auth: serverForUri.auth.clone(), description: `Server for workspace folder '${serverName}'`, }; } @@ -268,11 +268,11 @@ export async function resolveConnectionSpec( } async function resolvePassword( - serverSpec: Pick, + serverSpec: Pick, ignoreUnauthenticated = false ): Promise { - if (!(serverSpec.authorization.resolved() || ignoreUnauthenticated)) { - const scopes = [serverSpec.name, serverSpec.authorization.username || ""]; + if (!(serverSpec.auth.resolved() || ignoreUnauthenticated)) { + const scopes = [serverSpec.name, serverSpec.auth.username || ""]; // Handle Server Manager extension version < 3.8.0 const account = serverManagerApi.getAccount ? serverManagerApi.getAccount(serverSpec) : undefined; @@ -289,7 +289,10 @@ async function resolvePassword( } if (session) { // If original spec lacked username use the one obtained from the user by the authprovider (exact case) - serverSpec.authorization.resolve(session.accessToken, session.scopes[1]); + serverSpec.auth.resolve({ + username: session.scopes[1], + accessToken: session.accessToken, + }); return session.accessToken; } } @@ -300,16 +303,16 @@ export async function resolveUsernameAndPassword( serverName: string, oldSpec: serverManager.IServerSpec ): Promise<(serverManager.IServerSpec & { accessToken?: string }) | undefined> { - const { authorization: _authorization, ...newSpec } = oldSpec; + const { auth: _auth, ...newSpec } = oldSpec; newSpec.name = serverName; - const authorization = _authorization.clone(); + const auth = _auth.clone(); - const accessToken = await resolvePassword({ ...newSpec, authorization }, true); - if (authorization.resolved()) { + const accessToken = await resolvePassword({ ...newSpec, auth }, true); + if (auth.resolved()) { // Update the connection spec resolvedConnSpecs.set(serverName, { ...oldSpec, - authorization, + auth, accessToken, }); return resolvedConnSpecs.get(serverName); @@ -363,7 +366,7 @@ export async function checkConnection( _onDidChangeConnection.fire(); } let api = new AtelierAPI(apiTarget, false); - const { active, host = "", port = 0, superserverPort = 0, ns = "", authorization } = api.config; + const { active, host = "", port = 0, superserverPort = 0, ns = "", auth: authorization } = api.config; vscode.commands.executeCommand("setContext", "vscode-objectscript.connectActive", active); if (!panel.text) { panel.text = `${PANEL_LABEL}`; @@ -471,7 +474,7 @@ export async function checkConnection( let success = false; message = "Not Authorized."; errorMessage = `Authorization error: Check your credentials in Settings, and that you have sufficient privileges on the /api/atelier web application on ${connInfo}`; - if (!api.config.authorization.resolved()) { + if (!api.config.auth.resolved()) { vscode.window.showErrorMessage( `Unauthenticated access rejected by '${api.serverId}'.${ !api.config.serverName ? " Connection has been disabled." : "" @@ -515,7 +518,7 @@ export async function checkConnection( vscode.window .showInputBox({ password: true, - title: `Not Authorized. Enter password to connect as user '${api.config.authorization.username}' to ${connInfo}`, + title: `Not Authorized. Enter password to connect as user '${api.config.auth.username}' to ${connInfo}`, prompt: !api.externalServer ? "If no password is entered the connection will be disabled." : "", ignoreFocusOut: true, }) @@ -1978,7 +1981,7 @@ function serverForUri(uri: vscode.Uri): ServerForUri { port, superserverPort, pathPrefix, - authorization, + auth: authorization, ns, apiVersion, serverVersion, @@ -1997,7 +2000,7 @@ function serverForUri(uri: vscode.Uri): ServerForUri { ) .get("password") as string | undefined; if (password !== undefined) { - authorization.resolve(password); + authorization.resolve({ accessToken: password }); } } return { @@ -2008,7 +2011,7 @@ function serverForUri(uri: vscode.Uri): ServerForUri { port, superserverPort, pathPrefix, - authorization, + auth: authorization, namespace: ns, apiVersion: active ? apiVersion : undefined, serverVersion: active ? serverVersion : undefined, diff --git a/src/providers/FileSystemProvider/FileSystemProvider.ts b/src/providers/FileSystemProvider/FileSystemProvider.ts index 0d402e3a..5c7f4afe 100644 --- a/src/providers/FileSystemProvider/FileSystemProvider.ts +++ b/src/providers/FileSystemProvider/FileSystemProvider.ts @@ -476,10 +476,10 @@ export class FileSystemProvider implements vscode.FileSystemProvider { .catch((error) => { if (error) { if (error.errorText.includes(" #5540:")) { - const message = `User '${api.config.authorization.username}' cannot list ${ + const message = `User '${api.config.auth.username}' cannot list ${ csp ? `web application '${uri.path}'` : "namespace" } contents. If they do not have READ permission on the default code database of the ${api.config.ns.toUpperCase()} namespace then grant it and retry. If the problem remains then execute the following SQL in that namespace:\n\t GRANT EXECUTE ON %Library.RoutineMgr_StudioOpenDialog TO ${ - api.config.authorization.username + api.config.auth.username }`; handleError(message); } @@ -1006,7 +1006,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider { private async _lookup(uri: vscode.Uri, fillInPath?: boolean): Promise { const api = new AtelierAPI(uri); const config = api.config; - const rootName = `${config.authorization.username}@${config.host}:${config.port}${config.pathPrefix}/${config.ns.toUpperCase()}`; + const rootName = `${config.auth.username}@${config.host}:${config.port}${config.pathPrefix}/${config.ns.toUpperCase()}`; let entry: Entry = this.superRoot.entries.get(rootName); if (!entry) { entry = new Directory(rootName, ""); diff --git a/src/providers/LowCodeEditorProvider.ts b/src/providers/LowCodeEditorProvider.ts index 66bf15a2..0994a0f3 100644 --- a/src/providers/LowCodeEditorProvider.ts +++ b/src/providers/LowCodeEditorProvider.ts @@ -212,8 +212,8 @@ export class LowCodeEditorProvider implements vscode.CustomTextEditorProvider { webviewPanel.webview.postMessage({ direction: "editor", type: "auth", - username: api.config.authorization.username, - password: api.config.authorization.password, + username: api.config.auth.username, + password: api.config.auth.password, }); } return; diff --git a/src/utils/index.ts b/src/utils/index.ts index e26de5a6..9b022747 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -923,7 +923,7 @@ export async function getWsServerConnection(minVersion?: string): Promise Date: Thu, 2 Jul 2026 08:59:18 -0400 Subject: [PATCH 10/17] for diff --- src/api/index.ts | 11 +++++++---- src/providers/LowCodeEditorProvider.ts | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/api/index.ts b/src/api/index.ts index 4c321b24..4cc0ed4b 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -59,7 +59,7 @@ export class AtelierAPI { } public get config(): ConnectionSettings { - const { serverName, active = false, https = false, pathPrefix = "", auth: authorization } = this._config; + const { serverName, active = false, https = false, pathPrefix = "", auth } = this._config; const ns = this.namespace || this._config.ns; const wsKey = this.configName.toLowerCase(); const host = this.externalServer ? this._config.host : workspaceState.get(wsKey + ":host", this._config.host); @@ -69,7 +69,7 @@ export class AtelierAPI { : workspaceState.get(wsKey + ":superserverPort", this._config.superserverPort); const password = workspaceState.get(wsKey + ":password", undefined); if (password !== undefined) { - authorization.resolve(password); + auth.resolve({ accessToken: password }); } const apiVersion = workspaceState.get(wsKey + ":apiVersion", DEFAULT_API_VERSION); const serverVersion = workspaceState.get(wsKey + ":serverVersion", DEFAULT_SERVER_VERSION); @@ -86,7 +86,7 @@ export class AtelierAPI { superserverPort, pathPrefix, ns, - auth: authorization, + auth: auth, docker, dockerService, }; @@ -366,7 +366,6 @@ export class AtelierAPI { let authRequest = authRequestMap.get(mapKey); if (cookies.length || (method === "HEAD" && !originalPath)) { auth = Promise.resolve(cookies); - headers["Authorization"] = this.config.auth.resolved() ? this.config.auth.httpAuthorizationHeader : ""; } else if (!cookies.length) { if (!authRequest) { // Recursion point @@ -375,6 +374,10 @@ export class AtelierAPI { } auth = authRequest; } + // Always set Authorization header if credentials are resolved + if (this.config.auth.resolved()) { + headers["Authorization"] = this.config.auth.httpAuthorizationHeader; + } const outputTraffic = vscode.workspace.getConfiguration("objectscript").get("outputRESTTraffic"); let cookie; diff --git a/src/providers/LowCodeEditorProvider.ts b/src/providers/LowCodeEditorProvider.ts index 0994a0f3..06e6ae51 100644 --- a/src/providers/LowCodeEditorProvider.ts +++ b/src/providers/LowCodeEditorProvider.ts @@ -213,7 +213,7 @@ export class LowCodeEditorProvider implements vscode.CustomTextEditorProvider { direction: "editor", type: "auth", username: api.config.auth.username, - password: api.config.auth.password, + password: api.config.auth.accessToken, }); } return; From 255b724137e5a68423a56674f3c62d21856e5fa2 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Thu, 2 Jul 2026 09:14:29 -0400 Subject: [PATCH 11/17] fixing --- src/api/index.ts | 18 +++++++++--------- src/commands/restDebugPanel.ts | 3 +-- src/extension.ts | 21 +++++---------------- 3 files changed, 15 insertions(+), 27 deletions(-) diff --git a/src/api/index.ts b/src/api/index.ts index 4cc0ed4b..a91ab5dd 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -67,9 +67,9 @@ export class AtelierAPI { const superserverPort = this.externalServer ? this._config.superserverPort : workspaceState.get(wsKey + ":superserverPort", this._config.superserverPort); - const password = workspaceState.get(wsKey + ":password", undefined); - if (password !== undefined) { - auth.resolve({ accessToken: password }); + const accessToken = workspaceState.get(wsKey + ":password", undefined); + if (accessToken !== undefined) { + auth.resolve({ accessToken }); } const apiVersion = workspaceState.get(wsKey + ":apiVersion", DEFAULT_API_VERSION); const serverVersion = workspaceState.get(wsKey + ":serverVersion", DEFAULT_SERVER_VERSION); @@ -209,12 +209,12 @@ export class AtelierAPI { /** Return the key for getting values from connection-specific Maps for this connection */ private mapKey(): string { - const { host, port, auth: authorization } = this.config; + const { host, port, auth } = this.config; let pathPrefix = this._config.pathPrefix || ""; if (pathPrefix.length && !pathPrefix.startsWith("/")) { pathPrefix = "/" + pathPrefix; } - return `${authorization.username}@${host}:${port}${pathPrefix}`; + return `${auth.username}@${host}:${port}${pathPrefix}`; } private setConnection(workspaceFolderName: string, namespace?: string): void { @@ -239,7 +239,7 @@ export class AtelierAPI { if (serverName !== "") { const { webServer: { scheme, host, port, pathPrefix = "" }, - authorization, + auth, superServer, } = getResolvedConnectionSpec(serverName, config("intersystems.servers", workspaceFolderName).get(serverName)); this._config = { @@ -252,7 +252,7 @@ export class AtelierAPI { host, port, superserverPort: superServer?.port, - auth: authorization, + auth, pathPrefix, docker: false, }; @@ -262,7 +262,7 @@ export class AtelierAPI { if (resolvedSpec) { const { webServer: { scheme, host, port, pathPrefix = "" }, - authorization, + auth, superServer, } = resolvedSpec; this._config = { @@ -275,7 +275,7 @@ export class AtelierAPI { host, port, superserverPort: superServer?.port, - auth: authorization, + auth, pathPrefix, docker: true, dockerService: conn["docker-compose"].service, diff --git a/src/commands/restDebugPanel.ts b/src/commands/restDebugPanel.ts index fc9bc2df..29030211 100644 --- a/src/commands/restDebugPanel.ts +++ b/src/commands/restDebugPanel.ts @@ -548,8 +548,7 @@ export class RESTDebugPanel { .trim(); } }); - headers["authorization"] = - headers["authorization"] ?? (api.config.auth.resolved() ? api.config.auth.httpAuthorizationHeader : ""); + headers["authorization"] = headers["authorization"] ?? (api.config.auth.httpAuthorizationHeader || ""); const hasBody = typeof message.bodyContent == "string" && message.bodyContent != "" && message.bodyType != "No Body"; if (hasBody) { diff --git a/src/extension.ts b/src/extension.ts index 6e5a498d..960f8e0b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -320,7 +320,7 @@ export async function resolveUsernameAndPassword( } /** Accessor for the cache of resolved connection specs */ -export function getResolvedConnectionSpec(key: string, dflt: any): any { +export function getResolvedConnectionSpec(key: string, dflt: serverManager.IServerSpec): serverManager.IServerSpec { let spec = resolvedConnSpecs.get(key); if (spec) { return spec; @@ -1973,19 +1973,8 @@ function serverForUri(uri: vscode.Uri): ServerForUri { // This function intentionally no longer exposes the password for a named server UNLESS it is already exposed as plaintext in settings. // API client extensions should use Server Manager 3's authentication provider to request a missing password themselves, // which will require explicit user consent to divulge the password to the requesting extension. - const { - serverName, - active, - host, - https, - port, - superserverPort, - pathPrefix, - auth: authorization, - ns, - apiVersion, - serverVersion, - } = api.config; + const { serverName, active, host, https, port, superserverPort, pathPrefix, auth, ns, apiVersion, serverVersion } = + api.config; if (serverName !== "") { const password = vscode.workspace .getConfiguration( @@ -2000,7 +1989,7 @@ function serverForUri(uri: vscode.Uri): ServerForUri { ) .get("password") as string | undefined; if (password !== undefined) { - authorization.resolve({ accessToken: password }); + auth.resolve({ accessToken: password }); } } return { @@ -2011,7 +2000,7 @@ function serverForUri(uri: vscode.Uri): ServerForUri { port, superserverPort, pathPrefix, - auth: authorization, + auth: auth, namespace: ns, apiVersion: active ? apiVersion : undefined, serverVersion: active ? serverVersion : undefined, From 2d4a712793da7b23f23f6bf9de906740dd7e4dc0 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Thu, 2 Jul 2026 09:17:08 -0400 Subject: [PATCH 12/17] stage --- src/api/index.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/api/index.ts b/src/api/index.ts index a91ab5dd..f8957658 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -312,7 +312,7 @@ export class AtelierAPI { headers?: any, options?: any ): Promise { - const { active, apiVersion, host, port, auth: authorization, https } = this.config; + const { active, apiVersion, host, port, auth, https } = this.config; if (!active || !port || !host) { return Promise.reject(); } @@ -443,11 +443,7 @@ export class AtelierAPI { authRequestMap.delete(mapKey); if (this.wsOrFile && !checkingConnection) { setTimeout(() => { - checkConnection( - authorization.resolved(), - typeof this.wsOrFile === "object" ? this.wsOrFile : undefined, - true - ); + checkConnection(auth.resolved(), typeof this.wsOrFile === "object" ? this.wsOrFile : undefined, true); }, 500); } throw { statusCode: response.status, message: response.statusText }; From c6ac3a5cee517c1e164303711c3ed96e953dcc45 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Thu, 2 Jul 2026 09:46:42 -0400 Subject: [PATCH 13/17] simp --- src/api/index.ts | 10 +++++++--- src/extension.ts | 9 ++++----- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/api/index.ts b/src/api/index.ts index f8957658..529aa222 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -86,7 +86,7 @@ export class AtelierAPI { superserverPort, pathPrefix, ns, - auth: auth, + auth, docker, dockerService, }; @@ -312,7 +312,7 @@ export class AtelierAPI { headers?: any, options?: any ): Promise { - const { active, apiVersion, host, port, auth, https } = this.config; + const { active, apiVersion, host, port, https } = this.config; if (!active || !port || !host) { return Promise.reject(); } @@ -443,7 +443,11 @@ export class AtelierAPI { authRequestMap.delete(mapKey); if (this.wsOrFile && !checkingConnection) { setTimeout(() => { - checkConnection(auth.resolved(), typeof this.wsOrFile === "object" ? this.wsOrFile : undefined, true); + checkConnection( + this.config.auth.resolved(), + typeof this.wsOrFile === "object" ? this.wsOrFile : undefined, + true + ); }, 500); } throw { statusCode: response.status, message: response.statusText }; diff --git a/src/extension.ts b/src/extension.ts index 960f8e0b..785fd47c 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -235,7 +235,7 @@ export async function resolveConnectionSpec( } } - let connSpec = await serverManagerApi.getServerSpec(serverName, scope); + let connSpec: serverManager.IServerSpec = await serverManagerApi.getServerSpec(serverName, scope); if (!connSpec && uri) { // Caller passed uri as a signal to process any docker-compose settings @@ -254,7 +254,6 @@ export async function resolveConnectionSpec( superServer: { port: serverForUri.superserverPort, }, - auth: serverForUri.auth.clone(), description: `Server for workspace folder '${serverName}'`, }; } @@ -271,8 +270,8 @@ async function resolvePassword( serverSpec: Pick, ignoreUnauthenticated = false ): Promise { - if (!(serverSpec.auth.resolved() || ignoreUnauthenticated)) { - const scopes = [serverSpec.name, serverSpec.auth.username || ""]; + if (!((serverSpec.auth.resolved() as boolean) || ignoreUnauthenticated)) { + const scopes = [serverSpec.name, serverSpec.auth?.username || ""]; // Handle Server Manager extension version < 3.8.0 const account = serverManagerApi.getAccount ? serverManagerApi.getAccount(serverSpec) : undefined; @@ -2000,7 +1999,7 @@ function serverForUri(uri: vscode.Uri): ServerForUri { port, superserverPort, pathPrefix, - auth: auth, + auth, namespace: ns, apiVersion: active ? apiVersion : undefined, serverVersion: active ? serverVersion : undefined, From ea524a62bcb8a8a0e6b1013547a03d794ce5e1e6 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Thu, 2 Jul 2026 09:58:09 -0400 Subject: [PATCH 14/17] simp --- src/api/index.ts | 13 +++++-------- src/commands/serverActions.ts | 6 +++--- src/extension.ts | 17 ++++++++--------- 3 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/api/index.ts b/src/api/index.ts index 529aa222..981b02a1 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -67,10 +67,7 @@ export class AtelierAPI { const superserverPort = this.externalServer ? this._config.superserverPort : workspaceState.get(wsKey + ":superserverPort", this._config.superserverPort); - const accessToken = workspaceState.get(wsKey + ":password", undefined); - if (accessToken !== undefined) { - auth.resolve({ accessToken }); - } + auth.resolve({ accessToken: workspaceState.get(wsKey + ":password", undefined) }); const apiVersion = workspaceState.get(wsKey + ":apiVersion", DEFAULT_API_VERSION); const serverVersion = workspaceState.get(wsKey + ":serverVersion", DEFAULT_SERVER_VERSION); const docker = workspaceState.get(wsKey + ":docker", false); @@ -365,6 +362,10 @@ export class AtelierAPI { let auth: Promise; let authRequest = authRequestMap.get(mapKey); if (cookies.length || (method === "HEAD" && !originalPath)) { + // Only send basic authorization if username and password specified (including blank, for unauthenticated access) + if (this.config.auth.resolved()) { + headers["Authorization"] = this.config.auth.httpAuthorizationHeader; + } auth = Promise.resolve(cookies); } else if (!cookies.length) { if (!authRequest) { @@ -374,10 +375,6 @@ export class AtelierAPI { } auth = authRequest; } - // Always set Authorization header if credentials are resolved - if (this.config.auth.resolved()) { - headers["Authorization"] = this.config.auth.httpAuthorizationHeader; - } const outputTraffic = vscode.workspace.getConfiguration("objectscript").get("outputRESTTraffic"); let cookie; diff --git a/src/commands/serverActions.ts b/src/commands/serverActions.ts index de88070b..c157909c 100644 --- a/src/commands/serverActions.ts +++ b/src/commands/serverActions.ts @@ -25,7 +25,7 @@ type ServerAction = { detail: string; id: string; label: string; rawLink?: strin export async function serverActions(): Promise { const { apiTarget, configName: workspaceFolder } = connectionTarget(); const api = new AtelierAPI(apiTarget); - const { active, host = "", ns = "", https, port = 0, pathPrefix, auth: authorization, docker } = api.config; + const { active, host = "", ns = "", https, port = 0, pathPrefix, auth, docker } = api.config; const explorerCount = (await explorerProvider.getChildren()).length; if (!explorerCount && (!docker || host === "")) { await vscode.commands.executeCommand("ObjectScriptExplorer.focus"); @@ -152,7 +152,7 @@ export async function serverActions(): Promise { .replace("${serverAuth}", "") .replace("${ns}", nsEncoded) .replace("${namespace}", ns == "%SYS" ? "sys" : nsEncoded.toLowerCase()) - .replace("${username}", authorization.username) + .replace("${username}", auth.username) .replace("${classname}", classname) .replace("${classnameEncoded}", classnameEncoded) .replace("${project}", project); @@ -248,7 +248,7 @@ export async function serverActions(): Promise { if (addin) { sendStudioAddinTelemetryEvent(addin.label); let params = `Namespace=${nsEncoded}`; - params += `&User=${encodeURIComponent(authorization.username)}`; + params += `&User=${encodeURIComponent(auth.username)}`; if (project != "") { params += `&Project=${encodeURIComponent(project)}`; } diff --git a/src/extension.ts b/src/extension.ts index 785fd47c..409bf06c 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -208,7 +208,7 @@ export let checkingConnection = false; export let serverManagerApi: serverManager.ServerManagerAPI; /** Map of the intersystems.server connection specs we have resolved via the API to that extension */ -const resolvedConnSpecs = new Map(); +const resolvedConnSpecs = new Map(); /** * If servermanager extension is available, fetch the connection spec unless already cached. @@ -267,10 +267,10 @@ export async function resolveConnectionSpec( } async function resolvePassword( - serverSpec: Pick, + serverSpec: serverManager.IServerSpec, ignoreUnauthenticated = false ): Promise { - if (!((serverSpec.auth.resolved() as boolean) || ignoreUnauthenticated)) { + if (!(serverSpec.auth.resolved() as boolean) || ignoreUnauthenticated) { const scopes = [serverSpec.name, serverSpec.auth?.username || ""]; // Handle Server Manager extension version < 3.8.0 @@ -304,15 +304,14 @@ export async function resolveUsernameAndPassword( ): Promise<(serverManager.IServerSpec & { accessToken?: string }) | undefined> { const { auth: _auth, ...newSpec } = oldSpec; newSpec.name = serverName; - const auth = _auth.clone(); + const auth = _auth?.clone(); const accessToken = await resolvePassword({ ...newSpec, auth }, true); - if (auth.resolved()) { + if (auth.resolve({ accessToken })) { // Update the connection spec resolvedConnSpecs.set(serverName, { ...oldSpec, auth, - accessToken, }); return resolvedConnSpecs.get(serverName); } @@ -365,7 +364,7 @@ export async function checkConnection( _onDidChangeConnection.fire(); } let api = new AtelierAPI(apiTarget, false); - const { active, host = "", port = 0, superserverPort = 0, ns = "", auth: authorization } = api.config; + const { active, host = "", port = 0, superserverPort = 0, ns = "", auth } = api.config; vscode.commands.executeCommand("setContext", "vscode-objectscript.connectActive", active); if (!panel.text) { panel.text = `${PANEL_LABEL}`; @@ -451,10 +450,10 @@ export async function checkConnection( const { serverName, host, port, pathPrefix } = api.config; if (serverName) { panel.tooltip = new vscode.MarkdownString( - `Connected to \`${host}:${port}${pathPrefix}\` as \`${authorization.username}\`` + `Connected to \`${host}:${port}${pathPrefix}\` as \`${auth.username}\`` ); } else { - panel.tooltip = new vscode.MarkdownString(`Connected as \`${authorization.username}\``); + panel.tooltip = new vscode.MarkdownString(`Connected as \`${auth.username}\``); } inactiveServerIds.delete(api.serverId); if (!api.externalServer) await setConnectionState(configName, true); From ff0e4bc6f4ee3b8d808eeedd1d8fe845865ca139 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Thu, 2 Jul 2026 10:10:28 -0400 Subject: [PATCH 15/17] clean --- src/extension.ts | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 409bf06c..64874f79 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -207,8 +207,12 @@ export let checkingConnection = false; export let serverManagerApi: serverManager.ServerManagerAPI; +interface ResolvedConnSpec extends serverManager.IServerSpec { + auth: serverManager.ResolvedAuthorization; +} + /** Map of the intersystems.server connection specs we have resolved via the API to that extension */ -const resolvedConnSpecs = new Map(); +const resolvedConnSpecs = new Map(); /** * If servermanager extension is available, fetch the connection spec unless already cached. @@ -235,7 +239,7 @@ export async function resolveConnectionSpec( } } - let connSpec: serverManager.IServerSpec = await serverManagerApi.getServerSpec(serverName, scope); + let connSpec = await serverManagerApi.getServerSpec(serverName, scope); if (!connSpec && uri) { // Caller passed uri as a signal to process any docker-compose settings @@ -255,13 +259,15 @@ export async function resolveConnectionSpec( port: serverForUri.superserverPort, }, description: `Server for workspace folder '${serverName}'`, + auth: serverManagerApi.defaultAuth(), }; } } } if (connSpec) { - await resolvePassword(connSpec); + const accessToken = await resolvePassword(connSpec); + connSpec.auth.resolve({ accessToken }); resolvedConnSpecs.set(serverName, connSpec); } } @@ -287,11 +293,6 @@ async function resolvePassword( }); } if (session) { - // If original spec lacked username use the one obtained from the user by the authprovider (exact case) - serverSpec.auth.resolve({ - username: session.scopes[1], - accessToken: session.accessToken, - }); return session.accessToken; } } @@ -301,13 +302,13 @@ async function resolvePassword( export async function resolveUsernameAndPassword( serverName: string, oldSpec: serverManager.IServerSpec -): Promise<(serverManager.IServerSpec & { accessToken?: string }) | undefined> { +): Promise { const { auth: _auth, ...newSpec } = oldSpec; newSpec.name = serverName; const auth = _auth?.clone(); const accessToken = await resolvePassword({ ...newSpec, auth }, true); - if (auth.resolve({ accessToken })) { + if (auth?.resolve({ accessToken })) { // Update the connection spec resolvedConnSpecs.set(serverName, { ...oldSpec, @@ -488,7 +489,7 @@ export async function checkConnection( const newSpec = await resolveUsernameAndPassword(api.config.serverName, oldSpec); if (newSpec) { // We were able to resolve credentials, so try again - await workspaceState.update(wsKey + ":password", newSpec.accessToken); + await workspaceState.update(wsKey + ":password", newSpec.auth?.accessToken); api = new AtelierAPI(apiTarget, false); await api .serverInfo(true, serverInfoTimeout) From 3b74eba1dcf19e98f998fa4f17bd1ee4cd134a89 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Thu, 2 Jul 2026 10:15:18 -0400 Subject: [PATCH 16/17] clean --- src/extension.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 64874f79..d893299b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -267,8 +267,9 @@ export async function resolveConnectionSpec( if (connSpec) { const accessToken = await resolvePassword(connSpec); - connSpec.auth.resolve({ accessToken }); - resolvedConnSpecs.set(serverName, connSpec); + if (connSpec.auth.resolve({ accessToken })) { + resolvedConnSpecs.set(serverName, connSpec); + } } } @@ -302,7 +303,7 @@ async function resolvePassword( export async function resolveUsernameAndPassword( serverName: string, oldSpec: serverManager.IServerSpec -): Promise { +): Promise { const { auth: _auth, ...newSpec } = oldSpec; newSpec.name = serverName; const auth = _auth?.clone(); @@ -319,7 +320,7 @@ export async function resolveUsernameAndPassword( } /** Accessor for the cache of resolved connection specs */ -export function getResolvedConnectionSpec(key: string, dflt: serverManager.IServerSpec): serverManager.IServerSpec { +export function getResolvedConnectionSpec(key: string, dflt: ResolvedConnSpec): ResolvedConnSpec { let spec = resolvedConnSpecs.get(key); if (spec) { return spec; From 672d683064f5b01d2a4e647430da3574d9775f9c Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Thu, 2 Jul 2026 10:35:10 -0400 Subject: [PATCH 17/17] move types to server manager --- src/extension.ts | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index d893299b..2e61ebc2 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -71,7 +71,7 @@ import { ObjectScriptRoutineSymbolProvider } from "./providers/ObjectScriptRouti import { ObjectScriptCodeLensProvider } from "./providers/ObjectScriptCodeLensProvider"; import { XmlContentProvider } from "./providers/XmlContentProvider"; -import { AtelierAPI, ConnectionSettings } from "./api"; +import { AtelierAPI } from "./api"; import { ObjectScriptDebugAdapterDescriptorFactory } from "./debug/debugAdapterFactory"; import { ObjectScriptConfigurationProvider } from "./debug/debugConfProvider"; import { ProjectsExplorerProvider } from "./explorer/projectsExplorer"; @@ -853,7 +853,7 @@ let macLangConf: vscode.Disposable; let incLangConf: vscode.Disposable; let intLangConf: vscode.Disposable; -export async function activate(context: vscode.ExtensionContext): Promise { +export async function activate(context: vscode.ExtensionContext): Promise { if (!packageJson.version.includes("-") || packageJson.version.includes("-beta.")) { // Don't send telemetry for development builds try { @@ -1948,24 +1948,9 @@ export async function activate(context: vscode.ExtensionContext): Promise { return extensionApi; } -type HttpsAndScheme = - | { - scheme: "https"; - https: true; - } - | { - scheme: "http"; - https?: false; - }; - -export type ServerForUri = Omit & - HttpsAndScheme & { - namespace: ConnectionSettings["ns"]; - }; - // This function is exported as one of our API functions but is also used internally // for example to implement the async variant capable of resolving docker port number. -function serverForUri(uri: vscode.Uri): ServerForUri { +function serverForUri(uri: vscode.Uri): serverManager.ServerForUri { const { apiTarget, configName } = connectionTarget(uri); const configNameLower = configName.toLowerCase(); const api = new AtelierAPI(apiTarget); @@ -2009,7 +1994,7 @@ function serverForUri(uri: vscode.Uri): ServerForUri { // An async variant capable of resolving docker port number. // It is exported as one of our API functions but is also used internally. -async function asyncServerForUri(uri: vscode.Uri): Promise { +async function asyncServerForUri(uri: vscode.Uri): Promise { const server = serverForUri(uri); if (!server.port) { let { apiTarget } = connectionTarget(uri);