ai-chatbot/lib/openclaw-chat/task-secret.ts

68 lines
1.9 KiB
TypeScript

import "server-only";
import {
createCipheriv,
createDecipheriv,
createHash,
randomBytes,
} from "node:crypto";
const ENCRYPTED_SECRET_PREFIX = "enc:v1";
function getEncryptionKey() {
const secret = String(
process.env.OPENCLAW_TASK_SECRET ||
process.env.AUTH_SECRET ||
process.env.NEXTAUTH_SECRET ||
""
).trim();
if (!secret) {
throw new Error(
"OPENCLAW_TASK_SECRET or AUTH_SECRET is required for durable tasks"
);
}
return createHash("sha256").update(secret, "utf8").digest();
}
export function encryptOpenClawTaskSecret(value: string) {
const normalized = value.trim();
if (!normalized || normalized.startsWith(`${ENCRYPTED_SECRET_PREFIX}:`)) {
return normalized;
}
const iv = randomBytes(12);
const cipher = createCipheriv("aes-256-gcm", getEncryptionKey(), iv);
const encrypted = Buffer.concat([
cipher.update(normalized, "utf8"),
cipher.final(),
]);
const authTag = cipher.getAuthTag();
return [
ENCRYPTED_SECRET_PREFIX,
iv.toString("base64url"),
authTag.toString("base64url"),
encrypted.toString("base64url"),
].join(":");
}
export function decryptOpenClawTaskSecret(value: string) {
const normalized = value.trim();
if (!normalized.startsWith(`${ENCRYPTED_SECRET_PREFIX}:`)) {
// Backward compatibility for tasks persisted before encryption was added.
return normalized;
}
const [, , ivValue, authTagValue, encryptedValue] = normalized.split(":");
if (!ivValue || !authTagValue || !encryptedValue) {
throw new Error("Invalid encrypted OpenClaw task secret");
}
const decipher = createDecipheriv(
"aes-256-gcm",
getEncryptionKey(),
Buffer.from(ivValue, "base64url")
);
decipher.setAuthTag(Buffer.from(authTagValue, "base64url"));
return Buffer.concat([
decipher.update(Buffer.from(encryptedValue, "base64url")),
decipher.final(),
]).toString("utf8");
}