@ciscode/authentication-kit 1.2.1 → 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.
- package/dist/auth-kit.module.js +9 -0
- package/dist/filters/http-exception.filter.d.ts +5 -0
- package/dist/filters/http-exception.filter.js +89 -0
- package/dist/middleware/authenticate.guard.d.ts +3 -1
- package/dist/middleware/authenticate.guard.js +30 -18
- package/dist/services/admin-role.service.d.ts +3 -1
- package/dist/services/admin-role.service.js +22 -8
- package/dist/services/auth.service.d.ts +8 -1
- package/dist/services/auth.service.js +269 -112
- package/dist/services/logger.service.d.ts +8 -0
- package/dist/services/logger.service.js +38 -0
- package/dist/services/mail.service.d.ts +3 -0
- package/dist/services/mail.service.js +49 -17
- package/dist/services/oauth.service.d.ts +4 -1
- package/dist/services/oauth.service.js +175 -71
- package/dist/services/permissions.service.d.ts +3 -1
- package/dist/services/permissions.service.js +56 -14
- package/dist/services/roles.service.d.ts +3 -1
- package/dist/services/roles.service.js +76 -24
- package/dist/services/users.service.d.ts +3 -1
- package/dist/services/users.service.js +107 -45
- package/package.json +1 -1
|
@@ -53,11 +53,13 @@ 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
55
|
const helper_1 = require("../utils/helper");
|
|
56
|
+
const logger_service_1 = require("./logger.service");
|
|
56
57
|
let AuthService = class AuthService {
|
|
57
|
-
constructor(users, mail, roles) {
|
|
58
|
+
constructor(users, mail, roles, logger) {
|
|
58
59
|
this.users = users;
|
|
59
60
|
this.mail = mail;
|
|
60
61
|
this.roles = roles;
|
|
62
|
+
this.logger = logger;
|
|
61
63
|
}
|
|
62
64
|
resolveExpiry(value, fallback) {
|
|
63
65
|
return (value || fallback);
|
|
@@ -79,19 +81,30 @@ let AuthService = class AuthService {
|
|
|
79
81
|
return jwt.sign(payload, this.getEnv('JWT_RESET_SECRET'), { expiresIn });
|
|
80
82
|
}
|
|
81
83
|
async buildTokenPayload(userId) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
.
|
|
89
|
-
|
|
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
|
+
}
|
|
90
101
|
}
|
|
91
102
|
getEnv(name) {
|
|
92
103
|
const v = process.env[name];
|
|
93
|
-
if (!v)
|
|
94
|
-
|
|
104
|
+
if (!v) {
|
|
105
|
+
this.logger.error(`Environment variable ${name} is not set`, 'AuthService');
|
|
106
|
+
throw new common_1.InternalServerErrorException('Server configuration error');
|
|
107
|
+
}
|
|
95
108
|
return v;
|
|
96
109
|
}
|
|
97
110
|
async issueTokensForUser(userId) {
|
|
@@ -101,120 +114,263 @@ let AuthService = class AuthService {
|
|
|
101
114
|
return { accessToken, refreshToken };
|
|
102
115
|
}
|
|
103
116
|
async register(dto) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
dto.username
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
+
}
|
|
137
185
|
}
|
|
138
186
|
async verifyEmail(token) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
+
}
|
|
150
216
|
}
|
|
151
217
|
async resendVerification(email) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
+
}
|
|
158
233
|
}
|
|
159
234
|
async login(dto) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
+
}
|
|
174
263
|
}
|
|
175
264
|
async refresh(refreshToken) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
+
}
|
|
193
302
|
}
|
|
194
303
|
async forgotPassword(email) {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
return
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
+
}
|
|
201
319
|
}
|
|
202
320
|
async resetPassword(token, newPassword) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
+
}
|
|
214
358
|
}
|
|
215
359
|
async deleteAccount(userId) {
|
|
216
|
-
|
|
217
|
-
|
|
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
|
+
}
|
|
218
374
|
}
|
|
219
375
|
};
|
|
220
376
|
exports.AuthService = AuthService;
|
|
@@ -222,5 +378,6 @@ exports.AuthService = AuthService = __decorate([
|
|
|
222
378
|
(0, common_1.Injectable)(),
|
|
223
379
|
__metadata("design:paramtypes", [user_repository_1.UserRepository,
|
|
224
380
|
mail_service_1.MailService,
|
|
225
|
-
role_repository_1.RoleRepository
|
|
381
|
+
role_repository_1.RoleRepository,
|
|
382
|
+
logger_service_1.LoggerService])
|
|
226
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
|
-
|
|
9
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
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<{
|