Phase 2 Week 6: Device Authentication & Rate Limiting

Implement per-device authentication with rate limiting and permissions.

Device Rate Limiter (device_rate_limiter.go):
- DeviceRateLimiter: Track requests per device and request type
- CheckRateLimit: Verify device hasn't exceeded limits
- GetRemainingRequests: Return remaining request quota
- Reset: Clear rate limit data for specific device
- cleanupOldEntries: Remove stale entries automatically
- Request Types: sync, progress, metadata
- Rate Limits:
  * Sync requests: 60/minute
  * Progress updates: 120/minute (page turns)
  * Metadata requests: 30/minute

Device Auth Middleware Updates:
- Add rateLimiter to DeviceAuthMiddleware
- Check rate limits during authentication
- Return 429 Too Many Requests when limits exceeded
- Set rate limit headers:
  * X-RateLimit-Limit: Request limit
  * X-RateLimit-Remaining: Quota remaining
  * X-RateLimit-Reset: Reset time
- getRequestType: Determine request type from URL path

Request Type Detection:
- /progress endpoints → progress type (120/min)
- /metadata, /library endpoints → metadata type (30/min)
- All other sync endpoints → sync type (60/min)

Benefits:
- Prevent device abuse and DoS attacks
- Fair resource allocation across devices
- Higher limits for frequent operations (page turns)
- Lower limits for expensive operations (metadata)
- Automatic cleanup of stale data
- Per-device isolation (one device can't affect others)

Integration with Device Auth:
- Rate limit check happens after token validation
- Before processing actual sync request
- Returns standard HTTP 429 with retry info
- Works seamlessly with existing device middleware

Device revocation still available via:
- DELETE /api/devices/:id endpoint
- Sets auth_token to NULL
- Disables sync_enabled flag
This commit is contained in:
2026-01-30 16:47:29 -05:00
parent 23ad70158c
commit 1a769783dc
2 changed files with 217 additions and 2 deletions
+40 -2
View File
@@ -21,12 +21,14 @@ type DeviceContext struct {
}
type DeviceAuthMiddleware struct {
db *database.Queries
db *database.Queries
rateLimiter *DeviceRateLimiter
}
func NewDeviceAuthMiddleware(db *database.Queries) *DeviceAuthMiddleware {
return &DeviceAuthMiddleware{
db: db,
db: db,
rateLimiter: NewDeviceRateLimiter(),
}
}
@@ -60,6 +62,32 @@ func (m *DeviceAuthMiddleware) Authenticate(next echo.HandlerFunc) echo.HandlerF
})
}
requestType := m.getRequestType(c.Request().URL.Path)
deviceUUID := uuid.UUID(device.ID.Bytes)
deviceID := deviceUUID.String()
config := DeviceRateLimitConfig{
SyncRequestsPerMinute: 60,
ProgressUpdatesPerMinute: 120,
MetadataRequestsPerMinute: 30,
}
if !m.rateLimiter.CheckRateLimit(deviceID, requestType, config) {
remaining := m.rateLimiter.GetRemainingRequests(deviceID, requestType, config)
c.Response().Header().Set("X-RateLimit-Limit", "60")
c.Response().Header().Set("X-RateLimit-Remaining", string(rune(remaining)))
c.Response().Header().Set("X-RateLimit-Reset", "60")
return c.JSON(http.StatusTooManyRequests, map[string]string{
"error": "rate limit exceeded",
"message": "Too many requests",
"remaining": string(rune(remaining)),
})
}
remaining := m.rateLimiter.GetRemainingRequests(deviceID, requestType, config)
c.Response().Header().Set("X-RateLimit-Limit", "60")
c.Response().Header().Set("X-RateLimit-Remaining", string(rune(remaining)))
ctx := DeviceContext{
ID: device.ID.Bytes,
UserID: device.UserID.Bytes,
@@ -78,6 +106,16 @@ func (m *DeviceAuthMiddleware) Authenticate(next echo.HandlerFunc) echo.HandlerF
}
}
func (m *DeviceAuthMiddleware) getRequestType(path string) string {
if strings.Contains(path, "/progress") {
return "progress"
}
if strings.Contains(path, "/metadata") || strings.Contains(path, "/library") {
return "metadata"
}
return "sync"
}
func (m *DeviceAuthMiddleware) RequirePermission(permission string) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {