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:
Trey t
2026-03-02 09:48:01 -06:00
parent 56d6fa4514
commit 7690f07a2b
123 changed files with 8321 additions and 750 deletions

View File

@@ -323,10 +323,21 @@ func (c *AppleIAPClient) ValidateReceipt(ctx context.Context, receiptData string
}, nil
}
// validateLegacyReceipt uses Apple's legacy verifyReceipt endpoint
// validateLegacyReceipt uses Apple's legacy verifyReceipt endpoint.
// It delegates to validateLegacyReceiptWithSandbox using the client's
// configured sandbox setting. This avoids mutating the struct field
// during the sandbox-retry flow, which caused a data race when
// multiple goroutines shared the same AppleIAPClient.
func (c *AppleIAPClient) validateLegacyReceipt(ctx context.Context, receiptData string) (*AppleValidationResult, error) {
return c.validateLegacyReceiptWithSandbox(ctx, receiptData, c.sandbox)
}
// validateLegacyReceiptWithSandbox performs legacy receipt validation against
// the specified environment. The sandbox parameter is passed by value (not
// stored on the struct) so this function is safe for concurrent use.
func (c *AppleIAPClient) validateLegacyReceiptWithSandbox(ctx context.Context, receiptData string, useSandbox bool) (*AppleValidationResult, error) {
url := "https://buy.itunes.apple.com/verifyReceipt"
if c.sandbox {
if useSandbox {
url = "https://sandbox.itunes.apple.com/verifyReceipt"
}
@@ -378,12 +389,10 @@ func (c *AppleIAPClient) validateLegacyReceipt(ctx context.Context, receiptData
}
// Status codes: 0 = valid, 21007 = sandbox receipt on production, 21008 = production receipt on sandbox
if legacyResponse.Status == 21007 && !c.sandbox {
// Retry with sandbox
c.sandbox = true
result, err := c.validateLegacyReceipt(ctx, receiptData)
c.sandbox = false
return result, err
if legacyResponse.Status == 21007 && !useSandbox {
// Retry with sandbox -- pass sandbox=true as a parameter instead of
// mutating c.sandbox, which avoids a data race.
return c.validateLegacyReceiptWithSandbox(ctx, receiptData, true)
}
if legacyResponse.Status != 0 {