@xenterprises/fastify-xstripe 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/.dockerignore +62 -0
- package/.env.example +116 -0
- package/API.md +574 -0
- package/CHANGELOG.md +96 -0
- package/EXAMPLES.md +883 -0
- package/LICENSE +15 -0
- package/MIGRATION.md +374 -0
- package/QUICK_START.md +179 -0
- package/README.md +331 -0
- package/SECURITY.md +465 -0
- package/TESTING.md +357 -0
- package/index.d.ts +309 -0
- package/package.json +53 -0
- package/server/app.js +557 -0
- package/src/handlers/defaultHandlers.js +355 -0
- package/src/handlers/exampleHandlers.js +278 -0
- package/src/handlers/index.js +8 -0
- package/src/index.js +10 -0
- package/src/utils/helpers.js +220 -0
- package/src/webhooks/webhooks.js +72 -0
- package/src/xStripe.js +45 -0
- package/test/handlers.test.js +959 -0
- package/test/xStripe.integration.test.js +409 -0
package/SECURITY.md
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
xStripe is a Fastify plugin for Stripe webhook handling. This document outlines the security considerations, best practices, and guidelines for using xStripe safely in production environments.
|
|
6
|
+
|
|
7
|
+
## Security Model
|
|
8
|
+
|
|
9
|
+
### What xStripe Does Safely
|
|
10
|
+
- **Webhook Signature Verification** - All webhook requests verified against Stripe signature
|
|
11
|
+
- **Request Validation** - Ensures only valid Stripe events are processed
|
|
12
|
+
- **Isolated Handler Execution** - Each handler runs in isolated context
|
|
13
|
+
- **Error Handling** - Errors don't expose sensitive data
|
|
14
|
+
- **API Key Security** - Keys never logged or exposed in errors
|
|
15
|
+
|
|
16
|
+
### Attack Surface
|
|
17
|
+
The main security concerns are:
|
|
18
|
+
|
|
19
|
+
1. **Webhook Signature Verification** - Ensuring authentic Stripe requests
|
|
20
|
+
2. **Handler Code Injection** - Custom handlers with untrusted input
|
|
21
|
+
3. **API Key Exposure** - Secure key management
|
|
22
|
+
4. **Data Leakage** - Error messages revealing sensitive information
|
|
23
|
+
5. **Idempotency** - Duplicate event handling
|
|
24
|
+
|
|
25
|
+
## Security Guidelines
|
|
26
|
+
|
|
27
|
+
### 1. Webhook Signature Verification
|
|
28
|
+
|
|
29
|
+
**Current Implementation** ✅
|
|
30
|
+
```javascript
|
|
31
|
+
// Automatically verified by Stripe SDK
|
|
32
|
+
const event = await stripe.webhooks.constructEvent(
|
|
33
|
+
body,
|
|
34
|
+
sig,
|
|
35
|
+
webhookSecret
|
|
36
|
+
);
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Always Required**
|
|
40
|
+
- Webhook secret must be set in production
|
|
41
|
+
- Never skip signature verification
|
|
42
|
+
- Logs indicate if verification is disabled
|
|
43
|
+
|
|
44
|
+
**Best Practice**:
|
|
45
|
+
```javascript
|
|
46
|
+
await fastify.register(xStripe, {
|
|
47
|
+
apiKey: process.env.STRIPE_API_KEY,
|
|
48
|
+
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET, // Required
|
|
49
|
+
webhookPath: '/stripe/webhook',
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Failure Handling**:
|
|
54
|
+
```javascript
|
|
55
|
+
fastify.setErrorHandler((error, request, reply) => {
|
|
56
|
+
if (error.code === 'SIGNATURE_VERIFY_FAILURE') {
|
|
57
|
+
// Log security event
|
|
58
|
+
fastify.log.warn('Invalid Stripe webhook signature');
|
|
59
|
+
reply.status(401).send({ error: 'Unauthorized' });
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
### 2. API Key Management
|
|
67
|
+
|
|
68
|
+
**Risk**: Stripe API keys are highly sensitive credentials
|
|
69
|
+
|
|
70
|
+
**Secure Practices**:
|
|
71
|
+
```javascript
|
|
72
|
+
// ✅ Use environment variables
|
|
73
|
+
const apiKey = process.env.STRIPE_API_KEY;
|
|
74
|
+
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
|
|
75
|
+
|
|
76
|
+
// ✅ Rotate keys regularly
|
|
77
|
+
// ✅ Use restricted API keys for specific operations
|
|
78
|
+
// ✅ Monitor API key usage in Stripe Dashboard
|
|
79
|
+
|
|
80
|
+
// ❌ Never hardcode credentials
|
|
81
|
+
// ❌ Never commit credentials to git
|
|
82
|
+
// ❌ Never log credentials
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Environment Variable Setup**:
|
|
86
|
+
```bash
|
|
87
|
+
# .env (NEVER commit this file)
|
|
88
|
+
STRIPE_API_KEY=sk_live_xxxxx
|
|
89
|
+
STRIPE_WEBHOOK_SECRET=whsec_xxxxx
|
|
90
|
+
|
|
91
|
+
# .gitignore
|
|
92
|
+
.env
|
|
93
|
+
.env.local
|
|
94
|
+
*.key
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Monitoring**:
|
|
98
|
+
- Check Stripe Dashboard for suspicious API activity
|
|
99
|
+
- Rotate keys if compromised
|
|
100
|
+
- Use restricted API keys with specific scopes
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
### 3. Handler Code Security
|
|
105
|
+
|
|
106
|
+
**Risk**: Custom handlers processing untrusted event data
|
|
107
|
+
|
|
108
|
+
**Validation Pattern**:
|
|
109
|
+
```javascript
|
|
110
|
+
// ✅ Validate all event data
|
|
111
|
+
const handler = async (event, fastify, stripe) => {
|
|
112
|
+
// Validate required fields
|
|
113
|
+
if (!event.data?.object?.id) {
|
|
114
|
+
throw new Error('Invalid event structure');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const customerId = event.data.object.customer;
|
|
118
|
+
|
|
119
|
+
// Verify customer exists and belongs to correct account
|
|
120
|
+
const customer = await stripe.customers.retrieve(customerId);
|
|
121
|
+
if (!customer) {
|
|
122
|
+
throw new Error('Customer not found');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Process safely
|
|
126
|
+
await fastify.db.user.update({
|
|
127
|
+
where: { stripeCustomerId: customerId },
|
|
128
|
+
data: { subscriptionStatus: 'active' },
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// ❌ Never trust event data blindly
|
|
133
|
+
// ❌ Don't use event data in database queries without validation
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Error Handling**:
|
|
137
|
+
```javascript
|
|
138
|
+
// ✅ Catch errors in handlers
|
|
139
|
+
try {
|
|
140
|
+
// Handler logic
|
|
141
|
+
} catch (error) {
|
|
142
|
+
// Log full error internally
|
|
143
|
+
fastify.log.error(error);
|
|
144
|
+
|
|
145
|
+
// Return generic error to Stripe
|
|
146
|
+
reply.status(200).send({ received: true });
|
|
147
|
+
|
|
148
|
+
// Set up alerting for failures
|
|
149
|
+
await alerting.notify({
|
|
150
|
+
severity: 'high',
|
|
151
|
+
message: `Webhook handler failed: ${error.message}`,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ❌ Don't expose error details to client
|
|
156
|
+
// ❌ Don't return error messages in webhook response
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
### 4. Idempotency & Duplicate Events
|
|
162
|
+
|
|
163
|
+
**Risk**: Stripe might retry failed webhooks, causing duplicate processing
|
|
164
|
+
|
|
165
|
+
**Best Practice - Idempotent Operations**:
|
|
166
|
+
```javascript
|
|
167
|
+
// ✅ Use event ID for deduplication
|
|
168
|
+
const handler = async (event, fastify, stripe) => {
|
|
169
|
+
const eventId = event.id;
|
|
170
|
+
|
|
171
|
+
// Check if already processed
|
|
172
|
+
const processed = await fastify.db.webhookEvent.findUnique({
|
|
173
|
+
where: { stripeEventId: eventId },
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
if (processed) {
|
|
177
|
+
return; // Already handled, skip
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Process event
|
|
181
|
+
await processSubscription(event);
|
|
182
|
+
|
|
183
|
+
// Mark as processed
|
|
184
|
+
await fastify.db.webhookEvent.create({
|
|
185
|
+
data: {
|
|
186
|
+
stripeEventId: eventId,
|
|
187
|
+
type: event.type,
|
|
188
|
+
processedAt: new Date(),
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
};
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Database Schema**:
|
|
195
|
+
```javascript
|
|
196
|
+
// Track processed events
|
|
197
|
+
model WebhookEvent {
|
|
198
|
+
id String @id @default(cuid())
|
|
199
|
+
stripeEventId String @unique
|
|
200
|
+
type String
|
|
201
|
+
processedAt DateTime @default(now())
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
### 5. Error Handling
|
|
208
|
+
|
|
209
|
+
**Risk**: Error messages could expose sensitive information
|
|
210
|
+
|
|
211
|
+
**Secure Error Responses**:
|
|
212
|
+
```javascript
|
|
213
|
+
// ✅ Generic response to Stripe
|
|
214
|
+
reply.status(200).send({ received: true });
|
|
215
|
+
|
|
216
|
+
// ✅ Log detailed errors internally
|
|
217
|
+
fastify.log.error('Handler failed:', {
|
|
218
|
+
eventId: event.id,
|
|
219
|
+
type: event.type,
|
|
220
|
+
error: error.message,
|
|
221
|
+
stack: error.stack,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// ✅ Alert on critical failures
|
|
225
|
+
if (error.code === 'DATABASE_ERROR') {
|
|
226
|
+
await alerting.critical('Database failure in webhook handler');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ❌ Never send error details in response
|
|
230
|
+
// ❌ Never log API keys
|
|
231
|
+
// ❌ Never expose stack traces to clients
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
### 6. HTTP Endpoint Security
|
|
237
|
+
|
|
238
|
+
**Webhook Endpoint Protection**:
|
|
239
|
+
```javascript
|
|
240
|
+
// Basic rate limiting
|
|
241
|
+
import rateLimit from '@fastify/rate-limit';
|
|
242
|
+
|
|
243
|
+
await fastify.register(rateLimit, {
|
|
244
|
+
max: 1000, // 1000 requests
|
|
245
|
+
timeWindow: '1 minute',
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Webhook-specific protection
|
|
249
|
+
fastify.post('/stripe/webhook', {
|
|
250
|
+
config: {
|
|
251
|
+
rateLimit: {
|
|
252
|
+
max: 500, // Stripe sends frequently
|
|
253
|
+
timeWindow: '1 minute',
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
}, async (request, reply) => {
|
|
257
|
+
// Handler
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Input Validation**:
|
|
262
|
+
```javascript
|
|
263
|
+
// Validate Stripe signature header
|
|
264
|
+
const sig = request.headers['stripe-signature'];
|
|
265
|
+
if (!sig) {
|
|
266
|
+
return reply.status(401).send({ error: 'Missing signature' });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Size limits
|
|
270
|
+
const MAX_PAYLOAD = 1024 * 100; // 100KB
|
|
271
|
+
if (request.rawBody.length > MAX_PAYLOAD) {
|
|
272
|
+
return reply.status(413).send({ error: 'Payload too large' });
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
### 7. Logging & Monitoring
|
|
279
|
+
|
|
280
|
+
**Safe Logging**:
|
|
281
|
+
```javascript
|
|
282
|
+
// ✅ Log event IDs and types
|
|
283
|
+
fastify.log.info('Processing webhook', {
|
|
284
|
+
eventId: event.id,
|
|
285
|
+
type: event.type,
|
|
286
|
+
timestamp: event.created,
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// ✅ Log handler outcomes
|
|
290
|
+
fastify.log.info('Webhook processed', {
|
|
291
|
+
eventId: event.id,
|
|
292
|
+
handler: handlerName,
|
|
293
|
+
duration: endTime - startTime,
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// ❌ Never log sensitive data
|
|
297
|
+
// ❌ Never log API keys
|
|
298
|
+
// ❌ Never log customer financial info
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Monitoring Setup**:
|
|
302
|
+
```javascript
|
|
303
|
+
// Track success/failure rates
|
|
304
|
+
const metrics = {
|
|
305
|
+
successCount: 0,
|
|
306
|
+
failureCount: 0,
|
|
307
|
+
lastEvent: null,
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
// Alert on failures
|
|
311
|
+
if (handlerError) {
|
|
312
|
+
metrics.failureCount++;
|
|
313
|
+
|
|
314
|
+
if (metrics.failureCount > 5) {
|
|
315
|
+
await alerting.critical('High webhook failure rate');
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
### 8. Stripe Account Security
|
|
323
|
+
|
|
324
|
+
**Best Practices**:
|
|
325
|
+
- Enable 2FA on Stripe account
|
|
326
|
+
- Use team and roles for access control
|
|
327
|
+
- Rotate API keys periodically
|
|
328
|
+
- Monitor API activity in dashboard
|
|
329
|
+
- Use restricted API keys for webhooks
|
|
330
|
+
- Review connected accounts regularly
|
|
331
|
+
- Set up IP allowlisting if possible
|
|
332
|
+
- Enable email notifications for key events
|
|
333
|
+
|
|
334
|
+
**Webhook Settings**:
|
|
335
|
+
- Only subscribe to necessary events
|
|
336
|
+
- Use specific event types, not wildcards
|
|
337
|
+
- Monitor webhook failures in dashboard
|
|
338
|
+
- Test webhooks in test mode first
|
|
339
|
+
- Keep webhook endpoint updated
|
|
340
|
+
- Version your webhook handlers
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
### 9. Credential Rotation
|
|
345
|
+
|
|
346
|
+
**Rotating Stripe Keys**:
|
|
347
|
+
1. Generate new API key in Stripe Dashboard
|
|
348
|
+
2. Update environment variable
|
|
349
|
+
3. Restart application
|
|
350
|
+
4. Verify new key works
|
|
351
|
+
5. Delete old key in Stripe Dashboard
|
|
352
|
+
|
|
353
|
+
**Time-based Rotation**:
|
|
354
|
+
```bash
|
|
355
|
+
# Rotate keys every 90 days
|
|
356
|
+
0 0 1 */3 * /path/to/rotate-keys.sh
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
### 10. Incident Response
|
|
362
|
+
|
|
363
|
+
**If API Key is Compromised**:
|
|
364
|
+
1. Immediately deactivate key in Stripe Dashboard
|
|
365
|
+
2. Generate new key
|
|
366
|
+
3. Update all applications
|
|
367
|
+
4. Restart services
|
|
368
|
+
5. Review API activity for unauthorized use
|
|
369
|
+
6. Monitor account for suspicious activity
|
|
370
|
+
|
|
371
|
+
**If Webhook Fails Repeatedly**:
|
|
372
|
+
1. Check webhook endpoint is responding
|
|
373
|
+
2. Verify signature secret is correct
|
|
374
|
+
3. Review error logs
|
|
375
|
+
4. Check for rate limiting
|
|
376
|
+
5. Contact Stripe support if issue persists
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## Security Checklist for Production
|
|
381
|
+
|
|
382
|
+
- [ ] Webhook secret configured in environment
|
|
383
|
+
- [ ] Stripe API key in environment variable (not hardcoded)
|
|
384
|
+
- [ ] Handler validation for event data
|
|
385
|
+
- [ ] Idempotency checks implemented
|
|
386
|
+
- [ ] Error handling doesn't expose sensitive data
|
|
387
|
+
- [ ] Logging doesn't include credentials
|
|
388
|
+
- [ ] Rate limiting configured
|
|
389
|
+
- [ ] HTTPS enforced for webhook endpoint
|
|
390
|
+
- [ ] Database transactions for consistency
|
|
391
|
+
- [ ] Monitoring and alerting set up
|
|
392
|
+
- [ ] Backup/restore plan for webhook data
|
|
393
|
+
- [ ] Incident response plan documented
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## Dependencies Security
|
|
398
|
+
|
|
399
|
+
### Stripe SDK
|
|
400
|
+
- Keep stripe SDK updated
|
|
401
|
+
- Monitor npm for security advisories
|
|
402
|
+
- Test updates in staging first
|
|
403
|
+
|
|
404
|
+
### Fastify
|
|
405
|
+
- Keep Fastify updated
|
|
406
|
+
- Enable security headers
|
|
407
|
+
- Use official Fastify modules only
|
|
408
|
+
|
|
409
|
+
```bash
|
|
410
|
+
# Regular security checks
|
|
411
|
+
npm audit
|
|
412
|
+
npm audit fix
|
|
413
|
+
|
|
414
|
+
# Check outdated packages
|
|
415
|
+
npm outdated
|
|
416
|
+
|
|
417
|
+
# Update with caution
|
|
418
|
+
npm update --depth 3
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## Compliance
|
|
424
|
+
|
|
425
|
+
### PCI Compliance
|
|
426
|
+
- Never store raw card data
|
|
427
|
+
- Use Stripe for payment processing
|
|
428
|
+
- Don't log sensitive card information
|
|
429
|
+
- Ensure HTTPS for all communication
|
|
430
|
+
- Implement access controls
|
|
431
|
+
|
|
432
|
+
### GDPR Compliance
|
|
433
|
+
- Respect customer data privacy
|
|
434
|
+
- Implement data retention policies
|
|
435
|
+
- Provide data deletion on request
|
|
436
|
+
- Document processing activities
|
|
437
|
+
- Update privacy policy
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
## Additional Resources
|
|
442
|
+
|
|
443
|
+
- [Stripe Security](https://stripe.com/docs/security)
|
|
444
|
+
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
|
|
445
|
+
- [Node.js Security Best Practices](https://nodejs.org/en/docs/guides/security/)
|
|
446
|
+
- [Fastify Security](https://www.fastify.io/docs/latest/Guides/Security/)
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
450
|
+
## Reporting Security Issues
|
|
451
|
+
|
|
452
|
+
**Please do not open public GitHub/GitLab issues for security vulnerabilities.**
|
|
453
|
+
|
|
454
|
+
Email security concerns to: [contact info when available]
|
|
455
|
+
|
|
456
|
+
Include:
|
|
457
|
+
- Description of vulnerability
|
|
458
|
+
- Steps to reproduce
|
|
459
|
+
- Potential impact
|
|
460
|
+
- Suggested fix (if any)
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
**Last Updated**: 2024-12-29
|
|
465
|
+
**Maintained By**: Tim Mushen
|