dd
This commit is contained in:
@@ -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 }
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user