@ciscode/authentication-kit 1.2.0 → 1.4.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.
@@ -52,11 +52,14 @@ const jwt = __importStar(require("jsonwebtoken"));
52
52
  const user_repository_1 = require("../repositories/user.repository");
53
53
  const mail_service_1 = require("./mail.service");
54
54
  const role_repository_1 = require("../repositories/role.repository");
55
+ const helper_1 = require("../utils/helper");
56
+ const logger_service_1 = require("./logger.service");
55
57
  let AuthService = class AuthService {
56
- constructor(users, mail, roles) {
58
+ constructor(users, mail, roles, logger) {
57
59
  this.users = users;
58
60
  this.mail = mail;
59
61
  this.roles = roles;
62
+ this.logger = logger;
60
63
  }
61
64
  resolveExpiry(value, fallback) {
62
65
  return (value || fallback);
@@ -78,19 +81,30 @@ let AuthService = class AuthService {
78
81
  return jwt.sign(payload, this.getEnv('JWT_RESET_SECRET'), { expiresIn });
79
82
  }
80
83
  async buildTokenPayload(userId) {
81
- const user = await this.users.findByIdWithRolesAndPermissions(userId);
82
- if (!user)
83
- throw new Error('User not found.');
84
- const roles = (user.roles || []).map((r) => r._id.toString());
85
- const permissions = (user.roles || [])
86
- .flatMap((r) => (r.permissions || []).map((p) => p.name))
87
- .filter(Boolean);
88
- return { sub: user._id.toString(), roles, permissions };
84
+ try {
85
+ const user = await this.users.findByIdWithRolesAndPermissions(userId);
86
+ if (!user) {
87
+ throw new common_1.NotFoundException('User not found');
88
+ }
89
+ const roles = (user.roles || []).map((r) => r._id.toString());
90
+ const permissions = (user.roles || [])
91
+ .flatMap((r) => (r.permissions || []).map((p) => p.name))
92
+ .filter(Boolean);
93
+ return { sub: user._id.toString(), roles, permissions };
94
+ }
95
+ catch (error) {
96
+ if (error instanceof common_1.NotFoundException)
97
+ throw error;
98
+ this.logger.error(`Failed to build token payload: ${error.message}`, error.stack, 'AuthService');
99
+ throw new common_1.InternalServerErrorException('Failed to generate authentication token');
100
+ }
89
101
  }
90
102
  getEnv(name) {
91
103
  const v = process.env[name];
92
- if (!v)
93
- throw new Error(`${name} is not set`);
104
+ if (!v) {
105
+ this.logger.error(`Environment variable ${name} is not set`, 'AuthService');
106
+ throw new common_1.InternalServerErrorException('Server configuration error');
107
+ }
94
108
  return v;
95
109
  }
96
110
  async issueTokensForUser(userId) {
@@ -100,114 +114,263 @@ let AuthService = class AuthService {
100
114
  return { accessToken, refreshToken };
101
115
  }
102
116
  async register(dto) {
103
- if (await this.users.findByEmail(dto.email))
104
- throw new Error('Email already in use.');
105
- if (await this.users.findByUsername(dto.username))
106
- throw new Error('Username already in use.');
107
- if (dto.phoneNumber && (await this.users.findByPhone(dto.phoneNumber))) {
108
- throw new Error('Phone already in use.');
109
- }
110
- const salt = await bcryptjs_1.default.genSalt(10);
111
- const hashed = await bcryptjs_1.default.hash(dto.password, salt);
112
- const userRole = await this.roles.findByName('user');
113
- if (!userRole)
114
- throw new Error('Default role not seeded.');
115
- const user = await this.users.create({
116
- fullname: dto.fullname,
117
- username: dto.username,
118
- email: dto.email,
119
- phoneNumber: dto.phoneNumber,
120
- avatar: dto.avatar,
121
- password: hashed,
122
- roles: [userRole._id],
123
- isVerified: false,
124
- isBanned: false,
125
- passwordChangedAt: new Date()
126
- });
127
- const emailToken = this.signEmailToken({ sub: user._id.toString(), purpose: 'verify' });
128
- await this.mail.sendVerificationEmail(user.email, emailToken);
129
- return { id: user._id, email: user.email };
117
+ try {
118
+ // Generate username from fname-lname if not provided
119
+ if (!dto.username || dto.username.trim() === '') {
120
+ dto.username = (0, helper_1.generateUsernameFromName)(dto.fullname.fname, dto.fullname.lname);
121
+ }
122
+ // Check for existing user (use generic message to prevent enumeration)
123
+ const [existingEmail, existingUsername, existingPhone] = await Promise.all([
124
+ this.users.findByEmail(dto.email),
125
+ this.users.findByUsername(dto.username),
126
+ dto.phoneNumber ? this.users.findByPhone(dto.phoneNumber) : null,
127
+ ]);
128
+ if (existingEmail || existingUsername || existingPhone) {
129
+ throw new common_1.ConflictException('An account with these credentials already exists');
130
+ }
131
+ // Hash password
132
+ let hashed;
133
+ try {
134
+ const salt = await bcryptjs_1.default.genSalt(10);
135
+ hashed = await bcryptjs_1.default.hash(dto.password, salt);
136
+ }
137
+ catch (error) {
138
+ this.logger.error(`Password hashing failed: ${error.message}`, error.stack, 'AuthService');
139
+ throw new common_1.InternalServerErrorException('Registration failed');
140
+ }
141
+ // Get default role
142
+ const userRole = await this.roles.findByName('user');
143
+ if (!userRole) {
144
+ this.logger.error('Default user role not found - seed data may be missing', 'AuthService');
145
+ throw new common_1.InternalServerErrorException('System configuration error');
146
+ }
147
+ // Create user
148
+ const user = await this.users.create({
149
+ fullname: dto.fullname,
150
+ username: dto.username,
151
+ email: dto.email,
152
+ phoneNumber: dto.phoneNumber,
153
+ avatar: dto.avatar,
154
+ jobTitle: dto.jobTitle,
155
+ company: dto.company,
156
+ password: hashed,
157
+ roles: [userRole._id],
158
+ isVerified: false,
159
+ isBanned: false,
160
+ passwordChangedAt: new Date()
161
+ });
162
+ // Send verification email (don't let email failures crash registration)
163
+ try {
164
+ const emailToken = this.signEmailToken({ sub: user._id.toString(), purpose: 'verify' });
165
+ await this.mail.sendVerificationEmail(user.email, emailToken);
166
+ }
167
+ catch (error) {
168
+ this.logger.error(`Failed to send verification email: ${error.message}`, error.stack, 'AuthService');
169
+ // Continue - user is created, they can resend verification
170
+ }
171
+ return { id: user._id, email: user.email };
172
+ }
173
+ catch (error) {
174
+ // Re-throw HTTP exceptions
175
+ if (error instanceof common_1.ConflictException || error instanceof common_1.InternalServerErrorException) {
176
+ throw error;
177
+ }
178
+ // Handle MongoDB duplicate key error (race condition)
179
+ if ((error === null || error === void 0 ? void 0 : error.code) === 11000) {
180
+ throw new common_1.ConflictException('An account with these credentials already exists');
181
+ }
182
+ this.logger.error(`Registration failed: ${error.message}`, error.stack, 'AuthService');
183
+ throw new common_1.InternalServerErrorException('Registration failed. Please try again');
184
+ }
130
185
  }
131
186
  async verifyEmail(token) {
132
- const decoded = jwt.verify(token, this.getEnv('JWT_EMAIL_SECRET'));
133
- if (decoded.purpose !== 'verify')
134
- throw new Error('Invalid token purpose.');
135
- const user = await this.users.findById(decoded.sub);
136
- if (!user)
137
- throw new Error('User not found.');
138
- if (user.isVerified)
139
- return { ok: true };
140
- user.isVerified = true;
141
- await user.save();
142
- return { ok: true };
187
+ try {
188
+ const decoded = jwt.verify(token, this.getEnv('JWT_EMAIL_SECRET'));
189
+ if (decoded.purpose !== 'verify') {
190
+ throw new common_1.BadRequestException('Invalid verification token');
191
+ }
192
+ const user = await this.users.findById(decoded.sub);
193
+ if (!user) {
194
+ throw new common_1.NotFoundException('User not found');
195
+ }
196
+ if (user.isVerified) {
197
+ return { ok: true, message: 'Email already verified' };
198
+ }
199
+ user.isVerified = true;
200
+ await user.save();
201
+ return { ok: true, message: 'Email verified successfully' };
202
+ }
203
+ catch (error) {
204
+ if (error instanceof common_1.BadRequestException || error instanceof common_1.NotFoundException) {
205
+ throw error;
206
+ }
207
+ if (error.name === 'TokenExpiredError') {
208
+ throw new common_1.UnauthorizedException('Verification token has expired');
209
+ }
210
+ if (error.name === 'JsonWebTokenError') {
211
+ throw new common_1.UnauthorizedException('Invalid verification token');
212
+ }
213
+ this.logger.error(`Email verification failed: ${error.message}`, error.stack, 'AuthService');
214
+ throw new common_1.InternalServerErrorException('Email verification failed');
215
+ }
143
216
  }
144
217
  async resendVerification(email) {
145
- const user = await this.users.findByEmail(email);
146
- if (!user || user.isVerified)
147
- return { ok: true };
148
- const emailToken = this.signEmailToken({ sub: user._id.toString(), purpose: 'verify' });
149
- await this.mail.sendVerificationEmail(user.email, emailToken);
150
- return { ok: true };
218
+ try {
219
+ const user = await this.users.findByEmail(email);
220
+ // Return success even if user not found (prevent email enumeration)
221
+ if (!user || user.isVerified) {
222
+ return { ok: true, message: 'If the email exists and is unverified, a verification email has been sent' };
223
+ }
224
+ const emailToken = this.signEmailToken({ sub: user._id.toString(), purpose: 'verify' });
225
+ await this.mail.sendVerificationEmail(user.email, emailToken);
226
+ return { ok: true, message: 'Verification email sent successfully' };
227
+ }
228
+ catch (error) {
229
+ this.logger.error(`Resend verification failed: ${error.message}`, error.stack, 'AuthService');
230
+ // Return success to prevent email enumeration
231
+ return { ok: true, message: 'If the email exists and is unverified, a verification email has been sent' };
232
+ }
151
233
  }
152
234
  async login(dto) {
153
- const user = await this.users.findByEmailWithPassword(dto.email);
154
- if (!user)
155
- throw new Error('Invalid credentials.');
156
- if (user.isBanned)
157
- throw new Error('Account banned.');
158
- if (!user.isVerified)
159
- throw new Error('Email not verified.');
160
- const ok = await bcryptjs_1.default.compare(dto.password, user.password);
161
- if (!ok)
162
- throw new Error('Invalid credentials.');
163
- const payload = await this.buildTokenPayload(user._id.toString());
164
- const accessToken = this.signAccessToken(payload);
165
- const refreshToken = this.signRefreshToken({ sub: user._id.toString(), purpose: 'refresh' });
166
- return { accessToken, refreshToken };
235
+ try {
236
+ const user = await this.users.findByEmailWithPassword(dto.email);
237
+ // Use generic message to prevent user enumeration
238
+ if (!user) {
239
+ throw new common_1.UnauthorizedException('Invalid email or password');
240
+ }
241
+ if (user.isBanned) {
242
+ throw new common_1.ForbiddenException('Account has been banned. Please contact support');
243
+ }
244
+ if (!user.isVerified) {
245
+ throw new common_1.ForbiddenException('Email not verified. Please check your inbox');
246
+ }
247
+ const passwordMatch = await bcryptjs_1.default.compare(dto.password, user.password);
248
+ if (!passwordMatch) {
249
+ throw new common_1.UnauthorizedException('Invalid email or password');
250
+ }
251
+ const payload = await this.buildTokenPayload(user._id.toString());
252
+ const accessToken = this.signAccessToken(payload);
253
+ const refreshToken = this.signRefreshToken({ sub: user._id.toString(), purpose: 'refresh' });
254
+ return { accessToken, refreshToken };
255
+ }
256
+ catch (error) {
257
+ if (error instanceof common_1.UnauthorizedException || error instanceof common_1.ForbiddenException) {
258
+ throw error;
259
+ }
260
+ this.logger.error(`Login failed: ${error.message}`, error.stack, 'AuthService');
261
+ throw new common_1.InternalServerErrorException('Login failed. Please try again');
262
+ }
167
263
  }
168
264
  async refresh(refreshToken) {
169
- const decoded = jwt.verify(refreshToken, this.getEnv('JWT_REFRESH_SECRET'));
170
- if (decoded.purpose !== 'refresh')
171
- throw new Error('Invalid token purpose.');
172
- const user = await this.users.findById(decoded.sub);
173
- if (!user)
174
- throw new Error('User not found.');
175
- if (user.isBanned)
176
- throw new Error('Account banned.');
177
- if (!user.isVerified)
178
- throw new Error('Email not verified.');
179
- if (user.passwordChangedAt && decoded.iat * 1000 < user.passwordChangedAt.getTime()) {
180
- throw new Error('Token expired.');
181
- }
182
- const payload = await this.buildTokenPayload(user._id.toString());
183
- const accessToken = this.signAccessToken(payload);
184
- const newRefreshToken = this.signRefreshToken({ sub: user._id.toString(), purpose: 'refresh' });
185
- return { accessToken, refreshToken: newRefreshToken };
265
+ try {
266
+ const decoded = jwt.verify(refreshToken, this.getEnv('JWT_REFRESH_SECRET'));
267
+ if (decoded.purpose !== 'refresh') {
268
+ throw new common_1.UnauthorizedException('Invalid token type');
269
+ }
270
+ const user = await this.users.findById(decoded.sub);
271
+ if (!user) {
272
+ throw new common_1.UnauthorizedException('Invalid refresh token');
273
+ }
274
+ if (user.isBanned) {
275
+ throw new common_1.ForbiddenException('Account has been banned');
276
+ }
277
+ if (!user.isVerified) {
278
+ throw new common_1.ForbiddenException('Email not verified');
279
+ }
280
+ // Check if token was issued before password change
281
+ if (user.passwordChangedAt && decoded.iat * 1000 < user.passwordChangedAt.getTime()) {
282
+ throw new common_1.UnauthorizedException('Token expired due to password change');
283
+ }
284
+ const payload = await this.buildTokenPayload(user._id.toString());
285
+ const accessToken = this.signAccessToken(payload);
286
+ const newRefreshToken = this.signRefreshToken({ sub: user._id.toString(), purpose: 'refresh' });
287
+ return { accessToken, refreshToken: newRefreshToken };
288
+ }
289
+ catch (error) {
290
+ if (error instanceof common_1.UnauthorizedException || error instanceof common_1.ForbiddenException) {
291
+ throw error;
292
+ }
293
+ if (error.name === 'TokenExpiredError') {
294
+ throw new common_1.UnauthorizedException('Refresh token has expired');
295
+ }
296
+ if (error.name === 'JsonWebTokenError') {
297
+ throw new common_1.UnauthorizedException('Invalid refresh token');
298
+ }
299
+ this.logger.error(`Token refresh failed: ${error.message}`, error.stack, 'AuthService');
300
+ throw new common_1.InternalServerErrorException('Token refresh failed');
301
+ }
186
302
  }
187
303
  async forgotPassword(email) {
188
- const user = await this.users.findByEmail(email);
189
- if (!user)
190
- return { ok: true };
191
- const resetToken = this.signResetToken({ sub: user._id.toString(), purpose: 'reset' });
192
- await this.mail.sendPasswordResetEmail(user.email, resetToken);
193
- return { ok: true };
304
+ try {
305
+ const user = await this.users.findByEmail(email);
306
+ // Always return success to prevent email enumeration
307
+ if (!user) {
308
+ return { ok: true, message: 'If the email exists, a password reset link has been sent' };
309
+ }
310
+ const resetToken = this.signResetToken({ sub: user._id.toString(), purpose: 'reset' });
311
+ await this.mail.sendPasswordResetEmail(user.email, resetToken);
312
+ return { ok: true, message: 'Password reset link sent successfully' };
313
+ }
314
+ catch (error) {
315
+ this.logger.error(`Forgot password failed: ${error.message}`, error.stack, 'AuthService');
316
+ // Return success to prevent email enumeration
317
+ return { ok: true, message: 'If the email exists, a password reset link has been sent' };
318
+ }
194
319
  }
195
320
  async resetPassword(token, newPassword) {
196
- const decoded = jwt.verify(token, this.getEnv('JWT_RESET_SECRET'));
197
- if (decoded.purpose !== 'reset')
198
- throw new Error('Invalid token purpose.');
199
- const user = await this.users.findById(decoded.sub);
200
- if (!user)
201
- throw new Error('User not found.');
202
- const salt = await bcryptjs_1.default.genSalt(10);
203
- user.password = await bcryptjs_1.default.hash(newPassword, salt);
204
- user.passwordChangedAt = new Date();
205
- await user.save();
206
- return { ok: true };
321
+ try {
322
+ const decoded = jwt.verify(token, this.getEnv('JWT_RESET_SECRET'));
323
+ if (decoded.purpose !== 'reset') {
324
+ throw new common_1.BadRequestException('Invalid reset token');
325
+ }
326
+ const user = await this.users.findById(decoded.sub);
327
+ if (!user) {
328
+ throw new common_1.NotFoundException('User not found');
329
+ }
330
+ // Hash new password
331
+ let hashedPassword;
332
+ try {
333
+ const salt = await bcryptjs_1.default.genSalt(10);
334
+ hashedPassword = await bcryptjs_1.default.hash(newPassword, salt);
335
+ }
336
+ catch (error) {
337
+ this.logger.error(`Password hashing failed: ${error.message}`, error.stack, 'AuthService');
338
+ throw new common_1.InternalServerErrorException('Password reset failed');
339
+ }
340
+ user.password = hashedPassword;
341
+ user.passwordChangedAt = new Date();
342
+ await user.save();
343
+ return { ok: true, message: 'Password reset successfully' };
344
+ }
345
+ catch (error) {
346
+ if (error instanceof common_1.BadRequestException || error instanceof common_1.NotFoundException || error instanceof common_1.InternalServerErrorException) {
347
+ throw error;
348
+ }
349
+ if (error.name === 'TokenExpiredError') {
350
+ throw new common_1.UnauthorizedException('Reset token has expired');
351
+ }
352
+ if (error.name === 'JsonWebTokenError') {
353
+ throw new common_1.UnauthorizedException('Invalid reset token');
354
+ }
355
+ this.logger.error(`Password reset failed: ${error.message}`, error.stack, 'AuthService');
356
+ throw new common_1.InternalServerErrorException('Password reset failed');
357
+ }
207
358
  }
208
359
  async deleteAccount(userId) {
209
- await this.users.deleteById(userId);
210
- return { ok: true };
360
+ try {
361
+ const user = await this.users.deleteById(userId);
362
+ if (!user) {
363
+ throw new common_1.NotFoundException('User not found');
364
+ }
365
+ return { ok: true, message: 'Account deleted successfully' };
366
+ }
367
+ catch (error) {
368
+ if (error instanceof common_1.NotFoundException) {
369
+ throw error;
370
+ }
371
+ this.logger.error(`Account deletion failed: ${error.message}`, error.stack, 'AuthService');
372
+ throw new common_1.InternalServerErrorException('Account deletion failed');
373
+ }
211
374
  }
212
375
  };
213
376
  exports.AuthService = AuthService;
@@ -215,5 +378,6 @@ exports.AuthService = AuthService = __decorate([
215
378
  (0, common_1.Injectable)(),
216
379
  __metadata("design:paramtypes", [user_repository_1.UserRepository,
217
380
  mail_service_1.MailService,
218
- role_repository_1.RoleRepository])
381
+ role_repository_1.RoleRepository,
382
+ logger_service_1.LoggerService])
219
383
  ], AuthService);
@@ -0,0 +1,8 @@
1
+ export declare class LoggerService {
2
+ private logger;
3
+ log(message: string, context?: string): void;
4
+ error(message: string, trace?: string, context?: string): void;
5
+ warn(message: string, context?: string): void;
6
+ debug(message: string, context?: string): void;
7
+ verbose(message: string, context?: string): void;
8
+ }
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.LoggerService = void 0;
10
+ const common_1 = require("@nestjs/common");
11
+ let LoggerService = class LoggerService {
12
+ constructor() {
13
+ this.logger = new common_1.Logger('AuthKit');
14
+ }
15
+ log(message, context) {
16
+ this.logger.log(message, context);
17
+ }
18
+ error(message, trace, context) {
19
+ this.logger.error(message, trace, context);
20
+ }
21
+ warn(message, context) {
22
+ this.logger.warn(message, context);
23
+ }
24
+ debug(message, context) {
25
+ if (process.env.NODE_ENV === 'development') {
26
+ this.logger.debug(message, context);
27
+ }
28
+ }
29
+ verbose(message, context) {
30
+ if (process.env.NODE_ENV === 'development') {
31
+ this.logger.verbose(message, context);
32
+ }
33
+ }
34
+ };
35
+ exports.LoggerService = LoggerService;
36
+ exports.LoggerService = LoggerService = __decorate([
37
+ (0, common_1.Injectable)()
38
+ ], LoggerService);
@@ -1,5 +1,8 @@
1
+ import { LoggerService } from './logger.service';
1
2
  export declare class MailService {
3
+ private readonly logger;
2
4
  private transporter;
5
+ constructor(logger: LoggerService);
3
6
  sendVerificationEmail(email: string, token: string): Promise<void>;
4
7
  sendPasswordResetEmail(email: string, token: string): Promise<void>;
5
8
  }
@@ -1,12 +1,24 @@
1
1
  "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
2
11
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
13
  };
5
14
  Object.defineProperty(exports, "__esModule", { value: true });
6
15
  exports.MailService = void 0;
7
16
  const nodemailer_1 = __importDefault(require("nodemailer"));
8
- class MailService {
9
- constructor() {
17
+ const common_1 = require("@nestjs/common");
18
+ const logger_service_1 = require("./logger.service");
19
+ let MailService = class MailService {
20
+ constructor(logger) {
21
+ this.logger = logger;
10
22
  this.transporter = nodemailer_1.default.createTransport({
11
23
  host: process.env.SMTP_HOST,
12
24
  port: parseInt(process.env.SMTP_PORT, 10),
@@ -18,22 +30,42 @@ class MailService {
18
30
  });
19
31
  }
20
32
  async sendVerificationEmail(email, token) {
21
- const url = `${process.env.FRONTEND_URL}/confirm-email?token=${token}`;
22
- await this.transporter.sendMail({
23
- from: process.env.FROM_EMAIL,
24
- to: email,
25
- subject: 'Verify your email',
26
- text: `Click to verify your email: ${url}`
27
- });
33
+ try {
34
+ const url = `${process.env.FRONTEND_URL}/confirm-email?token=${token}`;
35
+ await this.transporter.sendMail({
36
+ from: process.env.FROM_EMAIL,
37
+ to: email,
38
+ subject: 'Verify your email',
39
+ text: `Click to verify your email: ${url}`,
40
+ html: `<p>Click <a href="${url}">here</a> to verify your email</p>`
41
+ });
42
+ this.logger.log(`Verification email sent to ${email}`, 'MailService');
43
+ }
44
+ catch (error) {
45
+ this.logger.error(`Failed to send verification email to ${email}: ${error.message}`, error.stack, 'MailService');
46
+ throw error; // Re-throw so caller can handle
47
+ }
28
48
  }
29
49
  async sendPasswordResetEmail(email, token) {
30
- const url = `${process.env.FRONTEND_URL}/reset-password?token=${token}`;
31
- await this.transporter.sendMail({
32
- from: process.env.FROM_EMAIL,
33
- to: email,
34
- subject: 'Reset your password',
35
- text: `Reset your password: ${url}`
36
- });
50
+ try {
51
+ const url = `${process.env.FRONTEND_URL}/reset-password?token=${token}`;
52
+ await this.transporter.sendMail({
53
+ from: process.env.FROM_EMAIL,
54
+ to: email,
55
+ subject: 'Reset your password',
56
+ text: `Reset your password: ${url}`,
57
+ html: `<p>Click <a href="${url}">here</a> to reset your password</p>`
58
+ });
59
+ this.logger.log(`Password reset email sent to ${email}`, 'MailService');
60
+ }
61
+ catch (error) {
62
+ this.logger.error(`Failed to send password reset email to ${email}: ${error.message}`, error.stack, 'MailService');
63
+ throw error; // Re-throw so caller can handle
64
+ }
37
65
  }
38
- }
66
+ };
39
67
  exports.MailService = MailService;
68
+ exports.MailService = MailService = __decorate([
69
+ (0, common_1.Injectable)(),
70
+ __metadata("design:paramtypes", [logger_service_1.LoggerService])
71
+ ], MailService);
@@ -1,12 +1,15 @@
1
1
  import { UserRepository } from '../repositories/user.repository';
2
2
  import { RoleRepository } from '../repositories/role.repository';
3
3
  import { AuthService } from './auth.service';
4
+ import { LoggerService } from './logger.service';
4
5
  export declare class OAuthService {
5
6
  private readonly users;
6
7
  private readonly roles;
7
8
  private readonly auth;
9
+ private readonly logger;
8
10
  private msJwks;
9
- constructor(users: UserRepository, roles: RoleRepository, auth: AuthService);
11
+ private axiosConfig;
12
+ constructor(users: UserRepository, roles: RoleRepository, auth: AuthService, logger: LoggerService);
10
13
  private getDefaultRoleId;
11
14
  private verifyMicrosoftIdToken;
12
15
  loginWithMicrosoft(idToken: string): Promise<{