nestcraftx 0.2.4 → 0.2.6
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/.gitattributes +6 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
- package/.github/ISSUE_TEMPLATE/pull_request_template.md +24 -0
- package/CHANGELOG.fr.md +97 -97
- package/CHANGELOG.md +98 -98
- package/CLI_USAGE.fr.md +331 -331
- package/CLI_USAGE.md +364 -364
- package/DEMO.fr.md +292 -292
- package/DEMO.md +294 -294
- package/LICENSE +21 -21
- package/MIGRATION_GUIDE.fr.md +127 -127
- package/MIGRATION_GUIDE.md +124 -124
- package/QUICK_START.fr.md +152 -152
- package/QUICK_START.md +169 -169
- package/README.fr.md +653 -659
- package/SECURITY.md +10 -0
- package/bin/nestcraft.js +84 -64
- package/commands/demo.js +333 -330
- package/commands/generate.js +93 -0
- package/commands/generateConf.js +91 -0
- package/commands/help.js +78 -78
- package/commands/info.js +48 -48
- package/commands/new.js +338 -335
- package/commands/start.js +19 -19
- package/commands/test.js +7 -7
- package/package.json +41 -41
- package/readme.md +638 -643
- package/utils/cliParser.js +133 -76
- package/utils/colors.js +62 -62
- package/utils/configs/configureDocker.js +120 -120
- package/utils/configs/setupCleanArchitecture.js +563 -557
- package/utils/configs/setupLightArchitecture.js +701 -660
- package/utils/envGenerator.js +122 -122
- package/utils/file-utils/packageJsonUtils.js +49 -55
- package/utils/file-utils/saveProjectConfig.js +36 -0
- package/utils/fullModeInput.js +607 -607
- package/utils/generators/application/dtoUpdater.js +54 -0
- package/utils/generators/cleanModuleGenerator.js +475 -0
- package/utils/generators/database/setupDatabase.js +31 -0
- package/utils/generators/domain/entityUpdater.js +78 -0
- package/utils/generators/infrastructure/mapperUpdater.js +65 -0
- package/utils/generators/lightModuleGenerator.js +131 -0
- package/utils/generators/relation/relation.engine.js +64 -0
- package/utils/interactive/askEntityInputs.js +165 -0
- package/utils/lightModeInput.js +460 -460
- package/utils/loggers/logError.js +7 -7
- package/utils/loggers/logInfo.js +7 -7
- package/utils/loggers/logSuccess.js +7 -7
- package/utils/loggers/logWarning.js +7 -7
- package/utils/setups/orms/typeOrmSetup.js +630 -630
- package/utils/setups/projectSetup.js +46 -46
- package/utils/setups/setupAuth.js +973 -926
- package/utils/setups/setupDatabase.js +75 -75
- package/utils/setups/setupLogger.js +69 -59
- package/utils/setups/setupMongoose.js +377 -432
- package/utils/setups/setupPrisma.js +802 -630
- package/utils/setups/setupSwagger.js +97 -88
- package/utils/shell.js +32 -32
- package/utils/spinner.js +57 -57
- package/utils/systemCheck.js +124 -124
- package/utils/userInput.js +421 -421
- package/utils/utils.js +2197 -1762
|
@@ -1,926 +1,973 @@
|
|
|
1
|
-
const { logInfo } = require("../loggers/logInfo");
|
|
2
|
-
const { runCommand } = require("../shell");
|
|
3
|
-
const { createDirectory, createFile, updateFile } = require("../userInput");
|
|
4
|
-
const { logSuccess } = require("../loggers/logSuccess");
|
|
5
|
-
const { generateDto } = require("../utils");
|
|
6
|
-
|
|
7
|
-
async function setupAuth(inputs) {
|
|
8
|
-
logInfo(
|
|
9
|
-
"🚀 Déploiement de l'architecture Auth Ultime (Mappers, DTOs, Multi-ORM)..."
|
|
10
|
-
);
|
|
11
|
-
|
|
12
|
-
const { dbConfig, useSwagger, mode = "full" } = inputs;
|
|
13
|
-
const isFull = mode === "full";
|
|
14
|
-
|
|
15
|
-
// 1. INSTALLATION DES DÉPENDANCES
|
|
16
|
-
await runCommand(
|
|
17
|
-
`npm install @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt uuid`,
|
|
18
|
-
"Erreur install deps auth"
|
|
19
|
-
);
|
|
20
|
-
await runCommand(
|
|
21
|
-
`npm install -D @types/passport-jwt @types/bcrypt @types/uuid`,
|
|
22
|
-
"Erreur install dev-deps auth"
|
|
23
|
-
);
|
|
24
|
-
|
|
25
|
-
// 2. DÉFINITION DES CHEMINS (CLEAN ARCHITECTURE)
|
|
26
|
-
const paths = {
|
|
27
|
-
root: "src/auth",
|
|
28
|
-
application: isFull ? "src/auth/application" : "src/auth/services",
|
|
29
|
-
interfaces: isFull ? "src/auth/domain/interfaces" : "",
|
|
30
|
-
services: isFull ? "src/auth/application/services" : "src/auth/services",
|
|
31
|
-
appDtos: isFull ? "src/auth/application/dtos" : "src/auth/dtos",
|
|
32
|
-
domain: isFull ? "src/auth/domain" : "src/auth/",
|
|
33
|
-
entities: isFull ? "src/auth/domain/entities" : "src/auth/entities",
|
|
34
|
-
infra: isFull ? "src/auth/infrastructure" : "src/auth",
|
|
35
|
-
controllers: isFull
|
|
36
|
-
? "src/auth/presentation/controllers"
|
|
37
|
-
: "src/auth/controllers",
|
|
38
|
-
persistence: isFull
|
|
39
|
-
? "src/auth/infrastructure/persistence"
|
|
40
|
-
: "src/auth/persistence",
|
|
41
|
-
mappers: isFull ? "src/auth/infrastructure/mappers" : "src/auth/mappers",
|
|
42
|
-
guards: isFull ? "src/auth/infrastructure/guards" : "src/auth/guards",
|
|
43
|
-
strategies: isFull
|
|
44
|
-
? "src/auth/infrastructure/strategies"
|
|
45
|
-
: "src/auth/strategies",
|
|
46
|
-
decorators: "src/common/decorators",
|
|
47
|
-
enums: isFull
|
|
48
|
-
? "import { Role } from 'src/user/domain/enums/role.enum';"
|
|
49
|
-
: "import { Role } from 'src/common/enums/role.enum';",
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
for (const path of Object.values(paths)) {
|
|
53
|
-
if (path.startsWith("src/") && !path.includes("common"))
|
|
54
|
-
await createDirectory(path);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// --- 3. COUCHE DOMAINE (ENTITÉS & INTERFACES) ---
|
|
58
|
-
|
|
59
|
-
await createFile({
|
|
60
|
-
path: `${paths.entities}/session.entity.ts`,
|
|
61
|
-
contente: `
|
|
62
|
-
export class Session {
|
|
63
|
-
id: string;
|
|
64
|
-
token: string;
|
|
65
|
-
userId: string;
|
|
66
|
-
expiresAt: Date;
|
|
67
|
-
createdAt?: Date;
|
|
68
|
-
}`.trim(),
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
if (isFull) {
|
|
72
|
-
await createFile({
|
|
73
|
-
path: `${paths.interfaces}/session.repository.interface.ts`,
|
|
74
|
-
contente: `
|
|
75
|
-
import { Session } from '${paths.entities}/session.entity';
|
|
76
|
-
import {
|
|
77
|
-
|
|
78
|
-
export
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
@
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
await this.
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
//
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
return
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
import {
|
|
435
|
-
|
|
436
|
-
import {
|
|
437
|
-
import {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
async save(
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
@
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
@
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
@
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
@
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
if (
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
if (!
|
|
730
|
-
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
{
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
},
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
1
|
+
const { logInfo } = require("../loggers/logInfo");
|
|
2
|
+
const { runCommand } = require("../shell");
|
|
3
|
+
const { createDirectory, createFile, updateFile } = require("../userInput");
|
|
4
|
+
const { logSuccess } = require("../loggers/logSuccess");
|
|
5
|
+
const { generateDto } = require("../utils");
|
|
6
|
+
|
|
7
|
+
async function setupAuth(inputs) {
|
|
8
|
+
logInfo(
|
|
9
|
+
"🚀 Déploiement de l'architecture Auth Ultime (Mappers, DTOs, Multi-ORM)...",
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
const { dbConfig, useSwagger, mode = "full" } = inputs;
|
|
13
|
+
const isFull = mode === "full";
|
|
14
|
+
|
|
15
|
+
// 1. INSTALLATION DES DÉPENDANCES
|
|
16
|
+
await runCommand(
|
|
17
|
+
`npm install @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt uuid`,
|
|
18
|
+
"Erreur install deps auth",
|
|
19
|
+
);
|
|
20
|
+
await runCommand(
|
|
21
|
+
`npm install -D @types/passport-jwt @types/bcrypt @types/uuid`,
|
|
22
|
+
"Erreur install dev-deps auth",
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
// 2. DÉFINITION DES CHEMINS (CLEAN ARCHITECTURE)
|
|
26
|
+
const paths = {
|
|
27
|
+
root: "src/auth",
|
|
28
|
+
application: isFull ? "src/auth/application" : "src/auth/services",
|
|
29
|
+
interfaces: isFull ? "src/auth/domain/interfaces" : "",
|
|
30
|
+
services: isFull ? "src/auth/application/services" : "src/auth/services",
|
|
31
|
+
appDtos: isFull ? "src/auth/application/dtos" : "src/auth/dtos",
|
|
32
|
+
domain: isFull ? "src/auth/domain" : "src/auth/",
|
|
33
|
+
entities: isFull ? "src/auth/domain/entities" : "src/auth/entities",
|
|
34
|
+
infra: isFull ? "src/auth/infrastructure" : "src/auth",
|
|
35
|
+
controllers: isFull
|
|
36
|
+
? "src/auth/presentation/controllers"
|
|
37
|
+
: "src/auth/controllers",
|
|
38
|
+
persistence: isFull
|
|
39
|
+
? "src/auth/infrastructure/persistence"
|
|
40
|
+
: "src/auth/persistence",
|
|
41
|
+
mappers: isFull ? "src/auth/infrastructure/mappers" : "src/auth/mappers",
|
|
42
|
+
guards: isFull ? "src/auth/infrastructure/guards" : "src/auth/guards",
|
|
43
|
+
strategies: isFull
|
|
44
|
+
? "src/auth/infrastructure/strategies"
|
|
45
|
+
: "src/auth/strategies",
|
|
46
|
+
decorators: "src/common/decorators",
|
|
47
|
+
enums: isFull
|
|
48
|
+
? "import { Role } from 'src/user/domain/enums/role.enum';"
|
|
49
|
+
: "import { Role } from 'src/common/enums/role.enum';",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
for (const path of Object.values(paths)) {
|
|
53
|
+
if (path.startsWith("src/") && !path.includes("common"))
|
|
54
|
+
await createDirectory(path);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// --- 3. COUCHE DOMAINE (ENTITÉS & INTERFACES) ---
|
|
58
|
+
|
|
59
|
+
await createFile({
|
|
60
|
+
path: `${paths.entities}/session.entity.ts`,
|
|
61
|
+
contente: `
|
|
62
|
+
export class Session {
|
|
63
|
+
id: string;
|
|
64
|
+
token: string;
|
|
65
|
+
userId: string;
|
|
66
|
+
expiresAt: Date;
|
|
67
|
+
createdAt?: Date;
|
|
68
|
+
}`.trim(),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (isFull) {
|
|
72
|
+
await createFile({
|
|
73
|
+
path: `${paths.interfaces}/session.repository.interface.ts`,
|
|
74
|
+
contente: `
|
|
75
|
+
import { Session } from '${paths.entities}/session.entity';
|
|
76
|
+
import { CreateSessionPersistenceDto } from '${paths.appDtos}/create-session.dto';
|
|
77
|
+
|
|
78
|
+
export const ISessionRepositoryName = 'ISessionRepository';
|
|
79
|
+
|
|
80
|
+
export interface ISessionRepository {
|
|
81
|
+
save(dto: CreateSessionPersistenceDto): Promise<Session>;
|
|
82
|
+
findByToken(token: string): Promise<Session | null>;
|
|
83
|
+
findById(id: string): Promise<Session | null>;
|
|
84
|
+
deleteByToken(token: string): Promise<void>;
|
|
85
|
+
deleteByUserId(userId: string): Promise<void>;
|
|
86
|
+
deleteById(sessionId: string): Promise<void>;
|
|
87
|
+
}`.trim(),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// --- GÉNÉRATION DU SCHÉMA SESSION (Spécifique Mongoose) ---
|
|
92
|
+
if (dbConfig.orm === "mongoose") {
|
|
93
|
+
const sessionSchemaPath = isFull
|
|
94
|
+
? "src/auth/infrastructure/persistence/mongoose"
|
|
95
|
+
: "src/auth/persistence";
|
|
96
|
+
|
|
97
|
+
await createDirectory(sessionSchemaPath);
|
|
98
|
+
|
|
99
|
+
await createFile({
|
|
100
|
+
path: `${sessionSchemaPath}/session.schema.ts`,
|
|
101
|
+
contente: `
|
|
102
|
+
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
|
|
103
|
+
import { Document } from 'mongoose';
|
|
104
|
+
|
|
105
|
+
export type SessionDocument = Session & Document;
|
|
106
|
+
|
|
107
|
+
@Schema({ timestamps: true })
|
|
108
|
+
export class Session {
|
|
109
|
+
@Prop({ required: true })
|
|
110
|
+
refreshToken: string;
|
|
111
|
+
|
|
112
|
+
@Prop({ required: true })
|
|
113
|
+
userId: string;
|
|
114
|
+
|
|
115
|
+
@Prop({ required: true })
|
|
116
|
+
expiresAt: Date;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export const SessionSchema = SchemaFactory.createForClass(Session);
|
|
120
|
+
`.trim(),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
// --- 4. COUCHE APPLICATION (DTOS & SERVICES) ---
|
|
124
|
+
|
|
125
|
+
await createFile({
|
|
126
|
+
path: `${paths.appDtos}/create-session.dto.ts`,
|
|
127
|
+
contente: `export class CreateSessionDto {
|
|
128
|
+
refreshToken: string;
|
|
129
|
+
userId: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface CreateSessionPersistenceDto {
|
|
133
|
+
userId: string;
|
|
134
|
+
refreshToken: string;
|
|
135
|
+
expiresAt: Date;
|
|
136
|
+
}`,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Définition dynamique des types et injections
|
|
140
|
+
const repoType = isFull ? "ISessionRepository" : "SessionRepository";
|
|
141
|
+
const repoImport = isFull
|
|
142
|
+
? `import { ISessionRepositoryName, type ISessionRepository } from '${paths.interfaces}/session.repository.interface';`
|
|
143
|
+
: `import { SessionRepository } from '${paths.persistence}/session.repository';`;
|
|
144
|
+
const injectDecorator = isFull ? `@Inject(ISessionRepositoryName) ` : "";
|
|
145
|
+
|
|
146
|
+
await createFile({
|
|
147
|
+
path: `${paths.services}/session.service.ts`,
|
|
148
|
+
contente: `
|
|
149
|
+
import { Injectable${
|
|
150
|
+
isFull ? ", Inject" : ""
|
|
151
|
+
}, UnauthorizedException } from '@nestjs/common';
|
|
152
|
+
import { CreateSessionDto } from '${paths.appDtos}/create-session.dto';
|
|
153
|
+
${repoImport}
|
|
154
|
+
|
|
155
|
+
@Injectable()
|
|
156
|
+
export class SessionService {
|
|
157
|
+
constructor(${injectDecorator}private readonly repo: ${repoType}) {}
|
|
158
|
+
|
|
159
|
+
async create(data: CreateSessionDto) {
|
|
160
|
+
const expiresAt = new Date();
|
|
161
|
+
expiresAt.setDate(expiresAt.getDate() + 7);
|
|
162
|
+
|
|
163
|
+
return this.repo.save({
|
|
164
|
+
...data,
|
|
165
|
+
expiresAt,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Safe validation (no exception)
|
|
171
|
+
*/
|
|
172
|
+
async validate(token: string) {
|
|
173
|
+
const session = await this.repo.findByToken(token);
|
|
174
|
+
|
|
175
|
+
if (!session) return null;
|
|
176
|
+
|
|
177
|
+
if (session.expiresAt && session.expiresAt < new Date()) {
|
|
178
|
+
await this.repo.deleteById(session.id);
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return session;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Boolean check (used by guards)
|
|
187
|
+
*/
|
|
188
|
+
async isValidById(sessionId: string): Promise<boolean> {
|
|
189
|
+
const session = await this.repo.findById(sessionId);
|
|
190
|
+
|
|
191
|
+
if (!session) return false;
|
|
192
|
+
|
|
193
|
+
if (session.expiresAt && session.expiresAt < new Date()) {
|
|
194
|
+
await this.repo.deleteById(sessionId);
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Hard revoke
|
|
203
|
+
*/
|
|
204
|
+
async revoke(token: string): Promise<void> {
|
|
205
|
+
const session = await this.repo.findByToken(token);
|
|
206
|
+
|
|
207
|
+
if (!session || session.expiresAt < new Date()) {
|
|
208
|
+
throw new UnauthorizedException('Invalid or expired session');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
await this.repo.deleteById(session.id);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async revokeById(id: string): Promise<void> {
|
|
215
|
+
await this.repo.deleteById(id);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
`.trim(),
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Auth Service
|
|
222
|
+
let enumImport;
|
|
223
|
+
let userDtoPath;
|
|
224
|
+
let userRepoPath;
|
|
225
|
+
let userRepoType;
|
|
226
|
+
if (mode === "light") {
|
|
227
|
+
userDtoPath = "src/user/dtos";
|
|
228
|
+
userRepoPath = "src/user/repositories/user.repository";
|
|
229
|
+
userRepoType = "UserRepository";
|
|
230
|
+
enumImport = "import { Role } from 'src/common/enums/role.enum';";
|
|
231
|
+
} else {
|
|
232
|
+
userDtoPath = "src/user/application/dtos";
|
|
233
|
+
userRepoPath = "src/user/domain/interfaces/user.repository.interface";
|
|
234
|
+
userRepoType = "IUserRepository";
|
|
235
|
+
enumImport = "import { Role } from 'src/user/domain/enums/role.enum';";
|
|
236
|
+
}
|
|
237
|
+
await createFile({
|
|
238
|
+
path: `${paths.services}/auth.service.ts`,
|
|
239
|
+
contente: `
|
|
240
|
+
import { Injectable, ConflictException, UnauthorizedException, Inject, NotFoundException } from '@nestjs/common';
|
|
241
|
+
import { JwtService } from '@nestjs/jwt';
|
|
242
|
+
import * as bcrypt from 'bcrypt';
|
|
243
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
244
|
+
|
|
245
|
+
import { SessionService } from './session.service';
|
|
246
|
+
import { LoginCredentialDto } from '${paths.appDtos}/loginCredential.dto';
|
|
247
|
+
import { RefreshTokenDto } from '${paths.appDtos}/refreshToken.dto';
|
|
248
|
+
import { SendOtpDto } from '${paths.appDtos}/sendOtp.dto';
|
|
249
|
+
import { VerifyOtpDto } from '${paths.appDtos}/verifyOtp.dto';
|
|
250
|
+
import { ForgotPasswordDto } from '${paths.appDtos}/forgotPassword.dto';
|
|
251
|
+
import { ResetPasswordDto } from '${paths.appDtos}/resetPassword.dto';
|
|
252
|
+
${enumImport}
|
|
253
|
+
${
|
|
254
|
+
mode === "light"
|
|
255
|
+
? `import { UserRepository } from '${userRepoPath}';
|
|
256
|
+
import { CreateUserDto } from '${userDtoPath}/user.dto';`
|
|
257
|
+
: `import type { IUserRepository } from '${userRepoPath}';
|
|
258
|
+
import { CreateUserDto } from '${userDtoPath}/user.dto';`
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
@Injectable()
|
|
262
|
+
export class AuthService {
|
|
263
|
+
private otps = new Map<string, string>();
|
|
264
|
+
constructor(
|
|
265
|
+
private readonly jwtService: JwtService,
|
|
266
|
+
private readonly sessionService: SessionService,
|
|
267
|
+
${
|
|
268
|
+
mode === "light"
|
|
269
|
+
? `private readonly userRepository: UserRepository,`
|
|
270
|
+
: `@Inject('IUserRepository')
|
|
271
|
+
private readonly userRepository: IUserRepository,`
|
|
272
|
+
}
|
|
273
|
+
) {}
|
|
274
|
+
|
|
275
|
+
// 🔒 Hash the user password
|
|
276
|
+
async hashPassword(password: string): Promise<string> {
|
|
277
|
+
return bcrypt.hash(password, 10);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// 🧪 Compare a plain password with a hash
|
|
281
|
+
async comparePassword(password: string, hash: string): Promise<boolean> {
|
|
282
|
+
return bcrypt.compare(password, hash);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// 🧾 Registration (register)
|
|
286
|
+
async register(dto: CreateUserDto): Promise<{ message: string }> {
|
|
287
|
+
const existing = await this.userRepository.findByEmail(dto.email);
|
|
288
|
+
if (existing) {
|
|
289
|
+
throw new ConflictException('Email already in use');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const password = await this.hashPassword(dto.password);
|
|
293
|
+
await this.userRepository.create({ ...dto, password });
|
|
294
|
+
|
|
295
|
+
return { message: 'Registration successful' };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// 🔑 Login
|
|
299
|
+
async login(dto: LoginCredentialDto) {
|
|
300
|
+
const user = await this.userRepository.findByEmail(dto.email);
|
|
301
|
+
if (!user || !(await bcrypt.compare(dto.password, user.getPassword()))) {
|
|
302
|
+
throw new UnauthorizedException('Invalid credentials');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const refreshToken = this.jwtService.sign(
|
|
306
|
+
{ sub: user.getId() },
|
|
307
|
+
{ expiresIn: '7d' },
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
const session = await this.sessionService.create({
|
|
311
|
+
userId: user.getId(),
|
|
312
|
+
refreshToken: refreshToken,
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
const payload = {
|
|
316
|
+
sub: user.getId(),
|
|
317
|
+
email: user.getEmail(),
|
|
318
|
+
sid: session.id,
|
|
319
|
+
role: user.getRole(),
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const accessToken = this.jwtService.sign(payload, { expiresIn: '15m' });
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
message: 'Login successful',
|
|
326
|
+
accessToken,
|
|
327
|
+
refreshToken,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// 🔁 Refresh an access token
|
|
332
|
+
async refreshToken(token: RefreshTokenDto) {
|
|
333
|
+
const session = await this.sessionService.validate(token.refreshToken);
|
|
334
|
+
if (!session) throw new UnauthorizedException('Session expired or invalid');
|
|
335
|
+
const payload: {
|
|
336
|
+
sub: string;
|
|
337
|
+
email: string;
|
|
338
|
+
sid: string;
|
|
339
|
+
role: Role;
|
|
340
|
+
} = this.jwtService.decode(token.refreshToken) as any;
|
|
341
|
+
return { accessToken: this.jwtService.sign({ sub: payload.sub, email: payload.email }, { expiresIn: '15m' }) };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// 📲 Send OTP
|
|
345
|
+
sendOtp(dto: SendOtpDto) {
|
|
346
|
+
const otp = Math.floor(100000 + Math.random() * 900000).toString();
|
|
347
|
+
this.otps.set(dto.email, otp);
|
|
348
|
+
console.log(\`[OTP] for \${dto.email} is \${otp}\`);
|
|
349
|
+
return { message: 'OTP sent' };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Verify OTP
|
|
353
|
+
verifyOtp(dto: VerifyOtpDto) {
|
|
354
|
+
const valid = this.otps.get(dto.email);
|
|
355
|
+
if (valid === dto.otp) {
|
|
356
|
+
this.otps.delete(dto.email);
|
|
357
|
+
return { message: 'OTP verified' };
|
|
358
|
+
}
|
|
359
|
+
throw new UnauthorizedException('Invalid OTP');
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// 📬 Forgot Password
|
|
363
|
+
async forgotPassword(dto: ForgotPasswordDto) {
|
|
364
|
+
const existingUser = await this.userRepository.findByEmail(dto.email);
|
|
365
|
+
if (!existingUser) throw new NotFoundException('User not found');
|
|
366
|
+
|
|
367
|
+
const token = uuidv4();
|
|
368
|
+
console.log(\`[ResetToken] for \${dto.email} is \${token}\`);
|
|
369
|
+
return { message: 'Reset token sent' };
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// 🔄 Reset Password
|
|
373
|
+
async resetPassword(dto: ResetPasswordDto) {
|
|
374
|
+
const existingUser = await this.userRepository.findByEmail(dto.email);
|
|
375
|
+
if (!existingUser) throw new UnauthorizedException('Invalid reset token');
|
|
376
|
+
|
|
377
|
+
const password = await this.hashPassword(dto.newPassword);
|
|
378
|
+
await this.userRepository.update(existingUser.getId(), { password });
|
|
379
|
+
|
|
380
|
+
return { message: 'Password reset successful' };
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// 🔧 Generate token manually
|
|
384
|
+
generateToken(payload: any) {
|
|
385
|
+
return this.jwtService.sign(payload);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
async logout(token: string) { await this.sessionService.revoke(token); return { success: true }; }
|
|
389
|
+
}`.trim(),
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// --- 5. COUCHE INFRASTRUCTURE (PERSISTENCE & MAPPERS) ---
|
|
393
|
+
|
|
394
|
+
// Génération du Mapper
|
|
395
|
+
await createFile({
|
|
396
|
+
path: `${paths.mappers}/session.mapper.ts`,
|
|
397
|
+
contente: `
|
|
398
|
+
import { Session } from '${paths.entities}/session.entity';
|
|
399
|
+
|
|
400
|
+
export class SessionMapper {
|
|
401
|
+
static toDomain(raw: any): Session {
|
|
402
|
+
const session = new Session();
|
|
403
|
+
session.id = raw.id || raw._id?.toString();
|
|
404
|
+
session.token = raw.token;
|
|
405
|
+
session.userId = raw.userId;
|
|
406
|
+
session.expiresAt = raw.expiresAt;
|
|
407
|
+
return session;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
static toPersistence(domain: any) {
|
|
411
|
+
return {
|
|
412
|
+
token: domain.token,
|
|
413
|
+
userId: domain.userId,
|
|
414
|
+
expiresAt: domain.expiresAt,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
}`.trim(),
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
// Implémentation des Repositories selon l'ORM
|
|
421
|
+
|
|
422
|
+
// On prépare l'entête dynamiquement
|
|
423
|
+
const interfaceImport = isFull
|
|
424
|
+
? `import type { ISessionRepository } from '${paths.interfaces}/session.repository.interface';`
|
|
425
|
+
: "";
|
|
426
|
+
const implementsClause = isFull ? "implements ISessionRepository " : "";
|
|
427
|
+
|
|
428
|
+
let repoContent = "";
|
|
429
|
+
if (dbConfig.orm === "typeorm") {
|
|
430
|
+
repoContent = `
|
|
431
|
+
import { Injectable } from '@nestjs/common';
|
|
432
|
+
import { InjectRepository } from '@nestjs/typeorm';
|
|
433
|
+
import { Repository } from 'typeorm';
|
|
434
|
+
import { SessionMapper } from '${paths.mappers}/session.mapper';
|
|
435
|
+
${interfaceImport}
|
|
436
|
+
import { Session as SessionEntity } from '${paths.entities}/session.entity';
|
|
437
|
+
import { CreateSessionPersistenceDto } from '${paths.appDtos}/create-session.dto';
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
@Injectable()
|
|
441
|
+
export class SessionRepository ${implementsClause} {
|
|
442
|
+
constructor(@InjectRepository(SessionEntity) private readonly repo: Repository<SessionEntity>) {}
|
|
443
|
+
|
|
444
|
+
async save(dto: CreateSessionPersistenceDto): Promise<SessionEntity> {
|
|
445
|
+
const s = await this.repo.save(dto);
|
|
446
|
+
return SessionMapper.toDomain(s);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
async findByToken(token: string): Promise<SessionEntity | null> {
|
|
450
|
+
const s = await this.repo.findOne({ where: { token: token } });
|
|
451
|
+
return s ? SessionMapper.toDomain(s) : null;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async deleteByToken(token: string): Promise<void> {
|
|
455
|
+
await this.repo.delete({ token: token });
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
async deleteByUserId(userId: string): Promise<void> {
|
|
459
|
+
await this.repo.delete({ userId });
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
async deleteById(id: string): Promise<void> {
|
|
463
|
+
await this.repo.delete({ id });
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
async findById(id: string): Promise<SessionEntity | null> {
|
|
467
|
+
const s = await this.repo.findOne({ where: { id } });
|
|
468
|
+
return s ? SessionMapper.toDomain(s) : null;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
`;
|
|
472
|
+
} else if (dbConfig.orm === "prisma") {
|
|
473
|
+
repoContent = `
|
|
474
|
+
import { Injectable } from '@nestjs/common';
|
|
475
|
+
import { PrismaService } from 'src/prisma/prisma.service';
|
|
476
|
+
import { SessionMapper } from '${paths.mappers}/session.mapper';
|
|
477
|
+
${interfaceImport}
|
|
478
|
+
import { Session as SessionEntity } from '${paths.entities}/session.entity';
|
|
479
|
+
import { CreateSessionPersistenceDto } from '${paths.appDtos}/create-session.dto';
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
@Injectable()
|
|
483
|
+
export class SessionRepository ${implementsClause} {
|
|
484
|
+
constructor(private readonly prisma: PrismaService) {}
|
|
485
|
+
|
|
486
|
+
async save(data: CreateSessionPersistenceDto): Promise<SessionEntity> {
|
|
487
|
+
const s = await this.prisma.session.create({ data });
|
|
488
|
+
return SessionMapper.toDomain(s);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
async findByToken(token: string): Promise<SessionEntity | null> {
|
|
492
|
+
const s = await this.prisma.session.findFirst({ where: { refreshToken: token } });
|
|
493
|
+
return s ? SessionMapper.toDomain(s) : null;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
async deleteByToken(token: string): Promise<void> {
|
|
497
|
+
await this.prisma.session.deleteMany({ where: { refreshToken: token } });
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
async deleteByUserId(userId: string): Promise<void> {
|
|
501
|
+
await this.prisma.session.deleteMany({ where: { userId } });
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
async deleteById(id: string): Promise<void> {
|
|
505
|
+
await this.prisma.session.delete({ where: { id } });
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
async findById(id: string): Promise<SessionEntity | null> {
|
|
509
|
+
const s = await this.prisma.session.findUnique({ where: { id } });
|
|
510
|
+
return s ? SessionMapper.toDomain(s) : null;
|
|
511
|
+
}
|
|
512
|
+
}`;
|
|
513
|
+
} else if (dbConfig.orm === "mongoose") {
|
|
514
|
+
const sessionSchemaPath = isFull
|
|
515
|
+
? "src/auth/infrastructure/persistence/mongoose"
|
|
516
|
+
: "src/auth/persistence";
|
|
517
|
+
repoContent = `
|
|
518
|
+
import { Injectable } from '@nestjs/common';
|
|
519
|
+
import { InjectModel } from '@nestjs/mongoose';
|
|
520
|
+
import { Model } from 'mongoose';
|
|
521
|
+
import { SessionMapper } from '${paths.mappers}/session.mapper';
|
|
522
|
+
${interfaceImport}
|
|
523
|
+
import { Session, SessionDocument } from '${sessionSchemaPath}/session.schema';
|
|
524
|
+
import { Session as SessionEntity } from '${paths.entities}/session.entity';
|
|
525
|
+
import { CreateSessionPersistenceDto } from '${paths.appDtos}/create-session.dto';
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
@Injectable()
|
|
529
|
+
export class SessionRepository ${implementsClause} {
|
|
530
|
+
constructor(@InjectModel(Session.name) private readonly model: Model<SessionDocument>) {}
|
|
531
|
+
|
|
532
|
+
async save(data: CreateSessionPersistenceDto): Promise<SessionEntity> {
|
|
533
|
+
const s = await new this.model(data).save();
|
|
534
|
+
return SessionMapper.toDomain(s);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
async findByToken(token: string): Promise<SessionEntity | null> {
|
|
538
|
+
const s = await this.model.findOne({ refreshToken: token }).exec();
|
|
539
|
+
return s ? SessionMapper.toDomain(s) : null;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
async deleteByToken(token: string): Promise<void> {
|
|
543
|
+
await this.model.deleteOne({ refreshToken: token }).exec();
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
async deleteByUserId(userId: string): Promise<void> {
|
|
547
|
+
await this.model.deleteMany({ userId: userId as any }).exec();
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
async findById(id: string): Promise<SessionEntity | null> {
|
|
551
|
+
const s = await this.model.findById(id).exec();
|
|
552
|
+
return s ? SessionMapper.toDomain(s) : null;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
async deleteById(id: string): Promise<void> {
|
|
556
|
+
await this.model.findByIdAndDelete(id).exec();
|
|
557
|
+
}
|
|
558
|
+
}`;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
await createFile({
|
|
562
|
+
path: `${paths.persistence}/session.repository.ts`,
|
|
563
|
+
contente: repoContent.trim(),
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
// --- 6. INFRASTRUCTURE WEB (CONTROLLER, GUARD, STRATEGY) ---
|
|
567
|
+
|
|
568
|
+
await createFile({
|
|
569
|
+
path: `${paths.controllers}/auth.controller.ts`,
|
|
570
|
+
contente: `
|
|
571
|
+
import { Controller, Post, Body, Get, UseGuards } from '@nestjs/common';
|
|
572
|
+
import { AuthService } from '${paths.services}/auth.service';
|
|
573
|
+
import { LoginCredentialDto } from '${paths.appDtos}/loginCredential.dto';
|
|
574
|
+
import { RefreshTokenDto } from '${paths.appDtos}/refreshToken.dto';
|
|
575
|
+
import { JwtAuthGuard } from '${paths.guards}/jwt-auth.guard';
|
|
576
|
+
import { CurrentUser } from 'src/common/decorators/current-user.decorator';
|
|
577
|
+
import { CreateUserDto } from '${userDtoPath}/user.dto';
|
|
578
|
+
import { SendOtpDto } from '${paths.appDtos}/sendOtp.dto';
|
|
579
|
+
import { VerifyOtpDto } from '${paths.appDtos}/verifyOtp.dto';
|
|
580
|
+
import { ForgotPasswordDto } from '${paths.appDtos}/forgotPassword.dto';
|
|
581
|
+
import { ResetPasswordDto } from '${paths.appDtos}/resetPassword.dto';
|
|
582
|
+
${
|
|
583
|
+
useSwagger
|
|
584
|
+
? "import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';"
|
|
585
|
+
: ""
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
${useSwagger ? "@ApiTags('auth')" : ""}
|
|
589
|
+
@Controller('auth')
|
|
590
|
+
export class AuthController {
|
|
591
|
+
constructor(private readonly authService: AuthService) {}
|
|
592
|
+
|
|
593
|
+
// 📝 Create user account (👤)
|
|
594
|
+
${useSwagger ? "@ApiOperation({ summary: 'User register' })" : ""}
|
|
595
|
+
@Post('register')
|
|
596
|
+
register(@Body() body: CreateUserDto) {
|
|
597
|
+
return this.authService.register(body);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// 🔐 User login (🔑)
|
|
601
|
+
${useSwagger ? "@ApiOperation({ summary: 'User login' })" : ""}
|
|
602
|
+
@Post('login')
|
|
603
|
+
login(@Body() dto: LoginCredentialDto) {
|
|
604
|
+
return this.authService.login(dto);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
${useSwagger ? "@ApiOperation({ summary: 'Refresh access token' })" : ""}
|
|
608
|
+
@Post('refresh')
|
|
609
|
+
refreshToken(@Body() dto: RefreshTokenDto) {
|
|
610
|
+
return this.authService.refreshToken(dto);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// 📤 Send OTP to email (📧)
|
|
614
|
+
${useSwagger ? "@ApiOperation({ summary: 'Send OTP to email' })" : ""}
|
|
615
|
+
@Post('send-otp')
|
|
616
|
+
sendOtp(@Body() dto: SendOtpDto) {
|
|
617
|
+
return this.authService.sendOtp(dto);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Verify sent OTP (✔️)
|
|
621
|
+
${useSwagger ? "@ApiOperation({ summary: 'Verify OTP code' })" : ""}
|
|
622
|
+
@Post('verify-otp')
|
|
623
|
+
verifyOtp(@Body() dto: VerifyOtpDto) {
|
|
624
|
+
return this.authService.verifyOtp(dto);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// 🔁 Forgot password (📨)
|
|
628
|
+
${useSwagger ? "@ApiOperation({ summary: 'Request password reset' })" : ""}
|
|
629
|
+
@Post('forgot-password')
|
|
630
|
+
forgotPassword(@Body() dto: ForgotPasswordDto) {
|
|
631
|
+
return this.authService.forgotPassword(dto);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// 🔄 Reset password (🔓)
|
|
635
|
+
${useSwagger ? "@ApiOperation({ summary: 'Reset user password' })" : ""}
|
|
636
|
+
@Post('reset-password')
|
|
637
|
+
resetPassword(@Body() dto: ResetPasswordDto) {
|
|
638
|
+
return this.authService.resetPassword(dto);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
${useSwagger ? "@ApiBearerAuth()" : ""}
|
|
642
|
+
${useSwagger ? "@ApiOperation({ summary: 'Logout user' })" : ""}
|
|
643
|
+
@UseGuards(JwtAuthGuard)
|
|
644
|
+
@Post('logout')
|
|
645
|
+
logout(@Body() dto: RefreshTokenDto) {
|
|
646
|
+
return this.authService.logout(dto.refreshToken);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// 👤 Get connected user profile (🧑💼)
|
|
650
|
+
${useSwagger ? "@ApiBearerAuth()" : ""}
|
|
651
|
+
${useSwagger ? "@ApiOperation({ summary: 'Get current user profile' })" : ""}
|
|
652
|
+
@UseGuards(JwtAuthGuard)
|
|
653
|
+
@Get('me')
|
|
654
|
+
getMe(@CurrentUser() user: any) {
|
|
655
|
+
return user;
|
|
656
|
+
}
|
|
657
|
+
}`.trim(),
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
await createFile({
|
|
661
|
+
path: `${paths.strategies}/jwt.strategy.ts`,
|
|
662
|
+
contente: `
|
|
663
|
+
import { Injectable } from '@nestjs/common';
|
|
664
|
+
import { PassportStrategy } from '@nestjs/passport';
|
|
665
|
+
import { ExtractJwt, Strategy } from 'passport-jwt';
|
|
666
|
+
import { ConfigService } from '@nestjs/config';
|
|
667
|
+
|
|
668
|
+
@Injectable()
|
|
669
|
+
export class JwtStrategy extends PassportStrategy(Strategy) {
|
|
670
|
+
constructor(config: ConfigService) {
|
|
671
|
+
const jwtSecret = config.get<string>('JWT_SECRET');
|
|
672
|
+
if (!jwtSecret) {
|
|
673
|
+
throw new Error('JWT_SECRET is not defined in configuration');
|
|
674
|
+
}
|
|
675
|
+
super({
|
|
676
|
+
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
|
677
|
+
ignoreExpiration: false,
|
|
678
|
+
secretOrKey: jwtSecret,
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
async validate(payload: any) {
|
|
683
|
+
return {
|
|
684
|
+
userId: payload.sub,
|
|
685
|
+
email: payload.email,
|
|
686
|
+
sid: payload.sid,
|
|
687
|
+
role: payload.role,
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
}`.trim(),
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
await createFile({
|
|
695
|
+
path: `${paths.guards}/jwt-auth.guard.ts`,
|
|
696
|
+
contente: `
|
|
697
|
+
import { Injectable, ExecutionContext, UnauthorizedException } from '@nestjs/common';
|
|
698
|
+
import { AuthGuard } from '@nestjs/passport';
|
|
699
|
+
import { Reflector } from '@nestjs/core';
|
|
700
|
+
import { IS_PUBLIC_KEY } from 'src/common/decorators/public.decorator';
|
|
701
|
+
import { SessionService } from '${paths.services}/session.service';
|
|
702
|
+
|
|
703
|
+
@Injectable()
|
|
704
|
+
export class JwtAuthGuard extends AuthGuard('jwt') {
|
|
705
|
+
constructor(
|
|
706
|
+
private reflector: Reflector,
|
|
707
|
+
private sessionService: SessionService,
|
|
708
|
+
) {
|
|
709
|
+
super();
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
async canActivate(context: ExecutionContext) {
|
|
713
|
+
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
|
|
714
|
+
context.getHandler(), context.getClass(),
|
|
715
|
+
]);
|
|
716
|
+
if (isPublic) return true;
|
|
717
|
+
|
|
718
|
+
const canActivate = await super.canActivate(context);
|
|
719
|
+
if (!canActivate) return false;
|
|
720
|
+
|
|
721
|
+
const request = context.switchToHttp().getRequest();
|
|
722
|
+
const user = request.user;
|
|
723
|
+
|
|
724
|
+
if (!user || !user.sid) {
|
|
725
|
+
throw new UnauthorizedException('Invalid token payload');
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
const isSessionValid = await this.sessionService.isValidById(user.sid);
|
|
729
|
+
if (!isSessionValid) {
|
|
730
|
+
throw new UnauthorizedException('Session has been revoked');
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
return true;
|
|
734
|
+
}
|
|
735
|
+
}`.trim(),
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
// role Guard
|
|
739
|
+
await createFile({
|
|
740
|
+
path: `${paths.guards}/role.guard.ts`,
|
|
741
|
+
contente: `import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
|
742
|
+
import { Reflector } from '@nestjs/core';
|
|
743
|
+
${paths.enums}
|
|
744
|
+
import { ROLES_KEY } from 'src/common/decorators/role.decorator';
|
|
745
|
+
import { IS_PUBLIC_KEY } from 'src/common/decorators/public.decorator';
|
|
746
|
+
|
|
747
|
+
@Injectable()
|
|
748
|
+
export class RolesGuard implements CanActivate {
|
|
749
|
+
constructor(private reflector: Reflector) {}
|
|
750
|
+
|
|
751
|
+
canActivate(context: ExecutionContext): boolean {
|
|
752
|
+
// Check if the route is public
|
|
753
|
+
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
|
|
754
|
+
context.getHandler(),
|
|
755
|
+
context.getClass(),
|
|
756
|
+
]);
|
|
757
|
+
|
|
758
|
+
if (isPublic) {
|
|
759
|
+
return true; // Allow access without authentication
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// Retrieve required roles for route access
|
|
763
|
+
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
|
|
764
|
+
context.getHandler(),
|
|
765
|
+
context.getClass(),
|
|
766
|
+
]);
|
|
767
|
+
|
|
768
|
+
if (!requiredRoles) {
|
|
769
|
+
return true; // If no roles are required, access is authorized
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// Retrieve user from request.user (added by JwtAuthGuard)
|
|
773
|
+
const request = context.switchToHttp().getRequest();
|
|
774
|
+
const user = request.user;
|
|
775
|
+
|
|
776
|
+
console.log('🔍 Required Roles:', requiredRoles);
|
|
777
|
+
console.log('👤 User Role:', user?.role);
|
|
778
|
+
|
|
779
|
+
// Check if the user has one of the required roles
|
|
780
|
+
return user && user.role && requiredRoles.includes(user.role);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
`,
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
// --- 7. CABLAGE FINAL DU MODULE ---
|
|
787
|
+
let dbImports = "";
|
|
788
|
+
let dbProviders = "";
|
|
789
|
+
if (dbConfig.orm === "typeorm") {
|
|
790
|
+
dbImports =
|
|
791
|
+
"import { TypeOrmModule } from '@nestjs/typeorm';\nimport { Session as SessionEntity } from 'src/entities/Session.entity';";
|
|
792
|
+
dbProviders = "TypeOrmModule.forFeature([SessionEntity]),";
|
|
793
|
+
} else if (dbConfig.orm === "mongoose") {
|
|
794
|
+
const schemaRelativePath = isFull
|
|
795
|
+
? "./infrastructure/persistence/mongoose/session.schema"
|
|
796
|
+
: "./persistence/session.schema";
|
|
797
|
+
|
|
798
|
+
dbImports = `import { MongooseModule } from '@nestjs/mongoose';
|
|
799
|
+
import { Session, SessionSchema } from '${schemaRelativePath}';`;
|
|
800
|
+
|
|
801
|
+
dbProviders = `MongooseModule.forFeature([{ name: Session.name, schema: SessionSchema }]),`;
|
|
802
|
+
} else if (dbConfig.orm === "prisma") {
|
|
803
|
+
dbImports = "import { PrismaModule } from 'src/prisma/prisma.module';";
|
|
804
|
+
dbProviders = "PrismaModule,";
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
if (inputs.mode == "full") {
|
|
808
|
+
dbImports =
|
|
809
|
+
+"import { ISessionRepositoryName } from '${paths.interfaces}/session.repository.interface';";
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
await createFile({
|
|
813
|
+
path: `${paths.root}/auth.module.ts`,
|
|
814
|
+
contente: `
|
|
815
|
+
import { Module, forwardRef } from '@nestjs/common';
|
|
816
|
+
import { JwtModule } from '@nestjs/jwt';
|
|
817
|
+
import { PassportModule } from '@nestjs/passport';
|
|
818
|
+
import { ConfigModule, ConfigService } from '@nestjs/config';
|
|
819
|
+
import { UserModule } from '../user/user.module';
|
|
820
|
+
${dbImports}
|
|
821
|
+
import { AuthService } from '${paths.services}/auth.service';
|
|
822
|
+
import { SessionService } from '${paths.services}/session.service';
|
|
823
|
+
import { AuthController } from '${paths.controllers}/auth.controller';
|
|
824
|
+
import { JwtStrategy } from '${paths.strategies}/jwt.strategy';
|
|
825
|
+
import { SessionRepository } from '${paths.persistence}/session.repository';
|
|
826
|
+
|
|
827
|
+
@Module({
|
|
828
|
+
imports: [
|
|
829
|
+
${dbProviders}
|
|
830
|
+
forwardRef(() => UserModule),
|
|
831
|
+
PassportModule,
|
|
832
|
+
JwtModule.registerAsync({
|
|
833
|
+
imports: [ConfigModule],
|
|
834
|
+
inject: [ConfigService],
|
|
835
|
+
useFactory: (config: ConfigService) => ({
|
|
836
|
+
secret: config.get('JWT_SECRET'),
|
|
837
|
+
signOptions: { expiresIn: '15m' },
|
|
838
|
+
}),
|
|
839
|
+
}),
|
|
840
|
+
],
|
|
841
|
+
controllers: [AuthController],
|
|
842
|
+
providers: [
|
|
843
|
+
AuthService,
|
|
844
|
+
SessionService,
|
|
845
|
+
JwtStrategy,
|
|
846
|
+
${
|
|
847
|
+
isFull
|
|
848
|
+
? "{ provide: ISessionRepositoryName, useClass: SessionRepository }"
|
|
849
|
+
: "SessionRepository"
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
],
|
|
853
|
+
exports: [AuthService, SessionService],
|
|
854
|
+
})
|
|
855
|
+
export class AuthModule {}`.trim(),
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
// auth DTOs in user entity
|
|
859
|
+
const dtos = [
|
|
860
|
+
{
|
|
861
|
+
name: "loginCredential",
|
|
862
|
+
fields: [
|
|
863
|
+
{ name: "email", type: "string", swaggerExample: "user@example.com" },
|
|
864
|
+
{
|
|
865
|
+
name: "password",
|
|
866
|
+
type: "string",
|
|
867
|
+
swaggerExample: "StrongPassword123!",
|
|
868
|
+
},
|
|
869
|
+
],
|
|
870
|
+
},
|
|
871
|
+
{
|
|
872
|
+
name: "refreshToken",
|
|
873
|
+
fields: [
|
|
874
|
+
{
|
|
875
|
+
name: "refreshToken",
|
|
876
|
+
type: "string",
|
|
877
|
+
swaggerExample: "eyJhbGciOiJIUzI1NiIsInR5cCI6...",
|
|
878
|
+
},
|
|
879
|
+
],
|
|
880
|
+
},
|
|
881
|
+
{
|
|
882
|
+
name: "sendOtp",
|
|
883
|
+
fields: [
|
|
884
|
+
{ name: "email", type: "string", swaggerExample: "user@example.com" },
|
|
885
|
+
],
|
|
886
|
+
},
|
|
887
|
+
{
|
|
888
|
+
name: "verifyOtp",
|
|
889
|
+
fields: [
|
|
890
|
+
{ name: "email", type: "string", swaggerExample: "user@example.com" },
|
|
891
|
+
{ name: "otp", type: "string", swaggerExample: "123456" },
|
|
892
|
+
],
|
|
893
|
+
},
|
|
894
|
+
{
|
|
895
|
+
name: "forgotPassword",
|
|
896
|
+
fields: [
|
|
897
|
+
{ name: "email", type: "string", swaggerExample: "user@example.com" },
|
|
898
|
+
],
|
|
899
|
+
},
|
|
900
|
+
{
|
|
901
|
+
name: "resetPassword",
|
|
902
|
+
fields: [
|
|
903
|
+
{ name: "email", type: "string", swaggerExample: "user@example.com" },
|
|
904
|
+
{
|
|
905
|
+
name: "newPassword",
|
|
906
|
+
type: "string",
|
|
907
|
+
swaggerExample: "NewStrongPass123!",
|
|
908
|
+
},
|
|
909
|
+
],
|
|
910
|
+
},
|
|
911
|
+
];
|
|
912
|
+
|
|
913
|
+
// Generation of each DTO
|
|
914
|
+
for (const dto of dtos) {
|
|
915
|
+
const DtoFileContent = await generateDto(dto, useSwagger, true, mode);
|
|
916
|
+
await createFile({
|
|
917
|
+
path: `${paths.appDtos}/${dto.name}.dto.ts`,
|
|
918
|
+
contente: DtoFileContent,
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// Modification of AppModule
|
|
923
|
+
const appModulePath = "src/app.module.ts";
|
|
924
|
+
const addAuthModuleInterface = `UserModule,`;
|
|
925
|
+
const replaceWithAuthModule = `UserModule,
|
|
926
|
+
AuthModule,`;
|
|
927
|
+
await updateFile({
|
|
928
|
+
path: appModulePath,
|
|
929
|
+
pattern: addAuthModuleInterface,
|
|
930
|
+
replacement: replaceWithAuthModule,
|
|
931
|
+
});
|
|
932
|
+
|
|
933
|
+
const guardsImportPattern = `import { Module } from '@nestjs/common';`;
|
|
934
|
+
const guardsImportReplacer = `import { Module } from '@nestjs/common';
|
|
935
|
+
// 🛡️ Uncomment the lines below if you want to enable global guards
|
|
936
|
+
// import { APP_GUARD } from '@nestjs/core';
|
|
937
|
+
// import { JwtAuthGuard } from 'src/auth/guards/jwt-auth.guard';
|
|
938
|
+
// import { RolesGuard } from 'src/auth/guards/role.guard';
|
|
939
|
+
import { AuthModule } from 'src/auth/auth.module';`;
|
|
940
|
+
|
|
941
|
+
const addNestModuleInterface = `providers: [`;
|
|
942
|
+
const replaceWithNestModule = `providers: [
|
|
943
|
+
// 🛡️ Uncomment these lines to apply guards to all routes automatically
|
|
944
|
+
/*
|
|
945
|
+
{
|
|
946
|
+
provide: APP_GUARD,
|
|
947
|
+
useClass: JwtAuthGuard, // 🛡️ Global AuthGuard
|
|
948
|
+
},
|
|
949
|
+
{
|
|
950
|
+
provide: APP_GUARD,
|
|
951
|
+
useClass: RolesGuard, // 🛡️ Global RoleGuard
|
|
952
|
+
},
|
|
953
|
+
*/
|
|
954
|
+
`;
|
|
955
|
+
|
|
956
|
+
await updateFile({
|
|
957
|
+
path: appModulePath,
|
|
958
|
+
pattern: guardsImportPattern,
|
|
959
|
+
replacement: guardsImportReplacer,
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
await updateFile({
|
|
963
|
+
path: appModulePath,
|
|
964
|
+
pattern: addNestModuleInterface,
|
|
965
|
+
replacement: replaceWithNestModule,
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
logSuccess(
|
|
969
|
+
` Authentification Enterprise avec support ${dbConfig.orm.toUpperCase()} et Mappers terminée !`,
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
module.exports = { setupAuth };
|