@ciscode/authentication-kit 1.4.0 → 1.4.2

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/README.md CHANGED
@@ -107,17 +107,18 @@ export class AppModule implements OnModuleInit {
107
107
 
108
108
  ## API Routes
109
109
 
110
- ### Local Auth Routes (Public)
110
+ ### Local Auth Routes
111
111
 
112
112
  ```
113
- POST /api/auth/register
114
- POST /api/auth/verify-email
115
- POST /api/auth/resend-verification
116
- POST /api/auth/login
117
- POST /api/auth/refresh-token
118
- POST /api/auth/forgot-password
119
- POST /api/auth/reset-password
120
- DELETE /api/auth/account (protected)
113
+ POST /api/auth/register | Register new user (public)
114
+ POST /api/auth/verify-email | Verify email with token (public)
115
+ POST /api/auth/resend-verification | Resend verification email (public)
116
+ POST /api/auth/login | Login with credentials (public)
117
+ POST /api/auth/refresh-token | Refresh access token (public)
118
+ POST /api/auth/forgot-password | Request password reset (public)
119
+ POST /api/auth/reset-password | Reset password with token (public)
120
+ GET /api/auth/me | Get current user profile (protected)
121
+ DELETE /api/auth/account | Delete own account (protected)
121
122
  ```
122
123
 
123
124
  ### OAuth Routes - Mobile Exchange (Public)
@@ -321,6 +322,54 @@ Content-Type: application/json
321
322
  }
322
323
  ```
323
324
 
325
+ ### Get Current User Profile
326
+
327
+ **Request:**
328
+
329
+ ```json
330
+ GET /api/auth/me
331
+ Authorization: Bearer access-token
332
+ ```
333
+
334
+ **Response:**
335
+
336
+ ```json
337
+ {
338
+ "ok": true,
339
+ "data": {
340
+ "_id": "507f1f77bcf86cd799439011",
341
+ "fullname": {
342
+ "fname": "Test",
343
+ "lname": "User"
344
+ },
345
+ "username": "test-user",
346
+ "email": "user@example.com",
347
+ "avatar": "https://example.com/avatar.jpg",
348
+ "phoneNumber": "+1234567890",
349
+ "jobTitle": "Software Engineer",
350
+ "company": "Ciscode",
351
+ "isVerified": true,
352
+ "isBanned": false,
353
+ "roles": [
354
+ {
355
+ "_id": "507f1f77bcf86cd799439012",
356
+ "name": "user",
357
+ "permissions": [
358
+ {
359
+ "_id": "507f1f77bcf86cd799439013",
360
+ "name": "read:profile"
361
+ }
362
+ ]
363
+ }
364
+ ],
365
+ "createdAt": "2026-01-28T10:00:00.000Z",
366
+ "updatedAt": "2026-01-28T10:00:00.000Z"
367
+ }
368
+ }
369
+ ```
370
+
371
+ **Note:** Sensitive fields like `password` and `passwordChangedAt` are automatically excluded from the response.
372
+
324
373
  ### Delete Account
325
374
 
326
375
  **Request:**
@@ -22,6 +22,7 @@ const auth_controller_1 = require("./controllers/auth.controller");
22
22
  const users_controller_1 = require("./controllers/users.controller");
23
23
  const roles_controller_1 = require("./controllers/roles.controller");
24
24
  const permissions_controller_1 = require("./controllers/permissions.controller");
25
+ const health_controller_1 = require("./controllers/health.controller");
25
26
  const user_model_1 = require("./models/user.model");
26
27
  const role_model_1 = require("./models/role.model");
27
28
  const permission_model_1 = require("./models/permission.model");
@@ -70,6 +71,7 @@ exports.AuthKitModule = AuthKitModule = __decorate([
70
71
  users_controller_1.UsersController,
71
72
  roles_controller_1.RolesController,
72
73
  permissions_controller_1.PermissionsController,
74
+ health_controller_1.HealthController,
73
75
  ],
74
76
  providers: [
75
77
  {
@@ -19,6 +19,7 @@ export declare class AuthController {
19
19
  refresh(dto: RefreshTokenDto, req: Request, res: Response): Promise<Response<any, Record<string, any>>>;
20
20
  forgotPassword(dto: ForgotPasswordDto, res: Response): Promise<Response<any, Record<string, any>>>;
21
21
  resetPassword(dto: ResetPasswordDto, res: Response): Promise<Response<any, Record<string, any>>>;
22
+ getMe(req: Request, res: Response): Promise<Response<any, Record<string, any>>>;
22
23
  deleteAccount(req: Request, res: Response): Promise<Response<any, Record<string, any>>>;
23
24
  microsoftExchange(body: {
24
25
  idToken: string;
@@ -84,6 +84,14 @@ let AuthController = class AuthController {
84
84
  const result = await this.auth.resetPassword(dto.token, dto.newPassword);
85
85
  return res.status(200).json(result);
86
86
  }
87
+ async getMe(req, res) {
88
+ var _a;
89
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.sub;
90
+ if (!userId)
91
+ return res.status(401).json({ message: 'Unauthorized.' });
92
+ const result = await this.auth.getMe(userId);
93
+ return res.status(200).json(result);
94
+ }
87
95
  async deleteAccount(req, res) {
88
96
  var _a;
89
97
  const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.sub;
@@ -202,6 +210,15 @@ __decorate([
202
210
  __metadata("design:paramtypes", [reset_password_dto_1.ResetPasswordDto, Object]),
203
211
  __metadata("design:returntype", Promise)
204
212
  ], AuthController.prototype, "resetPassword", null);
213
+ __decorate([
214
+ (0, common_1.Get)('me'),
215
+ (0, common_1.UseGuards)(authenticate_guard_1.AuthenticateGuard),
216
+ __param(0, (0, common_1.Req)()),
217
+ __param(1, (0, common_1.Res)()),
218
+ __metadata("design:type", Function),
219
+ __metadata("design:paramtypes", [Object, Object]),
220
+ __metadata("design:returntype", Promise)
221
+ ], AuthController.prototype, "getMe", null);
205
222
  __decorate([
206
223
  (0, common_1.Delete)('account'),
207
224
  (0, common_1.UseGuards)(authenticate_guard_1.AuthenticateGuard),
@@ -0,0 +1,48 @@
1
+ import { MailService } from '../services/mail.service';
2
+ import { LoggerService } from '../services/logger.service';
3
+ export declare class HealthController {
4
+ private readonly mail;
5
+ private readonly logger;
6
+ constructor(mail: MailService, logger: LoggerService);
7
+ checkSmtp(): Promise<{
8
+ config: {
9
+ host: string;
10
+ port: string;
11
+ secure: string;
12
+ user: string;
13
+ fromEmail: string;
14
+ };
15
+ error: string;
16
+ service: string;
17
+ status: string;
18
+ } | {
19
+ service: string;
20
+ status: string;
21
+ error: any;
22
+ }>;
23
+ checkAll(): Promise<{
24
+ status: string;
25
+ checks: {
26
+ smtp: {
27
+ config: {
28
+ host: string;
29
+ port: string;
30
+ secure: string;
31
+ user: string;
32
+ fromEmail: string;
33
+ };
34
+ error: string;
35
+ service: string;
36
+ status: string;
37
+ } | {
38
+ service: string;
39
+ status: string;
40
+ error: any;
41
+ };
42
+ };
43
+ environment: {
44
+ nodeEnv: string;
45
+ frontendUrl: string;
46
+ };
47
+ }>;
48
+ }
@@ -0,0 +1,77 @@
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
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.HealthController = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const mail_service_1 = require("../services/mail.service");
15
+ const logger_service_1 = require("../services/logger.service");
16
+ let HealthController = class HealthController {
17
+ constructor(mail, logger) {
18
+ this.mail = mail;
19
+ this.logger = logger;
20
+ }
21
+ async checkSmtp() {
22
+ try {
23
+ const result = await this.mail.verifyConnection();
24
+ return {
25
+ service: 'smtp',
26
+ status: result.connected ? 'connected' : 'disconnected',
27
+ ...(result.error && { error: result.error }),
28
+ config: {
29
+ host: process.env.SMTP_HOST || 'not set',
30
+ port: process.env.SMTP_PORT || 'not set',
31
+ secure: process.env.SMTP_SECURE || 'not set',
32
+ user: process.env.SMTP_USER ? '***' + process.env.SMTP_USER.slice(-4) : 'not set',
33
+ fromEmail: process.env.FROM_EMAIL || 'not set',
34
+ }
35
+ };
36
+ }
37
+ catch (error) {
38
+ this.logger.error(`SMTP health check failed: ${error.message}`, error.stack, 'HealthController');
39
+ return {
40
+ service: 'smtp',
41
+ status: 'error',
42
+ error: error.message
43
+ };
44
+ }
45
+ }
46
+ async checkAll() {
47
+ const smtp = await this.checkSmtp();
48
+ return {
49
+ status: smtp.status === 'connected' ? 'healthy' : 'degraded',
50
+ checks: {
51
+ smtp
52
+ },
53
+ environment: {
54
+ nodeEnv: process.env.NODE_ENV || 'not set',
55
+ frontendUrl: process.env.FRONTEND_URL || 'not set',
56
+ }
57
+ };
58
+ }
59
+ };
60
+ exports.HealthController = HealthController;
61
+ __decorate([
62
+ (0, common_1.Get)('smtp'),
63
+ __metadata("design:type", Function),
64
+ __metadata("design:paramtypes", []),
65
+ __metadata("design:returntype", Promise)
66
+ ], HealthController.prototype, "checkSmtp", null);
67
+ __decorate([
68
+ (0, common_1.Get)(),
69
+ __metadata("design:type", Function),
70
+ __metadata("design:paramtypes", []),
71
+ __metadata("design:returntype", Promise)
72
+ ], HealthController.prototype, "checkAll", null);
73
+ exports.HealthController = HealthController = __decorate([
74
+ (0, common_1.Controller)('api/health'),
75
+ __metadata("design:paramtypes", [mail_service_1.MailService,
76
+ logger_service_1.LoggerService])
77
+ ], HealthController);
@@ -21,9 +21,17 @@ export declare class AuthService {
21
21
  accessToken: string;
22
22
  refreshToken: string;
23
23
  }>;
24
+ getMe(userId: string): Promise<{
25
+ ok: boolean;
26
+ data: any;
27
+ }>;
24
28
  register(dto: RegisterDto): Promise<{
29
+ emailError: string;
30
+ emailHint: string;
31
+ ok: boolean;
25
32
  id: any;
26
33
  email: string;
34
+ emailSent: boolean;
27
35
  }>;
28
36
  verifyEmail(token: string): Promise<{
29
37
  ok: boolean;
@@ -32,6 +40,23 @@ export declare class AuthService {
32
40
  resendVerification(email: string): Promise<{
33
41
  ok: boolean;
34
42
  message: string;
43
+ emailSent?: undefined;
44
+ error?: undefined;
45
+ } | {
46
+ ok: boolean;
47
+ message: string;
48
+ emailSent: boolean;
49
+ error?: undefined;
50
+ } | {
51
+ ok: boolean;
52
+ message: string;
53
+ emailSent: boolean;
54
+ error: any;
55
+ } | {
56
+ ok: boolean;
57
+ message: string;
58
+ error: any;
59
+ emailSent?: undefined;
35
60
  }>;
36
61
  login(dto: LoginDto): Promise<{
37
62
  accessToken: string;
@@ -44,6 +69,23 @@ export declare class AuthService {
44
69
  forgotPassword(email: string): Promise<{
45
70
  ok: boolean;
46
71
  message: string;
72
+ emailSent?: undefined;
73
+ error?: undefined;
74
+ } | {
75
+ ok: boolean;
76
+ message: string;
77
+ emailSent: boolean;
78
+ error?: undefined;
79
+ } | {
80
+ ok: boolean;
81
+ message: string;
82
+ emailSent: boolean;
83
+ error: any;
84
+ } | {
85
+ ok: boolean;
86
+ message: string;
87
+ error: any;
88
+ emailSent?: undefined;
47
89
  }>;
48
90
  resetPassword(token: string, newPassword: string): Promise<{
49
91
  ok: boolean;
@@ -113,6 +113,31 @@ let AuthService = class AuthService {
113
113
  const refreshToken = this.signRefreshToken({ sub: userId, purpose: 'refresh' });
114
114
  return { accessToken, refreshToken };
115
115
  }
116
+ async getMe(userId) {
117
+ try {
118
+ const user = await this.users.findByIdWithRolesAndPermissions(userId);
119
+ if (!user) {
120
+ throw new common_1.NotFoundException('User not found');
121
+ }
122
+ if (user.isBanned) {
123
+ throw new common_1.ForbiddenException('Account has been banned. Please contact support');
124
+ }
125
+ // Return user data without sensitive information
126
+ const userObject = user.toObject ? user.toObject() : user;
127
+ const { password, passwordChangedAt, ...safeUser } = userObject;
128
+ return {
129
+ ok: true,
130
+ data: safeUser
131
+ };
132
+ }
133
+ catch (error) {
134
+ if (error instanceof common_1.NotFoundException || error instanceof common_1.ForbiddenException) {
135
+ throw error;
136
+ }
137
+ this.logger.error(`Get profile failed: ${error.message}`, error.stack, 'AuthService');
138
+ throw new common_1.InternalServerErrorException('Failed to retrieve profile');
139
+ }
140
+ }
116
141
  async register(dto) {
117
142
  try {
118
143
  // Generate username from fname-lname if not provided
@@ -160,15 +185,25 @@ let AuthService = class AuthService {
160
185
  passwordChangedAt: new Date()
161
186
  });
162
187
  // Send verification email (don't let email failures crash registration)
188
+ let emailSent = true;
189
+ let emailError;
163
190
  try {
164
191
  const emailToken = this.signEmailToken({ sub: user._id.toString(), purpose: 'verify' });
165
192
  await this.mail.sendVerificationEmail(user.email, emailToken);
166
193
  }
167
194
  catch (error) {
195
+ emailSent = false;
196
+ emailError = error.message || 'Failed to send verification email';
168
197
  this.logger.error(`Failed to send verification email: ${error.message}`, error.stack, 'AuthService');
169
198
  // Continue - user is created, they can resend verification
170
199
  }
171
- return { id: user._id, email: user.email };
200
+ return {
201
+ ok: true,
202
+ id: user._id,
203
+ email: user.email,
204
+ emailSent,
205
+ ...(emailError && { emailError, emailHint: 'User created successfully. You can resend verification email later.' })
206
+ };
172
207
  }
173
208
  catch (error) {
174
209
  // Re-throw HTTP exceptions
@@ -222,13 +257,29 @@ let AuthService = class AuthService {
222
257
  return { ok: true, message: 'If the email exists and is unverified, a verification email has been sent' };
223
258
  }
224
259
  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' };
260
+ try {
261
+ await this.mail.sendVerificationEmail(user.email, emailToken);
262
+ return { ok: true, message: 'Verification email sent successfully', emailSent: true };
263
+ }
264
+ catch (emailError) {
265
+ // Log the actual error but return generic message
266
+ this.logger.error(`Failed to send verification email: ${emailError.message}`, emailError.stack, 'AuthService');
267
+ return {
268
+ ok: false,
269
+ message: 'Failed to send verification email',
270
+ emailSent: false,
271
+ error: emailError.message || 'Email service error'
272
+ };
273
+ }
227
274
  }
228
275
  catch (error) {
229
276
  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' };
277
+ // Return error details for debugging
278
+ return {
279
+ ok: false,
280
+ message: 'Failed to resend verification email',
281
+ error: error.message
282
+ };
232
283
  }
233
284
  }
234
285
  async login(dto) {
@@ -303,17 +354,36 @@ let AuthService = class AuthService {
303
354
  async forgotPassword(email) {
304
355
  try {
305
356
  const user = await this.users.findByEmail(email);
306
- // Always return success to prevent email enumeration
357
+ // Always return success to prevent email enumeration (in production)
307
358
  if (!user) {
308
359
  return { ok: true, message: 'If the email exists, a password reset link has been sent' };
309
360
  }
310
361
  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' };
362
+ try {
363
+ await this.mail.sendPasswordResetEmail(user.email, resetToken);
364
+ return { ok: true, message: 'Password reset link sent successfully', emailSent: true };
365
+ }
366
+ catch (emailError) {
367
+ // Log the actual error but return generic message for security
368
+ this.logger.error(`Failed to send reset email: ${emailError.message}`, emailError.stack, 'AuthService');
369
+ // In development, return error details; in production, hide for security
370
+ if (process.env.NODE_ENV === 'development') {
371
+ return {
372
+ ok: false,
373
+ message: 'Failed to send password reset email',
374
+ emailSent: false,
375
+ error: emailError.message
376
+ };
377
+ }
378
+ return { ok: true, message: 'If the email exists, a password reset link has been sent' };
379
+ }
313
380
  }
314
381
  catch (error) {
315
382
  this.logger.error(`Forgot password failed: ${error.message}`, error.stack, 'AuthService');
316
- // Return success to prevent email enumeration
383
+ // In development, return error; in production, hide for security
384
+ if (process.env.NODE_ENV === 'development') {
385
+ return { ok: false, message: 'Failed to process password reset', error: error.message };
386
+ }
317
387
  return { ok: true, message: 'If the email exists, a password reset link has been sent' };
318
388
  }
319
389
  }
@@ -2,7 +2,14 @@ import { LoggerService } from './logger.service';
2
2
  export declare class MailService {
3
3
  private readonly logger;
4
4
  private transporter;
5
+ private smtpConfigured;
5
6
  constructor(logger: LoggerService);
7
+ private initializeTransporter;
8
+ verifyConnection(): Promise<{
9
+ connected: boolean;
10
+ error?: string;
11
+ }>;
6
12
  sendVerificationEmail(email: string, token: string): Promise<void>;
7
13
  sendPasswordResetEmail(email: string, token: string): Promise<void>;
14
+ private getDetailedSmtpError;
8
15
  }
@@ -19,17 +19,56 @@ const logger_service_1 = require("./logger.service");
19
19
  let MailService = class MailService {
20
20
  constructor(logger) {
21
21
  this.logger = logger;
22
- this.transporter = nodemailer_1.default.createTransport({
23
- host: process.env.SMTP_HOST,
24
- port: parseInt(process.env.SMTP_PORT, 10),
25
- secure: process.env.SMTP_SECURE === 'true',
26
- auth: {
27
- user: process.env.SMTP_USER,
28
- pass: process.env.SMTP_PASS
22
+ this.smtpConfigured = false;
23
+ this.initializeTransporter();
24
+ }
25
+ initializeTransporter() {
26
+ try {
27
+ // Check if SMTP is configured
28
+ if (!process.env.SMTP_HOST || !process.env.SMTP_PORT) {
29
+ this.logger.warn('SMTP not configured - email functionality will be disabled', 'MailService');
30
+ this.smtpConfigured = false;
31
+ return;
29
32
  }
30
- });
33
+ this.transporter = nodemailer_1.default.createTransport({
34
+ host: process.env.SMTP_HOST,
35
+ port: parseInt(process.env.SMTP_PORT, 10),
36
+ secure: process.env.SMTP_SECURE === 'true',
37
+ auth: {
38
+ user: process.env.SMTP_USER,
39
+ pass: process.env.SMTP_PASS
40
+ },
41
+ connectionTimeout: 10000, // 10 seconds
42
+ greetingTimeout: 10000,
43
+ });
44
+ this.smtpConfigured = true;
45
+ }
46
+ catch (error) {
47
+ this.logger.error(`Failed to initialize SMTP transporter: ${error.message}`, error.stack, 'MailService');
48
+ this.smtpConfigured = false;
49
+ }
50
+ }
51
+ async verifyConnection() {
52
+ if (!this.smtpConfigured) {
53
+ return { connected: false, error: 'SMTP not configured' };
54
+ }
55
+ try {
56
+ await this.transporter.verify();
57
+ this.logger.log('SMTP connection verified successfully', 'MailService');
58
+ return { connected: true };
59
+ }
60
+ catch (error) {
61
+ const errorMsg = `SMTP connection failed: ${error.message}`;
62
+ this.logger.error(errorMsg, error.stack, 'MailService');
63
+ return { connected: false, error: errorMsg };
64
+ }
31
65
  }
32
66
  async sendVerificationEmail(email, token) {
67
+ if (!this.smtpConfigured) {
68
+ const error = new common_1.InternalServerErrorException('SMTP not configured - cannot send emails');
69
+ this.logger.error('Attempted to send email but SMTP is not configured', '', 'MailService');
70
+ throw error;
71
+ }
33
72
  try {
34
73
  const url = `${process.env.FRONTEND_URL}/confirm-email?token=${token}`;
35
74
  await this.transporter.sendMail({
@@ -42,11 +81,17 @@ let MailService = class MailService {
42
81
  this.logger.log(`Verification email sent to ${email}`, 'MailService');
43
82
  }
44
83
  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
84
+ const detailedError = this.getDetailedSmtpError(error);
85
+ this.logger.error(`Failed to send verification email to ${email}: ${detailedError}`, error.stack, 'MailService');
86
+ throw new common_1.InternalServerErrorException(detailedError);
47
87
  }
48
88
  }
49
89
  async sendPasswordResetEmail(email, token) {
90
+ if (!this.smtpConfigured) {
91
+ const error = new common_1.InternalServerErrorException('SMTP not configured - cannot send emails');
92
+ this.logger.error('Attempted to send email but SMTP is not configured', '', 'MailService');
93
+ throw error;
94
+ }
50
95
  try {
51
96
  const url = `${process.env.FRONTEND_URL}/reset-password?token=${token}`;
52
97
  await this.transporter.sendMail({
@@ -59,9 +104,28 @@ let MailService = class MailService {
59
104
  this.logger.log(`Password reset email sent to ${email}`, 'MailService');
60
105
  }
61
106
  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
107
+ const detailedError = this.getDetailedSmtpError(error);
108
+ this.logger.error(`Failed to send password reset email to ${email}: ${detailedError}`, error.stack, 'MailService');
109
+ throw new common_1.InternalServerErrorException(detailedError);
110
+ }
111
+ }
112
+ getDetailedSmtpError(error) {
113
+ if (error.code === 'EAUTH') {
114
+ return 'SMTP authentication failed. Check SMTP_USER and SMTP_PASS environment variables.';
115
+ }
116
+ if (error.code === 'ESOCKET' || error.code === 'ECONNECTION') {
117
+ return `Cannot connect to SMTP server at ${process.env.SMTP_HOST}:${process.env.SMTP_PORT}. Check network/firewall settings.`;
118
+ }
119
+ if (error.code === 'ETIMEDOUT' || error.code === 'ECONNABORTED') {
120
+ return 'SMTP connection timed out. Server may be unreachable or firewalled.';
121
+ }
122
+ if (error.responseCode >= 500) {
123
+ return `SMTP server error (${error.responseCode}): ${error.response}`;
124
+ }
125
+ if (error.responseCode >= 400) {
126
+ return `SMTP client error (${error.responseCode}): Check FROM_EMAIL and recipient addresses.`;
64
127
  }
128
+ return error.message || 'Unknown SMTP error';
65
129
  }
66
130
  };
67
131
  exports.MailService = MailService;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ciscode/authentication-kit",
3
- "version": "1.4.0",
3
+ "version": "1.4.2",
4
4
  "description": "NestJS auth kit with local + OAuth, JWT, RBAC, password reset.",
5
5
  "publishConfig": {
6
6
  "access": "public"