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'
|
import { NextResponse, type NextRequest } from 'next/server'
|
||||||
|
|
||||||
export async function updateSession(request: NextRequest) {
|
export async function updateSession(request: NextRequest) {
|
||||||
// Force git update
|
// 2FA logic removed by user request
|
||||||
let supabaseResponse = NextResponse.next({
|
let supabaseResponse = NextResponse.next({
|
||||||
request,
|
request,
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user