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,281 @@
|
|
|
1
|
+
# Media Processing Module
|
|
2
|
+
|
|
3
|
+
Image and video processing with job queue support.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Image Processing** - Resize, crop, format conversion
|
|
8
|
+
- **Video Processing** - Transcoding, thumbnail extraction
|
|
9
|
+
- **Job Queue** - Async processing with status tracking
|
|
10
|
+
- **Concurrency Control** - Limit concurrent processing jobs
|
|
11
|
+
- **Media Info** - Extract metadata from media files
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### Configuration
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { MediaProcessingService } from 'servcraft/modules/media-processing';
|
|
19
|
+
|
|
20
|
+
const mediaService = new MediaProcessingService({
|
|
21
|
+
ffmpegPath: '/usr/bin/ffmpeg',
|
|
22
|
+
ffprobePath: '/usr/bin/ffprobe',
|
|
23
|
+
tempDir: './temp',
|
|
24
|
+
maxConcurrent: 3,
|
|
25
|
+
gpuAcceleration: false,
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Image Processing
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
// Process image with operations
|
|
33
|
+
const result = await mediaService.processImage(
|
|
34
|
+
'/uploads/original.jpg',
|
|
35
|
+
'/uploads/processed.jpg',
|
|
36
|
+
[
|
|
37
|
+
{ type: 'resize', width: 800, height: 600 },
|
|
38
|
+
{ type: 'format', format: 'webp', quality: 85 },
|
|
39
|
+
]
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// Result
|
|
43
|
+
// {
|
|
44
|
+
// success: true,
|
|
45
|
+
// outputPath: '/uploads/processed.jpg',
|
|
46
|
+
// processingTime: 245
|
|
47
|
+
// }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Video Processing
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
const result = await mediaService.processVideo(
|
|
54
|
+
'/uploads/video.mp4',
|
|
55
|
+
'/uploads/video-720p.mp4',
|
|
56
|
+
[
|
|
57
|
+
{ type: 'resize', width: 1280, height: 720 },
|
|
58
|
+
{ type: 'codec', video: 'h264', audio: 'aac' },
|
|
59
|
+
{ type: 'bitrate', video: '2000k', audio: '128k' },
|
|
60
|
+
]
|
|
61
|
+
);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Thumbnail Generation
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
// Generate thumbnail from video
|
|
68
|
+
const result = await mediaService.generateThumbnail(
|
|
69
|
+
'/uploads/video.mp4',
|
|
70
|
+
'/uploads/thumbnail.jpg',
|
|
71
|
+
{
|
|
72
|
+
width: 320,
|
|
73
|
+
height: 180,
|
|
74
|
+
time: '00:00:05', // Extract at 5 seconds
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Generate thumbnail from image
|
|
79
|
+
const imageThumb = await mediaService.generateThumbnail(
|
|
80
|
+
'/uploads/image.jpg',
|
|
81
|
+
'/uploads/thumb.jpg',
|
|
82
|
+
{ width: 150, height: 150, fit: 'cover' }
|
|
83
|
+
);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Media Info
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
const info = await mediaService.getMediaInfo('/uploads/video.mp4');
|
|
90
|
+
// {
|
|
91
|
+
// path: '/uploads/video.mp4',
|
|
92
|
+
// type: 'video',
|
|
93
|
+
// format: 'mp4',
|
|
94
|
+
// size: 15728640,
|
|
95
|
+
// width: 1920,
|
|
96
|
+
// height: 1080,
|
|
97
|
+
// duration: 120.5,
|
|
98
|
+
// bitrate: 1048576,
|
|
99
|
+
// codec: { video: 'h264', audio: 'aac' },
|
|
100
|
+
// fps: 30
|
|
101
|
+
// }
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Job Queue
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// Create async job
|
|
108
|
+
const job = await mediaService.createJob(
|
|
109
|
+
'video',
|
|
110
|
+
'/uploads/original.mp4',
|
|
111
|
+
'/uploads/processed.mp4',
|
|
112
|
+
[
|
|
113
|
+
{ type: 'resize', width: 1280, height: 720 },
|
|
114
|
+
{ type: 'format', format: 'mp4' },
|
|
115
|
+
]
|
|
116
|
+
);
|
|
117
|
+
// {
|
|
118
|
+
// id: 'job-uuid',
|
|
119
|
+
// status: 'pending',
|
|
120
|
+
// progress: 0,
|
|
121
|
+
// createdAt: Date
|
|
122
|
+
// }
|
|
123
|
+
|
|
124
|
+
// Check job status
|
|
125
|
+
const status = await mediaService.getJob(job.id);
|
|
126
|
+
// {
|
|
127
|
+
// id: 'job-uuid',
|
|
128
|
+
// status: 'processing', // pending | processing | completed | failed
|
|
129
|
+
// progress: 45,
|
|
130
|
+
// ...
|
|
131
|
+
// }
|
|
132
|
+
|
|
133
|
+
// Poll until complete
|
|
134
|
+
while (true) {
|
|
135
|
+
const job = await mediaService.getJob(jobId);
|
|
136
|
+
if (job.status === 'completed' || job.status === 'failed') {
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
await sleep(1000);
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Image Operations
|
|
144
|
+
|
|
145
|
+
| Operation | Description | Options |
|
|
146
|
+
|-----------|-------------|---------|
|
|
147
|
+
| `resize` | Resize image | `width`, `height`, `fit` |
|
|
148
|
+
| `crop` | Crop region | `x`, `y`, `width`, `height` |
|
|
149
|
+
| `rotate` | Rotate image | `angle` |
|
|
150
|
+
| `flip` | Flip image | `direction` (horizontal/vertical) |
|
|
151
|
+
| `format` | Convert format | `format`, `quality` |
|
|
152
|
+
| `grayscale` | Convert to grayscale | - |
|
|
153
|
+
| `blur` | Apply blur | `sigma` |
|
|
154
|
+
| `sharpen` | Sharpen image | `sigma` |
|
|
155
|
+
| `watermark` | Add watermark | `image`, `position`, `opacity` |
|
|
156
|
+
|
|
157
|
+
## Video Operations
|
|
158
|
+
|
|
159
|
+
| Operation | Description | Options |
|
|
160
|
+
|-----------|-------------|---------|
|
|
161
|
+
| `resize` | Resize video | `width`, `height` |
|
|
162
|
+
| `trim` | Trim video | `start`, `end` |
|
|
163
|
+
| `codec` | Change codec | `video`, `audio` |
|
|
164
|
+
| `bitrate` | Set bitrate | `video`, `audio` |
|
|
165
|
+
| `fps` | Change framerate | `fps` |
|
|
166
|
+
| `format` | Convert format | `format` |
|
|
167
|
+
| `audio` | Audio options | `remove`, `volume` |
|
|
168
|
+
| `watermark` | Add watermark | `image`, `position` |
|
|
169
|
+
|
|
170
|
+
## Configuration Types
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
interface MediaProcessingConfig {
|
|
174
|
+
ffmpegPath?: string; // Path to ffmpeg
|
|
175
|
+
ffprobePath?: string; // Path to ffprobe
|
|
176
|
+
tempDir?: string; // Temporary files directory
|
|
177
|
+
maxConcurrent?: number; // Max concurrent jobs
|
|
178
|
+
gpuAcceleration?: boolean; // Use GPU acceleration
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
interface ProcessingJob {
|
|
182
|
+
id: string;
|
|
183
|
+
mediaType: 'image' | 'video';
|
|
184
|
+
source: string;
|
|
185
|
+
output: string;
|
|
186
|
+
operations: Operation[];
|
|
187
|
+
status: 'pending' | 'processing' | 'completed' | 'failed';
|
|
188
|
+
progress: number;
|
|
189
|
+
error?: string;
|
|
190
|
+
createdAt: Date;
|
|
191
|
+
completedAt?: Date;
|
|
192
|
+
processingTime?: number;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
interface ThumbnailOptions {
|
|
196
|
+
width?: number;
|
|
197
|
+
height?: number;
|
|
198
|
+
time?: string; // For video: timestamp
|
|
199
|
+
fit?: 'cover' | 'contain' | 'fill';
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Fastify Integration
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
import fastifyMultipart from '@fastify/multipart';
|
|
207
|
+
|
|
208
|
+
fastify.post('/api/process/image', async (request, reply) => {
|
|
209
|
+
const file = await request.file();
|
|
210
|
+
const { width, height, format } = request.query;
|
|
211
|
+
|
|
212
|
+
const inputPath = `/tmp/${file.filename}`;
|
|
213
|
+
const outputPath = `/uploads/processed-${Date.now()}.${format || 'jpg'}`;
|
|
214
|
+
|
|
215
|
+
// Save uploaded file
|
|
216
|
+
await saveFile(file, inputPath);
|
|
217
|
+
|
|
218
|
+
// Process image
|
|
219
|
+
const operations = [];
|
|
220
|
+
if (width || height) {
|
|
221
|
+
operations.push({ type: 'resize', width: +width, height: +height });
|
|
222
|
+
}
|
|
223
|
+
if (format) {
|
|
224
|
+
operations.push({ type: 'format', format });
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const result = await mediaService.processImage(inputPath, outputPath, operations);
|
|
228
|
+
|
|
229
|
+
return result;
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Async job endpoint
|
|
233
|
+
fastify.post('/api/process/video', async (request, reply) => {
|
|
234
|
+
const { source, operations } = request.body;
|
|
235
|
+
|
|
236
|
+
const job = await mediaService.createJob(
|
|
237
|
+
'video',
|
|
238
|
+
source,
|
|
239
|
+
`/uploads/processed-${Date.now()}.mp4`,
|
|
240
|
+
operations
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
return { jobId: job.id, status: job.status };
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Job status endpoint
|
|
247
|
+
fastify.get('/api/jobs/:id', async (request, reply) => {
|
|
248
|
+
const job = await mediaService.getJob(request.params.id);
|
|
249
|
+
|
|
250
|
+
if (!job) {
|
|
251
|
+
return reply.status(404).send({ error: 'Job not found' });
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return job;
|
|
255
|
+
});
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Production Setup
|
|
259
|
+
|
|
260
|
+
For production, install required libraries:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
# Image processing
|
|
264
|
+
npm install sharp
|
|
265
|
+
|
|
266
|
+
# Video processing
|
|
267
|
+
npm install fluent-ffmpeg
|
|
268
|
+
|
|
269
|
+
# System dependencies
|
|
270
|
+
apt-get install ffmpeg # Ubuntu/Debian
|
|
271
|
+
brew install ffmpeg # macOS
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Best Practices
|
|
275
|
+
|
|
276
|
+
1. **Async Processing** - Use job queue for large files
|
|
277
|
+
2. **Temp Cleanup** - Clean up temporary files
|
|
278
|
+
3. **Size Limits** - Set appropriate file size limits
|
|
279
|
+
4. **Format Validation** - Validate input formats
|
|
280
|
+
5. **Error Handling** - Handle processing failures gracefully
|
|
281
|
+
6. **Resource Limits** - Control concurrent jobs
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# MFA Module
|
|
2
|
+
|
|
3
|
+
Multi-Factor Authentication with support for TOTP, SMS, Email, and Backup Codes.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **TOTP** - Time-based One-Time Password (Google Authenticator compatible)
|
|
8
|
+
- **SMS** - SMS-based verification codes
|
|
9
|
+
- **Email** - Email-based verification codes
|
|
10
|
+
- **Backup Codes** - Single-use recovery codes
|
|
11
|
+
- **Account Lockout** - Protection against brute-force attacks
|
|
12
|
+
- **Redis Challenges** - Temporary challenge storage with TTL
|
|
13
|
+
|
|
14
|
+
## Architecture
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
18
|
+
│ MFA Service │
|
|
19
|
+
├─────────────────────────────────────────────────────────────┤
|
|
20
|
+
│ TOTP Setup │ SMS/Email │ Backup Codes │ Verify │
|
|
21
|
+
└────────┬───────┴───────┬───────┴───────┬────────┴─────┬─────┘
|
|
22
|
+
│ │ │ │
|
|
23
|
+
▼ ▼ ▼ ▼
|
|
24
|
+
┌─────────────┐ ┌─────────────┐ ┌───────────┐ ┌───────────┐
|
|
25
|
+
│ Prisma │ │ Redis │ │ Prisma │ │ Redis │
|
|
26
|
+
│ (Settings) │ │ (Challenge) │ │ (Codes) │ │ (Attempts)│
|
|
27
|
+
└─────────────┘ └─────────────┘ └───────────┘ └───────────┘
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Storage
|
|
31
|
+
|
|
32
|
+
| Data | Storage | TTL |
|
|
33
|
+
|------|---------|-----|
|
|
34
|
+
| User MFA Settings | PostgreSQL (Prisma) | Permanent |
|
|
35
|
+
| TOTP Secrets | PostgreSQL (Prisma) | Permanent |
|
|
36
|
+
| Backup Codes | PostgreSQL (Prisma) | Permanent |
|
|
37
|
+
| Active Challenges | Redis | 5 minutes |
|
|
38
|
+
| Failed Attempts | Redis | 15 minutes |
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
### Basic Setup
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { getMFAService, createMFAService } from 'servcraft/modules/mfa';
|
|
46
|
+
|
|
47
|
+
// Use default configuration
|
|
48
|
+
const mfa = getMFAService();
|
|
49
|
+
|
|
50
|
+
// Or create with custom config
|
|
51
|
+
const mfa = createMFAService({
|
|
52
|
+
issuer: 'MyApp',
|
|
53
|
+
totpWindow: 1,
|
|
54
|
+
backupCodesCount: 10,
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### TOTP Setup
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// 1. Initiate TOTP setup
|
|
62
|
+
const setup = await mfa.setupTOTP(userId, userEmail);
|
|
63
|
+
// Returns: { secret, qrCode, manualEntry, uri }
|
|
64
|
+
|
|
65
|
+
// 2. Display QR code to user (setup.qrCode is a data URL)
|
|
66
|
+
// User scans with Google Authenticator
|
|
67
|
+
|
|
68
|
+
// 3. Verify initial setup with a code from the app
|
|
69
|
+
const verified = await mfa.verifyTOTPSetup(userId, '123456');
|
|
70
|
+
|
|
71
|
+
// 4. Later, verify during login
|
|
72
|
+
const result = await mfa.verifyChallenge(userId, '123456', 'totp');
|
|
73
|
+
if (result.success) {
|
|
74
|
+
// Allow login
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### SMS MFA
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
// 1. Setup SMS (sends verification code)
|
|
82
|
+
await mfa.setupSMS(userId, '+1234567890');
|
|
83
|
+
|
|
84
|
+
// 2. Verify phone number
|
|
85
|
+
const verified = await mfa.verifySMSSetup(userId, '123456');
|
|
86
|
+
|
|
87
|
+
// 3. During login, create challenge
|
|
88
|
+
const challenge = await mfa.createChallenge(userId, 'sms');
|
|
89
|
+
// SMS is sent automatically
|
|
90
|
+
|
|
91
|
+
// 4. Verify the code user received
|
|
92
|
+
const result = await mfa.verifyChallenge(userId, code, 'sms', challenge.id);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Email MFA
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
// 1. Setup Email MFA
|
|
99
|
+
await mfa.setupEmail(userId, 'user@example.com');
|
|
100
|
+
|
|
101
|
+
// 2. Verify email
|
|
102
|
+
const verified = await mfa.verifyEmailSetup(userId, '123456');
|
|
103
|
+
|
|
104
|
+
// 3. During login
|
|
105
|
+
const challenge = await mfa.createChallenge(userId, 'email');
|
|
106
|
+
const result = await mfa.verifyChallenge(userId, code, 'email', challenge.id);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Backup Codes
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
// Generate backup codes (display once to user)
|
|
113
|
+
const { codes, generatedAt } = await mfa.generateBackupCodes(userId);
|
|
114
|
+
// codes: ['ABCD-1234', 'EFGH-5678', ...]
|
|
115
|
+
|
|
116
|
+
// Check remaining codes
|
|
117
|
+
const remaining = await mfa.getRemainingBackupCodes(userId);
|
|
118
|
+
|
|
119
|
+
// Verify backup code during login
|
|
120
|
+
const result = await mfa.verifyChallenge(userId, 'ABCD-1234', 'backup_codes');
|
|
121
|
+
// Code is marked as used after successful verification
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### User MFA Status
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
// Check if MFA is enabled
|
|
128
|
+
const enabled = await mfa.isMFAEnabled(userId);
|
|
129
|
+
|
|
130
|
+
// Get enabled methods
|
|
131
|
+
const methods = await mfa.getEnabledMethods(userId);
|
|
132
|
+
// ['totp', 'backup_codes']
|
|
133
|
+
|
|
134
|
+
// Get full MFA status
|
|
135
|
+
const userMFA = await mfa.getUserMFA(userId);
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Disable MFA
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
// Disable TOTP (requires valid code)
|
|
142
|
+
await mfa.disableTOTP(userId, '123456');
|
|
143
|
+
|
|
144
|
+
// Disable all MFA methods
|
|
145
|
+
await mfa.disableAllMFA(userId);
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Configuration
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
interface MFAConfig {
|
|
152
|
+
issuer: string; // App name shown in authenticator (default: 'Servcraft')
|
|
153
|
+
totpWindow: number; // Time window tolerance (default: 1)
|
|
154
|
+
backupCodesCount: number; // Number of backup codes (default: 10)
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Security Features
|
|
159
|
+
|
|
160
|
+
### Account Lockout
|
|
161
|
+
|
|
162
|
+
After 5 failed verification attempts, the account is locked for 15 minutes:
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
const result = await mfa.verifyChallenge(userId, wrongCode);
|
|
166
|
+
// result: {
|
|
167
|
+
// success: false,
|
|
168
|
+
// remainingAttempts: 2,
|
|
169
|
+
// lockedUntil: undefined
|
|
170
|
+
// }
|
|
171
|
+
|
|
172
|
+
// After 5 failures:
|
|
173
|
+
// result: {
|
|
174
|
+
// success: false,
|
|
175
|
+
// remainingAttempts: 0,
|
|
176
|
+
// lockedUntil: Date (now + 15 minutes)
|
|
177
|
+
// }
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Challenge Expiration
|
|
181
|
+
|
|
182
|
+
Challenges expire after 5 minutes:
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
const challenge = await mfa.createChallenge(userId, 'sms');
|
|
186
|
+
// challenge.expiresAt = now + 5 minutes
|
|
187
|
+
|
|
188
|
+
// After expiration, verification fails
|
|
189
|
+
const result = await mfa.verifyChallenge(userId, code, 'sms', challenge.id);
|
|
190
|
+
// result.success = false
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## API Response Types
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
interface TOTPSetup {
|
|
197
|
+
secret: string; // Raw TOTP secret
|
|
198
|
+
qrCode: string; // QR code as data URL
|
|
199
|
+
manualEntry: string; // Formatted secret for manual entry
|
|
200
|
+
uri: string; // otpauth:// URI
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
interface MFAChallenge {
|
|
204
|
+
id: string;
|
|
205
|
+
userId: string;
|
|
206
|
+
method: MFAMethod;
|
|
207
|
+
expiresAt: Date;
|
|
208
|
+
attempts: number;
|
|
209
|
+
maxAttempts: number;
|
|
210
|
+
verified: boolean;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
interface MFAVerifyResult {
|
|
214
|
+
success: boolean;
|
|
215
|
+
method: MFAMethod;
|
|
216
|
+
remainingAttempts?: number;
|
|
217
|
+
lockedUntil?: Date;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
type MFAMethod = 'totp' | 'sms' | 'email' | 'backup_codes';
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Redis Key Structure
|
|
224
|
+
|
|
225
|
+
| Key Pattern | Purpose | TTL |
|
|
226
|
+
|-------------|---------|-----|
|
|
227
|
+
| `mfa:challenge:{id}` | Active verification challenge | 5 min |
|
|
228
|
+
| `mfa:attempts:{userId}` | Failed attempt counter | 15 min |
|
|
229
|
+
|
|
230
|
+
## Integration with Auth Flow
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
// Login flow with MFA
|
|
234
|
+
async function login(email: string, password: string, mfaCode?: string) {
|
|
235
|
+
// 1. Verify credentials
|
|
236
|
+
const user = await authService.verifyCredentials(email, password);
|
|
237
|
+
|
|
238
|
+
// 2. Check if MFA is required
|
|
239
|
+
if (await mfa.isMFAEnabled(user.id)) {
|
|
240
|
+
if (!mfaCode) {
|
|
241
|
+
const methods = await mfa.getEnabledMethods(user.id);
|
|
242
|
+
return { requiresMFA: true, methods };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// 3. Verify MFA code
|
|
246
|
+
const result = await mfa.verifyChallenge(user.id, mfaCode);
|
|
247
|
+
if (!result.success) {
|
|
248
|
+
throw new UnauthorizedError('Invalid MFA code', {
|
|
249
|
+
remainingAttempts: result.remainingAttempts,
|
|
250
|
+
lockedUntil: result.lockedUntil,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// 4. Issue tokens
|
|
256
|
+
return authService.generateTokens(user);
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Best Practices
|
|
261
|
+
|
|
262
|
+
1. **Always offer backup codes** - Users can lose access to their phone
|
|
263
|
+
2. **Display codes only once** - Show backup codes only when generated
|
|
264
|
+
3. **Log MFA events** - Track setup, verification, and failures for audit
|
|
265
|
+
4. **Rate limit challenges** - Prevent challenge spam with rate limiting
|
|
266
|
+
5. **Secure TOTP secrets** - Encrypt at rest in database
|