hata düzeltme

This commit is contained in:
2026-01-02 22:33:24 +03:00
parent aa5c77b4c7
commit 53006601ba
5 changed files with 13 additions and 229 deletions

View File

@@ -10,6 +10,12 @@ const nextConfig: NextConfig = {
port: '', port: '',
pathname: '/storage/v1/object/public/**', pathname: '/storage/v1/object/public/**',
}, },
{
protocol: 'https',
hostname: 'api-weeding.edoysoft.com',
port: '',
pathname: '/storage/v1/object/public/**',
},
{ {
protocol: 'https', protocol: 'https',
hostname: 'img.youtube.com', hostname: 'img.youtube.com',

View File

@@ -3,8 +3,7 @@
import { revalidatePath } from 'next/cache' import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation' import { redirect } from 'next/navigation'
import { createClient } from '@/lib/supabase/server' import { createClient } from '@/lib/supabase/server'
import { sendOTP } from '../verify/actions' import { logActivity } from '@/lib/security'
import { checkRateLimit, incrementRateLimit, logActivity } from '@/lib/security'
export type LoginState = { export type LoginState = {
error?: string error?: string
@@ -17,30 +16,19 @@ export async function login(prevState: LoginState, formData: FormData): Promise<
const email = formData.get('email') as string const email = formData.get('email') as string
const password = formData.get('password') 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({ const { data: { user }, error } = await supabase.auth.signInWithPassword({
email, email,
password, password,
}) })
if (error) { if (error) {
await incrementRateLimit('login_attempt')
await logActivity(null, 'login_failed', { email, error: error.message }) await logActivity(null, 'login_failed', { email, error: error.message })
return { error: 'Giriş yapılamadı. E-posta veya şifre hatalı.' } return { error: 'Giriş yapılamadı. E-posta veya şifre hatalı.' }
} }
await logActivity(user?.id || null, 'login_success', { email }) await logActivity(user?.id || null, 'login_success', { email })
revalidatePath('/', 'layout')
revalidatePath('/', 'layout') revalidatePath('/', 'layout')
// Trigger OTP email redirect('/dashboard')
await sendOTP()
redirect('/verify')
} }

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -36,22 +36,17 @@ export async function updateSession(request: NextRequest) {
if ( if (
!user && !user &&
!request.nextUrl.pathname.startsWith('/login') && !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() const url = request.nextUrl.clone()
url.pathname = '/login' url.pathname = '/login'
return NextResponse.redirect(url) return NextResponse.redirect(url)
} }
// 2FA Enforcement // 2FA Enforcement Removed
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)
}
}
return supabaseResponse return supabaseResponse
} }