mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-19 17:31:35 +00:00
59 lines
2.0 KiB
TypeScript
59 lines
2.0 KiB
TypeScript
// Operator approval runtime token.
|
|
// Uses an existing shared socket token when available, with a process-local fallback.
|
|
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
import { loadExecApprovals } from "../infra/exec-approvals.js";
|
|
|
|
const APPROVAL_RUNTIME_TOKEN_CONTEXT = "openclaw:gateway-approval-runtime-token:v1";
|
|
|
|
let fallbackApprovalRuntimeToken: string | null = null;
|
|
|
|
function deriveApprovalRuntimeToken(socketToken: string): string {
|
|
return createHmac("sha256", socketToken)
|
|
.update(APPROVAL_RUNTIME_TOKEN_CONTEXT)
|
|
.digest("base64url");
|
|
}
|
|
|
|
function readSharedApprovalRuntimeToken(): string | null {
|
|
const token = loadExecApprovals().socket?.token?.trim();
|
|
return token ? deriveApprovalRuntimeToken(token) : null;
|
|
}
|
|
|
|
function tokenMatches(token: string, expected: string | null | undefined): boolean {
|
|
if (!expected) {
|
|
return false;
|
|
}
|
|
const tokenBytes = Buffer.from(token);
|
|
const expectedBytes = Buffer.from(expected);
|
|
// timingSafeEqual requires equal lengths; keep length rejection explicit instead of catching.
|
|
return tokenBytes.length === expectedBytes.length && timingSafeEqual(tokenBytes, expectedBytes);
|
|
}
|
|
|
|
/**
|
|
* Returns the token used to authorize local operator-approval clients.
|
|
*/
|
|
export function getOperatorApprovalRuntimeToken(): string {
|
|
const sharedToken = readSharedApprovalRuntimeToken();
|
|
if (sharedToken) {
|
|
return sharedToken;
|
|
}
|
|
fallbackApprovalRuntimeToken ??= randomBytes(32).toString("base64url");
|
|
return fallbackApprovalRuntimeToken;
|
|
}
|
|
|
|
/**
|
|
* Validates a presented loopback approval token without accepting empty or partial matches.
|
|
*/
|
|
export function isOperatorApprovalRuntimeToken(value: string | null | undefined): boolean {
|
|
const token = value?.trim();
|
|
if (!token) {
|
|
return false;
|
|
}
|
|
const sharedToken = readSharedApprovalRuntimeToken();
|
|
if (tokenMatches(token, sharedToken)) {
|
|
return true;
|
|
}
|
|
const fallbackToken =
|
|
fallbackApprovalRuntimeToken ?? (sharedToken ? null : getOperatorApprovalRuntimeToken());
|
|
return tokenMatches(token, fallbackToken);
|
|
}
|