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