• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

apowers313 / aiforge / 20962792399

13 Jan 2026 03:39PM UTC coverage: 84.707%. First build
20962792399

push

github

apowers313
feat: initial commit

787 of 905 branches covered (86.96%)

Branch coverage included in aggregate %.

4248 of 5039 new or added lines in 70 files covered. (84.3%)

4248 of 5039 relevant lines covered (84.3%)

13.11 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.54
/src/server/api/middleware/rateLimit.ts
1
/**
2
 * Rate limiting middleware for authentication endpoints
3
 */
4
import type { Request, Response, NextFunction, RequestHandler } from 'express';
5
import { ApiError } from './error.js';
1✔
6

7
interface RateLimitOptions {
8
  windowMs: number;       // Time window in milliseconds
9
  maxRequests: number;    // Max requests per window
10
  message?: string;       // Error message when rate limited
11
}
12

13
interface RateLimitEntry {
14
  count: number;
15
  resetTime: number;
16
}
17

18
/**
19
 * Simple in-memory rate limiter
20
 * Uses IP address as the key
21
 */
22
export function createRateLimiter(options: RateLimitOptions): RequestHandler {
1✔
23
  const {
6✔
24
    windowMs,
6✔
25
    maxRequests,
6✔
26
    message = 'Too many requests, please try again later',
6✔
27
  } = options;
6✔
28

29
  // Store rate limit data per IP
30
  const store = new Map<string, RateLimitEntry>();
6✔
31

32
  // Cleanup old entries periodically
33
  const cleanupInterval = setInterval(() => {
6✔
NEW
34
    const now = Date.now();
×
NEW
35
    for (const [key, entry] of store.entries()) {
×
NEW
36
      if (entry.resetTime <= now) {
×
NEW
37
        store.delete(key);
×
NEW
38
      }
×
NEW
39
    }
×
40
  }, windowMs);
6✔
41

42
  // Don't prevent process from exiting
43
  cleanupInterval.unref();
6✔
44

45
  return (req: Request, res: Response, next: NextFunction): void => {
6✔
46
    const key = getClientKey(req);
34✔
47
    const now = Date.now();
34✔
48

49
    let entry = store.get(key);
34✔
50

51
    // If no entry or window expired, create new entry
52
    if (!entry || entry.resetTime <= now) {
34✔
53
      entry = {
6✔
54
        count: 0,
6✔
55
        resetTime: now + windowMs,
6✔
56
      };
6✔
57
      store.set(key, entry);
6✔
58
    }
6✔
59

60
    // Increment request count
61
    entry.count++;
34✔
62

63
    // Set rate limit headers
64
    res.setHeader('X-RateLimit-Limit', String(maxRequests));
34✔
65
    res.setHeader('X-RateLimit-Remaining', String(Math.max(0, maxRequests - entry.count)));
34✔
66
    res.setHeader('X-RateLimit-Reset', String(Math.ceil(entry.resetTime / 1000)));
34✔
67

68
    // Check if over limit
69
    if (entry.count > maxRequests) {
34✔
70
      const retryAfter = Math.ceil((entry.resetTime - now) / 1000);
12✔
71
      res.setHeader('Retry-After', String(retryAfter));
12✔
72

73
      next(ApiError.tooManyRequests(message));
12✔
74
      return;
12✔
75
    }
12✔
76

77
    next();
22✔
78
  };
34✔
79
}
6✔
80

81
/**
82
 * Get client identifier for rate limiting
83
 * Uses X-Forwarded-For if behind proxy, otherwise IP
84
 */
85
function getClientKey(req: Request): string {
34✔
86
  const forwarded = req.headers['x-forwarded-for'];
34✔
87
  if (forwarded) {
34!
NEW
88
    const firstIp = Array.isArray(forwarded) ? forwarded[0] : forwarded.split(',')[0];
×
NEW
89
    return firstIp?.trim() ?? req.ip ?? 'unknown';
×
NEW
90
  }
×
91
  return req.ip ?? 'unknown';
34!
92
}
34✔
93

94
/**
95
 * Pre-configured rate limiter for login endpoint
96
 * 10 attempts per 15 minutes
97
 */
98
export const loginRateLimiter = createRateLimiter({
1✔
99
  windowMs: 15 * 60 * 1000, // 15 minutes
1✔
100
  maxRequests: 10,
1✔
101
  message: 'Too many login attempts, please try again later',
1✔
102
});
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc