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:
@@ -1,6 +1,7 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
@@ -130,18 +131,25 @@ func (r *NotificationRepository) CreatePreferences(prefs *models.NotificationPre
|
||||
|
||||
// UpdatePreferences updates notification preferences
|
||||
func (r *NotificationRepository) UpdatePreferences(prefs *models.NotificationPreference) error {
|
||||
return r.db.Save(prefs).Error
|
||||
return r.db.Omit("User").Save(prefs).Error
|
||||
}
|
||||
|
||||
// GetOrCreatePreferences gets or creates notification preferences for a user
|
||||
// GetOrCreatePreferences gets or creates notification preferences for a user.
|
||||
// Uses a transaction to avoid TOCTOU race conditions on concurrent requests.
|
||||
func (r *NotificationRepository) GetOrCreatePreferences(userID uint) (*models.NotificationPreference, error) {
|
||||
prefs, err := r.FindPreferencesByUser(userID)
|
||||
if err == nil {
|
||||
return prefs, nil
|
||||
}
|
||||
var prefs models.NotificationPreference
|
||||
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
prefs = &models.NotificationPreference{
|
||||
err := r.db.Transaction(func(tx *gorm.DB) error {
|
||||
err := tx.Where("user_id = ?", userID).First(&prefs).Error
|
||||
if err == nil {
|
||||
return nil // Found existing preferences
|
||||
}
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err // Unexpected error
|
||||
}
|
||||
|
||||
// Record not found -- create with defaults
|
||||
prefs = models.NotificationPreference{
|
||||
UserID: userID,
|
||||
TaskDueSoon: true,
|
||||
TaskOverdue: true,
|
||||
@@ -151,17 +159,36 @@ func (r *NotificationRepository) GetOrCreatePreferences(userID uint) (*models.No
|
||||
WarrantyExpiring: true,
|
||||
EmailTaskCompleted: true,
|
||||
}
|
||||
if err := r.CreatePreferences(prefs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return prefs, nil
|
||||
return tx.Create(&prefs).Error
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, err
|
||||
return &prefs, nil
|
||||
}
|
||||
|
||||
// === Device Registration ===
|
||||
|
||||
// FindAPNSDeviceByID finds an APNS device by ID
|
||||
func (r *NotificationRepository) FindAPNSDeviceByID(id uint) (*models.APNSDevice, error) {
|
||||
var device models.APNSDevice
|
||||
err := r.db.First(&device, id).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &device, nil
|
||||
}
|
||||
|
||||
// FindGCMDeviceByID finds a GCM device by ID
|
||||
func (r *NotificationRepository) FindGCMDeviceByID(id uint) (*models.GCMDevice, error) {
|
||||
var device models.GCMDevice
|
||||
err := r.db.First(&device, id).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &device, nil
|
||||
}
|
||||
|
||||
// FindAPNSDeviceByToken finds an APNS device by registration token
|
||||
func (r *NotificationRepository) FindAPNSDeviceByToken(token string) (*models.APNSDevice, error) {
|
||||
var device models.APNSDevice
|
||||
@@ -243,12 +270,12 @@ func (r *NotificationRepository) DeactivateGCMDevice(id uint) error {
|
||||
// GetActiveTokensForUser gets all active push tokens for a user
|
||||
func (r *NotificationRepository) GetActiveTokensForUser(userID uint) (iosTokens []string, androidTokens []string, err error) {
|
||||
apnsDevices, err := r.FindAPNSDevicesByUser(userID)
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
gcmDevices, err := r.FindGCMDevicesByUser(userID)
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user