feat: Add security handler with fraud detection, SIM swap verification, and alert management

- Add SecurityHandler with fraud analysis, alert filtering, and metrics endpoints
- Add FraudAlert struct with ID, Type, Severity, ProfileID, RiskScore, Evidence, IPAddress, Status, and ActionsTaken fields
- Add FraudMetrics struct with Period, TotalAlerts, ResolvedAlerts, ResolutionRate, FalsePositiveRate, ByType, and BySeverity fields
- Add AnalyzeTransaction with risk scoring based on transaction amount and suspicious
This commit is contained in:
nutcas3
2026-05-03 13:19:40 +03:00
parent ef374ce122
commit 347698be10
@@ -0,0 +1,314 @@
package handlers
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
)
// SecurityHandler handles security-related HTTP requests
type SecurityHandler struct{}
// NewSecurityHandler creates a new security handler
func NewSecurityHandler() *SecurityHandler {
return &SecurityHandler{}
}
// FraudAlert represents a fraud alert
type FraudAlert struct {
ID string `json:"id"`
Type string `json:"type"`
Severity string `json:"severity"`
ProfileID string `json:"profile_id"`
Description string `json:"description"`
RiskScore float64 `json:"risk_score"`
Evidence []string `json:"evidence"`
IPAddress string `json:"ip_address"`
Timestamp time.Time `json:"timestamp"`
Status string `json:"status"`
ActionsTaken []string `json:"actions_taken"`
Metadata map[string]any `json:"metadata"`
}
// FraudMetrics represents fraud detection metrics
type FraudMetrics struct {
Period string `json:"period"`
TotalAlerts int64 `json:"total_alerts"`
ResolvedAlerts int64 `json:"resolved_alerts"`
FalsePositives int64 `json:"false_positives"`
ResolutionRate float64 `json:"resolution_rate_pct"`
FalsePositiveRate float64 `json:"false_positive_rate_pct"`
ByType map[string]int64 `json:"by_type"`
BySeverity map[string]int64 `json:"by_severity"`
GeneratedAt time.Time `json:"generated_at"`
}
// AnalyzeTransaction analyzes a transaction for fraud
func (h *SecurityHandler) AnalyzeTransaction(c *gin.Context) {
var req map[string]any
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
profileID, _ := req["profile_id"].(string)
ipAddress, _ := req["ip_address"].(string)
// Simulated fraud analysis
riskScore := 25.0 // Low risk by default
// Check for suspicious patterns
if amount, ok := req["amount"].(float64); ok && amount > 1000 {
riskScore += 20
}
alert := FraudAlert{
ID: "alert-" + time.Now().Format("20060102150405"),
Type: "transaction_analysis",
Severity: getSeverityFromScore(riskScore),
ProfileID: profileID,
Description: "Transaction analyzed for fraud indicators",
RiskScore: riskScore,
Evidence: []string{
"Transaction amount within normal range",
"IP address matches historical pattern",
"Device fingerprint recognized",
},
IPAddress: ipAddress,
Timestamp: time.Now(),
Status: "analyzed",
ActionsTaken: []string{"Logged for monitoring"},
Metadata: req,
}
c.JSON(http.StatusOK, alert)
}
// GetFraudAlerts returns fraud alerts with filtering
func (h *SecurityHandler) GetFraudAlerts(c *gin.Context) {
var filter struct {
Type string `json:"type"`
Severity string `json:"severity"`
Status string `json:"status"`
Limit int `json:"limit"`
}
c.ShouldBindJSON(&filter)
if filter.Limit == 0 || filter.Limit > 100 {
filter.Limit = 50
}
// Simulated alerts
alerts := []FraudAlert{
{
ID: "alert-001",
Type: "account_takeover",
Severity: "high",
ProfileID: "profile-123",
Description: "Multiple failed login attempts from new IP",
RiskScore: 85.0,
Evidence: []string{"10 failed logins", "New IP address", "Different country"},
IPAddress: "192.168.1.100",
Timestamp: time.Now().Add(-2 * time.Hour),
Status: "new",
},
{
ID: "alert-002",
Type: "payment_fraud",
Severity: "medium",
ProfileID: "profile-456",
Description: "Unusual payment pattern detected",
RiskScore: 55.0,
Evidence: []string{"Large transaction", "New payment method"},
IPAddress: "10.0.0.50",
Timestamp: time.Now().Add(-5 * time.Hour),
Status: "investigating",
},
{
ID: "alert-003",
Type: "sim_swap",
Severity: "critical",
ProfileID: "profile-789",
Description: "SIM swap attempt detected",
RiskScore: 95.0,
Evidence: []string{"SIM change request", "No prior notification", "High-value account"},
IPAddress: "172.16.0.25",
Timestamp: time.Now().Add(-30 * time.Minute),
Status: "blocked",
},
}
// Filter alerts
filtered := make([]FraudAlert, 0)
for _, alert := range alerts {
if filter.Type != "" && alert.Type != filter.Type {
continue
}
if filter.Severity != "" && alert.Severity != filter.Severity {
continue
}
if filter.Status != "" && alert.Status != filter.Status {
continue
}
filtered = append(filtered, alert)
if len(filtered) >= filter.Limit {
break
}
}
c.JSON(http.StatusOK, filtered)
}
// UpdateAlertStatus updates a fraud alert status
func (h *SecurityHandler) UpdateAlertStatus(c *gin.Context) {
alertID := c.Param("id")
var req struct {
Status string `json:"status" binding:"required"`
Actions []string `json:"actions"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"id": alertID,
"status": req.Status,
"actions_taken": req.Actions,
"updated_at": time.Now(),
})
}
// GetFraudMetrics returns fraud detection metrics
func (h *SecurityHandler) GetFraudMetrics(c *gin.Context) {
period := c.DefaultQuery("period", "monthly")
metrics := FraudMetrics{
Period: period,
TotalAlerts: 1250,
ResolvedAlerts: 1100,
FalsePositives: 125,
ResolutionRate: 88.0,
FalsePositiveRate: 10.0,
ByType: map[string]int64{
"account_takeover": 350,
"subscription_fraud": 200,
"payment_fraud": 400,
"usage_anomaly": 200,
"sim_swap": 100,
},
BySeverity: map[string]int64{
"low": 400,
"medium": 500,
"high": 250,
"critical": 100,
},
GeneratedAt: time.Now(),
}
c.JSON(http.StatusOK, metrics)
}
// GetFraudPatterns returns detected fraud patterns
func (h *SecurityHandler) GetFraudPatterns(c *gin.Context) {
patterns := []map[string]any{
{
"id": "pattern-1",
"name": "Velocity Attack",
"description": "Multiple rapid transactions from same source",
"frequency": "high",
"indicators": []string{"High transaction rate", "Same IP", "Different accounts"},
"mitigation": "Rate limiting, IP blocking",
},
{
"id": "pattern-2",
"name": "Account Enumeration",
"description": "Systematic probing of account existence",
"frequency": "medium",
"indicators": []string{"Sequential account checks", "Automated requests"},
"mitigation": "CAPTCHA, rate limiting",
},
{
"id": "pattern-3",
"name": "SIM Swap Attack",
"description": "Unauthorized SIM card replacement",
"frequency": "low",
"indicators": []string{"Sudden SIM change", "No customer contact", "High-value target"},
"mitigation": "Multi-factor verification, cooling period",
},
}
c.JSON(http.StatusOK, gin.H{"patterns": patterns})
}
// VerifySIMSwap verifies a SIM swap request
func (h *SecurityHandler) VerifySIMSwap(c *gin.Context) {
var req struct {
ProfileID string `json:"profile_id" binding:"required"`
MSISDN string `json:"msisdn" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Simulated SIM swap verification
c.JSON(http.StatusOK, gin.H{
"profile_id": req.ProfileID,
"msisdn": req.MSISDN,
"verified": true,
"risk_score": 15.0,
"risk_level": "low",
"last_sim_swap": time.Now().AddDate(-1, 0, 0).Format("2006-01-02"),
"checks_passed": []string{
"Identity verified",
"No recent SIM changes",
"Account in good standing",
},
})
}
// GetSIMSwapHistory returns SIM swap history for a profile
func (h *SecurityHandler) GetSIMSwapHistory(c *gin.Context) {
profileID := c.Param("profile_id")
history := []map[string]any{
{
"id": "swap-001",
"profile_id": profileID,
"old_iccid": "8901234567890123456",
"new_iccid": "8901234567890123457",
"timestamp": time.Now().AddDate(-1, 0, 0),
"reason": "Device upgrade",
"verified": true,
"status": "completed",
},
{
"id": "swap-002",
"profile_id": profileID,
"old_iccid": "8901234567890123455",
"new_iccid": "8901234567890123456",
"timestamp": time.Now().AddDate(-2, 0, 0),
"reason": "Lost device",
"verified": true,
"status": "completed",
},
}
c.JSON(http.StatusOK, gin.H{"history": history})
}
func getSeverityFromScore(score float64) string {
switch {
case score >= 80:
return "critical"
case score >= 60:
return "high"
case score >= 40:
return "medium"
default:
return "low"
}
}