261 lines
5 KiB
Text
261 lines
5 KiB
Text
---
|
|
title: Security Best Practices
|
|
description: Secure your Wails application
|
|
sidebar:
|
|
order: 8
|
|
---
|
|
|
|
## Overview
|
|
|
|
Security is critical for desktop applications. Follow these practices to keep your application secure.
|
|
|
|
## Input Validation
|
|
|
|
### Always Validate
|
|
|
|
```go
|
|
func (s *UserService) CreateUser(email, password string) (*User, error) {
|
|
// Validate email
|
|
if !isValidEmail(email) {
|
|
return nil, errors.New("invalid email")
|
|
}
|
|
|
|
// Validate password strength
|
|
if len(password) < 8 {
|
|
return nil, errors.New("password too short")
|
|
}
|
|
|
|
// Sanitise input
|
|
email = strings.TrimSpace(email)
|
|
email = html.EscapeString(email)
|
|
|
|
// Continue...
|
|
}
|
|
```
|
|
|
|
### Sanitise HTML
|
|
|
|
```go
|
|
import "html"
|
|
|
|
func (s *Service) SaveComment(text string) error {
|
|
// Escape HTML
|
|
text = html.EscapeString(text)
|
|
|
|
// Validate length
|
|
if len(text) > 1000 {
|
|
return errors.New("comment too long")
|
|
}
|
|
|
|
return s.db.Save(text)
|
|
}
|
|
```
|
|
|
|
## Authentication
|
|
|
|
### Secure Password Storage
|
|
|
|
```go
|
|
import "golang.org/x/crypto/bcrypt"
|
|
|
|
func hashPassword(password string) (string, error) {
|
|
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
|
return string(hash), err
|
|
}
|
|
|
|
func verifyPassword(hash, password string) bool {
|
|
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
|
return err == nil
|
|
}
|
|
```
|
|
|
|
### Session Management
|
|
|
|
```go
|
|
type Session struct {
|
|
UserID int
|
|
Token string
|
|
ExpiresAt time.Time
|
|
}
|
|
|
|
func (a *AuthService) CreateSession(userID int) (*Session, error) {
|
|
token := generateSecureToken()
|
|
|
|
session := &Session{
|
|
UserID: userID,
|
|
Token: token,
|
|
ExpiresAt: time.Now().Add(24 * time.Hour),
|
|
}
|
|
|
|
return session, a.saveSession(session)
|
|
}
|
|
```
|
|
|
|
## Data Protection
|
|
|
|
### Encrypt Sensitive Data
|
|
|
|
```go
|
|
import "crypto/aes"
|
|
import "crypto/cipher"
|
|
|
|
func encrypt(data []byte, key []byte) ([]byte, error) {
|
|
block, err := aes.NewCipher(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
gcm, err := cipher.NewGCM(block)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nonce := make([]byte, gcm.NonceSize())
|
|
return gcm.Seal(nonce, nonce, data, nil), nil
|
|
}
|
|
```
|
|
|
|
### Secure Storage
|
|
|
|
```go
|
|
// Use OS keychain for sensitive data
|
|
import "github.com/zalando/go-keyring"
|
|
|
|
func saveAPIKey(key string) error {
|
|
return keyring.Set("myapp", "api_key", key)
|
|
}
|
|
|
|
func getAPIKey() (string, error) {
|
|
return keyring.Get("myapp", "api_key")
|
|
}
|
|
```
|
|
|
|
## Network Security
|
|
|
|
### Use HTTPS
|
|
|
|
```go
|
|
func makeAPICall(url string) (*Response, error) {
|
|
// Always use HTTPS
|
|
if !strings.HasPrefix(url, "https://") {
|
|
return nil, errors.New("only HTTPS allowed")
|
|
}
|
|
|
|
return http.Get(url)
|
|
}
|
|
```
|
|
|
|
### Verify Certificates
|
|
|
|
```go
|
|
import "crypto/tls"
|
|
|
|
func secureClient() *http.Client {
|
|
return &http.Client{
|
|
Transport: &http.Transport{
|
|
TLSClientConfig: &tls.Config{
|
|
MinVersion: tls.VersionTLS12,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
```
|
|
|
|
## File Operations
|
|
|
|
### Validate Paths
|
|
|
|
```go
|
|
func readFile(path string) ([]byte, error) {
|
|
// Prevent path traversal
|
|
if strings.Contains(path, "..") {
|
|
return nil, errors.New("invalid path")
|
|
}
|
|
|
|
// Check file exists in allowed directory
|
|
absPath, err := filepath.Abs(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !strings.HasPrefix(absPath, allowedDir) {
|
|
return nil, errors.New("access denied")
|
|
}
|
|
|
|
return os.ReadFile(absPath)
|
|
}
|
|
```
|
|
|
|
## Rate Limiting
|
|
|
|
```go
|
|
type RateLimiter struct {
|
|
requests map[string][]time.Time
|
|
mu sync.Mutex
|
|
limit int
|
|
window time.Duration
|
|
}
|
|
|
|
func (r *RateLimiter) Allow(key string) bool {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
now := time.Now()
|
|
|
|
// Clean old requests
|
|
var recent []time.Time
|
|
for _, t := range r.requests[key] {
|
|
if now.Sub(t) < r.window {
|
|
recent = append(recent, t)
|
|
}
|
|
}
|
|
|
|
if len(recent) >= r.limit {
|
|
return false
|
|
}
|
|
|
|
r.requests[key] = append(recent, now)
|
|
return true
|
|
}
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### ✅ Do
|
|
|
|
- Validate all input
|
|
- Use HTTPS for network calls
|
|
- Encrypt sensitive data
|
|
- Use secure password hashing
|
|
- Implement rate limiting
|
|
- Keep dependencies updated
|
|
- Log security events
|
|
- Use OS keychains
|
|
|
|
### ❌ Don't
|
|
|
|
- Don't trust user input
|
|
- Don't store passwords in plain text
|
|
- Don't hardcode secrets
|
|
- Don't skip certificate verification
|
|
- Don't expose sensitive data in logs
|
|
- Don't use weak encryption
|
|
- Don't ignore security updates
|
|
|
|
## Security Checklist
|
|
|
|
- [ ] All user input validated
|
|
- [ ] Passwords hashed with bcrypt
|
|
- [ ] Sensitive data encrypted
|
|
- [ ] HTTPS used for all network calls
|
|
- [ ] Rate limiting implemented
|
|
- [ ] File paths validated
|
|
- [ ] Dependencies up to date
|
|
- [ ] Security logging enabled
|
|
- [ ] Error messages don't leak info
|
|
- [ ] Code reviewed for vulnerabilities
|
|
|
|
## Next Steps
|
|
|
|
- [Architecture](/guides/architecture) - Application architecture patterns
|
|
- [Best Practices](/features/bindings/best-practices) - Bindings best practices
|