Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .env.dev.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ ENCRYPTION_SECRET=H+KbL/OFrqbEuDy/1zX8bsPG+spXri3S

DATABASE_URL=postgresql://postgres:typebot@localhost:5432/typebot

NEXTAUTH_URL=http://localhost:3000
BETTER_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_BETTER_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_VIEWER_URL=http://localhost:3001

GITHUB_CLIENT_ID=534b549dd17709a743a2
Expand Down
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ DATABASE_URL=postgresql://postgres:typebot@typebot-db:5432/typebot

NODE_OPTIONS=--no-node-snapshot

NEXTAUTH_URL=
BETTER_AUTH_URL=
NEXT_PUBLIC_BETTER_AUTH_URL=
NEXT_PUBLIC_VIEWER_URL=

ADMIN_EMAIL=
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/daily.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
DATABASE_URL: "${{ secrets.DATABASE_URL }}"
DATABASE_URL_REPLICA: "${{ secrets.DATABASE_URL_REPLICA }}"
ENCRYPTION_SECRET: "${{ secrets.ENCRYPTION_SECRET }}"
NEXTAUTH_URL: "http://localhost:3000"
BETTER_AUTH_URL: "http://localhost:3000"
NEXT_PUBLIC_VIEWER_URL: "http://localhost:3001"
MESSAGE_WEBHOOK_URL: "${{ secrets.MESSAGE_WEBHOOK_URL }}"
POSTHOG_PROJECT_ID: "${{ secrets.POSTHOG_PROJECT_ID }}"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/hourly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
DATABASE_URL: "${{ secrets.DATABASE_URL }}"
DATABASE_URL_REPLICA: "${{ secrets.DATABASE_URL_REPLICA }}"
ENCRYPTION_SECRET: "${{ secrets.ENCRYPTION_SECRET }}"
NEXTAUTH_URL: "${{ secrets.NEXTAUTH_URL }}"
BETTER_AUTH_URL: "${{ secrets.BETTER_AUTH_URL }}"
NEXT_PUBLIC_VIEWER_URL: "${{ secrets.NEXT_PUBLIC_VIEWER_URL }}"
NEXT_PUBLIC_POSTHOG_KEY: "${{ secrets.NEXT_PUBLIC_POSTHOG_KEY }}"
POSTHOG_API_HOST: "${{ secrets.POSTHOG_API_HOST }}"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/monthly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
DATABASE_URL: "${{ secrets.DATABASE_URL }}"
DATABASE_URL_REPLICA: "${{ secrets.DATABASE_URL_REPLICA }}"
ENCRYPTION_SECRET: "${{ secrets.ENCRYPTION_SECRET }}"
NEXTAUTH_URL: "http://localhost:3000"
BETTER_AUTH_URL: "http://localhost:3000"
NEXT_PUBLIC_VIEWER_URL: "http://localhost:3001"
steps:
- uses: actions/checkout@v2
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,4 @@ snapshots
.nitro

**/test/.auth
.env*.local
4 changes: 2 additions & 2 deletions apps/builder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
"test": "dotenv -e ./.env -e ../../.env -- bun test"
},
"dependencies": {
"@auth/core": "^0.39.1",
"@better-auth/expo": "^1.4.10",
"@braintree/sanitize-url": "^7.0.1",
"better-auth": "^1.4.10",
"@dnd-kit/helpers": "^0.1.21",
"@dnd-kit/react": "^0.1.21",
"@giphy/js-fetch-api": "^5.7.0",
Expand Down Expand Up @@ -77,7 +78,6 @@
"micro-cors": "^0.1.1",
"nanoid": "^5.1.5",
"next": "^15.5.9",
"next-auth": "^5.0.0-beta.28",
"next-themes": "^0.4.6",
"nextjs-cors": "^2.1.2",
"nodemailer": "^7.0.6",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const GET = async (
const urlParams = {
response_type: "code",
client_id: clientId,
redirect_uri: `${env.NEXTAUTH_URL}/oauth/redirect`,
redirect_uri: `${env.BETTER_AUTH_URL}/oauth/redirect`,
scope: authConfig.scopes.join(" "),
...authConfig.extraAuthParams,
};
Expand Down
4 changes: 4 additions & 0 deletions apps/builder/src/app/api/auth/[...all]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { toNextJsHandler } from "better-auth/next-js";
import { auth } from "@/lib/auth/config";

export const { GET, POST } = toNextJsHandler(auth);
31 changes: 0 additions & 31 deletions apps/builder/src/app/api/auth/[...nextauth]/route.ts

This file was deleted.

12 changes: 8 additions & 4 deletions apps/builder/src/app/api/s3/private/[...key]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import { getFileTempUrl } from "@typebot.io/lib/s3/getFileTempUrl";
import prisma from "@typebot.io/prisma";
import { isReadTypebotForbidden } from "@typebot.io/typebot/helpers/isReadTypebotForbidden";
import type { NextRequest } from "next/server";
import { auth } from "@/features/auth/lib/nextAuth";
import { auth } from "@/lib/auth/config";

type PathParams = Record<string, string>;

type User = {
id: string;
email: string | null;
email: string;
};

const pathAuthorizationCheckers: Record<
Expand Down Expand Up @@ -57,9 +57,13 @@ export const GET = async (
req: NextRequest,
{ params }: { params: Promise<{ key: string[] }> },
) => {
// Try to get session from Better Auth
const session = await auth.api.getSession({
headers: req.headers,
});

const user =
(await auth())?.user ??
(await authenticateByToken(extractBearerToken(req)));
session?.user ?? (await authenticateByToken(extractBearerToken(req)));

if (!user) return new Response("Unauthorized", { status: 401 });

Expand Down
2 changes: 1 addition & 1 deletion apps/builder/src/components/Seo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const getOrigin = () => {
return window.location.origin;
}

return env.NEXTAUTH_URL;
return env.BETTER_AUTH_URL;
};

export const Seo = ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,20 @@ export const sendUpdateEmailVerifCodeEmail = authenticatedProcedure

const oneHourLater = new Date(Date.now() + 1000 * 60 * 60);
const nanoid = customAlphabet("abcdefghijklmnopqrstuvwxyz", 4);
const verificationToken = await prisma.verificationToken.create({
const tokenValue = `${nanoid(4)}-${nanoid(5)}-${nanoid(4)}-${nanoid(5)}`;
// Encode new email in identifier since Better Auth uses 'value' for the token
const identifier = `${user.id}-changeEmail-${Buffer.from(formattedNewEmail).toString("base64")}`;
await prisma.verification.create({
data: {
token: `${nanoid(4)}-${nanoid(5)}-${nanoid(4)}-${nanoid(5)}`,
expires: oneHourLater,
identifier: `${user.id}-changeEmail`,
value: formattedNewEmail,
value: tokenValue,
expiresAt: oneHourLater,
identifier,
},
});

await sendEmail({
to: formattedNewEmail,
code: verificationToken.token,
code: tokenValue,
});

return { status: "success" };
Expand Down
47 changes: 31 additions & 16 deletions apps/builder/src/features/auth/api/updateUserEmail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,44 @@ export const updateUserEmail = authenticatedProcedure
}),
)
.mutation(async ({ ctx: { user }, input: { token } }) => {
const verificationToken = await prisma.verificationToken.findUnique({
// Find verification record by searching for identifier prefix
// The identifier format is: ${userId}-changeEmail-${base64(newEmail)}
const verifications = await prisma.verification.findMany({
where: {
identifier_token: {
token,
identifier: `${user.id}-changeEmail`,
identifier: {
startsWith: `${user.id}-changeEmail-`,
},
value: token,
},
select: {
expires: true,
value: true,
id: true,
expiresAt: true,
identifier: true,
},
});

if (!verificationToken?.value)
const verification = verifications[0];

if (!verification)
throw new TRPCError({
code: "NOT_FOUND",
message: "Token not found",
});

if (verificationToken.expires < new Date()) {
await deleteToken(token);
// Extract new email from identifier
const identifierParts = verification.identifier.split("-changeEmail-");
if (identifierParts.length !== 2) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Invalid verification identifier format",
});
}
const newEmail = Buffer.from(identifierParts[1], "base64").toString(
"utf-8",
);

if (verification.expiresAt < new Date()) {
await deleteVerification(verification.id);
throw new TRPCError({
code: "BAD_REQUEST",
message: "Token expired",
Expand All @@ -44,7 +61,7 @@ export const updateUserEmail = authenticatedProcedure
id: user.id,
},
data: {
email: verificationToken.value,
email: newEmail,
},
});
} catch (error) {
Expand All @@ -59,16 +76,14 @@ export const updateUserEmail = authenticatedProcedure
throw error;
}

await deleteToken(token);
await deleteVerification(verification.id);

return {
status: "success",
};
});

const deleteToken = (token: string) =>
prisma.verificationToken.delete({
where: {
token,
},
const deleteVerification = (id: string) =>
prisma.verification.delete({
where: { id },
});
Loading