Harden API security: input validation, safe auth extraction, new tests, and deploy config
Comprehensive security hardening from audit findings: - Add validation tags to all DTO request structs (max lengths, ranges, enums) - Replace unsafe type assertions with MustGetAuthUser helper across all handlers - Remove query-param token auth from admin middleware (prevents URL token leakage) - Add request validation calls in handlers that were missing c.Validate() - Remove goroutines in handlers (timezone update now synchronous) - Add sanitize middleware and path traversal protection (path_utils) - Stop resetting admin passwords on migration restart - Warn on well-known default SECRET_KEY - Add ~30 new test files covering security regressions, auth safety, repos, and services - Add deploy/ config, audit digests, and AUDIT_FINDINGS documentation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -40,9 +40,12 @@ func generateTrackingID() string {
|
||||
// HasSentEmail checks if a specific email type has already been sent to a user
|
||||
func (s *OnboardingEmailService) HasSentEmail(userID uint, emailType models.OnboardingEmailType) bool {
|
||||
var count int64
|
||||
s.db.Model(&models.OnboardingEmail{}).
|
||||
if err := s.db.Model(&models.OnboardingEmail{}).
|
||||
Where("user_id = ? AND email_type = ?", userID, emailType).
|
||||
Count(&count)
|
||||
Count(&count).Error; err != nil {
|
||||
log.Error().Err(err).Uint("user_id", userID).Str("email_type", string(emailType)).Msg("Failed to check if email was sent")
|
||||
return false
|
||||
}
|
||||
return count > 0
|
||||
}
|
||||
|
||||
@@ -125,23 +128,31 @@ func (s *OnboardingEmailService) GetEmailStats() (*OnboardingEmailStats, error)
|
||||
|
||||
// No residence email stats
|
||||
var noResTotal, noResOpened int64
|
||||
s.db.Model(&models.OnboardingEmail{}).
|
||||
if err := s.db.Model(&models.OnboardingEmail{}).
|
||||
Where("email_type = ?", models.OnboardingEmailNoResidence).
|
||||
Count(&noResTotal)
|
||||
s.db.Model(&models.OnboardingEmail{}).
|
||||
Count(&noResTotal).Error; err != nil {
|
||||
log.Error().Err(err).Msg("Failed to count no-residence emails")
|
||||
}
|
||||
if err := s.db.Model(&models.OnboardingEmail{}).
|
||||
Where("email_type = ? AND opened_at IS NOT NULL", models.OnboardingEmailNoResidence).
|
||||
Count(&noResOpened)
|
||||
Count(&noResOpened).Error; err != nil {
|
||||
log.Error().Err(err).Msg("Failed to count opened no-residence emails")
|
||||
}
|
||||
stats.NoResidenceTotal = noResTotal
|
||||
stats.NoResidenceOpened = noResOpened
|
||||
|
||||
// No tasks email stats
|
||||
var noTasksTotal, noTasksOpened int64
|
||||
s.db.Model(&models.OnboardingEmail{}).
|
||||
if err := s.db.Model(&models.OnboardingEmail{}).
|
||||
Where("email_type = ?", models.OnboardingEmailNoTasks).
|
||||
Count(&noTasksTotal)
|
||||
s.db.Model(&models.OnboardingEmail{}).
|
||||
Count(&noTasksTotal).Error; err != nil {
|
||||
log.Error().Err(err).Msg("Failed to count no-tasks emails")
|
||||
}
|
||||
if err := s.db.Model(&models.OnboardingEmail{}).
|
||||
Where("email_type = ? AND opened_at IS NOT NULL", models.OnboardingEmailNoTasks).
|
||||
Count(&noTasksOpened)
|
||||
Count(&noTasksOpened).Error; err != nil {
|
||||
log.Error().Err(err).Msg("Failed to count opened no-tasks emails")
|
||||
}
|
||||
stats.NoTasksTotal = noTasksTotal
|
||||
stats.NoTasksOpened = noTasksOpened
|
||||
|
||||
@@ -351,7 +362,9 @@ func (s *OnboardingEmailService) SendOnboardingEmailToUser(userID uint, emailTyp
|
||||
// If already sent before, delete the old record first to allow re-recording
|
||||
// This allows admins to "resend" emails while still tracking them
|
||||
if alreadySent {
|
||||
s.db.Where("user_id = ? AND email_type = ?", userID, emailType).Delete(&models.OnboardingEmail{})
|
||||
if err := s.db.Where("user_id = ? AND email_type = ?", userID, emailType).Delete(&models.OnboardingEmail{}).Error; err != nil {
|
||||
log.Error().Err(err).Uint("user_id", userID).Str("email_type", string(emailType)).Msg("Failed to delete old onboarding email record before resend")
|
||||
}
|
||||
}
|
||||
|
||||
// Record that email was sent
|
||||
|
||||
Reference in New Issue
Block a user