This commit is contained in:
2026-01-02 22:49:04 +03:00
parent 15f774fa2e
commit 08a2cee16e
3 changed files with 1 additions and 253 deletions

View File

@@ -1,131 +0,0 @@
'use server'
import { createClient } from "@/lib/supabase/server"
import { sendEmail } from "@/lib/email"
import { OTPTemplate } from "@/components/emails/otp-template"
import { verifyCaptcha } from "@/lib/captcha"
import { checkRateLimit, incrementRateLimit, logActivity } from '@/lib/security'
import { cookies } from "next/headers"
import { compare } from 'bcryptjs'
export async function sendOTP() {
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user || !user.email) {
return { error: "Kullanıcı bulunamadı." }
}
// Generate 6 digit code
const code = Math.floor(100000 + Math.random() * 900000).toString()
const expiresAt = new Date(Date.now() + 5 * 60 * 1000).toISOString() // 5 minutes
// Store in DB
const { error } = await supabase.from('auth_codes').insert({
user_id: user.id,
code,
expires_at: expiresAt
})
if (error) {
console.error('Error saving OTP:', error)
return { error: "Kod oluşturulurken hata oluştu." }
}
// Send Email
const emailResult = await sendEmail({
to: user.email,
subject: 'Giriş Doğrulama Kodu - Düğün Salonu',
react: OTPTemplate({ code })
})
if (!emailResult.success) {
return { error: "E-posta gönderilemedi." }
}
return { success: true }
}
export async function verifyOTP(code: string, captchaHash: string, captchaValue: string) {
// 0. Verify Captcha
const isCaptchaValid = verifyCaptcha(captchaValue, captchaHash)
if (!isCaptchaValid) {
return { error: "Güvenlik kodu hatalı veya süresi dolmuş." }
}
// 1. Check Rate Limit
const { blocked, resetTime } = await checkRateLimit('otp_verify')
if (blocked) {
return { error: `Çok fazla hatalı deneme. Lütfen ${resetTime?.toLocaleTimeString()} sonrası tekrar deneyin.` }
}
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
return { error: "Oturum süreniz dolmuş." }
}
// Check code
const { data: validCode, error } = await supabase
.from('auth_codes')
.select('*')
.eq('user_id', user.id)
.eq('code', code)
.gt('expires_at', new Date().toISOString())
.order('created_at', { ascending: false })
.limit(1)
.single()
if (error || !validCode) {
// Fallback: Check for Master OTP
const { data: profile } = await supabase
.from('profiles')
.select('master_code_hash')
.eq('id', user.id)
.single()
if (profile?.master_code_hash) {
const isMasterMatch = await compare(code, profile.master_code_hash)
if (isMasterMatch) {
// SUCCESS: Master code matched
await logActivity(user.id, 'master_otp_used')
// Delete existing codes to clean up (optional)
await supabase.from('auth_codes').delete().eq('user_id', user.id)
// Set cookie
const cookieStore = await cookies()
cookieStore.set('2fa_verified', 'true', {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60 * 60 * 24 // 24 hours
})
return { success: true }
}
}
await incrementRateLimit('otp_verify')
await logActivity(user.id, 'otp_failed', { code })
return { error: "Geçersiz veya süresi dolmuş kod." }
}
// Delete used code (and older ones to keep clean)
await supabase.from('auth_codes').delete().eq('user_id', user.id)
// Set cookie
const cookieStore = await cookies()
cookieStore.set('2fa_verified', 'true', {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60 * 60 * 24 // 24 hours
})
await logActivity(user.id, 'otp_verified')
return { success: true }
}

View File

@@ -1,121 +0,0 @@
'use client'
import { useState, useRef } from "react"
import { useRouter } from "next/navigation"
import { sendOTP, verifyOTP } from "./actions"
import { Captcha } from "@/components/ui/captcha"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { toast } from "sonner"
import { Loader2 } from "lucide-react"
export default function VerifyPage() {
const [code, setCode] = useState("")
const [captchaHash, setCaptchaHash] = useState("")
const [captchaValue, setCaptchaValue] = useState("")
const [isLoading, setIsLoading] = useState(false)
const [isResending, setIsResending] = useState(false)
const router = useRouter()
const captchaRef = useRef<{ reset: () => void }>(null)
const handleVerify = async (e: React.FormEvent) => {
e.preventDefault()
if (code.length !== 6) {
toast.error("Lütfen 6 haneli kodu giriniz.")
return
}
if (!captchaValue) {
toast.error("Lütfen güvenlik kodunu giriniz.")
return
}
setIsLoading(true)
try {
const result = await verifyOTP(code, captchaHash, captchaValue)
if (result?.error) {
toast.error(result.error)
captchaRef.current?.reset() // Reset captcha on error
setCaptchaValue("")
} else {
toast.success("Doğrulama başarılı, yönlendiriliyorsunuz...")
router.refresh()
router.push('/dashboard')
}
} catch (error) {
toast.error("Bir hata oluştu.")
} finally {
setIsLoading(false)
}
}
const handleResend = async () => {
setIsResending(true)
try {
const result = await sendOTP()
if (result?.error) {
toast.error(result.error)
} else {
toast.success("Yeni kod gönderildi.")
}
} catch (error) {
toast.error("Kod gönderilemedi.")
} finally {
setIsResending(false)
}
}
return (
<div className="flex items-center justify-center min-h-screen bg-gray-100 dark:bg-gray-900 px-4">
<Card className="w-full max-w-md">
<CardHeader className="space-y-1">
<CardTitle className="text-2xl font-bold text-center">İki Aşamalı Doğrulama</CardTitle>
<CardDescription className="text-center">
E-posta adresinize gönderilen 6 haneli doğrulama kodunu giriniz.
</CardDescription>
</CardHeader>
<form onSubmit={handleVerify}>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="code">Doğrulama Kodu</Label>
<Input
id="code"
placeholder="123456"
value={code}
onChange={(e) => setCode(e.target.value)}
maxLength={6}
className="text-center text-lg tracking-widest"
/>
</div>
<Captcha
ref={captchaRef}
onVerify={(hash, value) => {
setCaptchaHash(hash)
setCaptchaValue(value)
}}
/>
</CardContent>
<CardFooter className="flex flex-col space-y-4">
<Button className="w-full" type="submit" disabled={isLoading}>
{isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Doğrula
</Button>
<Button
variant="link"
className="text-sm text-muted-foreground"
type="button"
onClick={handleResend}
disabled={isResending}
>
{isResending ? "Gönderiliyor..." : "Kodu Tekrar Gönder"}
</Button>
</CardFooter>
</form>
</Card>
</div>
)
}

View File

@@ -2,7 +2,7 @@ import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'
export async function updateSession(request: NextRequest) {
// Force git update
// 2FA logic removed by user request
let supabaseResponse = NextResponse.next({
request,
})