fix(feishu): feishu connector name validate (#797)
Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Haze <hazeone@users.noreply.github.com>
This commit is contained in:
@@ -38,6 +38,7 @@ import {
|
||||
OPENCLAW_WECHAT_CHANNEL_TYPE,
|
||||
UI_WECHAT_CHANNEL_TYPE,
|
||||
buildQrChannelEventName,
|
||||
isCanonicalOpenClawAccountId,
|
||||
toOpenClawChannelType,
|
||||
toUiChannelType,
|
||||
} from '../../utils/channel-alias';
|
||||
@@ -89,6 +90,47 @@ function buildQrLoginKey(channelType: string, accountId?: string): string {
|
||||
return `${toUiChannelType(channelType)}:${accountId?.trim() || '__new__'}`;
|
||||
}
|
||||
|
||||
async function isLegacyConfiguredAccountId(channelType: string, accountId: string): Promise<boolean> {
|
||||
const config = await readOpenClawConfig();
|
||||
const configuredAccounts = listConfiguredChannelAccountsFromConfig(config) ?? {};
|
||||
const storedChannelType = resolveStoredChannelType(channelType);
|
||||
const knownAccountIds = configuredAccounts[storedChannelType]?.accountIds ?? [];
|
||||
return knownAccountIds.includes(accountId);
|
||||
}
|
||||
|
||||
async function validateCanonicalAccountId(
|
||||
channelType: string,
|
||||
accountId: string | undefined,
|
||||
options?: { allowLegacyConfiguredId?: boolean },
|
||||
): Promise<string | null> {
|
||||
if (!accountId) return null;
|
||||
const trimmed = accountId.trim();
|
||||
if (!trimmed) return 'accountId cannot be empty';
|
||||
if (isCanonicalOpenClawAccountId(trimmed)) {
|
||||
return null;
|
||||
}
|
||||
if (options?.allowLegacyConfiguredId && await isLegacyConfiguredAccountId(channelType, trimmed)) {
|
||||
return null;
|
||||
}
|
||||
// Backward compatibility note:
|
||||
// existing legacy IDs can still be edited/bound if they already exist in config.
|
||||
// new account IDs must be canonical to match OpenClaw runtime routing behavior.
|
||||
return 'Invalid accountId format. Use lowercase letters, numbers, hyphens, or underscores only (max 64 chars, must start with a letter or number).';
|
||||
}
|
||||
|
||||
async function validateAccountIdOrReply(
|
||||
res: ServerResponse,
|
||||
channelType: string,
|
||||
accountId: string | undefined,
|
||||
): Promise<boolean> {
|
||||
const error = await validateCanonicalAccountId(channelType, accountId, { allowLegacyConfiguredId: true });
|
||||
if (!error) {
|
||||
return true;
|
||||
}
|
||||
sendJson(res, 400, { success: false, error });
|
||||
return false;
|
||||
}
|
||||
|
||||
function setActiveQrLogin(channelType: string, sessionKey: string, accountId?: string): string {
|
||||
const loginKey = buildQrLoginKey(channelType, accountId);
|
||||
activeQrLogins.set(loginKey, sessionKey);
|
||||
@@ -1087,6 +1129,10 @@ export async function handleChannelRoutes(
|
||||
if (url.pathname === '/api/channels/default-account' && req.method === 'PUT') {
|
||||
try {
|
||||
const body = await parseJsonBody<{ channelType: string; accountId: string }>(req);
|
||||
const validAccountId = await validateAccountIdOrReply(res, body.channelType, body.accountId);
|
||||
if (!validAccountId) {
|
||||
return true;
|
||||
}
|
||||
await setChannelDefaultAccount(body.channelType, body.accountId);
|
||||
scheduleGatewayChannelSaveRefresh(ctx, body.channelType, `channel:setDefaultAccount:${body.channelType}`);
|
||||
sendJson(res, 200, { success: true });
|
||||
@@ -1099,6 +1145,10 @@ export async function handleChannelRoutes(
|
||||
if (url.pathname === '/api/channels/binding' && req.method === 'PUT') {
|
||||
try {
|
||||
const body = await parseJsonBody<{ channelType: string; accountId: string; agentId: string }>(req);
|
||||
const validAccountId = await validateAccountIdOrReply(res, body.channelType, body.accountId);
|
||||
if (!validAccountId) {
|
||||
return true;
|
||||
}
|
||||
await assignChannelAccountToAgent(body.agentId, resolveStoredChannelType(body.channelType), body.accountId);
|
||||
scheduleGatewayChannelSaveRefresh(ctx, body.channelType, `channel:setBinding:${body.channelType}`);
|
||||
sendJson(res, 200, { success: true });
|
||||
@@ -1111,6 +1161,10 @@ export async function handleChannelRoutes(
|
||||
if (url.pathname === '/api/channels/binding' && req.method === 'DELETE') {
|
||||
try {
|
||||
const body = await parseJsonBody<{ channelType: string; accountId: string }>(req);
|
||||
const validAccountId = await validateAccountIdOrReply(res, body.channelType, body.accountId);
|
||||
if (!validAccountId) {
|
||||
return true;
|
||||
}
|
||||
await clearChannelBinding(resolveStoredChannelType(body.channelType), body.accountId);
|
||||
scheduleGatewayChannelSaveRefresh(ctx, body.channelType, `channel:clearBinding:${body.channelType}`);
|
||||
sendJson(res, 200, { success: true });
|
||||
@@ -1212,6 +1266,10 @@ export async function handleChannelRoutes(
|
||||
if (url.pathname === '/api/channels/config' && req.method === 'POST') {
|
||||
try {
|
||||
const body = await parseJsonBody<{ channelType: string; config: Record<string, unknown>; accountId?: string }>(req);
|
||||
const validAccountId = await validateAccountIdOrReply(res, body.channelType, body.accountId);
|
||||
if (!validAccountId) {
|
||||
return true;
|
||||
}
|
||||
const storedChannelType = resolveStoredChannelType(body.channelType);
|
||||
if (storedChannelType === 'dingtalk') {
|
||||
const installResult = await ensureDingTalkPluginInstalled();
|
||||
|
||||
Reference in New Issue
Block a user