Güvenlik Doğrulaması,Login Logları
This commit is contained in:
133
src/lib/security.ts
Normal file
133
src/lib/security.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { createClient } from "@/lib/supabase/server"
|
||||
import { createAdminClient } from "@/lib/supabase/admin"
|
||||
import { headers } from "next/headers"
|
||||
|
||||
export type SecurityEventType = 'login_success' | 'login_failed' | 'otp_sent' | 'otp_verified' | 'otp_failed' | 'logout' | 'master_otp_used'
|
||||
|
||||
export async function logActivity(
|
||||
userId: string | null,
|
||||
eventType: SecurityEventType,
|
||||
details: Record<string, any> = {}
|
||||
) {
|
||||
try {
|
||||
// Use Admin Client to bypass RLS for inserting logs
|
||||
// This is crucial because logging often happens when user is not yet authenticated (e.g. login failed)
|
||||
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 userAgent = headersList.get("user-agent") || 'unknown'
|
||||
|
||||
await supabase.from('auth_logs').insert({
|
||||
user_id: userId,
|
||||
event_type: eventType,
|
||||
ip_address: ip,
|
||||
user_agent: userAgent,
|
||||
details
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Failed to log activity:', error)
|
||||
// Fail silently to not block user flow
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkRateLimit(action: string): Promise<{ blocked: boolean, remaining?: number, resetTime?: Date }> {
|
||||
const MAX_ATTEMPTS = 5
|
||||
const WINDOW_MINUTES = 10
|
||||
|
||||
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
|
||||
const windowStart = new Date(Date.now() - WINDOW_MINUTES * 60 * 1000).toISOString()
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user