fix: Rate limiter fail-open bug - wrap createClient in try/catch (qa-requested)
Fixes: - Rate limiter now properly fails open when Supabase unavailable - Moved createClient() calls inside try/catch blocks - Prevents unhandled exceptions from reaching API handler - Improved error logging for debugging Impact: - isRateLimited() - Wrapped createClient (line 22) - getRemainingAttempts() - Wrapped createClient (line 93) - getTimeToReset() - Wrapped createClient (line 128) Context: - QA Session 2 found API returning 500 errors - Root cause: Database table doesn't exist (requires manual migration) - This fix ensures rate limiter fails gracefully when DB unavailable - With DB present, rate limiting will work as designed Verified: - TypeScript compiles without errors - Code follows fail-open pattern - Error logging improved QA Fix Session: 2 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -18,11 +18,10 @@ class SupabaseRateLimiter {
|
||||
* @returns true if rate limited, false otherwise
|
||||
*/
|
||||
async isRateLimited(identifier: string): Promise<boolean> {
|
||||
const supabase = await createClient();
|
||||
const now = Date.now();
|
||||
const windowStart = new Date(now - WINDOW_SIZE_MS).toISOString();
|
||||
|
||||
try {
|
||||
const supabase = await createClient();
|
||||
const now = Date.now();
|
||||
const windowStart = new Date(now - WINDOW_SIZE_MS).toISOString();
|
||||
// Get the current rate limit record for this identifier
|
||||
const { data: existingRecord, error: fetchError } = await supabase
|
||||
.from('rate_limits')
|
||||
@@ -79,8 +78,8 @@ class SupabaseRateLimiter {
|
||||
|
||||
return false; // Not rate limited yet
|
||||
} catch (error) {
|
||||
console.error('Unexpected error in rate limiting:', error);
|
||||
return false; // Fail open on unexpected errors
|
||||
console.error('[RateLimiter] Unexpected error in isRateLimited - FAILING OPEN:', error);
|
||||
return false; // Fail open on ALL errors
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,11 +89,10 @@ class SupabaseRateLimiter {
|
||||
* @returns Number of remaining attempts (0 if rate limited)
|
||||
*/
|
||||
async getRemainingAttempts(identifier: string): Promise<number> {
|
||||
const supabase = await createClient();
|
||||
const now = Date.now();
|
||||
const windowStart = new Date(now - WINDOW_SIZE_MS).toISOString();
|
||||
|
||||
try {
|
||||
const supabase = await createClient();
|
||||
const now = Date.now();
|
||||
const windowStart = new Date(now - WINDOW_SIZE_MS).toISOString();
|
||||
const { data: record, error } = await supabase
|
||||
.from('rate_limits')
|
||||
.select('count')
|
||||
@@ -115,8 +113,8 @@ class SupabaseRateLimiter {
|
||||
|
||||
return Math.max(0, MAX_REQUESTS - record.count);
|
||||
} catch (error) {
|
||||
console.error('Unexpected error getting remaining attempts:', error);
|
||||
return MAX_REQUESTS; // Fail open
|
||||
console.error('[RateLimiter] Unexpected error in getRemainingAttempts - FAILING OPEN:', error);
|
||||
return MAX_REQUESTS; // Fail open on ALL errors
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,11 +124,10 @@ class SupabaseRateLimiter {
|
||||
* @returns Milliseconds until reset (0 if no active limit)
|
||||
*/
|
||||
async getTimeToReset(identifier: string): Promise<number> {
|
||||
const supabase = await createClient();
|
||||
const now = Date.now();
|
||||
const windowStart = new Date(now - WINDOW_SIZE_MS).toISOString();
|
||||
|
||||
try {
|
||||
const supabase = await createClient();
|
||||
const now = Date.now();
|
||||
const windowStart = new Date(now - WINDOW_SIZE_MS).toISOString();
|
||||
const { data: record, error } = await supabase
|
||||
.from('rate_limits')
|
||||
.select('window_start')
|
||||
@@ -153,8 +150,8 @@ class SupabaseRateLimiter {
|
||||
const resetTime = windowStartTime + WINDOW_SIZE_MS;
|
||||
return Math.max(0, resetTime - now);
|
||||
} catch (error) {
|
||||
console.error('Unexpected error getting time to reset:', error);
|
||||
return 0;
|
||||
console.error('[RateLimiter] Unexpected error in getTimeToReset - FAILING OPEN:', error);
|
||||
return 0; // Fail open on ALL errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user