hata düzeltme
This commit is contained in:
@@ -10,6 +10,12 @@ const nextConfig: NextConfig = {
|
||||
port: '',
|
||||
pathname: '/storage/v1/object/public/**',
|
||||
},
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'api-weeding.edoysoft.com',
|
||||
port: '',
|
||||
pathname: '/storage/v1/object/public/**',
|
||||
},
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'img.youtube.com',
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
import { revalidatePath } from 'next/cache'
|
||||
import { redirect } from 'next/navigation'
|
||||
import { createClient } from '@/lib/supabase/server'
|
||||
import { sendOTP } from '../verify/actions'
|
||||
import { checkRateLimit, incrementRateLimit, logActivity } from '@/lib/security'
|
||||
import { logActivity } from '@/lib/security'
|
||||
|
||||
export type LoginState = {
|
||||
error?: string
|
||||
@@ -17,30 +16,19 @@ export async function login(prevState: LoginState, formData: FormData): Promise<
|
||||
const email = formData.get('email') as string
|
||||
const password = formData.get('password') as string
|
||||
|
||||
// 1. Check Rate Limit
|
||||
const { blocked, resetTime } = await checkRateLimit('login_attempt')
|
||||
if (blocked) {
|
||||
return { error: `Çok fazla hatalı deneme. Lütfen ${resetTime?.toLocaleTimeString()} sonrası tekrar deneyin.` }
|
||||
}
|
||||
|
||||
const { data: { user }, error } = await supabase.auth.signInWithPassword({
|
||||
email,
|
||||
password,
|
||||
})
|
||||
|
||||
if (error) {
|
||||
await incrementRateLimit('login_attempt')
|
||||
await logActivity(null, 'login_failed', { email, error: error.message })
|
||||
return { error: 'Giriş yapılamadı. E-posta veya şifre hatalı.' }
|
||||
}
|
||||
|
||||
await logActivity(user?.id || null, 'login_success', { email })
|
||||
|
||||
revalidatePath('/', 'layout')
|
||||
revalidatePath('/', 'layout')
|
||||
|
||||
// Trigger OTP email
|
||||
await sendOTP()
|
||||
|
||||
redirect('/verify')
|
||||
redirect('/dashboard')
|
||||
}
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
import { createHmac } from 'crypto'
|
||||
|
||||
export interface CaptchaData {
|
||||
image: string // SVG string
|
||||
hash: string // HMAC hash of the text
|
||||
}
|
||||
|
||||
const CAPTCHA_SECRET = process.env.CAPTCHA_SECRET || 'default-secret-change-me'
|
||||
|
||||
export function generateCaptcha(width = 200, height = 80): { text: string, data: string } {
|
||||
console.log('[Captcha] Generating new captcha...')
|
||||
// 1. Generate random text (5 chars)
|
||||
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789' // Removed confusing chars like I, 1, 0, O
|
||||
let text = ''
|
||||
for (let i = 0; i < 5; i++) {
|
||||
text += chars.charAt(Math.floor(Math.random() * chars.length))
|
||||
}
|
||||
console.log('[Captcha] Generated text:', text)
|
||||
|
||||
// 2. Create SVG
|
||||
const bg = '#f3f4f6'
|
||||
const fg = '#374151'
|
||||
|
||||
// Random noise lines
|
||||
let noise = ''
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const x1 = Math.random() * width
|
||||
const y1 = Math.random() * height
|
||||
const x2 = Math.random() * width
|
||||
const y2 = Math.random() * height
|
||||
noise += `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="${fg}" stroke-width="1" opacity="0.3" />`
|
||||
}
|
||||
|
||||
// Random noise dots
|
||||
for (let i = 0; i < 30; i++) {
|
||||
const x = Math.random() * width
|
||||
const y = Math.random() * height
|
||||
noise += `<circle cx="${x}" cy="${y}" r="1" fill="${fg}" opacity="0.5" />`
|
||||
}
|
||||
|
||||
// Text with slight rotation/position randomization
|
||||
let svgText = ''
|
||||
const fontSize = 32
|
||||
const startX = 20
|
||||
const spacing = 35
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const char = text[i]
|
||||
const x = startX + (i * spacing) + (Math.random() * 10 - 5)
|
||||
const y = (height / 2) + (fontSize / 3) + (Math.random() * 10 - 5)
|
||||
const rotate = Math.random() * 40 - 20 // +/- 20 degrees
|
||||
|
||||
svgText += `<text x="${x}" y="${y}" font-family="monospace" font-weight="bold" font-size="${fontSize}" fill="${fg}" transform="rotate(${rotate}, ${x}, ${y})">${char}</text>`
|
||||
}
|
||||
|
||||
const svg = `
|
||||
<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="100%" height="100%" fill="${bg}"/>
|
||||
${noise}
|
||||
${svgText}
|
||||
</svg>`
|
||||
|
||||
return { text, data: svg }
|
||||
}
|
||||
|
||||
export function signCaptcha(text: string): string {
|
||||
const expires = Date.now() + 5 * 60 * 1000 // 5 minutes
|
||||
const data = `${text.toUpperCase()}|${expires}`
|
||||
const signature = createHmac('sha256', CAPTCHA_SECRET).update(data).digest('hex')
|
||||
return `${data}|${signature}`
|
||||
}
|
||||
|
||||
export function verifyCaptcha(input: string, hash: string): boolean {
|
||||
if (!input || !hash) {
|
||||
console.log('[Captcha] Missing input or hash')
|
||||
return false
|
||||
}
|
||||
|
||||
const parts = hash.split('|')
|
||||
if (parts.length !== 3) {
|
||||
console.log('[Captcha] Invalid hash format')
|
||||
return false
|
||||
}
|
||||
|
||||
const [originalText, expiresStr, signature] = parts
|
||||
const expires = parseInt(expiresStr, 10)
|
||||
|
||||
// Check expiration
|
||||
if (Date.now() > expires) {
|
||||
console.log('[Captcha] Expired. Now:', Date.now(), 'Expires:', expires)
|
||||
return false
|
||||
}
|
||||
|
||||
// Check signature integrity
|
||||
const expectedData = `${originalText}|${expiresStr}`
|
||||
const expectedSignature = createHmac('sha256', CAPTCHA_SECRET).update(expectedData).digest('hex')
|
||||
|
||||
if (signature !== expectedSignature) {
|
||||
console.log('[Captcha] Signature mismatch')
|
||||
return false
|
||||
}
|
||||
|
||||
// Check content match
|
||||
const isValid = input.toUpperCase() === originalText
|
||||
if (!isValid) {
|
||||
console.log('[Captcha] Text mismatch. Expected:', originalText, 'Got:', input.toUpperCase())
|
||||
}
|
||||
return isValid
|
||||
}
|
||||
@@ -33,100 +33,4 @@ export async function logActivity(
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkRateLimit(action: string): Promise<{ blocked: boolean, remaining?: number, resetTime?: Date }> {
|
||||
const MAX_ATTEMPTS = 5
|
||||
|
||||
try {
|
||||
const supabase = await createAdminClient() || await createClient()
|
||||
const headersList = await headers()
|
||||
let ip = headersList.get("x-forwarded-for") || headersList.get("x-real-ip") || 'unknown'
|
||||
if (ip.includes(',')) ip = ip.split(',')[0].trim()
|
||||
if (ip === '::1') ip = '127.0.0.1'
|
||||
|
||||
// Clean up old limits
|
||||
// Clean up old limits (logic simplified, variable unused)
|
||||
|
||||
// Check current limit
|
||||
const { data: limit } = await supabase
|
||||
.from('rate_limits')
|
||||
.select('*')
|
||||
.eq('ip_address', ip)
|
||||
.eq('action', action)
|
||||
.single()
|
||||
|
||||
if (!limit) {
|
||||
return { blocked: false, remaining: MAX_ATTEMPTS }
|
||||
}
|
||||
|
||||
// If blocked
|
||||
if (limit.blocked_until && new Date(limit.blocked_until) > new Date()) {
|
||||
return { blocked: true, resetTime: new Date(limit.blocked_until) }
|
||||
}
|
||||
|
||||
// If window expired, reset code handling happens in increment logic usually.
|
||||
// But here we just check.
|
||||
// Actually, simpler logic:
|
||||
// We will increment on failure. This function just checks if currently blocked.
|
||||
|
||||
return { blocked: false, remaining: MAX_ATTEMPTS - (limit.count || 0) }
|
||||
|
||||
} catch (error) {
|
||||
console.error('Rate limit check failed:', error)
|
||||
return { blocked: false } // Fail open
|
||||
}
|
||||
}
|
||||
|
||||
export async function incrementRateLimit(action: string) {
|
||||
const BLOCK_DURATION_MINUTES = 15
|
||||
|
||||
try {
|
||||
const supabase = await createAdminClient() || await createClient()
|
||||
const headersList = await headers()
|
||||
let ip = headersList.get("x-forwarded-for") || headersList.get("x-real-ip") || 'unknown'
|
||||
if (ip.includes(',')) ip = ip.split(',')[0].trim()
|
||||
if (ip === '::1') ip = '127.0.0.1'
|
||||
|
||||
const { data: limit } = await supabase
|
||||
.from('rate_limits')
|
||||
.select('*')
|
||||
.eq('ip_address', ip)
|
||||
.eq('action', action)
|
||||
.single()
|
||||
|
||||
if (limit) {
|
||||
// Check if we should reset (if last attempt was long ago)
|
||||
const lastAttempt = new Date(limit.last_attempt)
|
||||
const tenMinutesAgo = new Date(Date.now() - 10 * 60 * 1000)
|
||||
|
||||
let newCount = limit.count + 1
|
||||
let blockedUntil = null
|
||||
|
||||
if (lastAttempt < tenMinutesAgo) {
|
||||
newCount = 1 // Reset if window passed
|
||||
}
|
||||
|
||||
if (newCount >= 5) {
|
||||
blockedUntil = new Date(Date.now() + BLOCK_DURATION_MINUTES * 60 * 1000).toISOString()
|
||||
}
|
||||
|
||||
await supabase
|
||||
.from('rate_limits')
|
||||
.update({
|
||||
count: newCount,
|
||||
last_attempt: new Date().toISOString(),
|
||||
blocked_until: blockedUntil
|
||||
})
|
||||
.eq('id', limit.id)
|
||||
} else {
|
||||
await supabase.from('rate_limits').insert({
|
||||
ip_address: ip,
|
||||
action,
|
||||
count: 1,
|
||||
last_attempt: new Date().toISOString()
|
||||
})
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Rate limit increment failed:', error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,22 +36,17 @@ export async function updateSession(request: NextRequest) {
|
||||
if (
|
||||
!user &&
|
||||
!request.nextUrl.pathname.startsWith('/login') &&
|
||||
!request.nextUrl.pathname.startsWith('/auth')
|
||||
!request.nextUrl.pathname.startsWith('/auth') &&
|
||||
request.nextUrl.pathname !== '/' &&
|
||||
!request.nextUrl.pathname.startsWith('/galeri')
|
||||
) {
|
||||
const url = request.nextUrl.clone()
|
||||
url.pathname = '/login'
|
||||
return NextResponse.redirect(url)
|
||||
}
|
||||
|
||||
// 2FA Enforcement
|
||||
if (user && !request.nextUrl.pathname.startsWith('/verify')) {
|
||||
const verifiedCookie = request.cookies.get('2fa_verified')
|
||||
if (!verifiedCookie) {
|
||||
const url = request.nextUrl.clone()
|
||||
url.pathname = '/verify'
|
||||
return NextResponse.redirect(url)
|
||||
}
|
||||
}
|
||||
// 2FA Enforcement Removed
|
||||
|
||||
|
||||
return supabaseResponse
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user