first commit

This commit is contained in:
domrichardson
2026-03-24 16:03:04 +00:00
commit df40cc57e1
80 changed files with 16766 additions and 0 deletions

284
SECURITY.md Normal file
View File

@@ -0,0 +1,284 @@
# Security Implementation Guide
This document outlines the security measures implemented in Notely.
## 🔐 Authentication Security
### Password Hashing
- **Algorithm**: Argon2id (memory-hard, resistant to GPU attacks)
- **Configuration**:
- Memory: 64 MB
- Time: 1 iteration
- Parallelism: 4 threads
- Salt: 16 random bytes (cryptographically secure)
```go
// Generated hash format:
$argon2id$v=19$m=65536,t=1,p=4$salt_hex$hash_hex
```
### JWT Tokens
- **Algorithm**: HS256 (HMAC-SHA256)
- **Access Token TTL**: 1 hour
- **Refresh Token TTL**: 7 days (HTTP-only secure cookie)
- **Claims**:
- `user_id`: User's MongoDB ObjectID
- `email`: User's email address
- `username`: User's username
- `iat`: Issued at timestamp
- `exp`: Expiration timestamp
- `iss`: Issuer (verified against hardcoded value)
### Brute-Force Protection
- Track failed login attempts in `login_attempts` collection
- Rate limit: Max 5 failed attempts per IP per 15 minutes
- Account lockout: 15 minutes after 5 consecutive failures
- Cleanup: Expired records auto-deleted via TTL index
## 🛡️ Authorization Security
### Role-Based Access Control (RBAC)
```
Space Roles:
├── Owner (all permissions)
├── Editor (create/edit/delete notes)
└── Viewer (read-only)
```
### Space-Level Data Isolation
**ALL queries include mandatory `space_id` filter**
```go
// Correct query pattern:
db.notes.find({ space_id: spaceID, ... })
// Never allow:
db.notes.find({ user_id: userID }) // ❌ Cross-space leak possible
```
### Middleware Authorization Flow
```
1. Extract JWT token → Verify signature & expiration
2. Load user credentials → Verify user is active
3. Check space membership → Verify user_id + space_id + role
4. Execute request → With space_id context
```
## 🔑 Data Encryption
### At Rest
- OAuth client secrets encrypted with AES-256-GCM
- Stored in MongoDB with encryption key in environment variables
- Decryption happens only when reading from database
```go
plaintext, err := encryptor.Encrypt(clientSecret) // Stores encrypted blob
recovered, err := encryptor.Decrypt(plaintext) // Decrypts on retrieval
```
### In Transit
- HTTPS/TLS required in production (enforced via Nginx)
- Secure cookies: `Secure`, `HttpOnly`, `SameSite=Lax` flags
- All sensitive data transmitted over encrypted channels
## 🚨 Input Validation
### Backend Validation (MANDATORY)
Every endpoint validates:
1. **Type validation** - JSON schema validation
2. **Length limits** - min/max string lengths
3. **Format validation** - email, ObjectID, URL formats
4. **Range validation** - pagination limits
```go
type CreateNoteRequest struct {
Title string `validate:"required,min=1,max=255"`
Content string `validate:"max=50000"`
Tags []string `validate:"max=100,dive,max=50"`
}
```
### Frontend Validation
- **Input sanitization** - trim whitespace
- **Format validation** - regex patterns
- **Debounced searches** - prevent query spam
- **Client-side feedback** - improve UX
### Output Sanitization
Markdown → HTML conversion sanitized with DOMPurify:
```javascript
// XSS prevention
const dirty = marked.parse(userMarkdown);
const clean = DOMPurify.sanitize(dirty);
// Blocks: scripts, event handlers, dangerous attributes
```
## 🌐 Web Security Headers
Implemented via Nginx and Go middleware:
| Header | Value | Purpose |
| --------------------------- | --------------------------------- | ------------------------------- |
| `Strict-Transport-Security` | `max-age=31536000` | Force HTTPS |
| `X-Content-Type-Options` | `nosniff` | Prevent MIME sniffing |
| `X-Frame-Options` | `DENY` | Prevent clickjacking |
| `X-XSS-Protection` | `1; mode=block` | XSS protection (older browsers) |
| `Content-Security-Policy` | Restrictive policy | Prevent XSS attacks |
| `Referrer-Policy` | `strict-origin-when-cross-origin` | Referrer control |
**CSP Policy:**
```
default-src 'self'
script-src 'self' 'unsafe-inline' (for development only)
style-src 'self' 'unsafe-inline'
img-src 'self' data: https:
font-src 'self'
connect-src 'self'
frame-ancestors 'none'
```
## 🍪 Cookie Security
### Access Token (via Authorization header)
- Stored in **memory** (not localStorage)
- Passed via `Authorization: Bearer {token}`
### Refresh Token (HTTP-only cookie)
```go
http.SetCookie(w, &http.Cookie{
Name: "refresh_token",
Value: token,
Path: "/",
MaxAge: 7 * 24 * 60 * 60, // 7 days
HttpOnly: true, // ✅ Cannot access from JavaScript
Secure: true, // ✅ HTTPS only
SameSite: http.SameSiteLaxMode, // ✅ CSRF protection
})
```
## 🔄 Rate Limiting
### API Rate Limiting
- **General**: 50 requests / second per IP
- **Login**: 10 requests / second per IP
- **Burst allowance**: 20 additional requests
```nginx
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
limit_req zone=api_limit burst=20 nodelay;
```
### Login Attempt Tracking
- Track per email + IP combination
- Maximum 5 attempts per 15 minutes
- Exponential backoff on repeated failures
## 🔒 Database Security
### MongoDB
- **Authentication**: Username/password with role-based access
- **Network**: Runs in secure Docker network (not exposed)
- **Admin credentials**: Stored in Kubernetes Secrets (not in code)
- **Backups**: TBD - use MongoDB Atlas or encrypted backups
### Connection String
```
mongodb://admin:password@mongodb:27017/dbname?authSource=admin
```
## 🚨 Logging & Monitoring
### Security Events Logged
- ✅ User registration attempts
- ✅ Login attempts (success/failure)
- ✅ Authorization failures
- ✅ Permission denied events
- ✅ Sensitive data access
### Data NOT logged
- ❌ Passwords/hashes
- ❌ JWT tokens
- ❌ Encryption keys
- ❌ OAuth secrets
## 🧪 Security Testing
### What to Test
1. **Authentication**: Register, login, token refresh, logout
2. **Authorization**: RBAC enforcement, space isolation
3. **Input validation**: Invalid data rejection
4. **XSS prevention**: Markdown sanitization
5. **CSRF protection**: Token validation
6. **Rate limiting**: Too many requests blocked
7. **SQL Injection**: MongoDB-specific (parameterized queries safe)
### Manual Testing Commands
```bash
# Test invalid input
curl -X POST http://localhost:8080/api/v1/auth/login \
-d '{"email":"not-an-email","password":""}'
# Test expired token
curl -H "Authorization: Bearer expired.token.here" \
http://localhost:8080/api/v1/spaces
# Test rate limiting
for i in {1..100}; do
curl http://localhost:8080/api/v1/auth/login &
done
```
## 🛠️ Production Checklist
- [ ] Change default JWT_SECRET (min 32 characters)
- [ ] Change default ENCRYPTION_KEY (32 bytes)
- [ ] Generate TLS certificates (Let's Encrypt recommended)
- [ ] Configure Nginx SSL/TLS
- [ ] Enable HTTPS redirect
- [ ] Set up database backups
- [ ] Configure logging & monitoring
- [ ] Implement CORS whitelist (specific domains)
- [ ] Set up rate limiting (tuned to your traffic)
- [ ] Enable database authentication
- [ ] Use Kubernetes Network Policies
- [ ] Set up Pod Security Policies
- [ ] Enable audit logging
- [ ] Configure Secrets encryption at rest
## 📚 References
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [MongoDB Security](https://docs.mongodb.com/manual/security/)
- [JWT Best Practices](https://tools.ietf.org/html/rfc8949)
- [Argon2 Specification](https://github.com/P-H-C/phc-winner-argon2)
- [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP)
---
**Last Updated**: March 2026
**Security Level**: Production-Grade