servcraft 0.1.0 → 0.1.1
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/.claude/settings.local.json +29 -0
- package/.github/CODEOWNERS +18 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +46 -0
- package/.github/dependabot.yml +59 -0
- package/.github/workflows/ci.yml +188 -0
- package/.github/workflows/release.yml +195 -0
- package/AUDIT.md +602 -0
- package/README.md +1070 -1
- package/dist/cli/index.cjs +2026 -2168
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +2026 -2168
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +595 -616
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +114 -52
- package/dist/index.d.ts +114 -52
- package/dist/index.js +595 -616
- package/dist/index.js.map +1 -1
- package/docs/CLI-001_MULTI_DB_PLAN.md +546 -0
- package/docs/DATABASE_MULTI_ORM.md +399 -0
- package/docs/PHASE1_BREAKDOWN.md +346 -0
- package/docs/PROGRESS.md +550 -0
- package/docs/modules/ANALYTICS.md +226 -0
- package/docs/modules/API-VERSIONING.md +252 -0
- package/docs/modules/AUDIT.md +192 -0
- package/docs/modules/AUTH.md +431 -0
- package/docs/modules/CACHE.md +346 -0
- package/docs/modules/EMAIL.md +254 -0
- package/docs/modules/FEATURE-FLAG.md +291 -0
- package/docs/modules/I18N.md +294 -0
- package/docs/modules/MEDIA-PROCESSING.md +281 -0
- package/docs/modules/MFA.md +266 -0
- package/docs/modules/NOTIFICATION.md +311 -0
- package/docs/modules/OAUTH.md +237 -0
- package/docs/modules/PAYMENT.md +804 -0
- package/docs/modules/QUEUE.md +540 -0
- package/docs/modules/RATE-LIMIT.md +339 -0
- package/docs/modules/SEARCH.md +288 -0
- package/docs/modules/SECURITY.md +327 -0
- package/docs/modules/SESSION.md +382 -0
- package/docs/modules/SWAGGER.md +305 -0
- package/docs/modules/UPLOAD.md +296 -0
- package/docs/modules/USER.md +505 -0
- package/docs/modules/VALIDATION.md +294 -0
- package/docs/modules/WEBHOOK.md +270 -0
- package/docs/modules/WEBSOCKET.md +691 -0
- package/package.json +53 -38
- package/prisma/schema.prisma +395 -1
- package/src/cli/commands/add-module.ts +520 -87
- package/src/cli/commands/db.ts +3 -4
- package/src/cli/commands/docs.ts +256 -6
- package/src/cli/commands/generate.ts +12 -19
- package/src/cli/commands/init.ts +384 -214
- package/src/cli/index.ts +0 -4
- package/src/cli/templates/repository.ts +6 -1
- package/src/cli/templates/routes.ts +6 -21
- package/src/cli/utils/docs-generator.ts +6 -7
- package/src/cli/utils/env-manager.ts +717 -0
- package/src/cli/utils/field-parser.ts +16 -7
- package/src/cli/utils/interactive-prompt.ts +223 -0
- package/src/cli/utils/template-manager.ts +346 -0
- package/src/config/database.config.ts +183 -0
- package/src/config/env.ts +0 -10
- package/src/config/index.ts +0 -14
- package/src/core/server.ts +1 -1
- package/src/database/adapters/mongoose.adapter.ts +132 -0
- package/src/database/adapters/prisma.adapter.ts +118 -0
- package/src/database/connection.ts +190 -0
- package/src/database/interfaces/database.interface.ts +85 -0
- package/src/database/interfaces/index.ts +7 -0
- package/src/database/interfaces/repository.interface.ts +129 -0
- package/src/database/models/mongoose/index.ts +7 -0
- package/src/database/models/mongoose/payment.schema.ts +347 -0
- package/src/database/models/mongoose/user.schema.ts +154 -0
- package/src/database/prisma.ts +1 -4
- package/src/database/redis.ts +101 -0
- package/src/database/repositories/mongoose/index.ts +7 -0
- package/src/database/repositories/mongoose/payment.repository.ts +380 -0
- package/src/database/repositories/mongoose/user.repository.ts +255 -0
- package/src/database/seed.ts +6 -1
- package/src/index.ts +9 -20
- package/src/middleware/security.ts +2 -6
- package/src/modules/analytics/analytics.routes.ts +80 -0
- package/src/modules/analytics/analytics.service.ts +364 -0
- package/src/modules/analytics/index.ts +18 -0
- package/src/modules/analytics/types.ts +180 -0
- package/src/modules/api-versioning/index.ts +15 -0
- package/src/modules/api-versioning/types.ts +86 -0
- package/src/modules/api-versioning/versioning.middleware.ts +120 -0
- package/src/modules/api-versioning/versioning.routes.ts +54 -0
- package/src/modules/api-versioning/versioning.service.ts +189 -0
- package/src/modules/audit/audit.repository.ts +206 -0
- package/src/modules/audit/audit.service.ts +27 -59
- package/src/modules/auth/auth.controller.ts +2 -2
- package/src/modules/auth/auth.middleware.ts +3 -9
- package/src/modules/auth/auth.routes.ts +10 -107
- package/src/modules/auth/auth.service.ts +126 -23
- package/src/modules/auth/index.ts +3 -4
- package/src/modules/cache/cache.service.ts +367 -0
- package/src/modules/cache/index.ts +10 -0
- package/src/modules/cache/types.ts +44 -0
- package/src/modules/email/email.service.ts +3 -10
- package/src/modules/email/templates.ts +2 -8
- package/src/modules/feature-flag/feature-flag.repository.ts +303 -0
- package/src/modules/feature-flag/feature-flag.routes.ts +247 -0
- package/src/modules/feature-flag/feature-flag.service.ts +566 -0
- package/src/modules/feature-flag/index.ts +20 -0
- package/src/modules/feature-flag/types.ts +192 -0
- package/src/modules/i18n/i18n.middleware.ts +186 -0
- package/src/modules/i18n/i18n.routes.ts +191 -0
- package/src/modules/i18n/i18n.service.ts +456 -0
- package/src/modules/i18n/index.ts +18 -0
- package/src/modules/i18n/types.ts +118 -0
- package/src/modules/media-processing/index.ts +17 -0
- package/src/modules/media-processing/media-processing.routes.ts +111 -0
- package/src/modules/media-processing/media-processing.service.ts +245 -0
- package/src/modules/media-processing/types.ts +156 -0
- package/src/modules/mfa/index.ts +20 -0
- package/src/modules/mfa/mfa.repository.ts +206 -0
- package/src/modules/mfa/mfa.routes.ts +595 -0
- package/src/modules/mfa/mfa.service.ts +572 -0
- package/src/modules/mfa/totp.ts +150 -0
- package/src/modules/mfa/types.ts +57 -0
- package/src/modules/notification/index.ts +20 -0
- package/src/modules/notification/notification.repository.ts +356 -0
- package/src/modules/notification/notification.service.ts +483 -0
- package/src/modules/notification/types.ts +119 -0
- package/src/modules/oauth/index.ts +20 -0
- package/src/modules/oauth/oauth.repository.ts +219 -0
- package/src/modules/oauth/oauth.routes.ts +446 -0
- package/src/modules/oauth/oauth.service.ts +293 -0
- package/src/modules/oauth/providers/apple.provider.ts +250 -0
- package/src/modules/oauth/providers/facebook.provider.ts +181 -0
- package/src/modules/oauth/providers/github.provider.ts +248 -0
- package/src/modules/oauth/providers/google.provider.ts +189 -0
- package/src/modules/oauth/providers/twitter.provider.ts +214 -0
- package/src/modules/oauth/types.ts +94 -0
- package/src/modules/payment/index.ts +19 -0
- package/src/modules/payment/payment.repository.ts +733 -0
- package/src/modules/payment/payment.routes.ts +390 -0
- package/src/modules/payment/payment.service.ts +354 -0
- package/src/modules/payment/providers/mobile-money.provider.ts +274 -0
- package/src/modules/payment/providers/paypal.provider.ts +190 -0
- package/src/modules/payment/providers/stripe.provider.ts +215 -0
- package/src/modules/payment/types.ts +140 -0
- package/src/modules/queue/cron.ts +438 -0
- package/src/modules/queue/index.ts +87 -0
- package/src/modules/queue/queue.routes.ts +600 -0
- package/src/modules/queue/queue.service.ts +842 -0
- package/src/modules/queue/types.ts +222 -0
- package/src/modules/queue/workers.ts +366 -0
- package/src/modules/rate-limit/index.ts +59 -0
- package/src/modules/rate-limit/rate-limit.middleware.ts +134 -0
- package/src/modules/rate-limit/rate-limit.routes.ts +269 -0
- package/src/modules/rate-limit/rate-limit.service.ts +348 -0
- package/src/modules/rate-limit/stores/memory.store.ts +165 -0
- package/src/modules/rate-limit/stores/redis.store.ts +322 -0
- package/src/modules/rate-limit/types.ts +153 -0
- package/src/modules/search/adapters/elasticsearch.adapter.ts +326 -0
- package/src/modules/search/adapters/meilisearch.adapter.ts +261 -0
- package/src/modules/search/adapters/memory.adapter.ts +278 -0
- package/src/modules/search/index.ts +21 -0
- package/src/modules/search/search.service.ts +234 -0
- package/src/modules/search/types.ts +214 -0
- package/src/modules/security/index.ts +40 -0
- package/src/modules/security/sanitize.ts +223 -0
- package/src/modules/security/security-audit.service.ts +388 -0
- package/src/modules/security/security.middleware.ts +398 -0
- package/src/modules/session/index.ts +3 -0
- package/src/modules/session/session.repository.ts +159 -0
- package/src/modules/session/session.service.ts +340 -0
- package/src/modules/session/types.ts +38 -0
- package/src/modules/swagger/index.ts +7 -1
- package/src/modules/swagger/schema-builder.ts +16 -4
- package/src/modules/swagger/swagger.service.ts +9 -10
- package/src/modules/swagger/types.ts +0 -2
- package/src/modules/upload/index.ts +14 -0
- package/src/modules/upload/types.ts +83 -0
- package/src/modules/upload/upload.repository.ts +199 -0
- package/src/modules/upload/upload.routes.ts +311 -0
- package/src/modules/upload/upload.service.ts +448 -0
- package/src/modules/user/index.ts +3 -3
- package/src/modules/user/user.controller.ts +15 -9
- package/src/modules/user/user.repository.ts +237 -113
- package/src/modules/user/user.routes.ts +39 -164
- package/src/modules/user/user.service.ts +4 -3
- package/src/modules/validation/validator.ts +12 -17
- package/src/modules/webhook/index.ts +91 -0
- package/src/modules/webhook/retry.ts +196 -0
- package/src/modules/webhook/signature.ts +135 -0
- package/src/modules/webhook/types.ts +181 -0
- package/src/modules/webhook/webhook.repository.ts +358 -0
- package/src/modules/webhook/webhook.routes.ts +442 -0
- package/src/modules/webhook/webhook.service.ts +457 -0
- package/src/modules/websocket/features.ts +504 -0
- package/src/modules/websocket/index.ts +106 -0
- package/src/modules/websocket/middlewares.ts +298 -0
- package/src/modules/websocket/types.ts +181 -0
- package/src/modules/websocket/websocket.service.ts +692 -0
- package/src/utils/errors.ts +7 -0
- package/src/utils/pagination.ts +4 -1
- package/tests/helpers/db-check.ts +79 -0
- package/tests/integration/auth-redis.test.ts +94 -0
- package/tests/integration/cache-redis.test.ts +387 -0
- package/tests/integration/mongoose-repositories.test.ts +410 -0
- package/tests/integration/payment-prisma.test.ts +637 -0
- package/tests/integration/queue-bullmq.test.ts +417 -0
- package/tests/integration/user-prisma.test.ts +441 -0
- package/tests/integration/websocket-socketio.test.ts +552 -0
- package/tests/setup.ts +11 -9
- package/vitest.config.ts +3 -8
- package/npm-cache/_cacache/content-v2/sha512/1c/d0/03440d500a0487621aad1d6402978340698976602046db8e24fa03c01ee6c022c69b0582f969042d9442ee876ac35c038e960dd427d1e622fa24b8eb7dba +0 -0
- package/npm-cache/_cacache/content-v2/sha512/42/55/28b493ca491833e5aab0e9c3108d29ab3f36c248ca88f45d4630674fce9130959e56ae308797ac2b6328fa7f09a610b9550ed09cb971d039876d293fc69d +0 -0
- package/npm-cache/_cacache/content-v2/sha512/e0/12/f360dc9315ee5f17844a0c8c233ee6bf7c30837c4a02ea0d56c61c7f7ab21c0e958e50ed2c57c59f983c762b93056778c9009b2398ffc26def0183999b13 +0 -0
- package/npm-cache/_cacache/content-v2/sha512/ed/b0/fae1161902898f4c913c67d7f6cdf6be0665aec3b389b9c4f4f0a101ca1da59badf1b59c4e0030f5223023b8d63cfe501c46a32c20c895d4fb3f11ca2232 +0 -0
- package/npm-cache/_cacache/index-v5/58/94/c2cba79e0f16b4c10e95a87e32255741149e8222cc314a476aab67c39cc0 +0 -5
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# Security Module
|
|
2
|
+
|
|
3
|
+
Comprehensive security features for protecting your application against common web vulnerabilities.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Input Sanitization** - XSS prevention with HTML escaping and script stripping
|
|
8
|
+
- **CSRF Protection** - Token-based Cross-Site Request Forgery protection
|
|
9
|
+
- **Security Headers** - Essential HTTP security headers
|
|
10
|
+
- **HPP Protection** - HTTP Parameter Pollution prevention
|
|
11
|
+
- **Suspicious Activity Detection** - Pattern-based attack detection
|
|
12
|
+
- **Security Audit Logging** - Comprehensive security event tracking
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
The security module is included in ServCraft by default.
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import {
|
|
20
|
+
registerSecurityMiddlewares,
|
|
21
|
+
sanitizeString,
|
|
22
|
+
sanitizeObject,
|
|
23
|
+
getSecurityAuditService,
|
|
24
|
+
} from 'servcraft/modules/security';
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### Register All Security Middlewares
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import Fastify from 'fastify';
|
|
33
|
+
import { registerSecurityMiddlewares } from 'servcraft/modules/security';
|
|
34
|
+
|
|
35
|
+
const app = Fastify();
|
|
36
|
+
|
|
37
|
+
await registerSecurityMiddlewares(app, {
|
|
38
|
+
sanitize: true, // Input sanitization
|
|
39
|
+
csrf: false, // CSRF (disabled for API-first apps)
|
|
40
|
+
hpp: true, // HTTP Parameter Pollution protection
|
|
41
|
+
headers: true, // Security headers
|
|
42
|
+
sizeLimit: 10 * 1024 * 1024, // 10MB request size limit
|
|
43
|
+
suspicionDetection: true, // Suspicious activity detection
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Input Sanitization
|
|
48
|
+
|
|
49
|
+
### Basic String Sanitization
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { sanitizeString, escapeHtml, stripDangerousHtml } from 'servcraft/modules/security';
|
|
53
|
+
|
|
54
|
+
// Escape HTML entities
|
|
55
|
+
const safe = escapeHtml('<script>alert("xss")</script>');
|
|
56
|
+
// Result: '<script>alert("xss")</script>'
|
|
57
|
+
|
|
58
|
+
// Strip dangerous HTML
|
|
59
|
+
const stripped = stripDangerousHtml('<div onclick="evil()">Safe text</div>');
|
|
60
|
+
// Result: '<div>Safe text</div>'
|
|
61
|
+
|
|
62
|
+
// Full sanitization
|
|
63
|
+
const sanitized = sanitizeString(userInput, {
|
|
64
|
+
escapeHtmlChars: true, // Escape HTML entities
|
|
65
|
+
stripScripts: true, // Remove script tags and event handlers
|
|
66
|
+
trim: true, // Trim whitespace
|
|
67
|
+
maxLength: 1000, // Limit string length
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Object Sanitization
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { sanitizeObject } from 'servcraft/modules/security';
|
|
75
|
+
|
|
76
|
+
const userInput = {
|
|
77
|
+
name: '<script>alert("xss")</script>',
|
|
78
|
+
email: 'user@example.com',
|
|
79
|
+
bio: 'Hello <img onerror="evil()" src="x">',
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const safe = sanitizeObject(userInput, {
|
|
83
|
+
escapeHtmlChars: true,
|
|
84
|
+
stripScripts: true,
|
|
85
|
+
});
|
|
86
|
+
// All string values are sanitized recursively
|
|
87
|
+
// Prototype pollution keys (__proto__, constructor) are removed
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### URL Sanitization
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { sanitizeUrl } from 'servcraft/modules/security';
|
|
94
|
+
|
|
95
|
+
sanitizeUrl('https://example.com'); // Returns: 'https://example.com'
|
|
96
|
+
sanitizeUrl('javascript:alert(1)'); // Returns: ''
|
|
97
|
+
sanitizeUrl('data:text/html,...'); // Returns: ''
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Filename Sanitization
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { sanitizeFilename } from 'servcraft/modules/security';
|
|
104
|
+
|
|
105
|
+
sanitizeFilename('../../../etc/passwd'); // Returns: '______etc_passwd'
|
|
106
|
+
sanitizeFilename('my file (1).pdf'); // Returns: 'my_file__1_.pdf'
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## CSRF Protection
|
|
110
|
+
|
|
111
|
+
### Setup
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { csrfProtection, generateCsrfToken } from 'servcraft/modules/security';
|
|
115
|
+
|
|
116
|
+
// Generate token for a session
|
|
117
|
+
app.get('/csrf-token', async (request, reply) => {
|
|
118
|
+
const sessionId = request.headers['x-session-id'] || request.ip;
|
|
119
|
+
const token = generateCsrfToken(sessionId);
|
|
120
|
+
return { csrfToken: token };
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Protect routes (skip for API with JWT)
|
|
124
|
+
app.addHook('preHandler', csrfProtection());
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Client Usage
|
|
128
|
+
|
|
129
|
+
```javascript
|
|
130
|
+
// Get CSRF token
|
|
131
|
+
const { csrfToken } = await fetch('/csrf-token').then(r => r.json());
|
|
132
|
+
|
|
133
|
+
// Include in subsequent requests
|
|
134
|
+
fetch('/api/action', {
|
|
135
|
+
method: 'POST',
|
|
136
|
+
headers: {
|
|
137
|
+
'Content-Type': 'application/json',
|
|
138
|
+
'X-CSRF-Token': csrfToken,
|
|
139
|
+
},
|
|
140
|
+
body: JSON.stringify(data),
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Security Headers
|
|
145
|
+
|
|
146
|
+
The following headers are automatically added:
|
|
147
|
+
|
|
148
|
+
| Header | Value | Purpose |
|
|
149
|
+
|--------|-------|---------|
|
|
150
|
+
| `X-Content-Type-Options` | `nosniff` | Prevent MIME sniffing |
|
|
151
|
+
| `X-Frame-Options` | `DENY` | Prevent clickjacking |
|
|
152
|
+
| `X-XSS-Protection` | `1; mode=block` | XSS filter |
|
|
153
|
+
| `Referrer-Policy` | `strict-origin-when-cross-origin` | Control referrer info |
|
|
154
|
+
| `Permissions-Policy` | `camera=(), microphone=()...` | Disable dangerous features |
|
|
155
|
+
| `Cache-Control` | `no-store` | Prevent caching sensitive data |
|
|
156
|
+
|
|
157
|
+
## HTTP Parameter Pollution Protection
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import { hppProtection } from 'servcraft/modules/security';
|
|
161
|
+
|
|
162
|
+
// Prevent array injection
|
|
163
|
+
// ?user=admin&user=hacker → user=hacker (takes last value)
|
|
164
|
+
app.addHook('preHandler', hppProtection());
|
|
165
|
+
|
|
166
|
+
// Allow specific parameters to be arrays
|
|
167
|
+
app.addHook('preHandler', hppProtection(['tags', 'categories']));
|
|
168
|
+
// ?tags=a&tags=b → tags=['a', 'b'] (allowed)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Suspicious Activity Detection
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import { suspiciousActivityDetection } from 'servcraft/modules/security';
|
|
175
|
+
|
|
176
|
+
// Log suspicious patterns (default)
|
|
177
|
+
app.addHook('preHandler', suspiciousActivityDetection());
|
|
178
|
+
|
|
179
|
+
// Block suspicious requests
|
|
180
|
+
app.addHook('preHandler', suspiciousActivityDetection({
|
|
181
|
+
blockSuspicious: true,
|
|
182
|
+
}));
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Detected Patterns
|
|
186
|
+
|
|
187
|
+
- Path traversal: `../`
|
|
188
|
+
- Script injection: `<script`
|
|
189
|
+
- SQL injection: `UNION SELECT`, `DROP TABLE`
|
|
190
|
+
- Template injection: `${}`, `{{}}`
|
|
191
|
+
- Code execution: `exec()`, `eval()`
|
|
192
|
+
|
|
193
|
+
## Security Audit Service
|
|
194
|
+
|
|
195
|
+
### Logging Security Events
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
import { getSecurityAuditService } from 'servcraft/modules/security';
|
|
199
|
+
|
|
200
|
+
const audit = getSecurityAuditService();
|
|
201
|
+
|
|
202
|
+
// Log login success
|
|
203
|
+
await audit.logLoginSuccess(userId, request);
|
|
204
|
+
|
|
205
|
+
// Log login failure
|
|
206
|
+
await audit.logLoginFailed(email, 'Invalid password', request);
|
|
207
|
+
|
|
208
|
+
// Log suspicious activity
|
|
209
|
+
await audit.logSuspiciousActivity('Multiple failed login attempts', request, {
|
|
210
|
+
attempts: 5,
|
|
211
|
+
targetEmail: 'user@example.com',
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Log rate limit exceeded
|
|
215
|
+
await audit.logRateLimitExceeded(request, 100);
|
|
216
|
+
|
|
217
|
+
// Log brute force detection
|
|
218
|
+
await audit.logBruteForceDetected(request, 10, '/api/login');
|
|
219
|
+
|
|
220
|
+
// Log access denied
|
|
221
|
+
await audit.logAccessDenied(userId, 'admin-panel', 'view', request);
|
|
222
|
+
|
|
223
|
+
// Log admin action
|
|
224
|
+
await audit.logAdminAction(adminId, 'delete_user', 'users', {
|
|
225
|
+
targetUserId: deletedUserId,
|
|
226
|
+
}, request);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Custom Event Logging
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
await audit.log({
|
|
233
|
+
type: 'auth.mfa.enabled',
|
|
234
|
+
userId: user.id,
|
|
235
|
+
request,
|
|
236
|
+
details: { method: 'totp' },
|
|
237
|
+
success: true,
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Event Types
|
|
242
|
+
|
|
243
|
+
| Type | Severity | Description |
|
|
244
|
+
|------|----------|-------------|
|
|
245
|
+
| `auth.login.success` | low | Successful login |
|
|
246
|
+
| `auth.login.failed` | medium | Failed login attempt |
|
|
247
|
+
| `auth.mfa.enabled` | medium | MFA enabled |
|
|
248
|
+
| `auth.mfa.disabled` | high | MFA disabled |
|
|
249
|
+
| `suspicious.activity` | high | Suspicious pattern detected |
|
|
250
|
+
| `csrf.violation` | high | CSRF token mismatch |
|
|
251
|
+
| `xss.attempt` | critical | XSS attack detected |
|
|
252
|
+
| `sqli.attempt` | critical | SQL injection detected |
|
|
253
|
+
| `brute.force.detected` | high | Brute force attack |
|
|
254
|
+
| `rate.limit.exceeded` | medium | Rate limit hit |
|
|
255
|
+
| `data.deletion` | critical | Data deletion |
|
|
256
|
+
| `admin.action` | high | Admin action performed |
|
|
257
|
+
|
|
258
|
+
### Retrieving Security Data
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
const audit = getSecurityAuditService();
|
|
262
|
+
|
|
263
|
+
// Get recent high/critical alerts
|
|
264
|
+
const alerts = await audit.getRecentAlerts(50);
|
|
265
|
+
|
|
266
|
+
// Get security events for a user
|
|
267
|
+
const userEvents = await audit.getUserEvents(userId, 100);
|
|
268
|
+
|
|
269
|
+
// Get security statistics
|
|
270
|
+
const stats = await audit.getStats(24); // Last 24 hours
|
|
271
|
+
// {
|
|
272
|
+
// totalEvents: 1250,
|
|
273
|
+
// failedLogins: 45,
|
|
274
|
+
// suspiciousActivities: 12,
|
|
275
|
+
// rateLimitExceeded: 230,
|
|
276
|
+
// criticalAlerts: 3,
|
|
277
|
+
// }
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Storage
|
|
281
|
+
|
|
282
|
+
Security audit events are stored in:
|
|
283
|
+
|
|
284
|
+
1. **Redis** (real-time, 24h TTL) - For monitoring dashboards
|
|
285
|
+
2. **PostgreSQL** (long-term) - For compliance and audit trails
|
|
286
|
+
|
|
287
|
+
### Redis Keys
|
|
288
|
+
|
|
289
|
+
| Key Pattern | TTL | Purpose |
|
|
290
|
+
|-------------|-----|---------|
|
|
291
|
+
| `security:audit:{id}` | 24h | Individual events |
|
|
292
|
+
| `security:alerts:recent` | - | Recent high/critical alerts list |
|
|
293
|
+
|
|
294
|
+
## Best Practices
|
|
295
|
+
|
|
296
|
+
1. **Always sanitize user input** before storing or displaying
|
|
297
|
+
2. **Enable CSRF protection** for browser-based apps
|
|
298
|
+
3. **Monitor security events** regularly
|
|
299
|
+
4. **Set up alerts** for critical severity events
|
|
300
|
+
5. **Review audit logs** for compliance requirements
|
|
301
|
+
6. **Use rate limiting** in conjunction with brute force detection
|
|
302
|
+
|
|
303
|
+
## Integration with Other Modules
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
// With Rate Limiting
|
|
307
|
+
import { createRateLimiter } from 'servcraft/modules/rate-limit';
|
|
308
|
+
|
|
309
|
+
const loginLimiter = createRateLimiter({
|
|
310
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
311
|
+
max: 5,
|
|
312
|
+
onLimitReached: async (request) => {
|
|
313
|
+
await audit.logBruteForceDetected(request, 5, '/api/login');
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// With Auth Module
|
|
318
|
+
import { authService } from 'servcraft/modules/auth';
|
|
319
|
+
|
|
320
|
+
authService.on('login:success', async (user, request) => {
|
|
321
|
+
await audit.logLoginSuccess(user.id, request);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
authService.on('login:failed', async (email, reason, request) => {
|
|
325
|
+
await audit.logLoginFailed(email, reason, request);
|
|
326
|
+
});
|
|
327
|
+
```
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
# Session Module
|
|
2
|
+
|
|
3
|
+
Server-side session management with Redis storage and optional PostgreSQL persistence.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Redis-based Storage** - Fast session access with configurable TTL
|
|
8
|
+
- **Sliding Expiration** - Extend session on activity
|
|
9
|
+
- **Multi-device Support** - List and manage user sessions across devices
|
|
10
|
+
- **Optional Persistence** - PostgreSQL backup for audit trails
|
|
11
|
+
- **Session Metadata** - Store device info, IP, user agent
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import {
|
|
17
|
+
SessionService,
|
|
18
|
+
createSessionService,
|
|
19
|
+
getSessionService,
|
|
20
|
+
} from 'servcraft/modules/session';
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { getSessionService } from 'servcraft/modules/session';
|
|
27
|
+
|
|
28
|
+
const sessionService = getSessionService();
|
|
29
|
+
|
|
30
|
+
// Create a session
|
|
31
|
+
const session = await sessionService.create({
|
|
32
|
+
userId: 'user-123',
|
|
33
|
+
metadata: {
|
|
34
|
+
device: 'Chrome on Windows',
|
|
35
|
+
ip: request.ip,
|
|
36
|
+
userAgent: request.headers['user-agent'],
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Get session
|
|
41
|
+
const currentSession = await sessionService.get(session.id);
|
|
42
|
+
|
|
43
|
+
// Extend session (sliding expiration)
|
|
44
|
+
await sessionService.touch(session.id);
|
|
45
|
+
|
|
46
|
+
// Destroy session
|
|
47
|
+
await sessionService.destroy(session.id);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Configuration
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { createSessionService } from 'servcraft/modules/session';
|
|
54
|
+
|
|
55
|
+
const sessionService = createSessionService({
|
|
56
|
+
// Session TTL in seconds (default: 24 hours)
|
|
57
|
+
ttl: 86400,
|
|
58
|
+
|
|
59
|
+
// Enable sliding expiration (default: true)
|
|
60
|
+
slidingExpiration: true,
|
|
61
|
+
|
|
62
|
+
// Redis key prefix (default: 'session:')
|
|
63
|
+
prefix: 'session:',
|
|
64
|
+
|
|
65
|
+
// Enable PostgreSQL persistence (default: false)
|
|
66
|
+
persistToDatabase: true,
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## API Reference
|
|
71
|
+
|
|
72
|
+
### Create Session
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
const session = await sessionService.create({
|
|
76
|
+
userId: 'user-123',
|
|
77
|
+
metadata: {
|
|
78
|
+
device: 'Mobile Safari on iOS',
|
|
79
|
+
ip: '192.168.1.1',
|
|
80
|
+
userAgent: 'Mozilla/5.0...',
|
|
81
|
+
location: 'Paris, France',
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Returns:
|
|
86
|
+
// {
|
|
87
|
+
// id: 'sess_abc123...',
|
|
88
|
+
// userId: 'user-123',
|
|
89
|
+
// metadata: { ... },
|
|
90
|
+
// createdAt: Date,
|
|
91
|
+
// expiresAt: Date,
|
|
92
|
+
// lastAccessedAt: Date,
|
|
93
|
+
// }
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Get Session
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
const session = await sessionService.get('sess_abc123');
|
|
100
|
+
|
|
101
|
+
if (!session) {
|
|
102
|
+
// Session expired or doesn't exist
|
|
103
|
+
throw new Error('Invalid session');
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Touch Session (Extend TTL)
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
// Extend session TTL on user activity
|
|
111
|
+
const extended = await sessionService.touch('sess_abc123');
|
|
112
|
+
|
|
113
|
+
if (extended) {
|
|
114
|
+
// Session TTL was reset
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Destroy Session
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
// Single session (logout)
|
|
122
|
+
await sessionService.destroy('sess_abc123');
|
|
123
|
+
|
|
124
|
+
// All user sessions (logout everywhere)
|
|
125
|
+
await sessionService.destroyUserSessions('user-123');
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### List User Sessions
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
const sessions = await sessionService.getUserSessions('user-123');
|
|
132
|
+
|
|
133
|
+
// Returns array of active sessions:
|
|
134
|
+
// [
|
|
135
|
+
// { id: 'sess_abc', device: 'Chrome', lastAccessedAt: Date, current: true },
|
|
136
|
+
// { id: 'sess_xyz', device: 'Mobile', lastAccessedAt: Date, current: false },
|
|
137
|
+
// ]
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Session Statistics
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
const stats = await sessionService.getStats();
|
|
144
|
+
// {
|
|
145
|
+
// totalSessions: 1523,
|
|
146
|
+
// activeSessions: 342,
|
|
147
|
+
// expiredSessions: 1181,
|
|
148
|
+
// }
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Middleware Integration
|
|
152
|
+
|
|
153
|
+
### Fastify Session Middleware
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
import { getSessionService } from 'servcraft/modules/session';
|
|
157
|
+
|
|
158
|
+
const sessionService = getSessionService();
|
|
159
|
+
|
|
160
|
+
// Session validation middleware
|
|
161
|
+
app.addHook('preHandler', async (request, reply) => {
|
|
162
|
+
const sessionId = request.cookies.sessionId;
|
|
163
|
+
|
|
164
|
+
if (!sessionId) {
|
|
165
|
+
return reply.status(401).send({ error: 'No session' });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const session = await sessionService.get(sessionId);
|
|
169
|
+
|
|
170
|
+
if (!session) {
|
|
171
|
+
return reply.status(401).send({ error: 'Invalid session' });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Extend session on activity
|
|
175
|
+
await sessionService.touch(sessionId);
|
|
176
|
+
|
|
177
|
+
// Attach to request
|
|
178
|
+
request.session = session;
|
|
179
|
+
request.userId = session.userId;
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Login Handler
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
app.post('/login', async (request, reply) => {
|
|
187
|
+
const { email, password } = request.body;
|
|
188
|
+
|
|
189
|
+
// Validate credentials
|
|
190
|
+
const user = await authService.validateCredentials(email, password);
|
|
191
|
+
|
|
192
|
+
if (!user) {
|
|
193
|
+
return reply.status(401).send({ error: 'Invalid credentials' });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Create session
|
|
197
|
+
const session = await sessionService.create({
|
|
198
|
+
userId: user.id,
|
|
199
|
+
metadata: {
|
|
200
|
+
ip: request.ip,
|
|
201
|
+
userAgent: request.headers['user-agent'],
|
|
202
|
+
loginMethod: 'password',
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Set session cookie
|
|
207
|
+
reply.setCookie('sessionId', session.id, {
|
|
208
|
+
httpOnly: true,
|
|
209
|
+
secure: process.env.NODE_ENV === 'production',
|
|
210
|
+
sameSite: 'strict',
|
|
211
|
+
maxAge: 86400, // 24 hours
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
return { user, sessionId: session.id };
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Logout Handler
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
// Logout current session
|
|
222
|
+
app.post('/logout', async (request, reply) => {
|
|
223
|
+
const sessionId = request.cookies.sessionId;
|
|
224
|
+
|
|
225
|
+
if (sessionId) {
|
|
226
|
+
await sessionService.destroy(sessionId);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
reply.clearCookie('sessionId');
|
|
230
|
+
return { success: true };
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Logout all sessions
|
|
234
|
+
app.post('/logout-all', async (request, reply) => {
|
|
235
|
+
await sessionService.destroyUserSessions(request.userId);
|
|
236
|
+
|
|
237
|
+
reply.clearCookie('sessionId');
|
|
238
|
+
return { success: true };
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Session Management UI
|
|
243
|
+
|
|
244
|
+
### List Active Sessions
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
app.get('/sessions', async (request, reply) => {
|
|
248
|
+
const sessions = await sessionService.getUserSessions(request.userId);
|
|
249
|
+
|
|
250
|
+
return sessions.map(session => ({
|
|
251
|
+
id: session.id,
|
|
252
|
+
device: session.metadata?.device || 'Unknown',
|
|
253
|
+
ip: session.metadata?.ip,
|
|
254
|
+
location: session.metadata?.location,
|
|
255
|
+
lastActive: session.lastAccessedAt,
|
|
256
|
+
current: session.id === request.cookies.sessionId,
|
|
257
|
+
}));
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Revoke Specific Session
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
app.delete('/sessions/:sessionId', async (request, reply) => {
|
|
265
|
+
const { sessionId } = request.params;
|
|
266
|
+
|
|
267
|
+
// Verify session belongs to user
|
|
268
|
+
const session = await sessionService.get(sessionId);
|
|
269
|
+
|
|
270
|
+
if (!session || session.userId !== request.userId) {
|
|
271
|
+
return reply.status(404).send({ error: 'Session not found' });
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
await sessionService.destroy(sessionId);
|
|
275
|
+
return { success: true };
|
|
276
|
+
});
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Storage Architecture
|
|
280
|
+
|
|
281
|
+
### Redis (Primary)
|
|
282
|
+
|
|
283
|
+
Sessions are stored in Redis for fast access:
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
session:{sessionId} -> {
|
|
287
|
+
id: string,
|
|
288
|
+
userId: string,
|
|
289
|
+
metadata: object,
|
|
290
|
+
createdAt: timestamp,
|
|
291
|
+
expiresAt: timestamp,
|
|
292
|
+
lastAccessedAt: timestamp,
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
TTL is managed automatically. Sessions expire and are removed by Redis.
|
|
297
|
+
|
|
298
|
+
### PostgreSQL (Optional)
|
|
299
|
+
|
|
300
|
+
When `persistToDatabase: true`, sessions are also stored in PostgreSQL:
|
|
301
|
+
|
|
302
|
+
```sql
|
|
303
|
+
CREATE TABLE sessions (
|
|
304
|
+
id VARCHAR(255) PRIMARY KEY,
|
|
305
|
+
user_id VARCHAR(255) NOT NULL,
|
|
306
|
+
metadata JSONB,
|
|
307
|
+
created_at TIMESTAMP DEFAULT NOW(),
|
|
308
|
+
expires_at TIMESTAMP NOT NULL,
|
|
309
|
+
last_accessed_at TIMESTAMP,
|
|
310
|
+
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
311
|
+
);
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
This provides:
|
|
315
|
+
- Audit trail of all sessions
|
|
316
|
+
- Survival across Redis restarts
|
|
317
|
+
- Analytics on session patterns
|
|
318
|
+
|
|
319
|
+
## Security Considerations
|
|
320
|
+
|
|
321
|
+
1. **Use secure cookies** - `httpOnly`, `secure`, `sameSite`
|
|
322
|
+
2. **Rotate session IDs** after sensitive operations
|
|
323
|
+
3. **Limit concurrent sessions** per user if needed
|
|
324
|
+
4. **Log session events** for security auditing
|
|
325
|
+
5. **Implement idle timeout** in addition to absolute timeout
|
|
326
|
+
|
|
327
|
+
### Session Rotation
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
app.post('/change-password', async (request, reply) => {
|
|
331
|
+
// Change password logic...
|
|
332
|
+
|
|
333
|
+
// Destroy all other sessions
|
|
334
|
+
const currentSessionId = request.cookies.sessionId;
|
|
335
|
+
const sessions = await sessionService.getUserSessions(request.userId);
|
|
336
|
+
|
|
337
|
+
for (const session of sessions) {
|
|
338
|
+
if (session.id !== currentSessionId) {
|
|
339
|
+
await sessionService.destroy(session.id);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return { success: true };
|
|
344
|
+
});
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## Integration with Auth Module
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
import { authService } from 'servcraft/modules/auth';
|
|
351
|
+
import { getSessionService } from 'servcraft/modules/session';
|
|
352
|
+
|
|
353
|
+
const sessionService = getSessionService();
|
|
354
|
+
|
|
355
|
+
// On successful login
|
|
356
|
+
authService.on('login:success', async (user, request) => {
|
|
357
|
+
const session = await sessionService.create({
|
|
358
|
+
userId: user.id,
|
|
359
|
+
metadata: {
|
|
360
|
+
ip: request.ip,
|
|
361
|
+
userAgent: request.headers['user-agent'],
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
return session;
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// On logout
|
|
368
|
+
authService.on('logout', async (sessionId) => {
|
|
369
|
+
await sessionService.destroy(sessionId);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
// On password change
|
|
373
|
+
authService.on('password:changed', async (userId, currentSessionId) => {
|
|
374
|
+
// Destroy all other sessions
|
|
375
|
+
const sessions = await sessionService.getUserSessions(userId);
|
|
376
|
+
for (const session of sessions) {
|
|
377
|
+
if (session.id !== currentSessionId) {
|
|
378
|
+
await sessionService.destroy(session.id);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
```
|