nest-authme 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +305 -0
  3. package/bin/cli.js +11 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +1619 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/generator/templates/decorators/current-user.decorator.ts.hbs +8 -0
  8. package/dist/generator/templates/decorators/public.decorator.ts.hbs +4 -0
  9. package/dist/generator/templates/decorators/roles.decorator.ts.hbs +4 -0
  10. package/dist/generator/templates/dto/auth-response.dto.ts.hbs +42 -0
  11. package/dist/generator/templates/dto/change-password.dto.ts.hbs +22 -0
  12. package/dist/generator/templates/dto/create-user.dto.ts.hbs +38 -0
  13. package/dist/generator/templates/dto/forgot-password.dto.ts.hbs +13 -0
  14. package/dist/generator/templates/dto/login.dto.ts.hbs +21 -0
  15. package/dist/generator/templates/dto/register.dto.ts.hbs +33 -0
  16. package/dist/generator/templates/dto/reset-password.dto.ts.hbs +22 -0
  17. package/dist/generator/templates/entities/refresh-token.entity.typeorm.hbs +24 -0
  18. package/dist/generator/templates/entities/user.entity.typeorm.hbs +51 -0
  19. package/dist/generator/templates/jwt/auth.controller.ts.hbs +177 -0
  20. package/dist/generator/templates/jwt/auth.module.ts.hbs +81 -0
  21. package/dist/generator/templates/jwt/auth.service.ts.hbs +416 -0
  22. package/dist/generator/templates/jwt/jwt-auth.guard.ts.hbs +24 -0
  23. package/dist/generator/templates/jwt/jwt.strategy.ts.hbs +61 -0
  24. package/dist/generator/templates/jwt/local-auth.guard.ts.hbs +5 -0
  25. package/dist/generator/templates/jwt/local.strategy.ts.hbs +22 -0
  26. package/dist/generator/templates/prisma/prisma.module.ts.hbs +9 -0
  27. package/dist/generator/templates/prisma/prisma.service.ts.hbs +9 -0
  28. package/dist/generator/templates/prisma/schema.prisma.additions.hbs +40 -0
  29. package/dist/generator/templates/rbac/role.enum.ts.hbs +5 -0
  30. package/dist/generator/templates/rbac/roles.guard.ts.hbs +22 -0
  31. package/dist/generator/templates/shared/README.auth.md.hbs +306 -0
  32. package/dist/generator/templates/shared/env.hbs +36 -0
  33. package/dist/generator/templates/shared/env.template.hbs +36 -0
  34. package/dist/generator/templates/shared/main.ts.snippet.hbs +49 -0
  35. package/dist/generator/templates/tests/auth.controller.spec.ts.hbs +189 -0
  36. package/dist/generator/templates/tests/auth.service.spec.ts.hbs +334 -0
  37. package/dist/generator/templates/users/users.controller.ts.hbs +55 -0
  38. package/dist/generator/templates/users/users.module.ts.hbs +31 -0
  39. package/dist/generator/templates/users/users.service.ts.hbs +192 -0
  40. package/dist/index.d.ts +9 -0
  41. package/dist/index.js +1566 -0
  42. package/dist/index.js.map +1 -0
  43. package/package.json +65 -0
@@ -0,0 +1,177 @@
1
+ import { Controller, Post, Body, HttpCode, HttpStatus{{#if features.refreshTokens}}, Req{{/if}}{{#if features.emailVerification}}, Get, Query{{/if}} } from '@nestjs/common';
2
+ {{#if features.rateLimiting}}
3
+ import { Throttle, SkipThrottle } from '@nestjs/throttler';
4
+ {{/if}}
5
+ {{#if features.swagger}}
6
+ import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
7
+ {{/if}}
8
+ import { AuthService } from './auth.service';
9
+ import { LoginDto } from './dto/login.dto';
10
+ import { RegisterDto } from './dto/register.dto';
11
+ import { ChangePasswordDto } from './dto/change-password.dto';
12
+ {{#if features.resetPassword}}
13
+ import { ForgotPasswordDto } from './dto/forgot-password.dto';
14
+ import { ResetPasswordDto } from './dto/reset-password.dto';
15
+ {{/if}}
16
+ import { AuthResponseDto } from './dto/auth-response.dto';
17
+ import { Public } from './decorators/public.decorator';
18
+ import { CurrentUser } from './decorators/current-user.decorator';
19
+
20
+ @Controller('auth')
21
+ {{#if features.swagger}}
22
+ @ApiTags('Authentication')
23
+ {{/if}}
24
+ export class AuthController {
25
+ constructor(private readonly authService: AuthService) {}
26
+
27
+ @Public()
28
+ @Post('login')
29
+ @HttpCode(HttpStatus.OK)
30
+ {{#if features.rateLimiting}}
31
+ @Throttle({ default: { limit: 5, ttl: 60000 } })
32
+ {{/if}}
33
+ {{#if features.swagger}}
34
+ @ApiOperation({ summary: 'User login' })
35
+ @ApiResponse({ status: 200, description: 'Login successful', type: AuthResponseDto })
36
+ @ApiResponse({ status: 401, description: 'Invalid credentials' })
37
+ {{/if}}
38
+ async login(@Body() loginDto: LoginDto): Promise<AuthResponseDto> {
39
+ return this.authService.login(loginDto);
40
+ }
41
+
42
+ @Public()
43
+ @Post('register')
44
+ {{#if features.rateLimiting}}
45
+ @Throttle({ default: { limit: 3, ttl: 60000 } })
46
+ {{/if}}
47
+ {{#if features.swagger}}
48
+ @ApiOperation({ summary: 'Register new user' })
49
+ @ApiResponse({ status: 201, description: 'User registered successfully', type: AuthResponseDto })
50
+ @ApiResponse({ status: 409, description: 'User already exists' })
51
+ {{/if}}
52
+ async register(@Body() registerDto: RegisterDto): Promise<AuthResponseDto> {
53
+ return this.authService.register(registerDto);
54
+ }
55
+
56
+ @Post('change-password')
57
+ @HttpCode(HttpStatus.OK)
58
+ {{#if features.swagger}}
59
+ @ApiBearerAuth()
60
+ @ApiOperation({ summary: 'Change password' })
61
+ @ApiResponse({ status: 200, description: 'Password changed successfully' })
62
+ @ApiResponse({ status: 401, description: 'Invalid current password' })
63
+ {{/if}}
64
+ async changePassword(
65
+ @CurrentUser() user: any,
66
+ @Body() changePasswordDto: ChangePasswordDto,
67
+ ): Promise<{ message: string }> {
68
+ await this.authService.changePassword(user.id, changePasswordDto);
69
+ return { message: 'Password changed successfully' };
70
+ }
71
+
72
+ {{#if features.emailVerification}}
73
+ @Public()
74
+ @Get('verify-email')
75
+ @HttpCode(HttpStatus.OK)
76
+ {{#if features.swagger}}
77
+ @ApiOperation({ summary: 'Verify email address' })
78
+ @ApiResponse({ status: 200, description: 'Email verified successfully' })
79
+ @ApiResponse({ status: 404, description: 'Invalid verification token' })
80
+ {{/if}}
81
+ async verifyEmail(@Query('token') token: string): Promise<{ message: string }> {
82
+ return this.authService.verifyEmail(token);
83
+ }
84
+
85
+ @Public()
86
+ @Post('resend-verification')
87
+ @HttpCode(HttpStatus.OK)
88
+ {{#if features.rateLimiting}}
89
+ @Throttle({ default: { limit: 3, ttl: 60000 } })
90
+ {{/if}}
91
+ {{#if features.swagger}}
92
+ @ApiOperation({ summary: 'Resend verification email' })
93
+ @ApiResponse({ status: 200, description: 'Verification token generated' })
94
+ @ApiResponse({ status: 404, description: 'User not found' })
95
+ {{/if}}
96
+ async resendVerification(@Body('email') email: string): Promise<{ verificationToken: string; message: string }> {
97
+ return this.authService.resendVerification(email);
98
+ }
99
+ {{/if}}
100
+
101
+ {{#if features.resetPassword}}
102
+ @Public()
103
+ @Post('forgot-password')
104
+ @HttpCode(HttpStatus.OK)
105
+ {{#if features.rateLimiting}}
106
+ @Throttle({ default: { limit: 3, ttl: 60000 } })
107
+ {{/if}}
108
+ {{#if features.swagger}}
109
+ @ApiOperation({ summary: 'Request password reset' })
110
+ @ApiResponse({ status: 200, description: 'Reset token generated if email exists' })
111
+ {{/if}}
112
+ async forgotPassword(@Body() forgotPasswordDto: ForgotPasswordDto): Promise<{ resetToken: string; message: string }> {
113
+ return this.authService.forgotPassword(forgotPasswordDto);
114
+ }
115
+
116
+ @Public()
117
+ @Post('reset-password')
118
+ @HttpCode(HttpStatus.OK)
119
+ {{#if features.rateLimiting}}
120
+ @Throttle({ default: { limit: 3, ttl: 60000 } })
121
+ {{/if}}
122
+ {{#if features.swagger}}
123
+ @ApiOperation({ summary: 'Reset password with token' })
124
+ @ApiResponse({ status: 200, description: 'Password reset successfully' })
125
+ @ApiResponse({ status: 400, description: 'Invalid or expired reset token' })
126
+ {{/if}}
127
+ async resetPassword(@Body() resetPasswordDto: ResetPasswordDto): Promise<{ message: string }> {
128
+ return this.authService.resetPassword(resetPasswordDto);
129
+ }
130
+ {{/if}}
131
+
132
+ {{#if features.refreshTokens}}
133
+ @Public()
134
+ @Post('refresh')
135
+ @HttpCode(HttpStatus.OK)
136
+ {{#if features.swagger}}
137
+ @ApiOperation({ summary: 'Refresh access token' })
138
+ @ApiResponse({ status: 200, description: 'Token refreshed successfully', type: AuthResponseDto })
139
+ @ApiResponse({ status: 401, description: 'Invalid refresh token' })
140
+ {{/if}}
141
+ async refresh(@Body('refreshToken') refreshToken: string): Promise<AuthResponseDto> {
142
+ return this.authService.refreshAccessToken(refreshToken);
143
+ }
144
+
145
+ {{#if features.rateLimiting}}
146
+ @SkipThrottle()
147
+ {{/if}}
148
+ @Post('logout')
149
+ @HttpCode(HttpStatus.OK)
150
+ {{#if features.swagger}}
151
+ @ApiBearerAuth()
152
+ @ApiOperation({ summary: 'Logout (invalidate refresh token)' })
153
+ @ApiResponse({ status: 200, description: 'Logged out successfully' })
154
+ @ApiResponse({ status: 401, description: 'Unauthorized' })
155
+ {{/if}}
156
+ async logout(@Body('refreshToken') refreshToken: string): Promise<{ message: string }> {
157
+ await this.authService.logout(refreshToken);
158
+ return { message: 'Logged out successfully' };
159
+ }
160
+
161
+ {{#if features.rateLimiting}}
162
+ @SkipThrottle()
163
+ {{/if}}
164
+ @Post('logout-all')
165
+ @HttpCode(HttpStatus.OK)
166
+ {{#if features.swagger}}
167
+ @ApiBearerAuth()
168
+ @ApiOperation({ summary: 'Logout from all devices' })
169
+ @ApiResponse({ status: 200, description: 'Logged out from all devices' })
170
+ @ApiResponse({ status: 401, description: 'Unauthorized' })
171
+ {{/if}}
172
+ async logoutAll(@Req() req: any): Promise<{ message: string }> {
173
+ await this.authService.logoutAll(req.user.id);
174
+ return { message: 'Logged out from all devices' };
175
+ }
176
+ {{/if}}
177
+ }
@@ -0,0 +1,81 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { JwtModule } from '@nestjs/jwt';
3
+ import { PassportModule } from '@nestjs/passport';
4
+ import { ConfigModule, ConfigService } from '@nestjs/config';
5
+ {{#if features.rateLimiting}}
6
+ import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
7
+ import { APP_GUARD } from '@nestjs/core';
8
+ {{/if}}
9
+ {{#if (eq orm "typeorm")}}
10
+ import { TypeOrmModule } from '@nestjs/typeorm';
11
+ {{/if}}
12
+ {{#if (eq orm "prisma")}}
13
+ import { PrismaService } from '../prisma/prisma.service';
14
+ {{/if}}
15
+ import { AuthService } from './auth.service';
16
+ import { AuthController } from './auth.controller';
17
+ import { JwtStrategy } from './strategies/jwt.strategy';
18
+ import { LocalStrategy } from './strategies/local.strategy';
19
+ import { UsersModule } from '../users/users.module';
20
+ {{#if (eq orm "typeorm")}}
21
+ import { User } from '../users/entities/user.entity';
22
+ {{#if features.refreshTokens}}
23
+ import { RefreshToken } from '../users/entities/refresh-token.entity';
24
+ {{/if}}
25
+ {{/if}}
26
+
27
+ @Module({
28
+ imports: [
29
+ UsersModule,
30
+ PassportModule,
31
+ ConfigModule,
32
+ {{#if features.rateLimiting}}
33
+ ThrottlerModule.forRoot([{
34
+ ttl: 60000,
35
+ limit: 10,
36
+ }]),
37
+ {{/if}}
38
+ JwtModule.registerAsync({
39
+ imports: [ConfigModule],
40
+ inject: [ConfigService],
41
+ useFactory: async (configService: ConfigService) => {
42
+ const secret = configService.get<string>('JWT_SECRET');
43
+ const expiresIn = configService.get<string>('JWT_EXPIRES_IN', '{{jwt.accessExpiration}}');
44
+
45
+ return {
46
+ secret,
47
+ signOptions: {
48
+ expiresIn: expiresIn as any, // NestJS accepts string or number
49
+ },
50
+ };
51
+ },
52
+ }),
53
+ {{#if (eq orm "typeorm")}}
54
+ TypeOrmModule.forFeature([
55
+ User,
56
+ {{#if features.refreshTokens}}
57
+ RefreshToken,
58
+ {{/if}}
59
+ ]),
60
+ {{/if}}
61
+ ],
62
+ controllers: [AuthController],
63
+ providers: [
64
+ AuthService,
65
+ JwtStrategy,
66
+ LocalStrategy,
67
+ {{#if (eq orm "prisma")}}
68
+ {{#if features.refreshTokens}}
69
+ PrismaService,
70
+ {{/if}}
71
+ {{/if}}
72
+ {{#if features.rateLimiting}}
73
+ {
74
+ provide: APP_GUARD,
75
+ useClass: ThrottlerGuard,
76
+ },
77
+ {{/if}}
78
+ ],
79
+ exports: [AuthService],
80
+ })
81
+ export class AuthModule {}
@@ -0,0 +1,416 @@
1
+ import { Injectable, UnauthorizedException{{#if features.resetPassword}}, BadRequestException{{/if}}{{#if features.emailVerification}}, NotFoundException{{/if}} } from '@nestjs/common';
2
+ {{#if (or features.emailVerification features.resetPassword)}}
3
+ import * as crypto from 'crypto';
4
+ {{/if}}
5
+ import { JwtService } from '@nestjs/jwt';
6
+ import { ConfigService } from '@nestjs/config';
7
+ {{#if (eq orm "typeorm")}}
8
+ import { InjectRepository } from '@nestjs/typeorm';
9
+ import { Repository } from 'typeorm';
10
+ {{/if}}
11
+ {{#if (eq orm "prisma")}}
12
+ import { PrismaService } from '../prisma/prisma.service';
13
+ {{/if}}
14
+ import * as bcrypt from 'bcrypt';
15
+ import { UsersService } from '../users/users.service';
16
+ {{#if (eq orm "typeorm")}}
17
+ import { User } from '../users/entities/user.entity';
18
+ {{#if features.refreshTokens}}
19
+ import { RefreshToken } from '../users/entities/refresh-token.entity';
20
+ {{/if}}
21
+ {{/if}}
22
+ import { LoginDto } from './dto/login.dto';
23
+ import { RegisterDto } from './dto/register.dto';
24
+ import { ChangePasswordDto } from './dto/change-password.dto';
25
+ {{#if features.resetPassword}}
26
+ import { ForgotPasswordDto } from './dto/forgot-password.dto';
27
+ import { ResetPasswordDto } from './dto/reset-password.dto';
28
+ {{/if}}
29
+ import { AuthResponseDto } from './dto/auth-response.dto';
30
+
31
+ @Injectable()
32
+ export class AuthService {
33
+ constructor(
34
+ private usersService: UsersService,
35
+ private jwtService: JwtService,
36
+ private configService: ConfigService,
37
+ {{#if (eq orm "typeorm")}}
38
+ {{#if features.refreshTokens}}
39
+ @InjectRepository(RefreshToken)
40
+ private refreshTokenRepository: Repository<RefreshToken>,
41
+ {{/if}}
42
+ {{/if}}
43
+ {{#if (eq orm "prisma")}}
44
+ {{#if features.refreshTokens}}
45
+ private prisma: PrismaService,
46
+ {{/if}}
47
+ {{/if}}
48
+ ) {}
49
+
50
+ /**
51
+ * Validate user credentials
52
+ */
53
+ async validateUser(email: string, password: string): Promise<any> {
54
+ const user = await this.usersService.findByEmail(email);
55
+ if (!user) {
56
+ return null;
57
+ }
58
+
59
+ const isPasswordValid = await bcrypt.compare(password, user.password);
60
+ if (!isPasswordValid) {
61
+ return null;
62
+ }
63
+
64
+ const { password: _, ...result } = user;
65
+ return result;
66
+ }
67
+
68
+ /**
69
+ * User login
70
+ */
71
+ async login(loginDto: LoginDto): Promise<AuthResponseDto> {
72
+ const user = await this.validateUser(loginDto.email, loginDto.password);
73
+ if (!user) {
74
+ throw new UnauthorizedException('Invalid credentials');
75
+ }
76
+
77
+ const payload = { email: user.email, sub: user.id{{#if rbac.enabled}}, roles: user.roles{{/if}} };
78
+ const accessToken = this.jwtService.sign(payload);
79
+
80
+ {{#if features.refreshTokens}}
81
+ const refreshToken = await this.createRefreshToken(user.id);
82
+
83
+ return {
84
+ accessToken,
85
+ refreshToken: refreshToken.token,
86
+ user: {
87
+ id: user.id,
88
+ email: user.email,
89
+ {{#if features.useUsername}}
90
+ username: user.username,
91
+ {{/if}}
92
+ {{#if rbac.enabled}}
93
+ roles: user.roles,
94
+ {{/if}}
95
+ },
96
+ };
97
+ {{else}}
98
+ return {
99
+ accessToken,
100
+ user: {
101
+ id: user.id,
102
+ email: user.email,
103
+ {{#if features.useUsername}}
104
+ username: user.username,
105
+ {{/if}}
106
+ {{#if rbac.enabled}}
107
+ roles: user.roles,
108
+ {{/if}}
109
+ },
110
+ };
111
+ {{/if}}
112
+ }
113
+
114
+ /**
115
+ * User registration
116
+ */
117
+ async register(registerDto: RegisterDto): Promise<AuthResponseDto> {
118
+ const bcryptRounds = parseInt(this.configService.get('BCRYPT_ROUNDS', '10'), 10);
119
+ const hashedPassword = await bcrypt.hash(registerDto.password, bcryptRounds);
120
+
121
+ const user = await this.usersService.create({
122
+ email: registerDto.email,
123
+ {{#if features.useUsername}}
124
+ username: registerDto.username,
125
+ {{/if}}
126
+ password: hashedPassword,
127
+ {{#if rbac.enabled}}
128
+ roles: ['User'], // Default role for new users. Admin roles should be assigned by admins only.
129
+ {{/if}}
130
+ });
131
+
132
+ {{#if features.emailVerification}}
133
+ // Generate email verification token
134
+ const verificationToken = crypto.randomBytes(32).toString('hex');
135
+ await this.usersService.setVerificationToken(user.id, verificationToken);
136
+ // TODO: Send verification email with token to user.email
137
+ {{/if}}
138
+
139
+ const payload = { email: user.email, sub: user.id{{#if rbac.enabled}}, roles: user.roles{{/if}} };
140
+ const accessToken = this.jwtService.sign(payload);
141
+
142
+ {{#if features.refreshTokens}}
143
+ const refreshToken = await this.createRefreshToken(user.id);
144
+
145
+ return {
146
+ accessToken,
147
+ refreshToken: refreshToken.token,
148
+ {{#if features.emailVerification}}
149
+ verificationToken,
150
+ {{/if}}
151
+ user: {
152
+ id: user.id,
153
+ email: user.email,
154
+ {{#if features.useUsername}}
155
+ username: user.username,
156
+ {{/if}}
157
+ {{#if rbac.enabled}}
158
+ roles: user.roles,
159
+ {{/if}}
160
+ },
161
+ };
162
+ {{else}}
163
+ return {
164
+ accessToken,
165
+ {{#if features.emailVerification}}
166
+ verificationToken,
167
+ {{/if}}
168
+ user: {
169
+ id: user.id,
170
+ email: user.email,
171
+ {{#if features.useUsername}}
172
+ username: user.username,
173
+ {{/if}}
174
+ {{#if rbac.enabled}}
175
+ roles: user.roles,
176
+ {{/if}}
177
+ },
178
+ };
179
+ {{/if}}
180
+ }
181
+
182
+ /**
183
+ * Change password
184
+ */
185
+ async changePassword(userId: string, changePasswordDto: ChangePasswordDto): Promise<void> {
186
+ const user = await this.usersService.findById(userId);
187
+ if (!user) {
188
+ throw new UnauthorizedException('User not found');
189
+ }
190
+
191
+ const isCurrentValid = await bcrypt.compare(changePasswordDto.currentPassword, user.password);
192
+ if (!isCurrentValid) {
193
+ throw new UnauthorizedException('Current password is incorrect');
194
+ }
195
+
196
+ const bcryptRounds = parseInt(this.configService.get('BCRYPT_ROUNDS', '10'), 10);
197
+ const hashedPassword = await bcrypt.hash(changePasswordDto.newPassword, bcryptRounds);
198
+ await this.usersService.updatePassword(userId, hashedPassword);
199
+ }
200
+
201
+ {{#if features.emailVerification}}
202
+ /**
203
+ * Verify email with token
204
+ */
205
+ async verifyEmail(token: string): Promise<{ message: string }> {
206
+ const user = await this.usersService.findByVerificationToken(token);
207
+ if (!user) {
208
+ throw new NotFoundException('Invalid verification token');
209
+ }
210
+
211
+ await this.usersService.markEmailVerified(user.id);
212
+ return { message: 'Email verified successfully' };
213
+ }
214
+
215
+ /**
216
+ * Resend verification email
217
+ */
218
+ async resendVerification(email: string): Promise<{ verificationToken: string; message: string }> {
219
+ const user = await this.usersService.findByEmail(email);
220
+ if (!user) {
221
+ throw new NotFoundException('User not found');
222
+ }
223
+
224
+ if (user.isEmailVerified) {
225
+ return { verificationToken: '', message: 'Email is already verified' };
226
+ }
227
+
228
+ const verificationToken = crypto.randomBytes(32).toString('hex');
229
+ await this.usersService.setVerificationToken(user.id, verificationToken);
230
+ // TODO: Send verification email with token to user.email
231
+
232
+ return { verificationToken, message: 'Verification token generated' };
233
+ }
234
+ {{/if}}
235
+
236
+ {{#if features.resetPassword}}
237
+ /**
238
+ * Forgot password - generate reset token
239
+ */
240
+ async forgotPassword(forgotPasswordDto: ForgotPasswordDto): Promise<{ resetToken: string; message: string }> {
241
+ const user = await this.usersService.findByEmail(forgotPasswordDto.email);
242
+ if (!user) {
243
+ // Return success even if user not found to prevent email enumeration
244
+ return { resetToken: '', message: 'If the email exists, a reset token has been generated' };
245
+ }
246
+
247
+ const resetToken = crypto.randomBytes(32).toString('hex');
248
+ const expires = new Date();
249
+ expires.setHours(expires.getHours() + 1); // Token expires in 1 hour
250
+
251
+ await this.usersService.setResetToken(user.id, resetToken, expires);
252
+ // TODO: Send password reset email with token to user.email
253
+
254
+ return { resetToken, message: 'Password reset token generated' };
255
+ }
256
+
257
+ /**
258
+ * Reset password with token
259
+ */
260
+ async resetPassword(resetPasswordDto: ResetPasswordDto): Promise<{ message: string }> {
261
+ const user = await this.usersService.findByResetToken(resetPasswordDto.token);
262
+ if (!user) {
263
+ throw new BadRequestException('Invalid or expired reset token');
264
+ }
265
+
266
+ if (user.passwordResetExpires && user.passwordResetExpires < new Date()) {
267
+ throw new BadRequestException('Reset token has expired');
268
+ }
269
+
270
+ const bcryptRounds = parseInt(this.configService.get('BCRYPT_ROUNDS', '10'), 10);
271
+ const hashedPassword = await bcrypt.hash(resetPasswordDto.newPassword, bcryptRounds);
272
+ await this.usersService.updatePassword(user.id, hashedPassword);
273
+ await this.usersService.clearResetToken(user.id);
274
+
275
+ return { message: 'Password reset successfully' };
276
+ }
277
+ {{/if}}
278
+
279
+ {{#if features.refreshTokens}}
280
+ /**
281
+ * Create a refresh token
282
+ */
283
+ private async createRefreshToken(userId: string): Promise<any> {
284
+ const token = this.jwtService.sign(
285
+ { sub: userId },
286
+ { expiresIn: '{{jwt.refreshExpiration}}' }
287
+ );
288
+
289
+ const expiresAt = new Date();
290
+ expiresAt.setDate(expiresAt.getDate() + 7); // 7 days
291
+
292
+ {{#if (eq orm "typeorm")}}
293
+ const refreshToken = this.refreshTokenRepository.create({
294
+ token,
295
+ userId,
296
+ expiresAt,
297
+ });
298
+
299
+ return await this.refreshTokenRepository.save(refreshToken);
300
+ {{else if (eq orm "prisma")}}
301
+ return await this.prisma.refreshToken.create({
302
+ data: {
303
+ token,
304
+ userId,
305
+ expiresAt,
306
+ },
307
+ });
308
+ {{else}}
309
+ // TODO: Implement refresh token storage for your ORM
310
+ return { token, userId, expiresAt } as any;
311
+ {{/if}}
312
+ }
313
+
314
+ /**
315
+ * Refresh access token (with token rotation)
316
+ */
317
+ async refreshAccessToken(refreshToken: string): Promise<AuthResponseDto> {
318
+ try {
319
+ const payload = this.jwtService.verify(refreshToken);
320
+
321
+ {{#if (eq orm "typeorm")}}
322
+ const storedToken = await this.refreshTokenRepository.findOne({
323
+ where: { token: refreshToken },
324
+ });
325
+
326
+ if (!storedToken || storedToken.expiresAt < new Date()) {
327
+ throw new UnauthorizedException('Invalid refresh token');
328
+ }
329
+
330
+ // Delete the old refresh token (one-time use)
331
+ await this.refreshTokenRepository.remove(storedToken);
332
+ {{else if (eq orm "prisma")}}
333
+ const storedToken = await this.prisma.refreshToken.findUnique({
334
+ where: { token: refreshToken },
335
+ });
336
+
337
+ if (!storedToken || storedToken.expiresAt < new Date()) {
338
+ throw new UnauthorizedException('Invalid refresh token');
339
+ }
340
+
341
+ // Delete the old refresh token (one-time use)
342
+ await this.prisma.refreshToken.delete({
343
+ where: { id: storedToken.id },
344
+ });
345
+ {{/if}}
346
+
347
+ const user = await this.usersService.findById(payload.sub);
348
+ if (!user) {
349
+ throw new UnauthorizedException('User not found');
350
+ }
351
+
352
+ const newPayload = { email: user.email, sub: user.id{{#if rbac.enabled}}, roles: user.roles{{/if}} };
353
+ const accessToken = this.jwtService.sign(newPayload);
354
+
355
+ // Issue a new refresh token (rotation)
356
+ const newRefreshToken = await this.createRefreshToken(user.id);
357
+
358
+ return {
359
+ accessToken,
360
+ refreshToken: newRefreshToken.token,
361
+ user: {
362
+ id: user.id,
363
+ email: user.email,
364
+ {{#if features.useUsername}}
365
+ username: user.username,
366
+ {{/if}}
367
+ {{#if rbac.enabled}}
368
+ roles: user.roles,
369
+ {{/if}}
370
+ },
371
+ };
372
+ } catch (error) {
373
+ if (error instanceof UnauthorizedException) {
374
+ throw error;
375
+ }
376
+ throw new UnauthorizedException('Invalid refresh token');
377
+ }
378
+ }
379
+
380
+ /**
381
+ * Logout - delete the specific refresh token
382
+ */
383
+ async logout(refreshToken: string): Promise<void> {
384
+ {{#if (eq orm "typeorm")}}
385
+ const storedToken = await this.refreshTokenRepository.findOne({
386
+ where: { token: refreshToken },
387
+ });
388
+
389
+ if (storedToken) {
390
+ await this.refreshTokenRepository.remove(storedToken);
391
+ }
392
+ {{else if (eq orm "prisma")}}
393
+ await this.prisma.refreshToken.deleteMany({
394
+ where: { token: refreshToken },
395
+ });
396
+ {{else}}
397
+ // TODO: Implement refresh token deletion for your ORM
398
+ {{/if}}
399
+ }
400
+
401
+ /**
402
+ * Logout from all devices - delete all refresh tokens for user
403
+ */
404
+ async logoutAll(userId: string): Promise<void> {
405
+ {{#if (eq orm "typeorm")}}
406
+ await this.refreshTokenRepository.delete({ userId });
407
+ {{else if (eq orm "prisma")}}
408
+ await this.prisma.refreshToken.deleteMany({
409
+ where: { userId },
410
+ });
411
+ {{else}}
412
+ // TODO: Implement all refresh token deletion for your ORM
413
+ {{/if}}
414
+ }
415
+ {{/if}}
416
+ }
@@ -0,0 +1,24 @@
1
+ import { Injectable, ExecutionContext } from '@nestjs/common';
2
+ import { Reflector } from '@nestjs/core';
3
+ import { AuthGuard } from '@nestjs/passport';
4
+ import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
5
+
6
+ @Injectable()
7
+ export class JwtAuthGuard extends AuthGuard('jwt') {
8
+ constructor(private reflector: Reflector) {
9
+ super();
10
+ }
11
+
12
+ canActivate(context: ExecutionContext) {
13
+ const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
14
+ context.getHandler(),
15
+ context.getClass(),
16
+ ]);
17
+
18
+ if (isPublic) {
19
+ return true;
20
+ }
21
+
22
+ return super.canActivate(context);
23
+ }
24
+ }