mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2025-12-13 23:16:45 +03:00
Fixing issues with Better Auth
This commit is contained in:
@@ -33,7 +33,7 @@ export function LoginForm() {
|
||||
toast.success('Login successful!');
|
||||
// Small delay before redirecting to see the success message
|
||||
setTimeout(() => {
|
||||
window.location.href = '/dashboard';
|
||||
window.location.href = '/';
|
||||
}, 1000);
|
||||
} catch (error) {
|
||||
showErrorToast(error, toast);
|
||||
|
||||
10
src/components/auth/LoginPage.tsx
Normal file
10
src/components/auth/LoginPage.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { LoginForm } from './LoginForm';
|
||||
import Providers from '@/components/layout/Providers';
|
||||
|
||||
export function LoginPage() {
|
||||
return (
|
||||
<Providers>
|
||||
<LoginForm />
|
||||
</Providers>
|
||||
);
|
||||
}
|
||||
@@ -16,12 +16,11 @@ export function SignupForm() {
|
||||
setIsLoading(true);
|
||||
const form = e.currentTarget;
|
||||
const formData = new FormData(form);
|
||||
const username = formData.get('username') as string | null;
|
||||
const email = formData.get('email') as string | null;
|
||||
const password = formData.get('password') as string | null;
|
||||
const confirmPassword = formData.get('confirmPassword') as string | null;
|
||||
|
||||
if (!username || !email || !password || !confirmPassword) {
|
||||
if (!email || !password || !confirmPassword) {
|
||||
toast.error('Please fill in all fields');
|
||||
setIsLoading(false);
|
||||
return;
|
||||
@@ -34,11 +33,13 @@ export function SignupForm() {
|
||||
}
|
||||
|
||||
try {
|
||||
// Derive username from email (part before @)
|
||||
const username = email.split('@')[0];
|
||||
await register(username, email, password);
|
||||
toast.success('Account created successfully! Redirecting to dashboard...');
|
||||
// Small delay before redirecting to see the success message
|
||||
setTimeout(() => {
|
||||
window.location.href = '/dashboard';
|
||||
window.location.href = '/';
|
||||
}, 1500);
|
||||
} catch (error) {
|
||||
showErrorToast(error, toast);
|
||||
@@ -71,20 +72,6 @@ export function SignupForm() {
|
||||
<CardContent>
|
||||
<form id="signup-form" onSubmit={handleSignup}>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="username" className="block text-sm font-medium mb-1">
|
||||
Username
|
||||
</label>
|
||||
<input
|
||||
id="username"
|
||||
name="username"
|
||||
type="text"
|
||||
required
|
||||
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
placeholder="Enter your username"
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium mb-1">
|
||||
Email
|
||||
@@ -97,6 +84,7 @@ export function SignupForm() {
|
||||
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
placeholder="Enter your email"
|
||||
disabled={isLoading}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
10
src/components/auth/SignupPage.tsx
Normal file
10
src/components/auth/SignupPage.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { SignupForm } from './SignupForm';
|
||||
import Providers from '@/components/layout/Providers';
|
||||
|
||||
export function SignupPage() {
|
||||
return (
|
||||
<Providers>
|
||||
<SignupForm />
|
||||
</Providers>
|
||||
);
|
||||
}
|
||||
@@ -37,28 +37,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const user = betterAuthSession.data?.user || null;
|
||||
const session = betterAuthSession.data || null;
|
||||
|
||||
// Check if this is first load and redirect if needed
|
||||
useEffect(() => {
|
||||
const checkFirstUser = async () => {
|
||||
if (!betterAuthSession.isPending && !user) {
|
||||
try {
|
||||
// Check if there are any users in the system
|
||||
const response = await fetch("/api/auth/check-users");
|
||||
if (response.status === 404) {
|
||||
// No users found, redirect to signup
|
||||
window.location.href = "/signup";
|
||||
} else if (!window.location.pathname.includes("/login")) {
|
||||
// User not authenticated, redirect to login
|
||||
window.location.href = "/login";
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to check users:", err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
checkFirstUser();
|
||||
}, [betterAuthSession.isPending, user]);
|
||||
// Don't do any redirects here - let the pages handle their own redirect logic
|
||||
|
||||
const login = async (email: string, password: string) => {
|
||||
setIsLoading(true);
|
||||
@@ -67,7 +46,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const result = await authClient.signIn.email({
|
||||
email,
|
||||
password,
|
||||
callbackURL: "/dashboard",
|
||||
callbackURL: "/",
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
@@ -93,9 +72,8 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const result = await authClient.signUp.email({
|
||||
email,
|
||||
password,
|
||||
name: username, // Better Auth uses 'name' field
|
||||
username, // Also pass username as additional field
|
||||
callbackURL: "/dashboard",
|
||||
name: username, // Better Auth uses 'name' field for display name
|
||||
callbackURL: "/",
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
|
||||
@@ -3,13 +3,6 @@ import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||
import { sso, oidcProvider } from "better-auth/plugins";
|
||||
import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite";
|
||||
|
||||
// Generate or use existing JWT secret
|
||||
const JWT_SECRET = process.env.JWT_SECRET || process.env.BETTER_AUTH_SECRET;
|
||||
|
||||
if (!JWT_SECRET) {
|
||||
throw new Error("JWT_SECRET or BETTER_AUTH_SECRET environment variable is required");
|
||||
}
|
||||
|
||||
// This function will be called with the actual database instance
|
||||
export function createAuth(db: BunSQLiteDatabase) {
|
||||
return betterAuth({
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
import { betterAuth } from "better-auth";
|
||||
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||
import { db } from "./db";
|
||||
|
||||
// Generate or use existing JWT secret
|
||||
const JWT_SECRET = process.env.JWT_SECRET || process.env.BETTER_AUTH_SECRET;
|
||||
|
||||
if (!JWT_SECRET) {
|
||||
throw new Error("JWT_SECRET or BETTER_AUTH_SECRET environment variable is required");
|
||||
}
|
||||
import { db, users } from "./db";
|
||||
import * as schema from "./db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
export const auth = betterAuth({
|
||||
// Database configuration
|
||||
database: drizzleAdapter(db, {
|
||||
provider: "sqlite",
|
||||
usePlural: true, // Our tables use plural names (users, not user)
|
||||
schema, // Pass the schema explicitly
|
||||
}),
|
||||
|
||||
// Secret for signing tokens
|
||||
secret: process.env.BETTER_AUTH_SECRET,
|
||||
|
||||
// Base URL configuration
|
||||
baseURL: process.env.BETTER_AUTH_URL || "http://localhost:3000",
|
||||
baseURL: process.env.BETTER_AUTH_URL || "http://localhost:4321",
|
||||
basePath: "/api/auth", // Specify the base path for auth endpoints
|
||||
|
||||
// Authentication methods
|
||||
@@ -30,6 +29,7 @@ export const auth = betterAuth({
|
||||
console.log("Reset URL:", url);
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
// Session configuration
|
||||
session: {
|
||||
@@ -44,9 +44,8 @@ export const auth = betterAuth({
|
||||
// Keep the username field from our existing schema
|
||||
username: {
|
||||
type: "string",
|
||||
required: true,
|
||||
defaultValue: "user", // Default for migration
|
||||
input: true, // Allow in signup form
|
||||
required: false,
|
||||
input: false, // Don't show in signup form - we'll derive from email
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -56,7 +55,7 @@ export const auth = betterAuth({
|
||||
|
||||
// Trusted origins for CORS
|
||||
trustedOrigins: [
|
||||
process.env.BETTER_AUTH_URL || "http://localhost:3000",
|
||||
process.env.BETTER_AUTH_URL || "http://localhost:4321",
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
@@ -18,9 +18,9 @@ export const ENV = {
|
||||
return "sqlite://data/gitea-mirror.db";
|
||||
},
|
||||
|
||||
// JWT secret for authentication
|
||||
JWT_SECRET:
|
||||
process.env.JWT_SECRET || "your-secret-key-change-this-in-production",
|
||||
// Better Auth secret for authentication
|
||||
BETTER_AUTH_SECRET:
|
||||
process.env.BETTER_AUTH_SECRET || "your-secret-key-change-this-in-production",
|
||||
|
||||
// Server host and port
|
||||
HOST: process.env.HOST || "localhost",
|
||||
|
||||
@@ -213,16 +213,18 @@ export const eventSchema = z.object({
|
||||
|
||||
export const users = sqliteTable("users", {
|
||||
id: text("id").primaryKey(),
|
||||
username: text("username").notNull(),
|
||||
password: text("password").notNull(),
|
||||
email: text("email").notNull(),
|
||||
name: text("name"),
|
||||
email: text("email").notNull().unique(),
|
||||
emailVerified: integer("email_verified", { mode: "boolean" }).notNull().default(false),
|
||||
image: text("image"),
|
||||
createdAt: integer("created_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.default(sql`(unixepoch())`),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.default(sql`(unixepoch())`),
|
||||
// Custom fields
|
||||
username: text("username"),
|
||||
});
|
||||
|
||||
export const events = sqliteTable("events", {
|
||||
@@ -463,9 +465,10 @@ export const sessions = sqliteTable("sessions", {
|
||||
// Accounts table (for OAuth providers and credentials)
|
||||
export const accounts = sqliteTable("accounts", {
|
||||
id: text("id").primaryKey(),
|
||||
accountId: text("account_id").notNull(),
|
||||
userId: text("user_id").notNull().references(() => users.id),
|
||||
providerId: text("provider_id").notNull(),
|
||||
providerUserId: text("provider_user_id").notNull(),
|
||||
providerUserId: text("provider_user_id"), // Make nullable for email/password auth
|
||||
accessToken: text("access_token"),
|
||||
refreshToken: text("refresh_token"),
|
||||
expiresAt: integer("expires_at", { mode: "timestamp" }),
|
||||
@@ -478,6 +481,7 @@ export const accounts = sqliteTable("accounts", {
|
||||
.default(sql`(unixepoch())`),
|
||||
}, (table) => {
|
||||
return {
|
||||
accountIdIdx: index("idx_accounts_account_id").on(table.accountId),
|
||||
userIdIdx: index("idx_accounts_user_id").on(table.userId),
|
||||
providerIdx: index("idx_accounts_provider").on(table.providerId, table.providerUserId),
|
||||
};
|
||||
|
||||
72
src/pages/api/auth/debug.ts
Normal file
72
src/pages/api/auth/debug.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import type { APIRoute } from "astro";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { db } from "@/lib/db";
|
||||
import { users } from "@/lib/db/schema";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
export const GET: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
// Get Better Auth configuration info
|
||||
const info = {
|
||||
baseURL: auth.options.baseURL,
|
||||
basePath: auth.options.basePath,
|
||||
trustedOrigins: auth.options.trustedOrigins,
|
||||
emailPasswordEnabled: auth.options.emailAndPassword?.enabled,
|
||||
userFields: auth.options.user?.additionalFields,
|
||||
databaseConfig: {
|
||||
usePlural: true,
|
||||
provider: "sqlite"
|
||||
}
|
||||
};
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
success: true,
|
||||
config: info
|
||||
}), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
} catch (error) {
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
}), {
|
||||
status: 500,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
// Test creating a user directly
|
||||
const userId = nanoid();
|
||||
const now = new Date();
|
||||
|
||||
await db.insert(users).values({
|
||||
id: userId,
|
||||
email: "test2@example.com",
|
||||
emailVerified: false,
|
||||
username: "test2",
|
||||
// Let the database handle timestamps with defaults
|
||||
});
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
success: true,
|
||||
userId,
|
||||
message: "User created successfully"
|
||||
}), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
} catch (error) {
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
details: error
|
||||
}), {
|
||||
status: 500,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
import '../styles/global.css';
|
||||
import ThemeScript from '@/components/theme/ThemeScript.astro';
|
||||
import { LoginForm } from '@/components/auth/LoginForm';
|
||||
import { LoginPage } from '@/components/auth/LoginPage';
|
||||
import { db, users } from '@/lib/db';
|
||||
import { sql } from 'drizzle-orm';
|
||||
|
||||
@@ -30,7 +30,7 @@ const generator = Astro.generator;
|
||||
</head>
|
||||
<body>
|
||||
<div class="h-dvh flex items-center justify-center bg-muted/30 p-4">
|
||||
<LoginForm client:load />
|
||||
<LoginPage client:load />
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
import '../styles/global.css';
|
||||
import ThemeScript from '@/components/theme/ThemeScript.astro';
|
||||
import { SignupForm } from '@/components/auth/SignupForm';
|
||||
import { SignupPage } from '@/components/auth/SignupPage';
|
||||
import { db, users } from '@/lib/db';
|
||||
import { sql } from 'drizzle-orm';
|
||||
|
||||
@@ -34,7 +34,7 @@ const generator = Astro.generator;
|
||||
<h1 class="text-3xl font-bold mb-2">Welcome to Gitea Mirror</h1>
|
||||
<p class="text-muted-foreground">Let's set up your administrator account to get started.</p>
|
||||
</div>
|
||||
<SignupForm client:load />
|
||||
<SignupPage client:load />
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user