fix: switch telegram connect to short token and single-window redirect

This commit is contained in:
Ruslan Bakiev
2026-02-22 09:20:22 +07:00
parent 25f7f8dfb4
commit 5679f22f7f
4 changed files with 44 additions and 88 deletions

View File

@@ -1,6 +1,5 @@
import { readBody } from "h3";
import { prisma } from "../../../../../utils/prisma";
import { verifyLinkToken } from "../../../../../utils/telegramBusinessConnect";
type CompleteBody = {
token?: string;
@@ -13,15 +12,9 @@ export default defineEventHandler(async (event) => {
throw createError({ statusCode: 400, statusMessage: "token is required" });
}
const payload = verifyLinkToken(token);
if (!payload) {
return { ok: false, status: "invalid_or_expired_token" };
}
const pendingId = `pending:${payload.nonce}`;
const pendingId = `pending:${token}`;
const pending = await prisma.telegramBusinessConnection.findFirst({
where: {
teamId: payload.teamId,
businessConnectionId: pendingId,
},
});
@@ -31,6 +24,11 @@ export default defineEventHandler(async (event) => {
}
const raw = (pending.rawJson ?? {}) as any;
const exp = Number(raw?.link?.exp ?? 0);
if (Number.isFinite(exp) && exp > 0 && Math.floor(Date.now() / 1000) > exp) {
return { ok: false, status: "invalid_or_expired_token" };
}
const telegramUserId = raw?.link?.telegramUserId != null ? String(raw.link.telegramUserId).trim() : "";
if (!telegramUserId) {
return { ok: false, status: "awaiting_telegram_start" };
@@ -41,12 +39,12 @@ export default defineEventHandler(async (event) => {
prisma.telegramBusinessConnection.upsert({
where: {
teamId_businessConnectionId: {
teamId: payload.teamId,
teamId: pending.teamId,
businessConnectionId: linkedConnectionId,
},
},
create: {
teamId: payload.teamId,
teamId: pending.teamId,
businessConnectionId: linkedConnectionId,
isEnabled: true,
canReply: true,
@@ -55,7 +53,7 @@ export default defineEventHandler(async (event) => {
mode: "token_link",
linkedAt: new Date().toISOString(),
telegramUserId,
tokenNonce: payload.nonce,
tokenNonce: token,
},
},
update: {
@@ -66,7 +64,7 @@ export default defineEventHandler(async (event) => {
mode: "token_link",
linkedAt: new Date().toISOString(),
telegramUserId,
tokenNonce: payload.nonce,
tokenNonce: token,
},
},
}),

View File

@@ -5,7 +5,6 @@ import {
extractLinkTokenFromStartText,
getBusinessConnectionFromUpdate,
getTelegramChatIdFromUpdate,
verifyLinkToken,
} from "../../../../utils/telegramBusinessConnect";
function hasValidSecret(event: any) {
@@ -78,8 +77,13 @@ export default defineEventHandler(async (event) => {
}
if (linkToken) {
const payload = verifyLinkToken(linkToken);
if (!payload) {
const pendingId = `pending:${linkToken}`;
const pending = await prisma.telegramBusinessConnection.findFirst({
where: {
businessConnectionId: pendingId,
},
});
if (!pending) {
if (startChatId) {
void telegramBotApi("sendMessage", {
chat_id: startChatId,
@@ -90,20 +94,31 @@ export default defineEventHandler(async (event) => {
return { ok: true, accepted: false, reason: "invalid_or_expired_link_token" };
}
const pendingId = `pending:${payload.nonce}`;
const rawPending = (pending.rawJson ?? {}) as any;
const exp = Number(rawPending?.link?.exp ?? 0);
if (Number.isFinite(exp) && exp > 0 && Math.floor(Date.now() / 1000) > exp) {
if (startChatId) {
void telegramBotApi("sendMessage", {
chat_id: startChatId,
text: "Ссылка привязки истекла. Вернись в CRM и нажми Connect заново.",
reply_markup: crmConnectButton(),
}).catch(() => {});
}
return { ok: true, accepted: false, reason: "invalid_or_expired_link_token" };
}
const chatId = startChatId;
await prisma.telegramBusinessConnection.updateMany({
where: {
teamId: payload.teamId,
teamId: pending.teamId,
businessConnectionId: pendingId,
},
data: {
rawJson: {
state: "pending_business_connection",
link: {
nonce: payload.nonce,
exp: payload.exp,
...(rawPending?.link ?? {}),
linkedAt: nowIso,
telegramUserId: chatId,
chatId,