@patricio0312rev/skillset 0.1.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/CHANGELOG.md +29 -0
- package/LICENSE +21 -0
- package/README.md +176 -0
- package/bin/cli.js +37 -0
- package/package.json +55 -0
- package/src/commands/init.js +301 -0
- package/src/index.js +168 -0
- package/src/lib/config.js +200 -0
- package/src/lib/generator.js +166 -0
- package/src/utils/display.js +95 -0
- package/src/utils/readme.js +196 -0
- package/src/utils/tool-specific.js +233 -0
- package/templates/ai-engineering/agent-orchestration-planner/ SKILL.md +266 -0
- package/templates/ai-engineering/cost-latency-optimizer/ SKILL.md +270 -0
- package/templates/ai-engineering/doc-to-vector-dataset-generator/ SKILL.md +239 -0
- package/templates/ai-engineering/evaluation-harness/ SKILL.md +219 -0
- package/templates/ai-engineering/guardrails-safety-filter-builder/ SKILL.md +226 -0
- package/templates/ai-engineering/llm-debugger/ SKILL.md +283 -0
- package/templates/ai-engineering/prompt-regression-tester/ SKILL.md +216 -0
- package/templates/ai-engineering/prompt-template-builder/ SKILL.md +393 -0
- package/templates/ai-engineering/rag-pipeline-builder/ SKILL.md +244 -0
- package/templates/ai-engineering/tool-function-schema-designer/ SKILL.md +219 -0
- package/templates/architecture/adr-writer/ SKILL.md +250 -0
- package/templates/architecture/api-versioning-deprecation-planner/ SKILL.md +331 -0
- package/templates/architecture/domain-model-boundaries-mapper/ SKILL.md +300 -0
- package/templates/architecture/migration-planner/ SKILL.md +376 -0
- package/templates/architecture/performance-budget-setter/ SKILL.md +318 -0
- package/templates/architecture/reliability-strategy-builder/ SKILL.md +286 -0
- package/templates/architecture/rfc-generator/ SKILL.md +362 -0
- package/templates/architecture/scalability-playbook/ SKILL.md +279 -0
- package/templates/architecture/system-design-generator/ SKILL.md +339 -0
- package/templates/architecture/tech-debt-prioritizer/ SKILL.md +329 -0
- package/templates/backend/api-contract-normalizer/ SKILL.md +487 -0
- package/templates/backend/api-endpoint-generator/ SKILL.md +415 -0
- package/templates/backend/auth-module-builder/ SKILL.md +99 -0
- package/templates/backend/background-jobs-designer/ SKILL.md +166 -0
- package/templates/backend/caching-strategist/ SKILL.md +190 -0
- package/templates/backend/error-handling-standardizer/ SKILL.md +174 -0
- package/templates/backend/rate-limiting-abuse-protection/ SKILL.md +147 -0
- package/templates/backend/rbac-permissions-builder/ SKILL.md +158 -0
- package/templates/backend/service-layer-extractor/ SKILL.md +269 -0
- package/templates/backend/webhook-receiver-hardener/ SKILL.md +211 -0
- package/templates/ci-cd/artifact-sbom-publisher/ SKILL.md +236 -0
- package/templates/ci-cd/caching-strategy-optimizer/ SKILL.md +195 -0
- package/templates/ci-cd/deployment-checklist-generator/ SKILL.md +381 -0
- package/templates/ci-cd/github-actions-pipeline-creator/ SKILL.md +348 -0
- package/templates/ci-cd/monorepo-ci-optimizer/ SKILL.md +298 -0
- package/templates/ci-cd/preview-environments-builder/ SKILL.md +187 -0
- package/templates/ci-cd/quality-gates-enforcer/ SKILL.md +342 -0
- package/templates/ci-cd/release-automation-builder/ SKILL.md +281 -0
- package/templates/ci-cd/rollback-workflow-builder/ SKILL.md +372 -0
- package/templates/ci-cd/secrets-env-manager/ SKILL.md +242 -0
- package/templates/db-management/backup-restore-runbook-generator/ SKILL.md +505 -0
- package/templates/db-management/data-integrity-auditor/ SKILL.md +505 -0
- package/templates/db-management/data-retention-archiving-planner/ SKILL.md +430 -0
- package/templates/db-management/data-seeding-fixtures-builder/ SKILL.md +375 -0
- package/templates/db-management/db-performance-watchlist/ SKILL.md +425 -0
- package/templates/db-management/etl-sync-job-builder/ SKILL.md +457 -0
- package/templates/db-management/multi-tenant-safety-checker/ SKILL.md +398 -0
- package/templates/db-management/prisma-migration-assistant/ SKILL.md +379 -0
- package/templates/db-management/schema-consistency-checker/ SKILL.md +440 -0
- package/templates/db-management/sql-query-optimizer/ SKILL.md +324 -0
- package/templates/foundation/changelog-writer/ SKILL.md +431 -0
- package/templates/foundation/code-formatter-installer/ SKILL.md +320 -0
- package/templates/foundation/codebase-summarizer/ SKILL.md +360 -0
- package/templates/foundation/dependency-doctor/ SKILL.md +163 -0
- package/templates/foundation/dev-environment-bootstrapper/ SKILL.md +259 -0
- package/templates/foundation/dev-onboarding-builder/ SKILL.md +556 -0
- package/templates/foundation/docs-starter-kit/ SKILL.md +574 -0
- package/templates/foundation/explaining-code/SKILL.md +13 -0
- package/templates/foundation/git-hygiene-enforcer/ SKILL.md +455 -0
- package/templates/foundation/project-scaffolder/ SKILL.md +65 -0
- package/templates/foundation/project-scaffolder/references/templates.md +126 -0
- package/templates/foundation/repo-structure-linter/ SKILL.md +0 -0
- package/templates/foundation/repo-structure-linter/references/conventions.md +98 -0
- package/templates/frontend/animation-micro-interaction-pack/ SKILL.md +41 -0
- package/templates/frontend/component-scaffold-generator/ SKILL.md +562 -0
- package/templates/frontend/design-to-component-translator/ SKILL.md +547 -0
- package/templates/frontend/form-wizard-builder/ SKILL.md +553 -0
- package/templates/frontend/frontend-refactor-planner/ SKILL.md +37 -0
- package/templates/frontend/i18n-frontend-implementer/ SKILL.md +44 -0
- package/templates/frontend/modal-drawer-system/ SKILL.md +377 -0
- package/templates/frontend/page-layout-builder/ SKILL.md +630 -0
- package/templates/frontend/state-ux-flow-builder/ SKILL.md +23 -0
- package/templates/frontend/table-builder/ SKILL.md +350 -0
- package/templates/performance/alerting-dashboard-builder/ SKILL.md +162 -0
- package/templates/performance/backend-latency-profiler-helper/ SKILL.md +108 -0
- package/templates/performance/caching-cdn-strategy-planner/ SKILL.md +150 -0
- package/templates/performance/capacity-planning-helper/ SKILL.md +242 -0
- package/templates/performance/core-web-vitals-tuner/ SKILL.md +126 -0
- package/templates/performance/incident-runbook-generator/ SKILL.md +162 -0
- package/templates/performance/load-test-scenario-builder/ SKILL.md +256 -0
- package/templates/performance/observability-setup/ SKILL.md +232 -0
- package/templates/performance/postmortem-writer/ SKILL.md +203 -0
- package/templates/performance/structured-logging-standardizer/ SKILL.md +122 -0
- package/templates/security/auth-security-reviewer/ SKILL.md +428 -0
- package/templates/security/dependency-vulnerability-triage/ SKILL.md +495 -0
- package/templates/security/input-validation-sanitization-auditor/ SKILL.md +76 -0
- package/templates/security/pii-redaction-logging-policy-builder/ SKILL.md +65 -0
- package/templates/security/rbac-policy-tester/ SKILL.md +80 -0
- package/templates/security/secrets-scanner/ SKILL.md +462 -0
- package/templates/security/secure-headers-csp-builder/ SKILL.md +404 -0
- package/templates/security/security-incident-playbook-generator/ SKILL.md +76 -0
- package/templates/security/security-pr-checklist-skill/ SKILL.md +62 -0
- package/templates/security/threat-model-generator/ SKILL.md +394 -0
- package/templates/testing/contract-testing-builder/ SKILL.md +492 -0
- package/templates/testing/coverage-strategist/ SKILL.md +436 -0
- package/templates/testing/e2e-test-builder/ SKILL.md +382 -0
- package/templates/testing/flaky-test-detective/ SKILL.md +416 -0
- package/templates/testing/integration-test-builder/ SKILL.md +525 -0
- package/templates/testing/mocking-assistant/ SKILL.md +383 -0
- package/templates/testing/snapshot-test-refactorer/ SKILL.md +375 -0
- package/templates/testing/test-data-factory-builder/ SKILL.md +449 -0
- package/templates/testing/test-reporting-triage-skill/ SKILL.md +469 -0
- package/templates/testing/unit-test-generator/ SKILL.md +548 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: caching-strategist
|
|
3
|
+
description: Defines caching strategies with cache keys, TTL values, invalidation triggers, consistency patterns, and correctness checklist. Provides code examples for Redis, CDN, and application-level caching. Use when implementing "caching", "performance optimization", "cache strategy", or "Redis caching".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Caching Strategist
|
|
7
|
+
|
|
8
|
+
Design effective caching strategies for performance and consistency.
|
|
9
|
+
|
|
10
|
+
## Cache Layers
|
|
11
|
+
|
|
12
|
+
**CDN**: Static assets, public pages (TTL: days/weeks)
|
|
13
|
+
**Application Cache** (Redis): API responses, sessions (TTL: minutes/hours)
|
|
14
|
+
**Database Cache**: Query results (TTL: seconds/minutes)
|
|
15
|
+
**Client Cache**: Browser/app local cache
|
|
16
|
+
|
|
17
|
+
## Cache Key Strategy
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
// Hierarchical key structure
|
|
21
|
+
const CACHE_KEYS = {
|
|
22
|
+
user: (id: string) => `user:${id}`,
|
|
23
|
+
userPosts: (userId: string, page: number) => `user:${userId}:posts:${page}`,
|
|
24
|
+
post: (id: string) => `post:${id}`,
|
|
25
|
+
postComments: (postId: string) => `post:${postId}:comments`,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Include version in keys for easy invalidation
|
|
29
|
+
const CACHE_VERSION = "v1";
|
|
30
|
+
const key = `${CACHE_VERSION}:${CACHE_KEYS.user(userId)}`;
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## TTL Strategy
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
const TTL = {
|
|
37
|
+
// Frequently changing
|
|
38
|
+
REALTIME: 10, // 10 seconds
|
|
39
|
+
SHORT: 60, // 1 minute
|
|
40
|
+
|
|
41
|
+
// Moderate updates
|
|
42
|
+
MEDIUM: 300, // 5 minutes
|
|
43
|
+
STANDARD: 3600, // 1 hour
|
|
44
|
+
|
|
45
|
+
// Rarely changing
|
|
46
|
+
LONG: 86400, // 1 day
|
|
47
|
+
VERY_LONG: 604800, // 1 week
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Usage
|
|
51
|
+
await redis.setex(key, TTL.MEDIUM, JSON.stringify(data));
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Cache-Aside Pattern
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
export const getCachedUser = async (userId: string): Promise<User> => {
|
|
58
|
+
const key = CACHE_KEYS.user(userId);
|
|
59
|
+
|
|
60
|
+
// Try cache first
|
|
61
|
+
const cached = await redis.get(key);
|
|
62
|
+
if (cached) {
|
|
63
|
+
return JSON.parse(cached);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Cache miss - fetch from DB
|
|
67
|
+
const user = await db.users.findById(userId);
|
|
68
|
+
|
|
69
|
+
// Store in cache
|
|
70
|
+
await redis.setex(key, TTL.STANDARD, JSON.stringify(user));
|
|
71
|
+
|
|
72
|
+
return user;
|
|
73
|
+
};
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Cache Invalidation
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// Invalidate on update
|
|
80
|
+
export const updateUser = async (userId: string, data: UpdateUserDto) => {
|
|
81
|
+
const user = await db.users.update(userId, data);
|
|
82
|
+
|
|
83
|
+
// Invalidate cache
|
|
84
|
+
await redis.del(CACHE_KEYS.user(userId));
|
|
85
|
+
|
|
86
|
+
// Invalidate related caches
|
|
87
|
+
await redis.del(CACHE_KEYS.userPosts(userId, "*"));
|
|
88
|
+
|
|
89
|
+
return user;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Tag-based invalidation
|
|
93
|
+
const addCacheTags = (key: string, tags: string[]) => {
|
|
94
|
+
tags.forEach((tag) => {
|
|
95
|
+
redis.sadd(`cache_tag:${tag}`, key);
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const invalidateByTag = async (tag: string) => {
|
|
100
|
+
const keys = await redis.smembers(`cache_tag:${tag}`);
|
|
101
|
+
if (keys.length) {
|
|
102
|
+
await redis.del(...keys);
|
|
103
|
+
await redis.del(`cache_tag:${tag}`);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Cache Warming
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
// Pre-populate cache for common queries
|
|
112
|
+
export const warmCache = async () => {
|
|
113
|
+
const popularPosts = await db.posts.findPopular(100);
|
|
114
|
+
|
|
115
|
+
for (const post of popularPosts) {
|
|
116
|
+
const key = CACHE_KEYS.post(post.id);
|
|
117
|
+
await redis.setex(key, TTL.LONG, JSON.stringify(post));
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// Schedule warming
|
|
122
|
+
cron.schedule("0 */6 * * *", warmCache); // Every 6 hours
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Cache Stampede Prevention
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
// Use locks to prevent multiple simultaneous fetches
|
|
129
|
+
export const getCachedWithLock = async (
|
|
130
|
+
key: string,
|
|
131
|
+
fetchFn: () => Promise<any>
|
|
132
|
+
) => {
|
|
133
|
+
const cached = await redis.get(key);
|
|
134
|
+
if (cached) return JSON.parse(cached);
|
|
135
|
+
|
|
136
|
+
const lockKey = `lock:${key}`;
|
|
137
|
+
const acquired = await redis.set(lockKey, "1", "EX", 10, "NX");
|
|
138
|
+
|
|
139
|
+
if (acquired) {
|
|
140
|
+
try {
|
|
141
|
+
// Fetch and cache
|
|
142
|
+
const data = await fetchFn();
|
|
143
|
+
await redis.setex(key, TTL.STANDARD, JSON.stringify(data));
|
|
144
|
+
return data;
|
|
145
|
+
} finally {
|
|
146
|
+
await redis.del(lockKey);
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
// Wait for other request to finish
|
|
150
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
151
|
+
return getCachedWithLock(key, fetchFn);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Cache Correctness Checklist
|
|
157
|
+
|
|
158
|
+
```markdown
|
|
159
|
+
- [ ] Cache keys are unique and predictable
|
|
160
|
+
- [ ] TTL is appropriate for data freshness
|
|
161
|
+
- [ ] Invalidation happens on all updates
|
|
162
|
+
- [ ] Related caches invalidated together
|
|
163
|
+
- [ ] Cache stampede prevention in place
|
|
164
|
+
- [ ] Fallback to DB if cache fails
|
|
165
|
+
- [ ] Monitoring cache hit rate
|
|
166
|
+
- [ ] Cache size doesn't grow unbounded
|
|
167
|
+
- [ ] Sensitive data not cached or encrypted
|
|
168
|
+
- [ ] Cache warming for critical paths
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Best Practices
|
|
172
|
+
|
|
173
|
+
- Cache immutable data aggressively
|
|
174
|
+
- Short TTLs for frequently changing data
|
|
175
|
+
- Invalidate on write, not on read
|
|
176
|
+
- Monitor hit rates and adjust
|
|
177
|
+
- Use tags for bulk invalidation
|
|
178
|
+
- Prevent cache stampedes
|
|
179
|
+
- Graceful degradation if cache down
|
|
180
|
+
|
|
181
|
+
## Output Checklist
|
|
182
|
+
|
|
183
|
+
- [ ] Cache key naming strategy
|
|
184
|
+
- [ ] TTL values per data type
|
|
185
|
+
- [ ] Invalidation triggers documented
|
|
186
|
+
- [ ] Cache-aside implementation
|
|
187
|
+
- [ ] Stampede prevention
|
|
188
|
+
- [ ] Cache warming strategy
|
|
189
|
+
- [ ] Monitoring/metrics setup
|
|
190
|
+
- [ ] Correctness checklist completed
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: error-handling-standardizer
|
|
3
|
+
description: Creates consistent error handling with custom error classes, HTTP status mapping, structured logging, safe client messages, and error taxonomy. Use when standardizing "error handling", "logging", "error responses", or "exception management".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Error Handling Standardizer
|
|
7
|
+
|
|
8
|
+
Build consistent, debuggable error handling across the application.
|
|
9
|
+
|
|
10
|
+
## Error Taxonomy
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
export class AppError extends Error {
|
|
14
|
+
constructor(
|
|
15
|
+
public code: string,
|
|
16
|
+
public message: string,
|
|
17
|
+
public statusCode: number = 500,
|
|
18
|
+
public isOperational: boolean = true,
|
|
19
|
+
public details?: any
|
|
20
|
+
) {
|
|
21
|
+
super(message);
|
|
22
|
+
Error.captureStackTrace(this, this.constructor);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class ValidationError extends AppError {
|
|
27
|
+
constructor(details: Record<string, string[]>) {
|
|
28
|
+
super("VALIDATION_ERROR", "Validation failed", 400, true, details);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class NotFoundError extends AppError {
|
|
33
|
+
constructor(resource: string) {
|
|
34
|
+
super("NOT_FOUND", `${resource} not found`, 404);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class UnauthorizedError extends AppError {
|
|
39
|
+
constructor(message = "Unauthorized") {
|
|
40
|
+
super("UNAUTHORIZED", message, 401);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class ForbiddenError extends AppError {
|
|
45
|
+
constructor(message = "Forbidden") {
|
|
46
|
+
super("FORBIDDEN", message, 403);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Error Handler Middleware
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
export const errorHandler = (
|
|
55
|
+
err: Error,
|
|
56
|
+
req: Request,
|
|
57
|
+
res: Response,
|
|
58
|
+
next: NextFunction
|
|
59
|
+
) => {
|
|
60
|
+
// Log error
|
|
61
|
+
logger.error("Request error", {
|
|
62
|
+
error: err.message,
|
|
63
|
+
stack: err.stack,
|
|
64
|
+
path: req.path,
|
|
65
|
+
method: req.method,
|
|
66
|
+
requestId: req.id,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Operational errors (known)
|
|
70
|
+
if (err instanceof AppError && err.isOperational) {
|
|
71
|
+
return res.status(err.statusCode).json({
|
|
72
|
+
success: false,
|
|
73
|
+
error: {
|
|
74
|
+
code: err.code,
|
|
75
|
+
message: err.message,
|
|
76
|
+
details: err.details,
|
|
77
|
+
trace_id: req.id,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Programming errors (unknown)
|
|
83
|
+
return res.status(500).json({
|
|
84
|
+
success: false,
|
|
85
|
+
error: {
|
|
86
|
+
code: "INTERNAL_ERROR",
|
|
87
|
+
message: "An unexpected error occurred",
|
|
88
|
+
trace_id: req.id,
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Structured Logging
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import winston from "winston";
|
|
98
|
+
|
|
99
|
+
export const logger = winston.createLogger({
|
|
100
|
+
level: "info",
|
|
101
|
+
format: winston.format.combine(
|
|
102
|
+
winston.format.timestamp(),
|
|
103
|
+
winston.format.errors({ stack: true }),
|
|
104
|
+
winston.format.json()
|
|
105
|
+
),
|
|
106
|
+
transports: [
|
|
107
|
+
new winston.transports.File({ filename: "error.log", level: "error" }),
|
|
108
|
+
new winston.transports.File({ filename: "combined.log" }),
|
|
109
|
+
],
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Log with context
|
|
113
|
+
logger.error("Payment processing failed", {
|
|
114
|
+
userId: user.id,
|
|
115
|
+
amount: payment.amount,
|
|
116
|
+
error: err.message,
|
|
117
|
+
trace_id: req.id,
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Safe Client Messages
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// Never expose internal errors to clients
|
|
125
|
+
const getSafeErrorMessage = (err: Error): string => {
|
|
126
|
+
if (err instanceof AppError && err.isOperational) {
|
|
127
|
+
return err.message; // Safe, user-facing message
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Generic message for unexpected errors
|
|
131
|
+
return "An unexpected error occurred";
|
|
132
|
+
};
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Async Error Handling
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
// Wrap async routes
|
|
139
|
+
export const asyncHandler = (fn: RequestHandler) => {
|
|
140
|
+
return (req: Request, res: Response, next: NextFunction) => {
|
|
141
|
+
Promise.resolve(fn(req, res, next)).catch(next);
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// Usage
|
|
146
|
+
router.get(
|
|
147
|
+
"/users",
|
|
148
|
+
asyncHandler(async (req, res) => {
|
|
149
|
+
const users = await userService.findAll();
|
|
150
|
+
res.json(users);
|
|
151
|
+
})
|
|
152
|
+
);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Best Practices
|
|
156
|
+
|
|
157
|
+
- Use custom error classes
|
|
158
|
+
- Distinguish operational vs programming errors
|
|
159
|
+
- Log all errors with context
|
|
160
|
+
- Never expose stack traces to clients
|
|
161
|
+
- Include trace IDs for debugging
|
|
162
|
+
- Monitor error rates by type
|
|
163
|
+
- Set up alerting for critical errors
|
|
164
|
+
|
|
165
|
+
## Output Checklist
|
|
166
|
+
|
|
167
|
+
- [ ] Custom error classes defined
|
|
168
|
+
- [ ] Error handler middleware
|
|
169
|
+
- [ ] HTTP status code mapping
|
|
170
|
+
- [ ] Structured logging setup
|
|
171
|
+
- [ ] Safe client error messages
|
|
172
|
+
- [ ] Async error wrapper
|
|
173
|
+
- [ ] Error monitoring/alerts
|
|
174
|
+
- [ ] Documentation of error codes
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rate-limiting-abuse-protection
|
|
3
|
+
description: Implements rate limiting and abuse prevention with per-route policies, IP/user-based limits, sliding windows, safe error responses, and observability. Use when adding "rate limiting", "API protection", "abuse prevention", or "DDoS protection".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Rate Limiting & Abuse Protection
|
|
7
|
+
|
|
8
|
+
Protect APIs from abuse with intelligent rate limiting.
|
|
9
|
+
|
|
10
|
+
## Rate Limit Strategies
|
|
11
|
+
|
|
12
|
+
**Fixed Window**: 100 requests per hour
|
|
13
|
+
**Sliding Window**: More accurate, prevents bursts
|
|
14
|
+
**Token Bucket**: Allow bursts up to limit
|
|
15
|
+
**Leaky Bucket**: Smooth request rate
|
|
16
|
+
|
|
17
|
+
## Implementation (Express)
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import rateLimit from "express-rate-limit";
|
|
21
|
+
|
|
22
|
+
// Global rate limit
|
|
23
|
+
const globalLimiter = rateLimit({
|
|
24
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
25
|
+
max: 100, // 100 requests per window
|
|
26
|
+
message: "Too many requests, please try again later",
|
|
27
|
+
standardHeaders: true, // Return rate limit info in headers
|
|
28
|
+
legacyHeaders: false,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Stricter limit for auth endpoints
|
|
32
|
+
const authLimiter = rateLimit({
|
|
33
|
+
windowMs: 15 * 60 * 1000,
|
|
34
|
+
max: 5, // Only 5 attempts
|
|
35
|
+
skipSuccessfulRequests: true, // Don't count successful logins
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
app.use("/api/", globalLimiter);
|
|
39
|
+
app.use("/api/auth/login", authLimiter);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Redis-based Rate Limiting
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import Redis from "ioredis";
|
|
46
|
+
|
|
47
|
+
const redis = new Redis();
|
|
48
|
+
|
|
49
|
+
export const checkRateLimit = async (
|
|
50
|
+
key: string,
|
|
51
|
+
max: number,
|
|
52
|
+
window: number
|
|
53
|
+
): Promise<{ allowed: boolean; remaining: number }> => {
|
|
54
|
+
const now = Date.now();
|
|
55
|
+
const windowStart = now - window;
|
|
56
|
+
|
|
57
|
+
await redis
|
|
58
|
+
.multi()
|
|
59
|
+
.zremrangebyscore(key, 0, windowStart)
|
|
60
|
+
.zadd(key, now, `${now}`)
|
|
61
|
+
.zcard(key)
|
|
62
|
+
.expire(key, Math.ceil(window / 1000))
|
|
63
|
+
.exec();
|
|
64
|
+
|
|
65
|
+
const count = await redis.zcard(key);
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
allowed: count <= max,
|
|
69
|
+
remaining: Math.max(0, max - count),
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Per-User Rate Limiting
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
export const userRateLimit = (max: number, window: number) => {
|
|
78
|
+
return async (req, res, next) => {
|
|
79
|
+
if (!req.user) return next();
|
|
80
|
+
|
|
81
|
+
const key = `rate_limit:user:${req.user.id}`;
|
|
82
|
+
const result = await checkRateLimit(key, max, window);
|
|
83
|
+
|
|
84
|
+
res.setHeader("X-RateLimit-Limit", max);
|
|
85
|
+
res.setHeader("X-RateLimit-Remaining", result.remaining);
|
|
86
|
+
|
|
87
|
+
if (!result.allowed) {
|
|
88
|
+
return res.status(429).json({
|
|
89
|
+
error: "Rate limit exceeded",
|
|
90
|
+
retryAfter: window / 1000,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
next();
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## IP-based Protection
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
// Block suspicious IPs
|
|
103
|
+
const ipBlocklist = new Set<string>();
|
|
104
|
+
|
|
105
|
+
export const checkIPReputation = async (ip: string): Promise<boolean> => {
|
|
106
|
+
if (ipBlocklist.has(ip)) return false;
|
|
107
|
+
|
|
108
|
+
// Check against threat intelligence API
|
|
109
|
+
const reputation = await checkThreatIntel(ip);
|
|
110
|
+
if (reputation.isMalicious) {
|
|
111
|
+
ipBlocklist.add(ip);
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return true;
|
|
116
|
+
};
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Response Headers
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
X-RateLimit-Limit: 100
|
|
123
|
+
X-RateLimit-Remaining: 95
|
|
124
|
+
X-RateLimit-Reset: 1640000000
|
|
125
|
+
Retry-After: 3600
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Best Practices
|
|
129
|
+
|
|
130
|
+
- Different limits for different endpoints
|
|
131
|
+
- Lower limits for expensive operations
|
|
132
|
+
- Skip rate limit for internal services
|
|
133
|
+
- Return helpful error messages
|
|
134
|
+
- Log rate limit violations
|
|
135
|
+
- Monitor for abuse patterns
|
|
136
|
+
- Allowlist trusted IPs
|
|
137
|
+
|
|
138
|
+
## Output Checklist
|
|
139
|
+
|
|
140
|
+
- [ ] Rate limiter middleware
|
|
141
|
+
- [ ] Per-route policies
|
|
142
|
+
- [ ] User-based limiting
|
|
143
|
+
- [ ] IP-based limiting
|
|
144
|
+
- [ ] Rate limit headers
|
|
145
|
+
- [ ] Safe error responses
|
|
146
|
+
- [ ] Observability/logging
|
|
147
|
+
- [ ] Bypass for internal services
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rbac-permissions-builder
|
|
3
|
+
description: Implements role-based access control with permission matrix, route guards, policy functions, and UI permission hints. Provides middleware/guards, helper utilities, test suggestions, and permission checking patterns. Use when building "RBAC", "permissions", "access control", or "authorization".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# RBAC/Permissions Builder
|
|
7
|
+
|
|
8
|
+
Implement flexible role-based access control systems.
|
|
9
|
+
|
|
10
|
+
## Permission Matrix
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
// Define permissions
|
|
14
|
+
export enum Permission {
|
|
15
|
+
USER_READ = "user:read",
|
|
16
|
+
USER_WRITE = "user:write",
|
|
17
|
+
USER_DELETE = "user:delete",
|
|
18
|
+
POST_READ = "post:read",
|
|
19
|
+
POST_WRITE = "post:write",
|
|
20
|
+
ADMIN_ACCESS = "admin:access",
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Define roles
|
|
24
|
+
export const ROLE_PERMISSIONS = {
|
|
25
|
+
user: [Permission.USER_READ, Permission.POST_READ, Permission.POST_WRITE],
|
|
26
|
+
moderator: [...userPermissions, Permission.POST_DELETE],
|
|
27
|
+
admin: Object.values(Permission), // All permissions
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Check permission
|
|
31
|
+
export const hasPermission = (user: User, permission: Permission): boolean => {
|
|
32
|
+
return ROLE_PERMISSIONS[user.role]?.includes(permission) ?? false;
|
|
33
|
+
};
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Route Guards (Express)
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
export const requirePermission = (...permissions: Permission[]) => {
|
|
40
|
+
return (req: Request, res: Response, next: NextFunction) => {
|
|
41
|
+
if (!req.user) {
|
|
42
|
+
return res.status(401).json({ error: "Unauthorized" });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const hasAllPermissions = permissions.every((p) =>
|
|
46
|
+
hasPermission(req.user, p)
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
if (!hasAllPermissions) {
|
|
50
|
+
return res.status(403).json({ error: "Forbidden" });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
next();
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Usage
|
|
58
|
+
router.delete(
|
|
59
|
+
"/users/:id",
|
|
60
|
+
authenticate,
|
|
61
|
+
requirePermission(Permission.USER_DELETE),
|
|
62
|
+
controller.delete
|
|
63
|
+
);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Policy Pattern
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// policies/user.policy.ts
|
|
70
|
+
export class UserPolicy {
|
|
71
|
+
static canUpdate(currentUser: User, targetUser: User): boolean {
|
|
72
|
+
// Users can update themselves
|
|
73
|
+
if (currentUser.id === targetUser.id) return true;
|
|
74
|
+
|
|
75
|
+
// Admins can update anyone
|
|
76
|
+
if (hasPermission(currentUser, Permission.USER_WRITE)) return true;
|
|
77
|
+
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
static canDelete(currentUser: User, targetUser: User): boolean {
|
|
82
|
+
// Can't delete yourself
|
|
83
|
+
if (currentUser.id === targetUser.id) return false;
|
|
84
|
+
|
|
85
|
+
// Only admins can delete
|
|
86
|
+
return hasPermission(currentUser, Permission.USER_DELETE);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Usage in controller
|
|
91
|
+
if (!UserPolicy.canUpdate(req.user, targetUser)) {
|
|
92
|
+
return res.status(403).json({ error: "Cannot update this user" });
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Resource Ownership
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
export const requireOwnership = (
|
|
100
|
+
getResourceUserId: (req: Request) => Promise<string>
|
|
101
|
+
) => {
|
|
102
|
+
return async (req: Request, res: Response, next: NextFunction) => {
|
|
103
|
+
const resourceUserId = await getResourceUserId(req);
|
|
104
|
+
|
|
105
|
+
// Owner can access
|
|
106
|
+
if (req.user.id === resourceUserId) {
|
|
107
|
+
return next();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Admin can access anything
|
|
111
|
+
if (hasPermission(req.user, Permission.ADMIN_ACCESS)) {
|
|
112
|
+
return next();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return res.status(403).json({ error: "Forbidden" });
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## UI Permission Hints
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
// Return permissions with user
|
|
124
|
+
GET /api/me
|
|
125
|
+
{
|
|
126
|
+
"user": { ... },
|
|
127
|
+
"permissions": ["user:read", "post:write"]
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Frontend helper
|
|
131
|
+
export const usePermission = (permission: Permission): boolean => {
|
|
132
|
+
const { user } = useAuth();
|
|
133
|
+
return user?.permissions?.includes(permission) ?? false;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// Usage
|
|
137
|
+
{usePermission('user:delete') && <DeleteButton />}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Best Practices
|
|
141
|
+
|
|
142
|
+
- Define permissions granularly (resource:action)
|
|
143
|
+
- Check at multiple layers (route, controller, UI)
|
|
144
|
+
- Use policies for complex rules
|
|
145
|
+
- Cache permission checks
|
|
146
|
+
- Log permission denials
|
|
147
|
+
- Test all permission paths
|
|
148
|
+
|
|
149
|
+
## Output Checklist
|
|
150
|
+
|
|
151
|
+
- [ ] Permission enum/constants
|
|
152
|
+
- [ ] Role-to-permission mapping
|
|
153
|
+
- [ ] Route guard middleware
|
|
154
|
+
- [ ] Policy classes for complex rules
|
|
155
|
+
- [ ] Ownership checking utilities
|
|
156
|
+
- [ ] Permission checking helpers
|
|
157
|
+
- [ ] UI permission hints endpoint
|
|
158
|
+
- [ ] Test cases for all permission paths
|