Security fixes
1. Password reset tokens: use crypto/rand instead of math/rand 2. IP spoofing: only trust X-Forwarded-For from configured trusted proxies 3. Cleanup: purge failed login attempts older than 24 hours 4. JWT secret: validate minimum 32 character length on startup Updated .env.example with TRUSTED_PROXIES setting
This commit is contained in:
parent
ccaa1eca7e
commit
269fb6f53a
6 changed files with 82 additions and 46 deletions
|
|
@ -3,10 +3,11 @@
|
|||
package mail
|
||||
|
||||
import (
|
||||
"crypto/tls" // TLS/SSL encryption for secure email sending
|
||||
"fmt" // string formatting for URLs and messages
|
||||
"net/smtp" // simple mail transfer protocol package
|
||||
"strings" // string manipulation for token validation
|
||||
"crypto/rand" // cryptographically secure random number generator
|
||||
"crypto/tls" // TLS/SSL encryption for secure email sending
|
||||
"encoding/hex" // hex encoding for token generation
|
||||
"fmt" // string formatting for URLs and messages
|
||||
"net/smtp" // simple mail transfer protocol package
|
||||
|
||||
"github.com/imc-vibe/backend/internal/config"
|
||||
)
|
||||
|
|
@ -166,40 +167,24 @@ func (s *EmailService) sendWithTLS(addr, to, msg string) error {
|
|||
return client.Quit()
|
||||
}
|
||||
|
||||
// GenerateToken creates a random token for password reset links.
|
||||
// GenerateToken creates a cryptographically random token for password reset links.
|
||||
// Returns a 64-character hexadecimal string (32 bytes encoded as hex).
|
||||
// This is cryptographically random and suitable for security-sensitive tokens.
|
||||
// Uses crypto/rand for security-sensitive token generation.
|
||||
func GenerateToken() string {
|
||||
// Create a byte slice to hold random bytes.
|
||||
b := make([]byte, 32)
|
||||
// Fill with random characters from our allowed set.
|
||||
// We iterate over the slice and pick characters based on position.
|
||||
for i := range b {
|
||||
b[i] = tokenChars[i%len(tokenChars)]
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
panic("crypto/rand failed: " + err.Error())
|
||||
}
|
||||
// Convert bytes to hex string. Each byte becomes two hex characters.
|
||||
// Result: 32 bytes = 64 hex characters.
|
||||
return fmt.Sprintf("%x", b)
|
||||
return hex.EncodeToString(b)
|
||||
}
|
||||
|
||||
// tokenChars defines the character set used for generating reset tokens.
|
||||
// Using alphanumeric characters makes tokens easier to copy/paste.
|
||||
const tokenChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
|
||||
// ValidateToken checks if a token has the correct format.
|
||||
// Returns true if the token is exactly 64 characters and contains only
|
||||
// characters from our allowed tokenChars set.
|
||||
// Returns true if the token is exactly 64 characters of valid hex.
|
||||
// This is a quick validation before checking the database.
|
||||
func ValidateToken(token string) bool {
|
||||
// Tokens should be 64 hex characters (32 bytes * 2 hex chars/byte).
|
||||
if len(token) != 64 {
|
||||
return false
|
||||
}
|
||||
// Check each character is in our allowed set.
|
||||
for _, c := range token {
|
||||
if !strings.ContainsRune(tokenChars, c) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
_, err := hex.DecodeString(token)
|
||||
return err == nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue