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:
@@ -355,20 +355,43 @@ func (s *NotificationService) ListDevices(userID uint) ([]DeviceResponse, error)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// DeleteDevice deletes a device
|
||||
// DeleteDevice deactivates a device after verifying it belongs to the requesting user.
|
||||
// Without ownership verification, an attacker could deactivate push notifications for other users.
|
||||
func (s *NotificationService) DeleteDevice(deviceID uint, platform string, userID uint) error {
|
||||
var err error
|
||||
switch platform {
|
||||
case push.PlatformIOS:
|
||||
err = s.notificationRepo.DeactivateAPNSDevice(deviceID)
|
||||
device, err := s.notificationRepo.FindAPNSDeviceByID(deviceID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return apperrors.NotFound("error.device_not_found")
|
||||
}
|
||||
return apperrors.Internal(err)
|
||||
}
|
||||
// Verify the device belongs to the requesting user
|
||||
if device.UserID == nil || *device.UserID != userID {
|
||||
return apperrors.Forbidden("error.device_access_denied")
|
||||
}
|
||||
if err := s.notificationRepo.DeactivateAPNSDevice(deviceID); err != nil {
|
||||
return apperrors.Internal(err)
|
||||
}
|
||||
case push.PlatformAndroid:
|
||||
err = s.notificationRepo.DeactivateGCMDevice(deviceID)
|
||||
device, err := s.notificationRepo.FindGCMDeviceByID(deviceID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return apperrors.NotFound("error.device_not_found")
|
||||
}
|
||||
return apperrors.Internal(err)
|
||||
}
|
||||
// Verify the device belongs to the requesting user
|
||||
if device.UserID == nil || *device.UserID != userID {
|
||||
return apperrors.Forbidden("error.device_access_denied")
|
||||
}
|
||||
if err := s.notificationRepo.DeactivateGCMDevice(deviceID); err != nil {
|
||||
return apperrors.Internal(err)
|
||||
}
|
||||
default:
|
||||
return apperrors.BadRequest("error.invalid_platform")
|
||||
}
|
||||
if err != nil {
|
||||
return apperrors.Internal(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -549,9 +572,9 @@ func NewGCMDeviceResponse(d *models.GCMDevice) *DeviceResponse {
|
||||
// RegisterDeviceRequest represents device registration request
|
||||
type RegisterDeviceRequest struct {
|
||||
Name string `json:"name"`
|
||||
DeviceID string `json:"device_id" binding:"required"`
|
||||
RegistrationID string `json:"registration_id" binding:"required"`
|
||||
Platform string `json:"platform" binding:"required,oneof=ios android"`
|
||||
DeviceID string `json:"device_id" validate:"required"`
|
||||
RegistrationID string `json:"registration_id" validate:"required"`
|
||||
Platform string `json:"platform" validate:"required,oneof=ios android"`
|
||||
}
|
||||
|
||||
// === Task Notifications with Actions ===
|
||||
|
||||
Reference in New Issue
Block a user