feat(zookeeper): support cluster authentication setup
This commit is contained in:
parent
50cd9a8f95
commit
db791b5aed
|
|
@ -35,6 +35,7 @@ import { parseConnectionDeepLink, type ConnectionDeepLinkDraft } from "@/lib/con
|
|||
import { connectionUrlPlaceholder as getUrlPlaceholder } from "@/lib/connection/connectionPresentation";
|
||||
import { h2ConnectionModeForConfig, h2FileJdbcUrlWithPath, h2FilePathFromJdbcUrl, isH2SplitJdbcUrl, type H2ConnectionMode } from "@/lib/database/h2Connection";
|
||||
import { firstZooKeeperEndpoint, normalizeZooKeeperConnectString } from "@/lib/zookeeper/zookeeperConnection";
|
||||
import { setZooKeeperAuthScheme, zooKeeperAuthScheme as resolveZooKeeperAuthScheme, type ZooKeeperAuthScheme } from "@/lib/zookeeper/zookeeperConnectionOptions";
|
||||
import { isLocalFileTypeDb } from "@/lib/connection/connectionFile";
|
||||
import { MQ_PINNED_VERSION_OPTIONS, pinnedVersionToSelection, selectionToPinnedVersion } from "@/lib/mq/mqPinnedVersionOptions";
|
||||
import { mongodbAuthFailureHint, mongoUrlParam, mongoUrlParamIsTrue, normalizeMongoTlsFormState, setMongoUrlParam, setMongoUrlParamBoolean } from "@/lib/mongo/mongoConnectionOptions";
|
||||
|
|
@ -2662,6 +2663,13 @@ const zookeeperConnectString = computed({
|
|||
form.value.connection_string = normalizeZooKeeperConnectString(value);
|
||||
},
|
||||
});
|
||||
const zookeeperAuthScheme = computed<ZooKeeperAuthScheme>({
|
||||
get: () => resolveZooKeeperAuthScheme(form.value.url_params),
|
||||
set: (scheme) => {
|
||||
form.value.url_params = setZooKeeperAuthScheme(form.value.url_params, scheme);
|
||||
resetTestState();
|
||||
},
|
||||
});
|
||||
const canUseTransportLayers = computed(() => form.value.db_type !== "sqlite" && form.value.db_type !== "access" && !isCloudflareD1Connection(form.value) && !isH2FileMode.value && !(form.value.db_type === "oracle" && form.value.oracle_connection_type === "tns"));
|
||||
const shouldShowAgentDriverInstallHint = computed(() => showAgentDriverInstallHint(form.value.db_type, agentDrivers.value, form.value.driver_profile));
|
||||
const h2DriverMissing = computed(() => form.value.db_type === "h2" && isH2FileMode.value && agentDrivers.value.find((d) => d.db_type === "h2")?.installed !== true);
|
||||
|
|
@ -4877,6 +4885,10 @@ function openExternalUrl(url: string) {
|
|||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="form.db_type === 'zookeeper'" class="grid grid-cols-4 items-start gap-4">
|
||||
<span />
|
||||
<p class="col-span-3 m-0 text-xs leading-5 text-muted-foreground">{{ t("connection.zookeeperClusterInputHint") }}</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label :class="connectionLabelClass">{{ t("connection.name") }}</Label>
|
||||
|
|
@ -5697,6 +5709,18 @@ function openExternalUrl(url: string) {
|
|||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label :class="connectionLabelClass">{{ t("connection.zookeeperAuthMethod") }}</Label>
|
||||
<Select v-model="zookeeperAuthScheme">
|
||||
<SelectTrigger class="col-span-3 h-9">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="digest">{{ t("connection.zookeeperAuthDigest") }}</SelectItem>
|
||||
<SelectItem value="sasl_digest">{{ t("connection.zookeeperAuthSaslDigest") }}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label :class="connectionLabelClass">{{ t("connection.user") }}</Label>
|
||||
<Input v-model="form.username" class="col-span-3" />
|
||||
|
|
|
|||
|
|
@ -348,7 +348,11 @@ export default {
|
|||
etcdClientKeyBrowse: "Choose client private key",
|
||||
etcdClientCertPairRequired: "Client certificate and private key must be provided together.",
|
||||
zookeeperConnectString: "Connect String",
|
||||
zookeeperConnectStringHint: "Comma or line separated host:port entries. Leave blank to use the host and port above.",
|
||||
zookeeperConnectStringHint: "Comma or line separated host:port entries, with an optional shared /chroot. Leave blank to use the host and port above.",
|
||||
zookeeperClusterInputHint: "For a ZooKeeper ensemble, enter all nodes either in the URL (for example zookeeper://zk-1:2181,zk-2:2181/app) or in Connect String below.",
|
||||
zookeeperAuthMethod: "Authentication",
|
||||
zookeeperAuthDigest: "Digest (default)",
|
||||
zookeeperAuthSaslDigest: "SASL DIGEST-MD5",
|
||||
zookeeperCreateModePersistent: "Persistent",
|
||||
zookeeperCreateModeEphemeral: "Ephemeral",
|
||||
zookeeperCreateModePersistentSequential: "Persistent Sequential",
|
||||
|
|
|
|||
|
|
@ -703,6 +703,10 @@ export default withEnglishFallback({
|
|||
damengJvmOptionsPlaceholder: "-Djava.net.preferIPv4Stack=true",
|
||||
damengJvmOptionsHint: "Una propiedad -Dkey o -Dkey=value por línea. Los valores con espacios no necesitan comillas del shell.",
|
||||
damengJvmOptionsInvalid: "La línea {line} debe ser una propiedad -Dkey o -Dkey=value sin comillas del shell.",
|
||||
zookeeperClusterInputHint: "El clúster ZooKeeper puede completarse con todos los nodos en la URL superior (por ejemplo, zookeeper://zk-1:2181,zk-2:2181/app), o en el Connect String a continuación.",
|
||||
zookeeperAuthMethod: "Método de autenticación",
|
||||
zookeeperAuthDigest: "Digest (predeterminado)",
|
||||
zookeeperAuthSaslDigest: "SASL DIGEST-MD5",
|
||||
},
|
||||
editor: {
|
||||
duckdbDraining: "La consulta anterior de DuckDB aún se está deteniendo. Vuelva a intentarlo en breve.",
|
||||
|
|
|
|||
|
|
@ -701,6 +701,10 @@ export default withEnglishFallback({
|
|||
damengJvmOptionsPlaceholder: "-Djava.net.preferIPv4Stack=true",
|
||||
damengJvmOptionsHint: "Una proprietà -Dkey o -Dkey=value per riga. I valori con spazi non richiedono virgolette della shell.",
|
||||
damengJvmOptionsInvalid: "La riga {line} deve essere una proprietà -Dkey o -Dkey=value senza virgolette della shell.",
|
||||
zookeeperClusterInputHint: "Il cluster ZooKeeper può essere specificato inserendo tutti i nodi nell'URL superiore (ad esempio zookeeper://zk-1:2181,zk-2:2181/app), oppure nella stringa di connessione sottostante.",
|
||||
zookeeperAuthMethod: "Metodo di autenticazione",
|
||||
zookeeperAuthDigest: "Digest (predefinito)",
|
||||
zookeeperAuthSaslDigest: "SASL DIGEST-MD5",
|
||||
},
|
||||
editor: {
|
||||
duckdbDraining: "La query DuckDB precedente è ancora in fase di arresto. Riprova a breve.",
|
||||
|
|
|
|||
|
|
@ -722,6 +722,10 @@ export default withEnglishFallback({
|
|||
httpTunnelInvalidOrder: "{hop}: HTTP トンネルは最初のトンネル/プロキシレイヤーに配置する必要があります",
|
||||
httpTunnelInvalidUrl: "{hop}: トンネルスクリプト URL は必須です",
|
||||
httpTunnelInvalidTimeout: "{hop}: HTTP トンネルのタイムアウト時間は 1 から 300 秒の間で設定してください",
|
||||
zookeeperClusterInputHint: "ZooKeeperクラスタは、上部のURLにすべてのノードを記入(例:zookeeper://zk-1:2181,zk-2:2181/app)、または下部のConnect Stringに記入できます。",
|
||||
zookeeperAuthMethod: "認証方式",
|
||||
zookeeperAuthDigest: "Digest(デフォルト)",
|
||||
zookeeperAuthSaslDigest: "SASL DIGEST-MD5",
|
||||
},
|
||||
editor: {
|
||||
duckdbDraining: "前回の DuckDB クエリはまだ停止処理中です。しばらくしてから再試行してください。",
|
||||
|
|
|
|||
|
|
@ -702,6 +702,10 @@ export default withEnglishFallback({
|
|||
damengJvmOptionsPlaceholder: "-Djava.net.preferIPv4Stack=true",
|
||||
damengJvmOptionsHint: "Uma propriedade -Dkey ou -Dkey=value por linha. Valores com espaços não precisam de aspas do shell.",
|
||||
damengJvmOptionsInvalid: "A linha {line} deve ser uma propriedade -Dkey ou -Dkey=value sem aspas do shell.",
|
||||
zookeeperClusterInputHint: "O cluster ZooKeeper pode ser preenchido com todos os nós na URL acima (por exemplo, zookeeper://zk-1:2181,zk-2:2181/app), ou também na Connect String abaixo.",
|
||||
zookeeperAuthMethod: "Método de autenticação",
|
||||
zookeeperAuthDigest: "Digest (padrão)",
|
||||
zookeeperAuthSaslDigest: "SASL DIGEST-MD5",
|
||||
},
|
||||
editor: {
|
||||
duckdbDraining: "A consulta anterior do DuckDB ainda está sendo interrompida. Tente novamente em breve.",
|
||||
|
|
|
|||
|
|
@ -350,7 +350,11 @@ export default withEnglishFallback({
|
|||
etcdClientKeyBrowse: "选择客户端私钥",
|
||||
etcdClientCertPairRequired: "客户端证书和私钥必须一起填写。",
|
||||
zookeeperConnectString: "Connect String",
|
||||
zookeeperConnectStringHint: "可用逗号或换行分隔 host:port;留空时使用上面的 host 和端口。",
|
||||
zookeeperConnectStringHint: "可用逗号或换行分隔 host:port,并可在末尾填写整个集群共用的 /chroot;留空时使用上面的 host 和端口。",
|
||||
zookeeperClusterInputHint: "ZooKeeper 集群可在上方 URL 中填写全部节点(例如 zookeeper://zk-1:2181,zk-2:2181/app),也可在下方 Connect String 中填写。",
|
||||
zookeeperAuthMethod: "认证方式",
|
||||
zookeeperAuthDigest: "Digest(默认)",
|
||||
zookeeperAuthSaslDigest: "SASL DIGEST-MD5",
|
||||
zookeeperCreateModePersistent: "持久节点",
|
||||
zookeeperCreateModeEphemeral: "临时节点",
|
||||
zookeeperCreateModePersistentSequential: "持久顺序节点",
|
||||
|
|
|
|||
|
|
@ -701,6 +701,10 @@ export default withEnglishFallback({
|
|||
damengJvmOptionsPlaceholder: "-Djava.net.preferIPv4Stack=true",
|
||||
damengJvmOptionsHint: "每行一個 -Dkey 或 -Dkey=value;值包含空格時也不要使用 shell 引號。",
|
||||
damengJvmOptionsInvalid: "第 {line} 行必須是未使用 shell 引號的 -Dkey 或 -Dkey=value 系統屬性。",
|
||||
zookeeperClusterInputHint: "ZooKeeper 叢集可在上方 URL 中填寫全部節點(例如 zookeeper://zk-1:2181,zk-2:2181/app),也可在下方 Connect String 中填寫。",
|
||||
zookeeperAuthMethod: "認證方式",
|
||||
zookeeperAuthDigest: "Digest(預設)",
|
||||
zookeeperAuthSaslDigest: "SASL DIGEST-MD5",
|
||||
},
|
||||
editor: {
|
||||
duckdbDraining: "上一筆 DuckDB 查詢仍在停止,請稍後重試。",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,56 @@ import { describe, expect, it } from "vitest";
|
|||
import { parseConnectionUrl } from "@/lib/connection/connectionUrl";
|
||||
|
||||
describe("ZooKeeper connection URLs", () => {
|
||||
it("parses a two-node ensemble and uses the first endpoint for host and port", () => {
|
||||
const parsed = parseConnectionUrl("zookeeper://zk-1:2181,zk-2:2182");
|
||||
|
||||
expect(parsed.host).toBe("zk-1");
|
||||
expect(parsed.port).toBe(2181);
|
||||
expect(parsed.connectionString).toBe("zk-1:2181,zk-2:2182");
|
||||
});
|
||||
|
||||
it("parses three nodes and preserves query parameters except name", () => {
|
||||
const parsed = parseConnectionUrl("zookeeper://zk-1:2181,zk-2:2182,zk-3:2183?session_timeout=30000&name=Production");
|
||||
|
||||
expect(parsed.name).toBe("Production");
|
||||
expect(parsed.urlParams).toBe("session_timeout=30000");
|
||||
expect(parsed.connectionString).toBe("zk-1:2181,zk-2:2182,zk-3:2183");
|
||||
});
|
||||
|
||||
it("preserves a shared chroot separately from query parameters", () => {
|
||||
const parsed = parseConnectionUrl("zookeeper://zk-1:2181,zk-2:2181/services/app?connect_timeout=5000");
|
||||
|
||||
expect(parsed.connectionString).toBe("zk-1:2181,zk-2:2181/services/app");
|
||||
expect(parsed.urlParams).toBe("connect_timeout=5000");
|
||||
});
|
||||
|
||||
it("decodes credentials that apply to the ensemble", () => {
|
||||
const parsed = parseConnectionUrl("zookeeper://dbx%40ops:p%40ss@zk-1:2181,zk-2:2181/app");
|
||||
|
||||
expect(parsed.username).toBe("dbx@ops");
|
||||
expect(parsed.password).toBe("p@ss");
|
||||
expect(parsed.connectionString).toBe("zk-1:2181,zk-2:2181/app");
|
||||
});
|
||||
|
||||
it("supports bracketed IPv6 endpoints without confusing address colons with separators", () => {
|
||||
const parsed = parseConnectionUrl("zookeeper://[2001:db8::1]:2181,[2001:db8::2]:2281/app");
|
||||
|
||||
expect(parsed.host).toBe("2001:db8::1");
|
||||
expect(parsed.port).toBe(2181);
|
||||
expect(parsed.connectionString).toBe("[2001:db8::1]:2181,[2001:db8::2]:2281/app");
|
||||
});
|
||||
|
||||
it("rejects malformed or empty ensemble endpoints", () => {
|
||||
expect(() => parseConnectionUrl("zookeeper://zk-1:2181,,zk-2:2181")).toThrow("Invalid connection URL");
|
||||
expect(() => parseConnectionUrl("zookeeper://zk-1:not-a-port,zk-2:2181")).toThrow("Invalid connection URL");
|
||||
expect(() => parseConnectionUrl("zookeeper://2001:db8::1:2181,zk-2:2181")).toThrow("Invalid connection URL");
|
||||
});
|
||||
|
||||
it("preserves the host:port chroot path as a ZooKeeper connect string", () => {
|
||||
expect(parseConnectionUrl("zookeeper://zk-main:2181/app").connectionString).toBe("zk-main:2181/app");
|
||||
const parsed = parseConnectionUrl("zookeeper://zk-main:2181/app");
|
||||
|
||||
expect(parsed.host).toBe("zk-main");
|
||||
expect(parsed.port).toBe(2181);
|
||||
expect(parsed.connectionString).toBe("zk-main:2181/app");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const dialogSource = readFileSync(new URL("../../../components/connection/ConnectionDialog.vue", import.meta.url), "utf8");
|
||||
const enSource = readFileSync(new URL("../../../i18n/locales/en.ts", import.meta.url), "utf8");
|
||||
const zhCnSource = readFileSync(new URL("../../../i18n/locales/zh-CN.ts", import.meta.url), "utf8");
|
||||
const fallbackLocaleSources = ["es", "it", "ja", "ko", "pt-BR", "zh-TW"].map((locale) => readFileSync(new URL(`../../../i18n/locales/${locale}.ts`, import.meta.url), "utf8"));
|
||||
|
||||
describe("ZooKeeper connection dialog", () => {
|
||||
it("binds the existing Select component to the ZooKeeper auth_scheme helper", () => {
|
||||
expect(dialogSource).toContain("setZooKeeperAuthScheme");
|
||||
expect(dialogSource).toContain("resolveZooKeeperAuthScheme");
|
||||
expect(dialogSource).toContain("const zookeeperAuthScheme = computed<ZooKeeperAuthScheme>");
|
||||
expect(dialogSource).toContain('<Select v-model="zookeeperAuthScheme">');
|
||||
expect(dialogSource).toContain('<SelectItem value="digest">');
|
||||
expect(dialogSource).toContain('<SelectItem value="sasl_digest">');
|
||||
});
|
||||
|
||||
it("uses localized auth labels and tells users both cluster input locations", () => {
|
||||
for (const source of [enSource, zhCnSource]) {
|
||||
expect(source).toContain("zookeeperAuthMethod:");
|
||||
expect(source).toContain("zookeeperAuthDigest:");
|
||||
expect(source).toContain("zookeeperAuthSaslDigest:");
|
||||
expect(source).toContain("zookeeperClusterInputHint:");
|
||||
}
|
||||
for (const source of fallbackLocaleSources) {
|
||||
expect(source).toContain("withEnglishFallback");
|
||||
}
|
||||
expect(dialogSource).toContain('t("connection.zookeeperAuthMethod")');
|
||||
expect(dialogSource).toContain('t("connection.zookeeperClusterInputHint")');
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { setZooKeeperAuthScheme, zooKeeperAuthScheme } from "@/lib/zookeeper/zookeeperConnectionOptions";
|
||||
|
||||
describe("ZooKeeper connection options", () => {
|
||||
it("defaults to digest when auth_scheme is absent", () => {
|
||||
expect(zooKeeperAuthScheme("connect_timeout=10")).toBe("digest");
|
||||
});
|
||||
|
||||
it("reads the supported SASL DIGEST-MD5 scheme", () => {
|
||||
expect(zooKeeperAuthScheme("connect_timeout=10&auth_scheme=sasl_digest")).toBe("sasl_digest");
|
||||
});
|
||||
|
||||
it("writes sasl_digest without losing unrelated URL parameters", () => {
|
||||
expect(setZooKeeperAuthScheme("connect_timeout=10&session_timeout=20", "sasl_digest")).toBe("connect_timeout=10&session_timeout=20&auth_scheme=sasl_digest");
|
||||
});
|
||||
|
||||
it("switches back to digest by removing only auth_scheme", () => {
|
||||
expect(setZooKeeperAuthScheme("connect_timeout=10&auth_scheme=sasl_digest&session_timeout=20", "digest")).toBe("connect_timeout=10&session_timeout=20");
|
||||
});
|
||||
|
||||
it("reads legacy semicolon-separated parameters and case-insensitive keys", () => {
|
||||
expect(zooKeeperAuthScheme("connect_timeout=10;AUTH_SCHEME=sasl_digest;session_timeout=20")).toBe("sasl_digest");
|
||||
});
|
||||
|
||||
it("removes every auth_scheme alias while preserving unrelated legacy parameters", () => {
|
||||
expect(setZooKeeperAuthScheme("connect_timeout=10;AUTH_SCHEME=digest&auth_scheme=sasl_digest;session_timeout=20", "digest")).toBe("connect_timeout=10&session_timeout=20");
|
||||
});
|
||||
});
|
||||
|
|
@ -171,6 +171,56 @@ function databaseFromPath(pathname: string): string | undefined {
|
|||
return decodeUrlPart(value.split("/")[0]);
|
||||
}
|
||||
|
||||
function parseZooKeeperUrl(source: string): ParsedConnectionUrl | null {
|
||||
const match = source.match(/^zookeeper:\/\/([^/?#]+)(\/[^?#]*)?(\?[^#]*)?$/i);
|
||||
if (!match) return null;
|
||||
|
||||
const profile = SCHEME_PROFILES.zookeeper;
|
||||
const authority = match[1];
|
||||
const userInfoEnd = authority.lastIndexOf("@");
|
||||
const userInfo = userInfoEnd >= 0 ? authority.slice(0, userInfoEnd) : "";
|
||||
const endpointList = userInfoEnd >= 0 ? authority.slice(userInfoEnd + 1) : authority;
|
||||
const [rawUsername, ...rawPasswordParts] = userInfo.split(":");
|
||||
const username = userInfo ? decodeUrlPart(rawUsername) : "";
|
||||
const password = userInfo ? decodeUrlPart(rawPasswordParts.join(":")) : "";
|
||||
const endpoints = endpointList.split(",").map((endpoint) => endpoint.trim());
|
||||
if (endpoints.some((endpoint) => !endpoint)) throw new Error("Invalid connection URL");
|
||||
|
||||
const normalizedEndpoints = endpoints.map((endpoint) => {
|
||||
let endpointUrl: URL;
|
||||
try {
|
||||
endpointUrl = new URL(`zookeeper://${endpoint}`);
|
||||
} catch {
|
||||
throw new Error("Invalid connection URL");
|
||||
}
|
||||
if (endpointUrl.username || endpointUrl.password || (endpointUrl.pathname && endpointUrl.pathname !== "/") || endpointUrl.search || endpointUrl.hash) {
|
||||
throw new Error("Invalid connection URL");
|
||||
}
|
||||
const rawHost = endpointUrl.hostname.replace(/^\[(.*)]$/, "$1");
|
||||
const host = rawHost.includes(":") ? `[${rawHost}]` : rawHost;
|
||||
const port = endpointUrl.port ? Number(endpointUrl.port) : profile.defaultPort;
|
||||
return { host: rawHost, port, connectString: `${host}:${port}` };
|
||||
});
|
||||
const chroot = match[2] && match[2] !== "/" ? match[2] : "";
|
||||
const urlParams = (match[3] || "").replace(/^\?/, "");
|
||||
const name = queryParamValue(urlParams, "name")?.trim();
|
||||
|
||||
return {
|
||||
...(name ? { name } : {}),
|
||||
dbType: profile.type,
|
||||
driverProfile: profile.profile,
|
||||
driverLabel: profile.label,
|
||||
host: normalizedEndpoints[0].host,
|
||||
port: normalizedEndpoints[0].port,
|
||||
username,
|
||||
password,
|
||||
database: undefined,
|
||||
urlParams: stripConnectionNameParam(urlParams),
|
||||
ssl: false,
|
||||
connectionString: `${normalizedEndpoints.map((endpoint) => endpoint.connectString).join(",")}${chroot}`,
|
||||
};
|
||||
}
|
||||
|
||||
function queryParamValue(params: string, key: string): string | undefined {
|
||||
for (const part of params.split(/[&;]/)) {
|
||||
if (!part) continue;
|
||||
|
|
@ -524,6 +574,9 @@ export function parseConnectionUrl(value: string, preferredProfile?: string): Pa
|
|||
const mongoResult = parseMongoUrl(source);
|
||||
if (mongoResult) return mongoResult;
|
||||
|
||||
const zooKeeperResult = parseZooKeeperUrl(source);
|
||||
if (zooKeeperResult) return zooKeeperResult;
|
||||
|
||||
let parsed: URL;
|
||||
try {
|
||||
parsed = new URL(source);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
export type ZooKeeperAuthScheme = "digest" | "sasl_digest";
|
||||
|
||||
const AUTH_SCHEME_PARAM = "auth_scheme";
|
||||
|
||||
function parseParams(params?: string): URLSearchParams {
|
||||
return new URLSearchParams((params || "").trim().replace(/^\?/, "").replace(/;/g, "&"));
|
||||
}
|
||||
|
||||
function isAuthSchemeParam(key: string): boolean {
|
||||
return key.trim().toLowerCase() === AUTH_SCHEME_PARAM;
|
||||
}
|
||||
|
||||
export function zooKeeperAuthScheme(params?: string): ZooKeeperAuthScheme {
|
||||
const configured = Array.from(parseParams(params).entries()).find(([key]) => isAuthSchemeParam(key))?.[1];
|
||||
return configured?.toLowerCase() === "sasl_digest" ? "sasl_digest" : "digest";
|
||||
}
|
||||
|
||||
export function setZooKeeperAuthScheme(params: string | undefined, scheme: ZooKeeperAuthScheme): string {
|
||||
const parsed = parseParams(params);
|
||||
for (const key of Array.from(parsed.keys())) {
|
||||
if (isAuthSchemeParam(key)) parsed.delete(key);
|
||||
}
|
||||
if (scheme === "sasl_digest") parsed.set(AUTH_SCHEME_PARAM, scheme);
|
||||
return parsed.toString();
|
||||
}
|
||||
Loading…
Reference in New Issue