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:
@@ -52,7 +52,8 @@ func (r *DocumentRepository) FindByResidence(residenceID uint) ([]models.Documen
|
||||
return documents, err
|
||||
}
|
||||
|
||||
// FindByUser finds all documents accessible to a user
|
||||
// FindByUser finds all documents accessible to a user.
|
||||
// A default limit of 500 is applied to prevent unbounded result sets.
|
||||
func (r *DocumentRepository) FindByUser(residenceIDs []uint) ([]models.Document, error) {
|
||||
var documents []models.Document
|
||||
err := r.db.Preload("CreatedBy").
|
||||
@@ -60,6 +61,7 @@ func (r *DocumentRepository) FindByUser(residenceIDs []uint) ([]models.Document,
|
||||
Preload("Images").
|
||||
Where("residence_id IN ? AND is_active = ?", residenceIDs, true).
|
||||
Order("created_at DESC").
|
||||
Limit(500).
|
||||
Find(&documents).Error
|
||||
return documents, err
|
||||
}
|
||||
@@ -89,7 +91,8 @@ func (r *DocumentRepository) FindByUserFiltered(residenceIDs []uint, filter *Doc
|
||||
query = query.Where("expiry_date IS NOT NULL AND expiry_date > ? AND expiry_date <= ?", now, threshold)
|
||||
}
|
||||
if filter.Search != "" {
|
||||
searchPattern := "%" + filter.Search + "%"
|
||||
escaped := escapeLikeWildcards(filter.Search)
|
||||
searchPattern := "%" + escaped + "%"
|
||||
query = query.Where("(title ILIKE ? OR description ILIKE ?)", searchPattern, searchPattern)
|
||||
}
|
||||
}
|
||||
@@ -169,6 +172,19 @@ func (r *DocumentRepository) CountByResidence(residenceID uint) (int64, error) {
|
||||
return count, err
|
||||
}
|
||||
|
||||
// CountByResidenceIDs counts all active documents across multiple residences in a single query.
|
||||
// Returns the total count of active documents for the given residence IDs.
|
||||
func (r *DocumentRepository) CountByResidenceIDs(residenceIDs []uint) (int64, error) {
|
||||
if len(residenceIDs) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
var count int64
|
||||
err := r.db.Model(&models.Document{}).
|
||||
Where("residence_id IN ? AND is_active = ?", residenceIDs, true).
|
||||
Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// FindByIDIncludingInactive finds a document by ID including inactive ones
|
||||
func (r *DocumentRepository) FindByIDIncludingInactive(id uint, document *models.Document) error {
|
||||
return r.db.Preload("CreatedBy").Preload("Images").First(document, id).Error
|
||||
|
||||
Reference in New Issue
Block a user