Skip to content
Open
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
19 changes: 19 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,25 @@ GIT_USEREMAIL=snomiao+comfy-pr@gmail.com
# Use docker compose up to get this mongodb before test
# MONGODB_URI=mongodb://localhost:27017

# Better Auth Configuration
# BETTER_AUTH_SECRET=your-secret-key-here
#
# Base URL for auth callbacks (auto-detected on Vercel via VERCEL_URL)
# BETTER_AUTH_URL=http://localhost:3000
# NEXTAUTH_URL=http://localhost:3000 # Backward compatible with NextAuth
#
# GitHub OAuth (supports both AUTH_GITHUB_* and GITHUB_* for backward compatibility)
# AUTH_GITHUB_ID=your-github-client-id
# AUTH_GITHUB_SECRET=your-github-client-secret
# GITHUB_ID=your-github-client-id # NextAuth backward compatibility
# GITHUB_SECRET=your-github-client-secret # NextAuth backward compatibility
#
# Google OAuth (supports both AUTH_GOOGLE_* and GOOGLE_* for backward compatibility)
# AUTH_GOOGLE_ID=your-google-client-id
# AUTH_GOOGLE_SECRET=your-google-client-secret
# GOOGLE_ID=your-google-client-id # NextAuth backward compatibility
# GOOGLE_SECRET=your-google-client-secret # NextAuth backward compatibility

AUTH_ADMINS=snomiao@gmail.com

SLACK_BOT_TOKEN="FILL_THIS_INTO_ .env.local"
122 changes: 122 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# NextAuth to Better Auth Migration

## Overview

This PR migrates the authentication system from NextAuth v5 to Better Auth.

## Changes Made

### New Files Created

1. **`lib/auth.ts`** - Better Auth server configuration
- Configured MongoDB adapter
- Set up GitHub and Google OAuth providers
- Disabled email/password authentication (not used in original setup)

2. **`lib/auth-client.ts`** - Better Auth client exports
- Exports `signIn`, `signOut`, and `useSession` for client components

3. **`lib/getAuthUser.ts`** - Migrated auth user utility
- Moved from `app/api/auth/[...nextauth]/getAuthUser.tsx`
- Updated to use Better Auth session API

4. **`app/api/auth/[...all]/route.ts`** - New Better Auth API route
- Replaces `app/api/auth/[...nextauth]/route.ts`

### Modified Files

1. **`app/auth/login/page.tsx`**
- Updated imports from `next-auth/react` to `@/lib/auth-client`
- Updated `signIn()` calls to use Better Auth's `signIn.social({ provider })` syntax

2. **`package.json`**
- Added `better-auth@^1.3.28`
- Kept `next-auth` for now (can be removed after testing)

3. **`.env.example`**
- Added Better Auth environment variable documentation

### Files to Deprecate (After Testing)

- `app/api/auth/[...nextauth]/auth.ts`
- `app/api/auth/[...nextauth]/route.ts`
- `app/api/auth/[...nextauth]/getAuthUser.tsx`
- `app/api/auth/[...nextauth]/Users.tsx` (if not used elsewhere)

## Environment Variables

Better Auth uses the same environment variables as NextAuth for OAuth providers:

- `AUTH_GITHUB_ID` - GitHub OAuth client ID
- `AUTH_GITHUB_SECRET` - GitHub OAuth client secret
- `AUTH_GOOGLE_ID` - Google OAuth client ID
- `AUTH_GOOGLE_SECRET` - Google OAuth client secret

Additional Better Auth-specific variables:

- `BETTER_AUTH_SECRET` - Secret key for session encryption (optional in dev)
- `BETTER_AUTH_URL` - Base URL for the application (optional, defaults to localhost:3000)
- `NEXT_PUBLIC_APP_URL` - Public URL for client-side auth (optional)

## Testing Checklist

- [ ] GitHub OAuth login works
- [ ] Google OAuth login works
- [ ] Session persistence across page refreshes
- [ ] Admin role assignment (@comfy.org and @drip.art emails)
- [ ] Sign out functionality
- [ ] Protected routes/pages still work
- [ ] User data in MongoDB is correctly associated

## Breaking Changes

### API Changes

1. **Session Object Structure**: Better Auth may have a different session object structure. Review all places where `session.user` is accessed.

2. **Server-side Session Access**: Changed from:

```ts
const session = await auth();
```

to:

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

3. **Client-side Sign In**: Changed from:
```ts
signIn("google");
```
to:
```ts
signIn.social({ provider: "google" });
```

## Migration Steps

1. Install Better Auth: ✅
2. Create Better Auth configuration: ✅
3. Update API routes: ✅
4. Update client components: ✅
5. Test authentication flows: ⏳
6. Remove old NextAuth files: ⏳
7. Update documentation: ⏳

## Rollback Plan

If issues are encountered:

1. Revert changes to `app/auth/login/page.tsx`
2. Remove `app/api/auth/[...all]/` directory
3. Remove `lib/auth.ts` and `lib/auth-client.ts`
4. Restore imports to use NextAuth
5. Remove `better-auth` from package.json

## Notes

- The MongoDB adapter connection is shared with the existing setup
- Admin role logic remains unchanged
- Better Auth provides a more modern and actively maintained alternative to NextAuth v5
18 changes: 7 additions & 11 deletions app/(dashboard)/followup/actions/send-gmail/page.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
// /followup/actions/email

import { getAuthUser } from "@/app/api/auth/[...nextauth]/getAuthUser";
import {
TaskDataOrNull,
TaskError,
TaskErrorOrNull,
TaskOK,
} from "@/packages/mongodb-pipeline-ts/Task";
import {
GCloudOAuth2Credentials,
getGCloudOAuth2Client,
} from "@/src/gcloud/GCloudOAuth2Credentials";
import { getAuthUser } from "@/lib/getAuthUser";
import { TaskDataOrNull, TaskError, TaskErrorOrNull, TaskOK } from "@/packages/mongodb-pipeline-ts/Task";
import { GCloudOAuth2Credentials, getGCloudOAuth2Client } from "@/src/gcloud/GCloudOAuth2Credentials";
import { sendGmail } from "@/src/sendGmail";
import { yaml } from "@/src/utils/yaml";
import DIE from "@snomiao/die";
Expand All @@ -26,6 +18,10 @@ export const dynamic = "force-dynamic";
*/
export default async function GmailPage() {
const user = await getAuthUser();
if (!user) {
return <div>Please log in to continue</div>;
}

let authorizeUrl = "";
const getOAuth2Client = async () =>
await getGCloudOAuth2Client({
Expand Down
4 changes: 2 additions & 2 deletions app/(dashboard)/rules/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { getAuthUser } from "@/lib/getAuthUser";
import { forbidden } from "next/navigation";
import type { ReactNode } from "react";
import { getAuthUser } from "../../api/auth/[...nextauth]/getAuthUser";

export default async function RulesLayout({ children }: { children: ReactNode }) {
const user = await getAuthUser();
const isAdmin = user.admin;
const isAdmin = user?.admin;
if (!isAdmin) return forbidden();

return (
Expand Down
4 changes: 4 additions & 0 deletions app/api/auth/[...all]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";

export const { GET, POST } = toNextJsHandler(auth.handler);
12 changes: 0 additions & 12 deletions app/api/auth/[...nextauth]/Users.tsx

This file was deleted.

23 changes: 0 additions & 23 deletions app/api/auth/[...nextauth]/auth.ts

This file was deleted.

19 changes: 0 additions & 19 deletions app/api/auth/[...nextauth]/getAuthUser.tsx

This file was deleted.

2 changes: 0 additions & 2 deletions app/api/auth/[...nextauth]/route.ts

This file was deleted.

30 changes: 0 additions & 30 deletions app/auth/login/page-bak.tsx

This file was deleted.

10 changes: 4 additions & 6 deletions app/auth/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { signIn } from "next-auth/react";
import { signIn } from "@/lib/auth-client";
import { FaGithub } from "react-icons/fa";
import { FcGoogle } from "react-icons/fc";

Expand All @@ -24,18 +24,16 @@ export default function LoginPage() {
<div className="space-y-4">
{/* Google OAuth Button */}
<button
onClick={() => signIn("google")}
onClick={() => signIn.social({ provider: "google" })}
className="flex items-center justify-center w-full py-3 px-4 border-2 border-gray-200 rounded-lg bg-white hover:bg-gray-50 hover:border-gray-300 transition-all duration-200 shadow-sm hover:shadow-md group"
>
<FcGoogle className="w-6 h-6 mr-3" />
<span className="font-medium text-gray-700 group-hover:text-gray-900">
Continue with Google
</span>
<span className="font-medium text-gray-700 group-hover:text-gray-900">Continue with Google</span>
</button>

{/* GitHub OAuth Button */}
<button
onClick={() => signIn("github")}
onClick={() => signIn.social({ provider: "github" })}
className="flex items-center justify-center w-full py-3 px-4 border-2 border-gray-800 bg-gray-900 hover:bg-gray-800 rounded-lg transition-all duration-200 shadow-sm hover:shadow-md group"
>
<FaGithub className="w-6 h-6 mr-3 text-white" />
Expand Down
2 changes: 1 addition & 1 deletion app/tasks/github-action-update/actions.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"use server";
import { getAuthUser } from "@/app/api/auth/[...nextauth]/getAuthUser";
import { getAuthUser } from "@/lib/getAuthUser";
import { GithubActionUpdateTask } from "@/src/GithubActionUpdateTask/GithubActionUpdateTask";
import { z } from "zod";

Expand Down
4 changes: 2 additions & 2 deletions app/tasks/github-action-update/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getAuthUser } from "@/app/api/auth/[...nextauth]/getAuthUser";
import "@/app/markdown.css";
import "@/app/tasks-panel.css";
import { getAuthUser } from "@/lib/getAuthUser";
import { parseTitleBodyOfMarkdown } from "@/src/parseTitleBodyOfMarkdown";
import { yaml } from "@/src/utils/yaml";
import { compareBy } from "comparing";
Expand Down Expand Up @@ -31,7 +31,7 @@ export const metadata: Metadata = {
*/
export default async function GithubActionUpdateTaskPage() {
const user = await getAuthUser();
if (!user.admin) return forbidden();
if (!user?.admin) return forbidden();
const data = await listGithubActionUpdateTask();

function filterHopper<T>(values: T[], predicates: Array<(value: T) => any>) {
Expand Down
Loading