import { httpRouter } from "convex/server"; import { internal } from "./_generated/api"; import type { Id } from "./_generated/dataModel"; import { httpAction } from "./_generated/server"; import { normalizeCredentialPayloadForKind } from "./payload-validation";
type ActorRole = "ci" | "maintainer";
class BrokerHttpError extends Error {
code: string;
httpStatus: number;
if (!maintainerSecret && !ciSecret) { thrownew BrokerHttpError( 500, "SERVER_MISCONFIGURED", "No Convex broker role secrets are configured on this deployment.",
);
} if (maintainerSecret && token === maintainerSecret) { return"maintainer";
} if (ciSecret && token === ciSecret) { return"ci";
} thrownew BrokerHttpError(401, "AUTH_INVALID", "Credential broker secret is invalid.");
}
function assertMaintainerAdminAuth(token: string | null) { if (!token) { thrownew BrokerHttpError( 401, "AUTH_REQUIRED", "Missing Authorization: Bearer <secret> header.",
);
} const maintainerSecret = process.env.OPENCLAW_QA_CONVEX_SECRET_MAINTAINER?.trim(); if (!maintainerSecret) { thrownew BrokerHttpError( 500, "SERVER_MISCONFIGURED", "Admin endpoints require OPENCLAW_QA_CONVEX_SECRET_MAINTAINER on this deployment.",
);
} if (token === maintainerSecret) { return;
} const ciSecret = process.env.OPENCLAW_QA_CONVEX_SECRET_CI?.trim(); if (ciSecret && token === ciSecret) { thrownew BrokerHttpError( 403, "AUTH_ROLE_MISMATCH", "Admin endpoints require maintainer credentials.",
);
} thrownew BrokerHttpError(401, "AUTH_INVALID", "Credential broker secret is invalid.");
}
function asObject(value: unknown) { if (!value || typeof value !== "object" || Array.isArray(value)) { returnnull;
} return value as Record<string, unknown>;
}
async function parseJsonObject(request: Request) {
let parsed: unknown; try {
parsed = await request.json();
} catch { thrownew BrokerHttpError(400, "INVALID_JSON", "Request body must be valid JSON.");
} const body = asObject(parsed); if (!body) { thrownew BrokerHttpError(400, "INVALID_BODY", "Request body must be a JSON object.");
} return body;
}
function requireString(body: Record<string, unknown>, key: string) { const raw = body[key]; if (typeof raw !== "string") { thrownew BrokerHttpError(400, "INVALID_BODY", `Expected "${key}" to be a string.`);
} const value = raw.trim(); if (!value) { thrownew BrokerHttpError(400, "INVALID_BODY", `Expected "${key}" to be non-empty.`);
} return value;
}
function optionalString(body: Record<string, unknown>, key: string) { if (!(key in body) || body[key] === undefined || body[key] === null) { return undefined;
} const raw = body[key]; if (typeof raw !== "string") { thrownew BrokerHttpError(400, "INVALID_BODY", `Expected "${key}" to be a string.`);
} const value = raw.trim(); return value.length > 0 ? value : undefined;
}
function requireObject(body: Record<string, unknown>, key: string) { const raw = body[key]; const parsed = asObject(raw); if (!parsed) { thrownew BrokerHttpError(400, "INVALID_BODY", `Expected "${key}" to be a JSON object.`);
} return parsed;
}
function optionalPositiveInteger(body: Record<string, unknown>, key: string) { if (!(key in body) || body[key] === undefined || body[key] === null) { return undefined;
} const raw = body[key]; if (typeof raw !== "number" || !Number.isFinite(raw) || !Number.isInteger(raw) || raw < 1) { thrownew BrokerHttpError(400, "INVALID_BODY", `Expected "${key}" to be a positive integer.`);
} return raw;
}
function optionalBoolean(body: Record<string, unknown>, key: string) { if (!(key in body) || body[key] === undefined || body[key] === null) { return undefined;
} if (typeof body[key] !== "boolean") { thrownew BrokerHttpError(400, "INVALID_BODY", `Expected "${key}" to be a boolean.`);
} return body[key];
}
function optionalCredentialStatus(body: Record<string, unknown>, key: string) { const value = optionalString(body, key); if (!value) { return undefined;
} if (value !== "active" && value !== "disabled") { thrownew BrokerHttpError( 400, "INVALID_BODY",
`Expected "${key}" to be "active" or "disabled".`,
);
} return value;
}
function optionalListStatus(body: Record<string, unknown>, key: string) { const value = optionalString(body, key); if (!value) { return undefined;
} if (value !== "active" && value !== "disabled" && value !== "all") { thrownew BrokerHttpError( 400, "INVALID_BODY",
`Expected "${key}" to be "active", "disabled", or "all".`,
);
} return value;
}
function parseActorRole(body: Record<string, unknown>) { const actorRole = requireString(body, "actorRole"); if (actorRole !== "ci" && actorRole !== "maintainer") { thrownew BrokerHttpError( 400, "INVALID_ACTOR_ROLE", 'Expected "actorRole" to be "maintainer" or "ci".',
);
} return actorRole as ActorRole;
}
function assertRoleAllowed(tokenRole: ActorRole, requestedRole: ActorRole) { if (tokenRole !== requestedRole) { thrownew BrokerHttpError( 403, "AUTH_ROLE_MISMATCH",
`Secret role "${tokenRole}" cannot be used as actorRole "${requestedRole}".`,
);
}
}
function normalizeCredentialId(raw: string) { // Convex Ids are opaque strings. We only enforce non-empty shape at HTTP boundary. return raw;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.