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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@
- Data Persistence
- [Neon Serverless Postgres](https://vercel.com/marketplace/neon) for saving chat history and user data
- [Vercel Blob](https://vercel.com/storage/blob) for efficient file storage
- [Auth.js](https://authjs.dev)
- Simple and secure authentication
- [BetterAuth](https://www.better-auth.com)
- Modern, type-safe authentication with email/password support
- Built-in session management with customizable password hashing

## Model Providers

Expand Down
83 changes: 46 additions & 37 deletions app/(auth)/actions.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
"use server";
"use server"

import { z } from "zod";

import { createUser, getUser } from "@/lib/db/queries";

import { signIn } from "./auth";
import { z } from "zod"
import { auth } from "@/lib/auth"
import { createUser, getUser } from "@/lib/db/queries"
import { headers } from "next/headers"

const authFormSchema = z.object({
email: z.string().email(),
password: z.string().min(6),
});
})

export type LoginActionState = {
status: "idle" | "in_progress" | "success" | "failed" | "invalid_data";
};
status: "idle" | "in_progress" | "success" | "failed" | "invalid_data"
}

export const login = async (
_: LoginActionState,
Expand All @@ -23,23 +22,26 @@ export const login = async (
const validatedData = authFormSchema.parse({
email: formData.get("email"),
password: formData.get("password"),
});
})

await signIn("credentials", {
email: validatedData.email,
password: validatedData.password,
redirect: false,
});
const response = await auth.api.signInEmail({
body: validatedData,
headers: await headers(),
asResponse: true
})

return { status: "success" };
if (!response.ok) {
return { status: "failed" }
}

return { status: "success" }
} catch (error) {
if (error instanceof z.ZodError) {
return { status: "invalid_data" };
return { status: "invalid_data" }
}

return { status: "failed" };
return { status: "failed" }
}
};
}

export type RegisterActionState = {
status:
Expand All @@ -48,8 +50,8 @@ export type RegisterActionState = {
| "success"
| "failed"
| "user_exists"
| "invalid_data";
};
| "invalid_data"
}

export const register = async (
_: RegisterActionState,
Expand All @@ -59,26 +61,33 @@ export const register = async (
const validatedData = authFormSchema.parse({
email: formData.get("email"),
password: formData.get("password"),
});
})

const [user] = await getUser(validatedData.email);
// Check if user exists
const [existingUser] = await getUser(validatedData.email)
if (existingUser) {
return { status: "user_exists" }
}

// Create user in database first
await createUser(validatedData.email, validatedData.password)

if (user) {
return { status: "user_exists" } as RegisterActionState;
// Then sign them in
const response = await auth.api.signInEmail({
body: validatedData,
headers: await headers(),
asResponse: true
})

if (!response.ok) {
return { status: "failed" }
}
await createUser(validatedData.email, validatedData.password);
await signIn("credentials", {
email: validatedData.email,
password: validatedData.password,
redirect: false,
});

return { status: "success" };
return { status: "success" }
} catch (error) {
if (error instanceof z.ZodError) {
return { status: "invalid_data" };
return { status: "invalid_data" }
}

return { status: "failed" };
return { status: "failed" }
}
};
}
2 changes: 0 additions & 2 deletions app/(auth)/api/auth/[...nextauth]/route.ts

This file was deleted.

60 changes: 45 additions & 15 deletions app/(auth)/api/auth/guest/route.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,51 @@
import { NextResponse } from "next/server";
import { getToken } from "next-auth/jwt";
import { signIn } from "@/app/(auth)/auth";
import { isDevelopmentEnvironment } from "@/lib/constants";
import { headers } from "next/headers";
import { NextResponse } from "next/server"
import { auth } from "@/lib/auth"
import { createGuestUser } from "@/lib/db/queries"

export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const redirectUrl = searchParams.get("redirectUrl") || "/";
const { searchParams } = new URL(request.url)
const redirectUrl = searchParams.get("redirectUrl") || "/"

const token = await getToken({
req: request,
secret: process.env.AUTH_SECRET,
secureCookie: !isDevelopmentEnvironment,
});
try {
// Check if already authenticated
const session = await auth.api.getSession({
headers: request.headers
})

if (token) {
return NextResponse.redirect(new URL("/", request.url));
}
if (session) {
return NextResponse.redirect(new URL("/", request.url))
}

// Create guest user
const [guestUser] = await createGuestUser()

// Sign in the guest user programmatically
// This approach manually creates a session for the guest user
const signInResponse = await auth.api.signInEmail({
body: {
email: guestUser.email!,
password: "guest-password", // Use a consistent guest password
},
asResponse: true
})

if (signInResponse.ok) {
// Forward the session cookies from the sign-in response
const response = NextResponse.redirect(new URL(redirectUrl, request.url))

return signIn("guest", { redirect: true, redirectTo: redirectUrl });
// Copy auth cookies from the sign-in response
const authCookies = signInResponse.headers.get('set-cookie')
if (authCookies) {
response.headers.set('set-cookie', authCookies)
}

return response
}

throw new Error('Guest sign-in failed')
} catch (error) {
console.error('Guest authentication error:', error)
return NextResponse.redirect(new URL("/login", request.url))
}
}
13 changes: 0 additions & 13 deletions app/(auth)/auth.config.ts

This file was deleted.

95 changes: 0 additions & 95 deletions app/(auth)/auth.ts

This file was deleted.

7 changes: 3 additions & 4 deletions app/(auth)/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import Link from "next/link";
import { useRouter } from "next/navigation";
import { useSession } from "next-auth/react";
import { useSession } from "@/lib/auth-client";
import { useActionState, useEffect, useState } from "react";

import { AuthForm } from "@/components/auth-form";
Expand All @@ -23,7 +23,7 @@ export default function Page() {
}
);

const { update: updateSession } = useSession();
const { data: session, isPending } = useSession();

useEffect(() => {
if (state.status === "failed") {
Expand All @@ -38,11 +38,10 @@ export default function Page() {
});
} else if (state.status === "success") {
setIsSuccessful(true);
updateSession();
router.refresh();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [state.status, router.refresh, updateSession]);
}, [state.status, router]);

const handleSubmit = (formData: FormData) => {
setEmail(formData.get("email") as string);
Expand Down
7 changes: 3 additions & 4 deletions app/(auth)/register/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import Link from "next/link";
import { useRouter } from "next/navigation";
import { useSession } from "next-auth/react";
import { useSession } from "@/lib/auth-client";
import { useActionState, useEffect, useState } from "react";
import { AuthForm } from "@/components/auth-form";
import { SubmitButton } from "@/components/submit-button";
Expand All @@ -22,7 +22,7 @@ export default function Page() {
}
);

const { update: updateSession } = useSession();
const { data: session, isPending } = useSession();

useEffect(() => {
if (state.status === "user_exists") {
Expand All @@ -38,11 +38,10 @@ export default function Page() {
toast({ type: "success", description: "Account created successfully!" });

setIsSuccessful(true);
updateSession();
router.refresh();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [state.status, router.refresh, updateSession]);
}, [state.status, router]);

const handleSubmit = (formData: FormData) => {
setEmail(formData.get("email") as string);
Expand Down
5 changes: 3 additions & 2 deletions app/(chat)/api/chat/[id]/stream/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { headers } from "next/headers";
import { createUIMessageStream, JsonToSseTransformStream } from "ai";
import { differenceInSeconds } from "date-fns";
import { auth } from "@/app/(auth)/auth";
import { auth } from "@/lib/auth";
import {
getChatById,
getMessagesByChatId,
Expand Down Expand Up @@ -28,7 +29,7 @@ export async function GET(
return new ChatSDKError("bad_request:api").toResponse();
}

const session = await auth();
const session = await auth.api.getSession({ headers: await headers() });

if (!session?.user) {
return new ChatSDKError("unauthorized:chat").toResponse();
Expand Down
Loading