🔧 Protect preview chat with enforced auth

This commit is contained in:
Baptiste Arnaud 2026-03-23 17:47:54 +01:00
parent 57aa0a4aee
commit d96f572e60
No known key found for this signature in database
9 changed files with 43 additions and 5 deletions

View File

@ -17,6 +17,7 @@ All scripts must be ran with `bunx nx`:
- Typescript project references can be automatically updated with `bunx nx sync`.
- Most of the scripts are inferred with nx plugins. A few examples:
- fastest way to typecheck `builder` and/or `viewer`: run root `bunx nx typecheck` (runs `tsc --build --emitDeclarationOnly`)
- typecheck a particular package: `bunx nx typecheck package_name`.
- test a package: `bunx nx test package_name`
- typecheck all afffected packages: `bunx nx affected -t typecheck` (**IMPORTANT**: Rely first on IDE's TS server diagnostics first for faster feedback loop)

View File

@ -1,6 +1,7 @@
import { ORPCError } from "@orpc/server";
import { authRouter } from "@typebot.io/auth/api/router";
import { billingRouter } from "@typebot.io/billing/api/router";
import { builderChatRouter } from "@typebot.io/bot-engine/api/router";
import { publicProcedure } from "@typebot.io/config/orpc/builder/middlewares";
import { featureFlagsRouter } from "@typebot.io/feature-flags/orpc/router";
import { fileUploadBuilderRouter } from "@typebot.io/file-input-block/api/router";
@ -77,6 +78,7 @@ export const appRouter: AppRouter = {
email: emailRouter,
telemetry: telemetryRouter,
generateGroupTitle,
chat: builderChatRouter,
credentials: credentialsRouter,
featureFlags: featureFlagsRouter,
auth: authRouter,
@ -107,6 +109,7 @@ export type AppRouter = {
email: typeof emailRouter;
telemetry: typeof telemetryRouter;
generateGroupTitle: typeof generateGroupTitle;
chat: typeof builderChatRouter;
credentials: typeof credentialsRouter;
featureFlags: typeof featureFlagsRouter;
auth: typeof authRouter;

View File

@ -31,6 +31,7 @@ export const WebPreview = () => {
<Standard
key={`web-preview-${startPreviewFrom?.id ?? ""}`}
typebot={typebot}
apiHost={window.location.origin}
sessionId={user ? `${typebot.id}-${user.id}` : undefined}
startFrom={
startPreviewFrom?.type === "group"

View File

@ -18,6 +18,7 @@ export const SettingsPage = () => {
{typebot && (
<Standard
typebot={typebot}
apiHost={window.location.origin}
style={{
borderRadius: "0.75rem",
width: "100%",

View File

@ -173,6 +173,7 @@ export const TemplatesDialog = ({
<Standard
key={typebot.id}
typebot={typebot}
apiHost={window.location.origin}
style={{
borderRadius: "0.25rem",
backgroundColor: "#fff",

View File

@ -18,6 +18,7 @@ export const ThemePage = () => {
{typebot && (
<Standard
typebot={typebot}
apiHost={window.location.origin}
style={{
borderRadius: "0.75rem",
width: "100%",

View File

@ -3,6 +3,7 @@ import {
startChatResponseSchema,
startPreviewChatResponseSchema,
} from "@typebot.io/chat-api/schemas";
import { authenticatedProcedure } from "@typebot.io/config/orpc/builder/middlewares";
import {
procedureWithOptionalUser,
protectedProcedure,
@ -37,6 +38,33 @@ import {
import { handleSendMessageV1 } from "./legacy/handleSendMessageV1";
import { handleSendMessageV2 } from "./legacy/handleSendMessageV2";
export const builderChatRouter = {
continueChatProcedure: authenticatedProcedure
.route({
method: "POST",
path: "/v1/sessions/{sessionId}/continueChat",
})
.input(continueChatInputSchema)
.output(continueChatResponseSchema)
.handler(({ input }) =>
handleContinueChat({
input,
context: {
origin: undefined,
iframeReferrerOrigin: undefined,
},
}),
),
startChatPreviewProcedure: authenticatedProcedure
.route({
method: "POST",
path: "/v1/typebots/{typebotId}/preview/startChat",
})
.input(startPreviewChatInputSchema)
.output(startPreviewChatResponseSchema)
.handler(handleStartChatPreview),
};
export const chatRouter = {
startChatProcedure: publicProcedure
.route({
@ -68,7 +96,7 @@ export const chatRouter = {
.input(saveClientLogsInputSchema)
.output(z.object({ message: z.string() }))
.handler(handleSaveClientLogs),
startChatPreviewProcedure: procedureWithOptionalUser
startChatPreviewProcedure: protectedProcedure
.route({
method: "POST",
path: "/v1/typebots/{typebotId}/preview/startChat",

View File

@ -312,7 +312,9 @@ const parseEmailRecipient = (
): { email?: string; name?: string } => {
if (!recipient) return {};
const namedRecipientMatch = recipient.match(/^(?<name>[^<]+)<(?<email>[^>]+)>$/);
const namedRecipientMatch = recipient.match(
/^(?<name>[^<]+)<(?<email>[^>]+)>$/,
);
const email = namedRecipientMatch?.groups?.email?.trim();
const name = namedRecipientMatch?.groups?.name?.trim().replaceAll('"', "");

View File

@ -332,14 +332,14 @@ export const startSession = async ({
};
const getTypebot = async (startParams: StartParams) => {
if (startParams.type === "preview" && startParams.typebot)
return startParams.typebot;
if (startParams.type === "preview" && !startParams.userId)
throw new ORPCError("UNAUTHORIZED", {
message: "You need to be authenticated to perform this action",
});
if (startParams.type === "preview" && startParams.typebot)
return startParams.typebot;
const typebotQuery =
startParams.type === "preview"
? await findTypebot({