forked from hikmet4434/winninghunter
feat: Google OAuth ile giris
- User modeline googleId (unique) eklendi - google start/callback API route'lari (state korumali, find-or-create) - Login/register sayfalarina Google butonu - docker-compose'a GOOGLE_CLIENT_ID/SECRET env gecisi - tsconfig.tsbuildinfo gitignore'a eklendi
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { cookies } from "next/headers";
|
||||
import { UserRole } from "@prisma/client";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { createSession } from "@/lib/auth/session";
|
||||
import { isConfiguredAdminEmail } from "@/lib/admin-emails";
|
||||
import { exchangeGoogleCode, fetchGoogleProfile, GOOGLE_STATE_COOKIE } from "@/lib/auth/google";
|
||||
|
||||
function redirectToLogin(error: string) {
|
||||
return NextResponse.redirect(
|
||||
new URL(`/login?error=${encodeURIComponent(error)}`, process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000")
|
||||
);
|
||||
}
|
||||
|
||||
export async function GET(req: Request) {
|
||||
const url = new URL(req.url);
|
||||
const code = url.searchParams.get("code");
|
||||
const state = url.searchParams.get("state");
|
||||
const oauthError = url.searchParams.get("error");
|
||||
|
||||
if (oauthError) return redirectToLogin(oauthError);
|
||||
|
||||
const cookieStore = await cookies();
|
||||
const expectedState = cookieStore.get(GOOGLE_STATE_COOKIE)?.value;
|
||||
cookieStore.delete(GOOGLE_STATE_COOKIE);
|
||||
|
||||
if (!code || !state || !expectedState || state !== expectedState) {
|
||||
return redirectToLogin("GOOGLE_STATE_MISMATCH");
|
||||
}
|
||||
|
||||
let tokens;
|
||||
try {
|
||||
tokens = await exchangeGoogleCode(code);
|
||||
} catch (err) {
|
||||
console.error("[google/callback] token exchange failed:", err);
|
||||
return redirectToLogin("GOOGLE_AUTH_FAILED");
|
||||
}
|
||||
|
||||
let profile;
|
||||
try {
|
||||
profile = await fetchGoogleProfile(tokens.access_token);
|
||||
} catch (err) {
|
||||
console.error("[google/callback] profile fetch failed:", err);
|
||||
return redirectToLogin("GOOGLE_AUTH_FAILED");
|
||||
}
|
||||
|
||||
if (!profile.sub) return redirectToLogin("GOOGLE_AUTH_FAILED");
|
||||
|
||||
const email = (profile.email || "").trim().toLowerCase();
|
||||
if (!email) return redirectToLogin("GOOGLE_EMAIL_REQUIRED");
|
||||
|
||||
const isAdmin = isConfiguredAdminEmail(email);
|
||||
const plan = await prisma.plan.findUnique({ where: { code: isAdmin ? "PREMIUM" : "FREE" } });
|
||||
|
||||
let user = await prisma.user.findUnique({ where: { googleId: profile.sub } });
|
||||
if (!user) {
|
||||
user = await prisma.user.findUnique({ where: { email } });
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
if (!plan) return redirectToLogin("PLAN_NOT_SEEDED");
|
||||
user = await prisma.user.create({
|
||||
data: {
|
||||
email,
|
||||
googleId: profile.sub,
|
||||
name: profile.name || email.split("@")[0],
|
||||
avatarUrl: profile.picture || null,
|
||||
locale: profile.locale || "tr",
|
||||
role: isAdmin ? UserRole.ADMIN : UserRole.USER,
|
||||
subscription: { create: { planId: plan.id, status: "ACTIVE" } }
|
||||
}
|
||||
});
|
||||
} else {
|
||||
user = await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
googleId: user.googleId || profile.sub,
|
||||
name: user.name || profile.name || null,
|
||||
avatarUrl: user.avatarUrl || profile.picture || null,
|
||||
lastLoginAt: new Date()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await createSession(user.id);
|
||||
return NextResponse.redirect(
|
||||
new URL("/dashboard/ads", process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000")
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { cookies } from "next/headers";
|
||||
import { randomToken } from "@/lib/auth/crypto";
|
||||
import { googleAuthUrl, googleConfig, GOOGLE_STATE_COOKIE } from "@/lib/auth/google";
|
||||
|
||||
export async function GET() {
|
||||
const config = googleConfig();
|
||||
if (!config) {
|
||||
return NextResponse.redirect(
|
||||
new URL("/login?error=GOOGLE_NOT_CONFIGURED", process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000")
|
||||
);
|
||||
}
|
||||
|
||||
const state = randomToken(16);
|
||||
const cookieStore = await cookies();
|
||||
cookieStore.set(GOOGLE_STATE_COOKIE, state, {
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
path: "/",
|
||||
maxAge: 600
|
||||
});
|
||||
|
||||
const authUrl = googleAuthUrl(state);
|
||||
if (!authUrl) {
|
||||
return NextResponse.redirect(
|
||||
new URL("/login?error=GOOGLE_NOT_CONFIGURED", process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000")
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.redirect(authUrl);
|
||||
}
|
||||
Reference in New Issue
Block a user