@ciscode/authentication-kit 1.4.1 → 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/dist/auth-kit.module.js +2 -0
- package/dist/controllers/health.controller.d.ts +48 -0
- package/dist/controllers/health.controller.js +77 -0
- package/dist/services/auth.service.d.ts +38 -0
- package/dist/services/auth.service.js +54 -9
- package/dist/services/mail.service.d.ts +7 -0
- package/dist/services/mail.service.js +76 -12
- package/package.json +1 -1
package/dist/auth-kit.module.js
CHANGED
|
@@ -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
|
{
|
|
@@ -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);
|
|
@@ -26,8 +26,12 @@ export declare class AuthService {
|
|
|
26
26
|
data: any;
|
|
27
27
|
}>;
|
|
28
28
|
register(dto: RegisterDto): Promise<{
|
|
29
|
+
emailError: string;
|
|
30
|
+
emailHint: string;
|
|
31
|
+
ok: boolean;
|
|
29
32
|
id: any;
|
|
30
33
|
email: string;
|
|
34
|
+
emailSent: boolean;
|
|
31
35
|
}>;
|
|
32
36
|
verifyEmail(token: string): Promise<{
|
|
33
37
|
ok: boolean;
|
|
@@ -36,6 +40,23 @@ export declare class AuthService {
|
|
|
36
40
|
resendVerification(email: string): Promise<{
|
|
37
41
|
ok: boolean;
|
|
38
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;
|
|
39
60
|
}>;
|
|
40
61
|
login(dto: LoginDto): Promise<{
|
|
41
62
|
accessToken: string;
|
|
@@ -48,6 +69,23 @@ export declare class AuthService {
|
|
|
48
69
|
forgotPassword(email: string): Promise<{
|
|
49
70
|
ok: boolean;
|
|
50
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;
|
|
51
89
|
}>;
|
|
52
90
|
resetPassword(token: string, newPassword: string): Promise<{
|
|
53
91
|
ok: boolean;
|
|
@@ -185,15 +185,25 @@ let AuthService = class AuthService {
|
|
|
185
185
|
passwordChangedAt: new Date()
|
|
186
186
|
});
|
|
187
187
|
// Send verification email (don't let email failures crash registration)
|
|
188
|
+
let emailSent = true;
|
|
189
|
+
let emailError;
|
|
188
190
|
try {
|
|
189
191
|
const emailToken = this.signEmailToken({ sub: user._id.toString(), purpose: 'verify' });
|
|
190
192
|
await this.mail.sendVerificationEmail(user.email, emailToken);
|
|
191
193
|
}
|
|
192
194
|
catch (error) {
|
|
195
|
+
emailSent = false;
|
|
196
|
+
emailError = error.message || 'Failed to send verification email';
|
|
193
197
|
this.logger.error(`Failed to send verification email: ${error.message}`, error.stack, 'AuthService');
|
|
194
198
|
// Continue - user is created, they can resend verification
|
|
195
199
|
}
|
|
196
|
-
return {
|
|
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
|
+
};
|
|
197
207
|
}
|
|
198
208
|
catch (error) {
|
|
199
209
|
// Re-throw HTTP exceptions
|
|
@@ -247,13 +257,29 @@ let AuthService = class AuthService {
|
|
|
247
257
|
return { ok: true, message: 'If the email exists and is unverified, a verification email has been sent' };
|
|
248
258
|
}
|
|
249
259
|
const emailToken = this.signEmailToken({ sub: user._id.toString(), purpose: 'verify' });
|
|
250
|
-
|
|
251
|
-
|
|
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
|
+
}
|
|
252
274
|
}
|
|
253
275
|
catch (error) {
|
|
254
276
|
this.logger.error(`Resend verification failed: ${error.message}`, error.stack, 'AuthService');
|
|
255
|
-
// Return
|
|
256
|
-
return {
|
|
277
|
+
// Return error details for debugging
|
|
278
|
+
return {
|
|
279
|
+
ok: false,
|
|
280
|
+
message: 'Failed to resend verification email',
|
|
281
|
+
error: error.message
|
|
282
|
+
};
|
|
257
283
|
}
|
|
258
284
|
}
|
|
259
285
|
async login(dto) {
|
|
@@ -328,17 +354,36 @@ let AuthService = class AuthService {
|
|
|
328
354
|
async forgotPassword(email) {
|
|
329
355
|
try {
|
|
330
356
|
const user = await this.users.findByEmail(email);
|
|
331
|
-
// Always return success to prevent email enumeration
|
|
357
|
+
// Always return success to prevent email enumeration (in production)
|
|
332
358
|
if (!user) {
|
|
333
359
|
return { ok: true, message: 'If the email exists, a password reset link has been sent' };
|
|
334
360
|
}
|
|
335
361
|
const resetToken = this.signResetToken({ sub: user._id.toString(), purpose: 'reset' });
|
|
336
|
-
|
|
337
|
-
|
|
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
|
+
}
|
|
338
380
|
}
|
|
339
381
|
catch (error) {
|
|
340
382
|
this.logger.error(`Forgot password failed: ${error.message}`, error.stack, 'AuthService');
|
|
341
|
-
//
|
|
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
|
+
}
|
|
342
387
|
return { ok: true, message: 'If the email exists, a password reset link has been sent' };
|
|
343
388
|
}
|
|
344
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.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
46
|
-
|
|
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
|
-
|
|
63
|
-
|
|
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;
|