Security: - requireAdmin() session check added to every admin-only server action (previously relied only on middleware path matching, which Next.js Server Actions don't reliably respect) - Real Prisma + bcrypt admin auth, replacing hardcoded credentials; split into an Edge-safe auth.config.ts (used by proxy.ts) and the full Prisma-backed auth.ts (route handler, server actions, server components) - Removed hardcoded fallback secret on the Instagram sync cron endpoint - Honeypot field + per-IP rate limiting on contact/business-submission forms and the analytics events endpoint Features: - AI trip planner (/plan-olustur, /plan/[id]) backed by DeepSeek, grounded to only recommend isLocalApproved listings, with a deterministic link-injection fallback for anything the model doesn't format as markdown - Interactive Leaflet/OpenStreetMap view on category listing pages - Telegram notifications for new contact messages and business submissions SEO: - Brand-consistent favicon/apple-icon/PWA icons and default Open Graph/ Twitter share images, generated via next/og (replacing default Next.js placeholders) - BreadcrumbList structured data on category and listing detail pages - Fixed two remaining raw <img> tags to use next/image Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
47 lines
1.4 KiB
TypeScript
47 lines
1.4 KiB
TypeScript
import NextAuth from "next-auth"
|
|
import CredentialsProvider from "next-auth/providers/credentials"
|
|
import bcrypt from "bcryptjs"
|
|
|
|
import { authConfig } from "./auth.config"
|
|
import { db } from "./db"
|
|
|
|
export const { handlers, auth, signIn, signOut } = NextAuth({
|
|
...authConfig,
|
|
providers: [
|
|
CredentialsProvider({
|
|
name: "Credentials",
|
|
credentials: {
|
|
email: { label: "Email", type: "email" },
|
|
password: { label: "Password", type: "password" }
|
|
},
|
|
async authorize(credentials) {
|
|
const email = credentials?.email as string | undefined
|
|
const password = credentials?.password as string | undefined
|
|
if (!email || !password) return null
|
|
|
|
const user = await db.user.findUnique({ where: { email } })
|
|
if (!user?.password || user.role !== "ADMIN") return null
|
|
|
|
const isValid = await bcrypt.compare(password, user.password)
|
|
if (!isValid) return null
|
|
|
|
return {
|
|
id: user.id,
|
|
name: user.name,
|
|
email: user.email,
|
|
role: user.role
|
|
}
|
|
}
|
|
})
|
|
]
|
|
})
|
|
|
|
/** Server actions / route handlers should call this before any admin-only mutation. */
|
|
export async function requireAdmin() {
|
|
const session = await auth()
|
|
if (!session || (session.user as any)?.role !== "ADMIN") {
|
|
throw new Error("Yetkisiz erişim: Bu işlem için admin girişi gerekli.")
|
|
}
|
|
return session
|
|
}
|