codingwithagent 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +37 -0
- package/bin/init.js +257 -0
- package/package.json +56 -0
- package/templates/accessibility/.cursorrules +342 -0
- package/templates/accessibility/README.md +47 -0
- package/templates/antigravity/accessibility/.agent/rules/accessibility.md +501 -0
- package/templates/antigravity/accessibility/.agent/rules/aria-patterns.md +568 -0
- package/templates/antigravity/accessibility/.agent/rules/wcag-standard.md +225 -0
- package/templates/antigravity/accessibility/README.md +42 -0
- package/templates/antigravity/minimal/.agent/rules/accessibility.md +53 -0
- package/templates/antigravity/minimal/.agent/rules/code-quality.md +86 -0
- package/templates/antigravity/minimal/.agent/rules/react-components.md +164 -0
- package/templates/antigravity/minimal/README.md +34 -0
- package/templates/antigravity/standard/.agent/rules/accessibility.md +98 -0
- package/templates/antigravity/standard/.agent/rules/code-quality.md +166 -0
- package/templates/antigravity/standard/.agent/rules/pull-request-review.md +192 -0
- package/templates/antigravity/standard/.agent/rules/react-components.md +204 -0
- package/templates/antigravity/standard/.agent/rules/testing.md +197 -0
- package/templates/antigravity/standard/README.md +39 -0
- package/templates/antigravity/strict/.agent/README.md +46 -0
- package/templates/antigravity/strict/.agent/rules/accessibility.md +199 -0
- package/templates/antigravity/strict/.agent/rules/code-quality.md +268 -0
- package/templates/antigravity/strict/.agent/rules/pull-request-review.md +114 -0
- package/templates/antigravity/strict/.agent/rules/react-components.md +423 -0
- package/templates/antigravity/strict/.agent/rules/security.md +483 -0
- package/templates/antigravity/strict/.agent/rules/testing.md +280 -0
- package/templates/minimal/.cursorrules +48 -0
- package/templates/minimal/README.md +40 -0
- package/templates/standard/.cursorrules +184 -0
- package/templates/standard/README.md +43 -0
- package/templates/strict/.cursorrules +227 -0
- package/templates/strict/README.md +47 -0
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: always_on
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Security Standards - STRICT (Zero Vulnerabilities)
|
|
6
|
+
|
|
7
|
+
## Security Philosophy - ZERO TOLERANCE
|
|
8
|
+
|
|
9
|
+
- Security is NOT optional
|
|
10
|
+
- NO security vulnerabilities accepted
|
|
11
|
+
- Security reviews REQUIRED for sensitive changes
|
|
12
|
+
- Compliance with OWASP Top 10 MANDATORY
|
|
13
|
+
- PCI/PII compliance STRICTLY enforced
|
|
14
|
+
|
|
15
|
+
## Input Validation - MANDATORY
|
|
16
|
+
|
|
17
|
+
### ALL User Inputs MUST Be Validated
|
|
18
|
+
|
|
19
|
+
- Schema validation REQUIRED (use Yup, Zod, or Joi)
|
|
20
|
+
- Client-side AND server-side validation (both required)
|
|
21
|
+
- Whitelist approach (define what's allowed, not what's blocked)
|
|
22
|
+
- Length limits enforced
|
|
23
|
+
- Type checking enforced
|
|
24
|
+
- Format validation enforced
|
|
25
|
+
|
|
26
|
+
### Validation Pattern - REQUIRED
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import * as Yup from "yup";
|
|
30
|
+
|
|
31
|
+
// Define schema
|
|
32
|
+
const userSchema = Yup.object().shape({
|
|
33
|
+
email: Yup.string()
|
|
34
|
+
.email("Invalid email")
|
|
35
|
+
.required("Email required")
|
|
36
|
+
.max(255, "Email too long"),
|
|
37
|
+
password: Yup.string()
|
|
38
|
+
.required("Password required")
|
|
39
|
+
.min(12, "Password must be at least 12 characters")
|
|
40
|
+
.matches(/[A-Z]/, "Must contain uppercase")
|
|
41
|
+
.matches(/[a-z]/, "Must contain lowercase")
|
|
42
|
+
.matches(/[0-9]/, "Must contain number")
|
|
43
|
+
.matches(/[^A-Za-z0-9]/, "Must contain special character"),
|
|
44
|
+
age: Yup.number()
|
|
45
|
+
.positive("Must be positive")
|
|
46
|
+
.integer("Must be integer")
|
|
47
|
+
.min(18, "Must be 18+")
|
|
48
|
+
.max(120, "Invalid age")
|
|
49
|
+
.required("Age required"),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Validate
|
|
53
|
+
try {
|
|
54
|
+
await userSchema.validate(userData, { abortEarly: false });
|
|
55
|
+
} catch (error) {
|
|
56
|
+
// Handle validation errors
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Prohibited Input Patterns - AUTOMATIC REJECTION
|
|
61
|
+
|
|
62
|
+
- NO unvalidated user input
|
|
63
|
+
- NO relying only on client-side validation
|
|
64
|
+
- NO blacklist approach
|
|
65
|
+
- NO regex without validation
|
|
66
|
+
- NO trusting ANY external data
|
|
67
|
+
|
|
68
|
+
## Output Sanitization - MANDATORY
|
|
69
|
+
|
|
70
|
+
### ALL Output MUST Be Sanitized
|
|
71
|
+
|
|
72
|
+
- HTML entities escaped
|
|
73
|
+
- SQL injection prevention
|
|
74
|
+
- XSS prevention
|
|
75
|
+
- LDAP injection prevention
|
|
76
|
+
- Command injection prevention
|
|
77
|
+
|
|
78
|
+
### Sanitization Libraries - REQUIRED
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import DOMPurify from 'dompurify';
|
|
82
|
+
import he from 'he';
|
|
83
|
+
|
|
84
|
+
// Sanitize HTML
|
|
85
|
+
const safeHTML = DOMPurify.sanitize(userInput, {
|
|
86
|
+
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
|
|
87
|
+
ALLOWED_ATTR: ['href']
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Escape HTML entities
|
|
91
|
+
const safeText = he.encode(userInput);
|
|
92
|
+
|
|
93
|
+
// For React (automatic escaping)
|
|
94
|
+
<div>{userInput}</div> // Safe by default
|
|
95
|
+
|
|
96
|
+
// DANGEROUS - requires sanitization
|
|
97
|
+
<div dangerouslySetInnerHTML={{__html: DOMPurify.sanitize(html)}} />
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Prohibited Output Patterns - AUTOMATIC REJECTION
|
|
101
|
+
|
|
102
|
+
- NO dangerouslySetInnerHTML without sanitization
|
|
103
|
+
- NO eval() or Function() constructor
|
|
104
|
+
- NO document.write()
|
|
105
|
+
- NO innerHTML without sanitization
|
|
106
|
+
- NO unescaped user content in HTML
|
|
107
|
+
|
|
108
|
+
## Authentication - STRICT REQUIREMENTS
|
|
109
|
+
|
|
110
|
+
### Password Requirements - MANDATORY
|
|
111
|
+
|
|
112
|
+
- Minimum length: 12 characters (not 8)
|
|
113
|
+
- Must contain: uppercase, lowercase, number, special character
|
|
114
|
+
- NO common passwords (check against list)
|
|
115
|
+
- Password strength meter REQUIRED
|
|
116
|
+
- bcrypt with salt rounds ≥12 for hashing
|
|
117
|
+
|
|
118
|
+
### Authentication Tokens - STRICT
|
|
119
|
+
|
|
120
|
+
- JWT tokens MUST expire (max 15 minutes for access tokens)
|
|
121
|
+
- Refresh tokens MUST expire (max 7 days)
|
|
122
|
+
- httpOnly cookies REQUIRED for tokens
|
|
123
|
+
- secure flag REQUIRED (HTTPS only)
|
|
124
|
+
- SameSite=Strict REQUIRED
|
|
125
|
+
- NO tokens in localStorage (use httpOnly cookies)
|
|
126
|
+
- NO tokens in URLs or query parameters
|
|
127
|
+
|
|
128
|
+
### Multi-Factor Authentication - REQUIRED
|
|
129
|
+
|
|
130
|
+
- MFA REQUIRED for:
|
|
131
|
+
|
|
132
|
+
- Admin accounts (no exceptions)
|
|
133
|
+
- Payment operations
|
|
134
|
+
- PII access
|
|
135
|
+
- Sensitive operations
|
|
136
|
+
|
|
137
|
+
- TOTP or SMS-based (TOTP preferred)
|
|
138
|
+
- Backup codes REQUIRED
|
|
139
|
+
|
|
140
|
+
## Authorization - ZERO TOLERANCE
|
|
141
|
+
|
|
142
|
+
### Access Control - MANDATORY
|
|
143
|
+
|
|
144
|
+
- Principle of least privilege ENFORCED
|
|
145
|
+
- Role-Based Access Control (RBAC) REQUIRED
|
|
146
|
+
- Authorization checks on EVERY endpoint
|
|
147
|
+
- Resource-level permissions ENFORCED
|
|
148
|
+
- NO client-side only authorization
|
|
149
|
+
|
|
150
|
+
### Authorization Pattern - REQUIRED
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
// Server-side authorization check
|
|
154
|
+
const checkPermission = (
|
|
155
|
+
user: User,
|
|
156
|
+
resource: string,
|
|
157
|
+
action: string
|
|
158
|
+
): boolean => {
|
|
159
|
+
if (!user || !user.roles) return false;
|
|
160
|
+
|
|
161
|
+
return user.roles.some((role) =>
|
|
162
|
+
permissions[role]?.[resource]?.includes(action)
|
|
163
|
+
);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// Middleware
|
|
167
|
+
const authorize = (resource: string, action: string) => {
|
|
168
|
+
return (req, res, next) => {
|
|
169
|
+
if (!checkPermission(req.user, resource, action)) {
|
|
170
|
+
return res.status(403).json({ error: "Forbidden" });
|
|
171
|
+
}
|
|
172
|
+
next();
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// Usage
|
|
177
|
+
app.get("/users/:id", authorize("users", "read"), getUserHandler);
|
|
178
|
+
app.put("/users/:id", authorize("users", "update"), updateUserHandler);
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Prohibited Authorization Patterns - AUTOMATIC REJECTION
|
|
182
|
+
|
|
183
|
+
- NO authorization checks only in frontend
|
|
184
|
+
- NO role checks without permission checks
|
|
185
|
+
- NO assuming user is authorized
|
|
186
|
+
- NO exposing sensitive data without checks
|
|
187
|
+
|
|
188
|
+
## Data Protection - STRICT COMPLIANCE
|
|
189
|
+
|
|
190
|
+
### PII (Personally Identifiable Information) - MANDATORY
|
|
191
|
+
|
|
192
|
+
- PII MUST be encrypted at rest
|
|
193
|
+
- PII MUST be encrypted in transit (HTTPS)
|
|
194
|
+
- PII MUST be masked in logs
|
|
195
|
+
- PII MUST be masked in error messages
|
|
196
|
+
- PII MUST be masked in URLs
|
|
197
|
+
- PII access MUST be logged and audited
|
|
198
|
+
|
|
199
|
+
### PII Masking - REQUIRED
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
// Email masking
|
|
203
|
+
const maskEmail = (email: string): string => {
|
|
204
|
+
const [user, domain] = email.split("@");
|
|
205
|
+
return `${user[0]}***${user[user.length - 1]}@${domain}`;
|
|
206
|
+
};
|
|
207
|
+
// Example: john@example.com → j***n@example.com
|
|
208
|
+
|
|
209
|
+
// Phone masking
|
|
210
|
+
const maskPhone = (phone: string): string => {
|
|
211
|
+
return phone.replace(/\d(?=\d{4})/g, "*");
|
|
212
|
+
};
|
|
213
|
+
// Example: 1234567890 → ******7890
|
|
214
|
+
|
|
215
|
+
// Credit card masking
|
|
216
|
+
const maskCard = (card: string): string => {
|
|
217
|
+
return card.replace(/\d(?=\d{4})/g, "*");
|
|
218
|
+
};
|
|
219
|
+
// Example: 1234567890123456 → **3456
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### PCI (Payment Card Industry) - STRICT COMPLIANCE
|
|
223
|
+
|
|
224
|
+
- Credit cards MUST show last 4 digits ONLY
|
|
225
|
+
- Full card numbers NEVER stored
|
|
226
|
+
- CVV NEVER stored
|
|
227
|
+
- Use payment gateway (Stripe, PayPal) - NO direct handling
|
|
228
|
+
- PCI DSS compliance REQUIRED
|
|
229
|
+
|
|
230
|
+
### Prohibited PII/PCI Patterns - AUTOMATIC REJECTION
|
|
231
|
+
|
|
232
|
+
- NO PII in logs
|
|
233
|
+
- NO PII in URLs or query parameters
|
|
234
|
+
- NO PII in error messages
|
|
235
|
+
- NO PII in analytics
|
|
236
|
+
- NO credit card data stored
|
|
237
|
+
- NO CVV stored
|
|
238
|
+
- NO unencrypted PII
|
|
239
|
+
|
|
240
|
+
## Secrets Management - ZERO TOLERANCE
|
|
241
|
+
|
|
242
|
+
### API Keys and Secrets - MANDATORY
|
|
243
|
+
|
|
244
|
+
- NO secrets in code (automatic rejection)
|
|
245
|
+
- NO secrets in version control
|
|
246
|
+
- Environment variables REQUIRED
|
|
247
|
+
- Secrets rotation policy ENFORCED
|
|
248
|
+
- AWS Secrets Manager / HashiCorp Vault REQUIRED
|
|
249
|
+
|
|
250
|
+
### Environment Variables - REQUIRED
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
// .env file (NOT committed)
|
|
254
|
+
API_KEY=your_api_key_here
|
|
255
|
+
DATABASE_URL=postgresql://...
|
|
256
|
+
JWT_SECRET=your_jwt_secret_here
|
|
257
|
+
|
|
258
|
+
// Usage
|
|
259
|
+
const apiKey = process.env.API_KEY;
|
|
260
|
+
if (!apiKey) {
|
|
261
|
+
throw new Error('API_KEY not configured');
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Prohibited Secrets Patterns - AUTOMATIC REJECTION
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
// NEVER DO THIS
|
|
269
|
+
const API_KEY = 'abc123def456'; // REJECTED!
|
|
270
|
+
const password = 'mypassword'; // REJECTED!
|
|
271
|
+
const token = 'ghp_xxxxxxxxxxx'; // REJECTED!
|
|
272
|
+
|
|
273
|
+
// Git pre-commit hooks check for:
|
|
274
|
+
- API keys
|
|
275
|
+
- AWS keys
|
|
276
|
+
- Private keys
|
|
277
|
+
- Passwords
|
|
278
|
+
- Tokens
|
|
279
|
+
- Database URLs with credentials
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## HTTPS and Transport Security - MANDATORY
|
|
283
|
+
|
|
284
|
+
### HTTPS Requirements - ALL ENVIRONMENTS
|
|
285
|
+
|
|
286
|
+
- HTTPS REQUIRED everywhere (no HTTP)
|
|
287
|
+
- TLS 1.2 minimum (TLS 1.3 preferred)
|
|
288
|
+
- Strong cipher suites ONLY
|
|
289
|
+
- HSTS header REQUIRED (max-age=31536000)
|
|
290
|
+
- Certificate pinning for mobile apps
|
|
291
|
+
|
|
292
|
+
## Security Headers - ALL REQUIRED
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
// Express middleware
|
|
296
|
+
app.use((req, res, next) => {
|
|
297
|
+
// Prevent clickjacking
|
|
298
|
+
res.setHeader("X-Frame-Options", "DENY");
|
|
299
|
+
|
|
300
|
+
// Prevent MIME sniffing
|
|
301
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
302
|
+
|
|
303
|
+
// XSS protection
|
|
304
|
+
res.setHeader("X-XSS-Protection", "1; mode=block");
|
|
305
|
+
|
|
306
|
+
// Strict transport security
|
|
307
|
+
res.setHeader(
|
|
308
|
+
"Strict-Transport-Security",
|
|
309
|
+
"max-age=31536000; includeSubDomains"
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
// Content security policy
|
|
313
|
+
res.setHeader(
|
|
314
|
+
"Content-Security-Policy",
|
|
315
|
+
"default-src 'self'; script-src 'self'"
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
// Referrer policy
|
|
319
|
+
res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
320
|
+
|
|
321
|
+
// Permissions policy
|
|
322
|
+
res.setHeader("Permissions-Policy", "geolocation=(), microphone=()");
|
|
323
|
+
|
|
324
|
+
next();
|
|
325
|
+
});
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## CORS - STRICT CONFIGURATION
|
|
329
|
+
|
|
330
|
+
### CORS Policy - MANDATORY
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
import cors from "cors";
|
|
334
|
+
|
|
335
|
+
// Whitelist approach ONLY
|
|
336
|
+
const allowedOrigins = ["https://yourdomain.com", "https://app.yourdomain.com"];
|
|
337
|
+
|
|
338
|
+
app.use(
|
|
339
|
+
cors({
|
|
340
|
+
origin: (origin, callback) => {
|
|
341
|
+
if (!origin || allowedOrigins.includes(origin)) {
|
|
342
|
+
callback(null, true);
|
|
343
|
+
} else {
|
|
344
|
+
callback(new Error("Not allowed by CORS"));
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
credentials: true,
|
|
348
|
+
methods: ["GET", "POST", "PUT", "DELETE"],
|
|
349
|
+
allowedHeaders: ["Content-Type", "Authorization"],
|
|
350
|
+
})
|
|
351
|
+
);
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Prohibited CORS Patterns - AUTOMATIC REJECTION
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
// NEVER DO THIS
|
|
358
|
+
app.use(cors()); // Allows all origins - REJECTED!
|
|
359
|
+
app.use(cors({ origin: "*" })); // Allows all origins - REJECTED!
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## Dependency Security - MANDATORY
|
|
363
|
+
|
|
364
|
+
### Dependency Management - REQUIRED
|
|
365
|
+
|
|
366
|
+
- npm audit run BEFORE every release
|
|
367
|
+
- Snyk or similar scan REQUIRED in CI/CD
|
|
368
|
+
- NO dependencies with known vulnerabilities
|
|
369
|
+
- Dependency updates within 7 days of security advisory
|
|
370
|
+
- Minimal dependencies (audit all new additions)
|
|
371
|
+
|
|
372
|
+
### Dependency Scanning - AUTOMATED
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
# Run before every commit
|
|
376
|
+
npm audit
|
|
377
|
+
|
|
378
|
+
# Fix vulnerabilities automatically
|
|
379
|
+
npm audit fix
|
|
380
|
+
|
|
381
|
+
# Check for updates
|
|
382
|
+
npm outdated
|
|
383
|
+
|
|
384
|
+
# Snyk scan
|
|
385
|
+
snyk test
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### Prohibited Dependency Patterns - AUTOMATIC REJECTION
|
|
389
|
+
|
|
390
|
+
- Dependencies with HIGH/CRITICAL vulnerabilities
|
|
391
|
+
- Unmaintained dependencies (no updates >2 years)
|
|
392
|
+
- Dependencies from untrusted sources
|
|
393
|
+
- Unnecessary dependencies
|
|
394
|
+
|
|
395
|
+
## SQL Injection Prevention - MANDATORY
|
|
396
|
+
|
|
397
|
+
### Parameterized Queries - REQUIRED
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
// CORRECT - Parameterized query
|
|
401
|
+
const user = await db.query("SELECT * FROM users WHERE email = $1", [email]);
|
|
402
|
+
|
|
403
|
+
// INCORRECT - String concatenation
|
|
404
|
+
const user = await db.query(
|
|
405
|
+
`SELECT * FROM users WHERE email = '${email}'` // REJECTED!
|
|
406
|
+
);
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### ORM Usage - RECOMMENDED
|
|
410
|
+
|
|
411
|
+
```typescript
|
|
412
|
+
// Using an ORM (Prisma, TypeORM, Sequelize)
|
|
413
|
+
const user = await prisma.user.findUnique({
|
|
414
|
+
where: { email: email },
|
|
415
|
+
});
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
## XSS Prevention - MANDATORY
|
|
419
|
+
|
|
420
|
+
### React Default Protection - USE IT
|
|
421
|
+
|
|
422
|
+
```typescript
|
|
423
|
+
// Safe by default (React escapes automatically)
|
|
424
|
+
<div>{userInput}</div>
|
|
425
|
+
|
|
426
|
+
// DANGEROUS - requires sanitization
|
|
427
|
+
<div dangerouslySetInnerHTML={{
|
|
428
|
+
__html: DOMPurify.sanitize(userInput)
|
|
429
|
+
}} />
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### Content Security Policy - REQUIRED
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
// Strict CSP
|
|
436
|
+
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self' https://api.yourdomain.com;";
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
## CSRF Prevention - MANDATORY
|
|
440
|
+
|
|
441
|
+
### CSRF Token - REQUIRED
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
import csrf from "csurf";
|
|
445
|
+
|
|
446
|
+
// CSRF protection middleware
|
|
447
|
+
const csrfProtection = csrf({ cookie: true });
|
|
448
|
+
|
|
449
|
+
app.post("/api/data", csrfProtection, (req, res) => {
|
|
450
|
+
// Token automatically validated
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
// Send token to client
|
|
454
|
+
app.get("/api/csrf-token", csrfProtection, (req, res) => {
|
|
455
|
+
res.json({ csrfToken: req.csrfToken() });
|
|
456
|
+
});
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
## Rate Limiting - MANDATORY
|
|
460
|
+
|
|
461
|
+
### API Rate Limiting - REQUIRED
|
|
462
|
+
|
|
463
|
+
```typescript
|
|
464
|
+
import rateLimit from "express-rate-limit";
|
|
465
|
+
|
|
466
|
+
// General API rate limit
|
|
467
|
+
const apiLimiter = rateLimit({
|
|
468
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
469
|
+
max: 100, // limit each IP to 100 requests per windowMs
|
|
470
|
+
message: "Too many requests from this IP",
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
// Login rate limit (stricter)
|
|
474
|
+
const loginLimiter = rateLimit({
|
|
475
|
+
windowMs: 15 * 60 * 1000,
|
|
476
|
+
max: 5, // 5 attempts per 15 minutes
|
|
477
|
+
skipSuccessfulRequests: true,
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// Apply to routes
|
|
481
|
+
app.use("/api/", apiLimiter);
|
|
482
|
+
app.post("/api/login", loginLimiter, loginHandler);
|
|
483
|
+
```
|