mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-05 01:10:21 +00:00
fix(config): redact Nostr privateKey in config views (#58177)
* wip(config): preserve nostr redaction progress * fix(config): add private key redaction fallback * fix(config): align nostr privateKey secret input handling * fix(config): require resolved nostr private keys
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
} from "../../../test/helpers/plugins/setup-wizard.js";
|
||||
import type { OpenClawConfig } from "../runtime-api.js";
|
||||
import { nostrPlugin } from "./channel.js";
|
||||
import { nostrSetupWizard } from "./setup-surface.js";
|
||||
import {
|
||||
TEST_HEX_PRIVATE_KEY,
|
||||
TEST_SETUP_RELAY_URLS,
|
||||
@@ -225,6 +226,21 @@ describe("nostr account helpers", () => {
|
||||
const cfg = createConfiguredNostrCfg({ defaultAccount: "work" });
|
||||
expect(listNostrAccountIds(cfg)).toEqual(["work"]);
|
||||
});
|
||||
|
||||
it("does not treat unresolved SecretRef privateKey as configured", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
nostr: {
|
||||
privateKey: {
|
||||
source: "env",
|
||||
provider: "default",
|
||||
id: "NOSTR_PRIVATE_KEY",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(listNostrAccountIds(cfg)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveDefaultNostrAccountId", () => {
|
||||
@@ -313,6 +329,27 @@ describe("nostr account helpers", () => {
|
||||
expect(account.publicKey).toBe("");
|
||||
});
|
||||
|
||||
it("does not treat unresolved SecretRef privateKey as configured", () => {
|
||||
const secretRef = {
|
||||
source: "env" as const,
|
||||
provider: "default",
|
||||
id: "NOSTR_PRIVATE_KEY",
|
||||
};
|
||||
const cfg = {
|
||||
channels: {
|
||||
nostr: {
|
||||
privateKey: secretRef,
|
||||
},
|
||||
},
|
||||
};
|
||||
const account = resolveNostrAccount({ cfg });
|
||||
|
||||
expect(account.configured).toBe(false);
|
||||
expect(account.privateKey).toBe("");
|
||||
expect(account.publicKey).toBe("");
|
||||
expect(account.config.privateKey).toEqual(secretRef);
|
||||
});
|
||||
|
||||
it("preserves all config options", () => {
|
||||
const cfg = createConfiguredNostrCfg({
|
||||
name: "Bot",
|
||||
@@ -333,4 +370,32 @@ describe("nostr account helpers", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("setup wizard", () => {
|
||||
it("keeps unresolved SecretRef privateKey visible without marking the account configured", () => {
|
||||
const secretRef = {
|
||||
source: "env" as const,
|
||||
provider: "default",
|
||||
id: "NOSTR_PRIVATE_KEY",
|
||||
};
|
||||
const cfg = {
|
||||
channels: {
|
||||
nostr: {
|
||||
privateKey: secretRef,
|
||||
},
|
||||
},
|
||||
};
|
||||
const credential = nostrSetupWizard.credentials?.[0];
|
||||
if (!credential?.inspect) {
|
||||
throw new Error("nostr setup credential inspect missing");
|
||||
}
|
||||
|
||||
expect(credential.inspect({ cfg, accountId: "default" })).toEqual({
|
||||
accountConfigured: false,
|
||||
hasConfiguredValue: true,
|
||||
resolvedValue: undefined,
|
||||
envValue: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
DmPolicySchema,
|
||||
MarkdownConfigSchema,
|
||||
} from "openclaw/plugin-sdk/channel-config-primitives";
|
||||
import { buildSecretInputSchema } from "openclaw/plugin-sdk/secret-input";
|
||||
import { z } from "openclaw/plugin-sdk/zod";
|
||||
|
||||
/**
|
||||
@@ -73,7 +74,7 @@ export const NostrConfigSchema = z.object({
|
||||
markdown: MarkdownConfigSchema,
|
||||
|
||||
/** Private key in hex or nsec bech32 format */
|
||||
privateKey: z.string().optional(),
|
||||
privateKey: buildSecretInputSchema().optional(),
|
||||
|
||||
/** WebSocket relay URLs to connect to */
|
||||
relays: z.array(z.string()).optional(),
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import type { ChannelSetupAdapter } from "openclaw/plugin-sdk/channel-setup";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/routing";
|
||||
import {
|
||||
hasConfiguredSecretInput,
|
||||
normalizeSecretInputString,
|
||||
} from "openclaw/plugin-sdk/secret-input";
|
||||
import {
|
||||
createTopLevelChannelParsedAllowFromPrompt,
|
||||
createTopLevelChannelDmPolicy,
|
||||
@@ -165,7 +169,7 @@ export const nostrSetupWizard: ChannelSetupWizard = {
|
||||
isAvailable: ({ cfg, accountId }) =>
|
||||
accountId === DEFAULT_ACCOUNT_ID &&
|
||||
Boolean(process.env.NOSTR_PRIVATE_KEY?.trim()) &&
|
||||
!resolveNostrAccount({ cfg, accountId }).config.privateKey?.trim(),
|
||||
!hasConfiguredSecretInput(resolveNostrAccount({ cfg, accountId }).config.privateKey),
|
||||
apply: async ({ cfg }) =>
|
||||
patchTopLevelChannelConfigSection({
|
||||
cfg,
|
||||
@@ -191,8 +195,8 @@ export const nostrSetupWizard: ChannelSetupWizard = {
|
||||
const account = resolveNostrAccount({ cfg, accountId });
|
||||
return {
|
||||
accountConfigured: account.configured,
|
||||
hasConfiguredValue: Boolean(account.config.privateKey?.trim()),
|
||||
resolvedValue: account.config.privateKey?.trim(),
|
||||
hasConfiguredValue: hasConfiguredSecretInput(account.config.privateKey),
|
||||
resolvedValue: normalizeSecretInputString(account.config.privateKey),
|
||||
envValue: process.env.NOSTR_PRIVATE_KEY?.trim(),
|
||||
};
|
||||
},
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
listCombinedAccountIds,
|
||||
resolveListedDefaultAccountId,
|
||||
} from "openclaw/plugin-sdk/account-resolution";
|
||||
import { normalizeSecretInputString, type SecretInput } from "openclaw/plugin-sdk/secret-input";
|
||||
import type { OpenClawConfig } from "../api.js";
|
||||
import type { NostrProfile } from "./config-schema.js";
|
||||
import { DEFAULT_RELAYS } from "./default-relays.js";
|
||||
@@ -16,7 +17,7 @@ export interface NostrAccountConfig {
|
||||
enabled?: boolean;
|
||||
name?: string;
|
||||
defaultAccount?: string;
|
||||
privateKey?: string;
|
||||
privateKey?: SecretInput;
|
||||
relays?: string[];
|
||||
dmPolicy?: "pairing" | "allowlist" | "open" | "disabled";
|
||||
allowFrom?: Array<string | number>;
|
||||
@@ -49,9 +50,10 @@ export function listNostrAccountIds(cfg: OpenClawConfig): string[] {
|
||||
const nostrCfg = (cfg.channels as Record<string, unknown> | undefined)?.nostr as
|
||||
| NostrAccountConfig
|
||||
| undefined;
|
||||
const privateKey = normalizeSecretInputString(nostrCfg?.privateKey);
|
||||
return listCombinedAccountIds({
|
||||
configuredAccountIds: [],
|
||||
implicitAccountId: nostrCfg?.privateKey
|
||||
implicitAccountId: privateKey
|
||||
? (resolveConfiguredDefaultNostrAccountId(cfg) ?? DEFAULT_ACCOUNT_ID)
|
||||
: undefined,
|
||||
});
|
||||
@@ -80,11 +82,11 @@ export function resolveNostrAccount(opts: {
|
||||
| undefined;
|
||||
|
||||
const baseEnabled = nostrCfg?.enabled !== false;
|
||||
const privateKey = nostrCfg?.privateKey ?? "";
|
||||
const configured = Boolean(privateKey.trim());
|
||||
const privateKey = normalizeSecretInputString(nostrCfg?.privateKey) ?? "";
|
||||
const configured = Boolean(privateKey);
|
||||
|
||||
let publicKey = "";
|
||||
if (configured) {
|
||||
if (privateKey) {
|
||||
try {
|
||||
publicKey = getPublicKeyFromPrivate(privateKey);
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user