agentic-team-templates 0.3.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/README.md +280 -0
- package/bin/cli.js +5 -0
- package/package.json +47 -0
- package/src/index.js +521 -0
- package/templates/_shared/code-quality.md +162 -0
- package/templates/_shared/communication.md +114 -0
- package/templates/_shared/core-principles.md +62 -0
- package/templates/_shared/git-workflow.md +165 -0
- package/templates/_shared/security-fundamentals.md +173 -0
- package/templates/blockchain/.cursorrules/defi-patterns.md +520 -0
- package/templates/blockchain/.cursorrules/gas-optimization.md +339 -0
- package/templates/blockchain/.cursorrules/overview.md +130 -0
- package/templates/blockchain/.cursorrules/security.md +318 -0
- package/templates/blockchain/.cursorrules/smart-contracts.md +364 -0
- package/templates/blockchain/.cursorrules/testing.md +415 -0
- package/templates/blockchain/.cursorrules/web3-integration.md +538 -0
- package/templates/blockchain/CLAUDE.md +389 -0
- package/templates/cli-tools/.cursorrules/architecture.md +412 -0
- package/templates/cli-tools/.cursorrules/arguments.md +406 -0
- package/templates/cli-tools/.cursorrules/distribution.md +546 -0
- package/templates/cli-tools/.cursorrules/error-handling.md +455 -0
- package/templates/cli-tools/.cursorrules/overview.md +136 -0
- package/templates/cli-tools/.cursorrules/testing.md +537 -0
- package/templates/cli-tools/.cursorrules/user-experience.md +545 -0
- package/templates/cli-tools/CLAUDE.md +356 -0
- package/templates/data-engineering/.cursorrules/data-modeling.md +367 -0
- package/templates/data-engineering/.cursorrules/data-quality.md +455 -0
- package/templates/data-engineering/.cursorrules/overview.md +85 -0
- package/templates/data-engineering/.cursorrules/performance.md +339 -0
- package/templates/data-engineering/.cursorrules/pipeline-design.md +280 -0
- package/templates/data-engineering/.cursorrules/security.md +460 -0
- package/templates/data-engineering/.cursorrules/testing.md +452 -0
- package/templates/data-engineering/CLAUDE.md +974 -0
- package/templates/devops-sre/.cursorrules/capacity-planning.md +653 -0
- package/templates/devops-sre/.cursorrules/change-management.md +584 -0
- package/templates/devops-sre/.cursorrules/chaos-engineering.md +651 -0
- package/templates/devops-sre/.cursorrules/disaster-recovery.md +641 -0
- package/templates/devops-sre/.cursorrules/incident-management.md +565 -0
- package/templates/devops-sre/.cursorrules/observability.md +714 -0
- package/templates/devops-sre/.cursorrules/overview.md +230 -0
- package/templates/devops-sre/.cursorrules/postmortems.md +588 -0
- package/templates/devops-sre/.cursorrules/runbooks.md +760 -0
- package/templates/devops-sre/.cursorrules/slo-sli.md +617 -0
- package/templates/devops-sre/.cursorrules/toil-reduction.md +567 -0
- package/templates/devops-sre/CLAUDE.md +1007 -0
- package/templates/documentation/.cursorrules/adr.md +277 -0
- package/templates/documentation/.cursorrules/api-documentation.md +411 -0
- package/templates/documentation/.cursorrules/code-comments.md +253 -0
- package/templates/documentation/.cursorrules/maintenance.md +260 -0
- package/templates/documentation/.cursorrules/overview.md +82 -0
- package/templates/documentation/.cursorrules/readme-standards.md +306 -0
- package/templates/documentation/CLAUDE.md +120 -0
- package/templates/fullstack/.cursorrules/api-contracts.md +331 -0
- package/templates/fullstack/.cursorrules/architecture.md +298 -0
- package/templates/fullstack/.cursorrules/overview.md +109 -0
- package/templates/fullstack/.cursorrules/shared-types.md +348 -0
- package/templates/fullstack/.cursorrules/testing.md +386 -0
- package/templates/fullstack/CLAUDE.md +349 -0
- package/templates/ml-ai/.cursorrules/data-engineering.md +483 -0
- package/templates/ml-ai/.cursorrules/deployment.md +601 -0
- package/templates/ml-ai/.cursorrules/model-development.md +538 -0
- package/templates/ml-ai/.cursorrules/monitoring.md +658 -0
- package/templates/ml-ai/.cursorrules/overview.md +131 -0
- package/templates/ml-ai/.cursorrules/security.md +637 -0
- package/templates/ml-ai/.cursorrules/testing.md +678 -0
- package/templates/ml-ai/CLAUDE.md +1136 -0
- package/templates/mobile/.cursorrules/navigation.md +246 -0
- package/templates/mobile/.cursorrules/offline-first.md +302 -0
- package/templates/mobile/.cursorrules/overview.md +71 -0
- package/templates/mobile/.cursorrules/performance.md +345 -0
- package/templates/mobile/.cursorrules/testing.md +339 -0
- package/templates/mobile/CLAUDE.md +233 -0
- package/templates/platform-engineering/.cursorrules/ci-cd.md +778 -0
- package/templates/platform-engineering/.cursorrules/developer-experience.md +632 -0
- package/templates/platform-engineering/.cursorrules/infrastructure-as-code.md +600 -0
- package/templates/platform-engineering/.cursorrules/kubernetes.md +710 -0
- package/templates/platform-engineering/.cursorrules/observability.md +747 -0
- package/templates/platform-engineering/.cursorrules/overview.md +215 -0
- package/templates/platform-engineering/.cursorrules/security.md +855 -0
- package/templates/platform-engineering/.cursorrules/testing.md +878 -0
- package/templates/platform-engineering/CLAUDE.md +850 -0
- package/templates/utility-agent/.cursorrules/action-control.md +284 -0
- package/templates/utility-agent/.cursorrules/context-management.md +186 -0
- package/templates/utility-agent/.cursorrules/hallucination-prevention.md +253 -0
- package/templates/utility-agent/.cursorrules/overview.md +78 -0
- package/templates/utility-agent/.cursorrules/token-optimization.md +369 -0
- package/templates/utility-agent/CLAUDE.md +513 -0
- package/templates/web-backend/.cursorrules/api-design.md +255 -0
- package/templates/web-backend/.cursorrules/authentication.md +309 -0
- package/templates/web-backend/.cursorrules/database-patterns.md +298 -0
- package/templates/web-backend/.cursorrules/error-handling.md +366 -0
- package/templates/web-backend/.cursorrules/overview.md +69 -0
- package/templates/web-backend/.cursorrules/security.md +358 -0
- package/templates/web-backend/.cursorrules/testing.md +395 -0
- package/templates/web-backend/CLAUDE.md +366 -0
- package/templates/web-frontend/.cursorrules/accessibility.md +296 -0
- package/templates/web-frontend/.cursorrules/component-patterns.md +204 -0
- package/templates/web-frontend/.cursorrules/overview.md +72 -0
- package/templates/web-frontend/.cursorrules/performance.md +325 -0
- package/templates/web-frontend/.cursorrules/state-management.md +227 -0
- package/templates/web-frontend/.cursorrules/styling.md +271 -0
- package/templates/web-frontend/.cursorrules/testing.md +311 -0
- package/templates/web-frontend/CLAUDE.md +399 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# API Design
|
|
2
|
+
|
|
3
|
+
Best practices for designing consistent, intuitive APIs.
|
|
4
|
+
|
|
5
|
+
## RESTful Conventions
|
|
6
|
+
|
|
7
|
+
### Resource Naming
|
|
8
|
+
|
|
9
|
+
Use nouns, not verbs. Plural for collections.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
# Good
|
|
13
|
+
GET /users # List users
|
|
14
|
+
POST /users # Create user
|
|
15
|
+
GET /users/:id # Get user
|
|
16
|
+
PUT /users/:id # Update user
|
|
17
|
+
DELETE /users/:id # Delete user
|
|
18
|
+
|
|
19
|
+
GET /users/:id/posts # Get user's posts
|
|
20
|
+
|
|
21
|
+
# Bad
|
|
22
|
+
GET /getUsers
|
|
23
|
+
POST /createUser
|
|
24
|
+
GET /user/:id
|
|
25
|
+
POST /users/:id/delete
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### HTTP Methods
|
|
29
|
+
|
|
30
|
+
| Method | Purpose | Idempotent | Safe |
|
|
31
|
+
|--------|---------|------------|------|
|
|
32
|
+
| GET | Read resource | Yes | Yes |
|
|
33
|
+
| POST | Create resource | No | No |
|
|
34
|
+
| PUT | Replace resource | Yes | No |
|
|
35
|
+
| PATCH | Partial update | No | No |
|
|
36
|
+
| DELETE | Remove resource | Yes | No |
|
|
37
|
+
|
|
38
|
+
### Status Codes
|
|
39
|
+
|
|
40
|
+
**Success:**
|
|
41
|
+
- `200 OK` - Request succeeded
|
|
42
|
+
- `201 Created` - Resource created
|
|
43
|
+
- `204 No Content` - Success with no body (DELETE)
|
|
44
|
+
|
|
45
|
+
**Client Errors:**
|
|
46
|
+
- `400 Bad Request` - Invalid input
|
|
47
|
+
- `401 Unauthorized` - Authentication required
|
|
48
|
+
- `403 Forbidden` - Not permitted
|
|
49
|
+
- `404 Not Found` - Resource doesn't exist
|
|
50
|
+
- `409 Conflict` - Resource conflict
|
|
51
|
+
- `422 Unprocessable Entity` - Validation failed
|
|
52
|
+
- `429 Too Many Requests` - Rate limited
|
|
53
|
+
|
|
54
|
+
**Server Errors:**
|
|
55
|
+
- `500 Internal Server Error` - Unexpected error
|
|
56
|
+
- `502 Bad Gateway` - Upstream error
|
|
57
|
+
- `503 Service Unavailable` - Temporarily down
|
|
58
|
+
|
|
59
|
+
## Request/Response Format
|
|
60
|
+
|
|
61
|
+
### Request Body
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
// POST /users
|
|
65
|
+
{
|
|
66
|
+
"email": "user@example.com",
|
|
67
|
+
"name": "John Doe",
|
|
68
|
+
"role": "admin"
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Success Response
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
// 201 Created
|
|
76
|
+
{
|
|
77
|
+
"data": {
|
|
78
|
+
"id": "usr_123",
|
|
79
|
+
"email": "user@example.com",
|
|
80
|
+
"name": "John Doe",
|
|
81
|
+
"role": "admin",
|
|
82
|
+
"createdAt": "2025-01-20T10:00:00Z"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Error Response
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
// 422 Unprocessable Entity
|
|
91
|
+
{
|
|
92
|
+
"error": {
|
|
93
|
+
"code": "VALIDATION_ERROR",
|
|
94
|
+
"message": "Validation failed",
|
|
95
|
+
"details": [
|
|
96
|
+
{ "field": "email", "message": "Invalid email format" },
|
|
97
|
+
{ "field": "role", "message": "Must be 'user' or 'admin'" }
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Collection Response
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
// GET /users?page=1&limit=20
|
|
107
|
+
{
|
|
108
|
+
"data": [
|
|
109
|
+
{ "id": "usr_123", "name": "John" },
|
|
110
|
+
{ "id": "usr_456", "name": "Jane" }
|
|
111
|
+
],
|
|
112
|
+
"pagination": {
|
|
113
|
+
"page": 1,
|
|
114
|
+
"limit": 20,
|
|
115
|
+
"total": 150,
|
|
116
|
+
"totalPages": 8
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Query Parameters
|
|
122
|
+
|
|
123
|
+
### Filtering
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
GET /users?role=admin&status=active
|
|
127
|
+
GET /posts?createdAfter=2025-01-01
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Sorting
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
GET /users?sort=name # Ascending
|
|
134
|
+
GET /users?sort=-createdAt # Descending (prefix with -)
|
|
135
|
+
GET /users?sort=role,-name # Multiple fields
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Pagination
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
# Offset-based
|
|
142
|
+
GET /users?page=2&limit=20
|
|
143
|
+
|
|
144
|
+
# Cursor-based (for large datasets)
|
|
145
|
+
GET /users?cursor=eyJpZCI6MTIzfQ&limit=20
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Field Selection
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
GET /users?fields=id,name,email
|
|
152
|
+
GET /users/:id?include=posts,comments
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Versioning
|
|
156
|
+
|
|
157
|
+
### URL Versioning (Recommended)
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
GET /v1/users
|
|
161
|
+
GET /v2/users
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Header Versioning
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
GET /users
|
|
168
|
+
Accept: application/vnd.api+json; version=1
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Rate Limiting
|
|
172
|
+
|
|
173
|
+
Include rate limit headers:
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
HTTP/1.1 200 OK
|
|
177
|
+
X-RateLimit-Limit: 100
|
|
178
|
+
X-RateLimit-Remaining: 95
|
|
179
|
+
X-RateLimit-Reset: 1640000000
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
When exceeded:
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
HTTP/1.1 429 Too Many Requests
|
|
186
|
+
Retry-After: 60
|
|
187
|
+
|
|
188
|
+
{
|
|
189
|
+
"error": {
|
|
190
|
+
"code": "RATE_LIMIT_EXCEEDED",
|
|
191
|
+
"message": "Too many requests. Please retry after 60 seconds."
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Idempotency
|
|
197
|
+
|
|
198
|
+
Support idempotency keys for POST requests:
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
POST /payments
|
|
202
|
+
Idempotency-Key: unique-client-id-123
|
|
203
|
+
|
|
204
|
+
{
|
|
205
|
+
"amount": 1000,
|
|
206
|
+
"currency": "USD"
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## HATEOAS (Optional)
|
|
211
|
+
|
|
212
|
+
Include links for discoverability:
|
|
213
|
+
|
|
214
|
+
```json
|
|
215
|
+
{
|
|
216
|
+
"data": {
|
|
217
|
+
"id": "usr_123",
|
|
218
|
+
"name": "John"
|
|
219
|
+
},
|
|
220
|
+
"links": {
|
|
221
|
+
"self": "/users/usr_123",
|
|
222
|
+
"posts": "/users/usr_123/posts",
|
|
223
|
+
"avatar": "/users/usr_123/avatar"
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Documentation
|
|
229
|
+
|
|
230
|
+
- Use OpenAPI/Swagger specification
|
|
231
|
+
- Include examples for all endpoints
|
|
232
|
+
- Document error codes
|
|
233
|
+
- Keep documentation in sync with code
|
|
234
|
+
|
|
235
|
+
```yaml
|
|
236
|
+
# openapi.yaml
|
|
237
|
+
paths:
|
|
238
|
+
/users:
|
|
239
|
+
post:
|
|
240
|
+
summary: Create a new user
|
|
241
|
+
requestBody:
|
|
242
|
+
required: true
|
|
243
|
+
content:
|
|
244
|
+
application/json:
|
|
245
|
+
schema:
|
|
246
|
+
$ref: '#/components/schemas/CreateUserRequest'
|
|
247
|
+
example:
|
|
248
|
+
email: "user@example.com"
|
|
249
|
+
name: "John Doe"
|
|
250
|
+
responses:
|
|
251
|
+
'201':
|
|
252
|
+
description: User created
|
|
253
|
+
'422':
|
|
254
|
+
description: Validation error
|
|
255
|
+
```
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# Authentication & Authorization
|
|
2
|
+
|
|
3
|
+
Best practices for securing backend APIs.
|
|
4
|
+
|
|
5
|
+
## Authentication vs Authorization
|
|
6
|
+
|
|
7
|
+
- **Authentication**: Who are you? (Identity verification)
|
|
8
|
+
- **Authorization**: What can you do? (Permission checking)
|
|
9
|
+
|
|
10
|
+
## Authentication Strategies
|
|
11
|
+
|
|
12
|
+
### JWT (JSON Web Tokens)
|
|
13
|
+
|
|
14
|
+
**When to use**: Stateless APIs, microservices, mobile backends
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
// Generate token
|
|
18
|
+
const generateToken = (user: User): string => {
|
|
19
|
+
return jwt.sign(
|
|
20
|
+
{ sub: user.id, email: user.email, role: user.role },
|
|
21
|
+
process.env.JWT_SECRET,
|
|
22
|
+
{ expiresIn: '15m' } // Short-lived access tokens
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Verify token
|
|
27
|
+
const verifyToken = (token: string): TokenPayload => {
|
|
28
|
+
try {
|
|
29
|
+
return jwt.verify(token, process.env.JWT_SECRET) as TokenPayload;
|
|
30
|
+
} catch (error) {
|
|
31
|
+
throw new UnauthorizedError('Invalid or expired token');
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Token structure:**
|
|
37
|
+
```ts
|
|
38
|
+
// Header
|
|
39
|
+
{ "alg": "HS256", "typ": "JWT" }
|
|
40
|
+
|
|
41
|
+
// Payload (claims)
|
|
42
|
+
{
|
|
43
|
+
"sub": "user_123", // Subject (user ID)
|
|
44
|
+
"email": "user@example.com",
|
|
45
|
+
"role": "admin",
|
|
46
|
+
"iat": 1640000000, // Issued at
|
|
47
|
+
"exp": 1640000900 // Expires at
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Session-Based
|
|
52
|
+
|
|
53
|
+
**When to use**: Traditional web apps, when you need server-side session control
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
// Create session
|
|
57
|
+
app.post('/login', async (req, res) => {
|
|
58
|
+
const user = await authenticateUser(req.body);
|
|
59
|
+
if (!user) {
|
|
60
|
+
return res.status(401).json({ error: 'Invalid credentials' });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
req.session.userId = user.id;
|
|
64
|
+
req.session.role = user.role;
|
|
65
|
+
res.json({ success: true });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Session middleware
|
|
69
|
+
const requireAuth = (req, res, next) => {
|
|
70
|
+
if (!req.session?.userId) {
|
|
71
|
+
return res.status(401).json({ error: 'Not authenticated' });
|
|
72
|
+
}
|
|
73
|
+
next();
|
|
74
|
+
};
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### OAuth 2.0
|
|
78
|
+
|
|
79
|
+
**When to use**: Third-party login (Google, GitHub), delegated authorization
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
// OAuth callback handler
|
|
83
|
+
app.get('/auth/callback', async (req, res) => {
|
|
84
|
+
const { code, state } = req.query;
|
|
85
|
+
|
|
86
|
+
// Verify state to prevent CSRF
|
|
87
|
+
if (state !== req.session.oauthState) {
|
|
88
|
+
return res.status(403).json({ error: 'Invalid state' });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Exchange code for tokens
|
|
92
|
+
const tokens = await exchangeCodeForTokens(code);
|
|
93
|
+
|
|
94
|
+
// Get user info
|
|
95
|
+
const userInfo = await getUserInfo(tokens.access_token);
|
|
96
|
+
|
|
97
|
+
// Create or update user
|
|
98
|
+
const user = await upsertUser(userInfo);
|
|
99
|
+
|
|
100
|
+
// Create session/JWT
|
|
101
|
+
const jwt = generateToken(user);
|
|
102
|
+
res.json({ token: jwt });
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Refresh Tokens
|
|
107
|
+
|
|
108
|
+
Use refresh tokens for long-lived sessions:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
// Generate token pair
|
|
112
|
+
const generateTokens = (user: User) => {
|
|
113
|
+
const accessToken = jwt.sign(
|
|
114
|
+
{ sub: user.id, type: 'access' },
|
|
115
|
+
process.env.JWT_SECRET,
|
|
116
|
+
{ expiresIn: '15m' }
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const refreshToken = jwt.sign(
|
|
120
|
+
{ sub: user.id, type: 'refresh' },
|
|
121
|
+
process.env.REFRESH_SECRET,
|
|
122
|
+
{ expiresIn: '7d' }
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
return { accessToken, refreshToken };
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Refresh endpoint
|
|
129
|
+
app.post('/auth/refresh', async (req, res) => {
|
|
130
|
+
const { refreshToken } = req.body;
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
const payload = jwt.verify(refreshToken, process.env.REFRESH_SECRET);
|
|
134
|
+
|
|
135
|
+
// Optional: Check if refresh token is revoked
|
|
136
|
+
if (await isTokenRevoked(refreshToken)) {
|
|
137
|
+
throw new Error('Token revoked');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const user = await getUser(payload.sub);
|
|
141
|
+
const tokens = generateTokens(user);
|
|
142
|
+
|
|
143
|
+
res.json(tokens);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
res.status(401).json({ error: 'Invalid refresh token' });
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Authorization
|
|
151
|
+
|
|
152
|
+
### Role-Based Access Control (RBAC)
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
// Define roles and permissions
|
|
156
|
+
const permissions = {
|
|
157
|
+
admin: ['users:read', 'users:write', 'posts:read', 'posts:write', 'posts:delete'],
|
|
158
|
+
editor: ['posts:read', 'posts:write'],
|
|
159
|
+
viewer: ['posts:read'],
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// Authorization middleware
|
|
163
|
+
const requirePermission = (permission: string) => {
|
|
164
|
+
return (req, res, next) => {
|
|
165
|
+
const userPermissions = permissions[req.user.role] || [];
|
|
166
|
+
|
|
167
|
+
if (!userPermissions.includes(permission)) {
|
|
168
|
+
return res.status(403).json({ error: 'Forbidden' });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
next();
|
|
172
|
+
};
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// Usage
|
|
176
|
+
app.delete('/posts/:id', requirePermission('posts:delete'), deletePost);
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Resource-Based Authorization
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
// Check ownership
|
|
183
|
+
const requireOwnership = (resourceType: string) => {
|
|
184
|
+
return async (req, res, next) => {
|
|
185
|
+
const resource = await getResource(resourceType, req.params.id);
|
|
186
|
+
|
|
187
|
+
if (!resource) {
|
|
188
|
+
return res.status(404).json({ error: 'Not found' });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (resource.userId !== req.user.id && req.user.role !== 'admin') {
|
|
192
|
+
return res.status(403).json({ error: 'Forbidden' });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
req.resource = resource;
|
|
196
|
+
next();
|
|
197
|
+
};
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// Usage
|
|
201
|
+
app.put('/posts/:id', requireOwnership('post'), updatePost);
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Password Security
|
|
205
|
+
|
|
206
|
+
### Hashing
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
import bcrypt from 'bcrypt';
|
|
210
|
+
|
|
211
|
+
const SALT_ROUNDS = 12;
|
|
212
|
+
|
|
213
|
+
// Hash password
|
|
214
|
+
const hashPassword = async (password: string): Promise<string> => {
|
|
215
|
+
return bcrypt.hash(password, SALT_ROUNDS);
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// Verify password
|
|
219
|
+
const verifyPassword = async (password: string, hash: string): Promise<boolean> => {
|
|
220
|
+
return bcrypt.compare(password, hash);
|
|
221
|
+
};
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Password Requirements
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
const validatePassword = (password: string): ValidationResult => {
|
|
228
|
+
const errors: string[] = [];
|
|
229
|
+
|
|
230
|
+
if (password.length < 8) {
|
|
231
|
+
errors.push('Password must be at least 8 characters');
|
|
232
|
+
}
|
|
233
|
+
if (!/[A-Z]/.test(password)) {
|
|
234
|
+
errors.push('Password must contain an uppercase letter');
|
|
235
|
+
}
|
|
236
|
+
if (!/[a-z]/.test(password)) {
|
|
237
|
+
errors.push('Password must contain a lowercase letter');
|
|
238
|
+
}
|
|
239
|
+
if (!/[0-9]/.test(password)) {
|
|
240
|
+
errors.push('Password must contain a number');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return { valid: errors.length === 0, errors };
|
|
244
|
+
};
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Security Best Practices
|
|
248
|
+
|
|
249
|
+
### Rate Limiting
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
import rateLimit from 'express-rate-limit';
|
|
253
|
+
|
|
254
|
+
const authLimiter = rateLimit({
|
|
255
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
256
|
+
max: 5, // 5 attempts
|
|
257
|
+
message: { error: 'Too many login attempts. Please try again later.' },
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
app.post('/login', authLimiter, loginHandler);
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Secure Cookie Settings
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
res.cookie('session', sessionId, {
|
|
267
|
+
httpOnly: true, // Not accessible via JavaScript
|
|
268
|
+
secure: true, // HTTPS only
|
|
269
|
+
sameSite: 'strict', // CSRF protection
|
|
270
|
+
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
|
|
271
|
+
});
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Token Storage
|
|
275
|
+
|
|
276
|
+
**Access tokens**: Memory or short-lived secure cookie
|
|
277
|
+
**Refresh tokens**: HTTP-only secure cookie or secure storage
|
|
278
|
+
|
|
279
|
+
### Never Log Sensitive Data
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
// Bad
|
|
283
|
+
logger.info(`Login attempt: ${email}, password: ${password}`);
|
|
284
|
+
|
|
285
|
+
// Good
|
|
286
|
+
logger.info(`Login attempt: ${email}`);
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Common Anti-Patterns
|
|
290
|
+
|
|
291
|
+
### Storing Plain Text Passwords
|
|
292
|
+
|
|
293
|
+
❌ Never store passwords in plain text
|
|
294
|
+
✅ Always hash with bcrypt or argon2
|
|
295
|
+
|
|
296
|
+
### Long-Lived Access Tokens
|
|
297
|
+
|
|
298
|
+
❌ Access tokens valid for days/months
|
|
299
|
+
✅ Short-lived access tokens (15-60 min) + refresh tokens
|
|
300
|
+
|
|
301
|
+
### Exposing Internal Errors
|
|
302
|
+
|
|
303
|
+
❌ `res.status(401).json({ error: 'User admin@example.com not found' })`
|
|
304
|
+
✅ `res.status(401).json({ error: 'Invalid credentials' })`
|
|
305
|
+
|
|
306
|
+
### Missing CSRF Protection
|
|
307
|
+
|
|
308
|
+
❌ State-changing requests without CSRF tokens
|
|
309
|
+
✅ CSRF tokens for all non-GET requests in session-based auth
|