@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.
@@ -16,6 +16,7 @@ exports.AuthKitModule = void 0;
16
16
  require("dotenv/config");
17
17
  const common_1 = require("@nestjs/common");
18
18
  const mongoose_1 = require("@nestjs/mongoose");
19
+ const core_1 = require("@nestjs/core");
19
20
  const cookie_parser_1 = __importDefault(require("cookie-parser"));
20
21
  const auth_controller_1 = require("./controllers/auth.controller");
21
22
  const users_controller_1 = require("./controllers/users.controller");
@@ -30,6 +31,7 @@ const roles_service_1 = require("./services/roles.service");
30
31
  const permissions_service_1 = require("./services/permissions.service");
31
32
  const mail_service_1 = require("./services/mail.service");
32
33
  const seed_service_1 = require("./services/seed.service");
34
+ const logger_service_1 = require("./services/logger.service");
33
35
  const user_repository_1 = require("./repositories/user.repository");
34
36
  const role_repository_1 = require("./repositories/role.repository");
35
37
  const permission_repository_1 = require("./repositories/permission.repository");
@@ -37,6 +39,7 @@ const authenticate_guard_1 = require("./middleware/authenticate.guard");
37
39
  const admin_guard_1 = require("./middleware/admin.guard");
38
40
  const admin_role_service_1 = require("./services/admin-role.service");
39
41
  const oauth_service_1 = require("./services/oauth.service");
42
+ const http_exception_filter_1 = require("./filters/http-exception.filter");
40
43
  const passport_1 = __importDefault(require("passport"));
41
44
  const passport_config_1 = require("./config/passport.config");
42
45
  let AuthKitModule = class AuthKitModule {
@@ -69,12 +72,17 @@ exports.AuthKitModule = AuthKitModule = __decorate([
69
72
  permissions_controller_1.PermissionsController,
70
73
  ],
71
74
  providers: [
75
+ {
76
+ provide: core_1.APP_FILTER,
77
+ useClass: http_exception_filter_1.GlobalExceptionFilter,
78
+ },
72
79
  auth_service_1.AuthService,
73
80
  users_service_1.UsersService,
74
81
  roles_service_1.RolesService,
75
82
  permissions_service_1.PermissionsService,
76
83
  mail_service_1.MailService,
77
84
  seed_service_1.SeedService,
85
+ logger_service_1.LoggerService,
78
86
  user_repository_1.UserRepository,
79
87
  role_repository_1.RoleRepository,
80
88
  permission_repository_1.PermissionRepository,
@@ -89,6 +97,7 @@ exports.AuthKitModule = AuthKitModule = __decorate([
89
97
  roles_service_1.RolesService,
90
98
  permissions_service_1.PermissionsService,
91
99
  seed_service_1.SeedService,
100
+ logger_service_1.LoggerService,
92
101
  authenticate_guard_1.AuthenticateGuard,
93
102
  user_repository_1.UserRepository,
94
103
  role_repository_1.RoleRepository,
@@ -0,0 +1,5 @@
1
+ import { ExceptionFilter, ArgumentsHost } from '@nestjs/common';
2
+ export declare class GlobalExceptionFilter implements ExceptionFilter {
3
+ private readonly logger;
4
+ catch(exception: any, host: ArgumentsHost): void;
5
+ }
@@ -0,0 +1,89 @@
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.GlobalExceptionFilter = void 0;
10
+ const common_1 = require("@nestjs/common");
11
+ let GlobalExceptionFilter = class GlobalExceptionFilter {
12
+ constructor() {
13
+ this.logger = new common_1.Logger('ExceptionFilter');
14
+ }
15
+ catch(exception, host) {
16
+ const ctx = host.switchToHttp();
17
+ const response = ctx.getResponse();
18
+ const request = ctx.getRequest();
19
+ let status = common_1.HttpStatus.INTERNAL_SERVER_ERROR;
20
+ let message = 'Internal server error';
21
+ let errors = null;
22
+ if (exception instanceof common_1.HttpException) {
23
+ status = exception.getStatus();
24
+ const exceptionResponse = exception.getResponse();
25
+ if (typeof exceptionResponse === 'string') {
26
+ message = exceptionResponse;
27
+ }
28
+ else if (typeof exceptionResponse === 'object') {
29
+ message = exceptionResponse.message || exception.message;
30
+ errors = exceptionResponse.errors || null;
31
+ }
32
+ }
33
+ else if ((exception === null || exception === void 0 ? void 0 : exception.code) === 11000) {
34
+ // MongoDB duplicate key error
35
+ status = common_1.HttpStatus.CONFLICT;
36
+ message = 'Resource already exists';
37
+ }
38
+ else if ((exception === null || exception === void 0 ? void 0 : exception.name) === 'ValidationError') {
39
+ // Mongoose validation error
40
+ status = common_1.HttpStatus.BAD_REQUEST;
41
+ message = 'Validation failed';
42
+ errors = exception.errors;
43
+ }
44
+ else if ((exception === null || exception === void 0 ? void 0 : exception.name) === 'CastError') {
45
+ // Mongoose cast error (invalid ObjectId)
46
+ status = common_1.HttpStatus.BAD_REQUEST;
47
+ message = 'Invalid resource identifier';
48
+ }
49
+ else {
50
+ message = 'An unexpected error occurred';
51
+ }
52
+ // Log the error (but not in test environment)
53
+ if (process.env.NODE_ENV !== 'test') {
54
+ const errorLog = {
55
+ timestamp: new Date().toISOString(),
56
+ path: request.url,
57
+ method: request.method,
58
+ statusCode: status,
59
+ message: (exception === null || exception === void 0 ? void 0 : exception.message) || message,
60
+ stack: exception === null || exception === void 0 ? void 0 : exception.stack,
61
+ };
62
+ if (status >= 500) {
63
+ this.logger.error('Server error', JSON.stringify(errorLog));
64
+ }
65
+ else if (status >= 400) {
66
+ this.logger.warn('Client error', JSON.stringify(errorLog));
67
+ }
68
+ }
69
+ // Send response
70
+ const errorResponse = {
71
+ statusCode: status,
72
+ message,
73
+ timestamp: new Date().toISOString(),
74
+ path: request.url,
75
+ };
76
+ if (errors) {
77
+ errorResponse.errors = errors;
78
+ }
79
+ // Don't send stack trace in production
80
+ if (process.env.NODE_ENV === 'development' && (exception === null || exception === void 0 ? void 0 : exception.stack)) {
81
+ errorResponse.stack = exception.stack;
82
+ }
83
+ response.status(status).json(errorResponse);
84
+ }
85
+ };
86
+ exports.GlobalExceptionFilter = GlobalExceptionFilter;
87
+ exports.GlobalExceptionFilter = GlobalExceptionFilter = __decorate([
88
+ (0, common_1.Catch)()
89
+ ], GlobalExceptionFilter);
@@ -1,8 +1,10 @@
1
1
  import { CanActivate, ExecutionContext } from '@nestjs/common';
2
2
  import { UserRepository } from '../repositories/user.repository';
3
+ import { LoggerService } from '../services/logger.service';
3
4
  export declare class AuthenticateGuard implements CanActivate {
4
5
  private readonly users;
5
- constructor(users: UserRepository);
6
+ private readonly logger;
7
+ constructor(users: UserRepository, logger: LoggerService);
6
8
  private getEnv;
7
9
  canActivate(context: ExecutionContext): Promise<boolean>;
8
10
  }
@@ -16,56 +16,68 @@ exports.AuthenticateGuard = void 0;
16
16
  const common_1 = require("@nestjs/common");
17
17
  const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
18
18
  const user_repository_1 = require("../repositories/user.repository");
19
+ const logger_service_1 = require("../services/logger.service");
19
20
  let AuthenticateGuard = class AuthenticateGuard {
20
- constructor(users) {
21
+ constructor(users, logger) {
21
22
  this.users = users;
23
+ this.logger = logger;
22
24
  }
23
25
  getEnv(name) {
24
26
  const v = process.env[name];
25
- if (!v)
26
- throw new Error(`${name} is not set`);
27
+ if (!v) {
28
+ this.logger.error(`Environment variable ${name} is not set`, 'AuthenticateGuard');
29
+ throw new common_1.InternalServerErrorException('Server configuration error');
30
+ }
27
31
  return v;
28
32
  }
29
33
  async canActivate(context) {
30
34
  var _a;
31
35
  const req = context.switchToHttp().getRequest();
32
- const res = context.switchToHttp().getResponse();
33
36
  const authHeader = (_a = req.headers) === null || _a === void 0 ? void 0 : _a.authorization;
34
37
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
35
- res.status(401).json({ message: 'Missing or invalid Authorization header.' });
36
- return false;
38
+ throw new common_1.UnauthorizedException('Missing or invalid Authorization header');
37
39
  }
38
40
  const token = authHeader.split(' ')[1];
39
41
  try {
40
42
  const decoded = jsonwebtoken_1.default.verify(token, this.getEnv('JWT_SECRET'));
41
43
  const user = await this.users.findById(decoded.sub);
42
44
  if (!user) {
43
- res.status(401).json({ message: 'User not found.' });
44
- return false;
45
+ throw new common_1.UnauthorizedException('User not found');
45
46
  }
46
47
  if (!user.isVerified) {
47
- res.status(403).json({ message: 'Email not verified.' });
48
- return false;
48
+ throw new common_1.ForbiddenException('Email not verified. Please check your inbox');
49
49
  }
50
50
  if (user.isBanned) {
51
- res.status(403).json({ message: 'Account banned.' });
52
- return false;
51
+ throw new common_1.ForbiddenException('Account has been banned. Please contact support');
53
52
  }
53
+ // Check if token was issued before password change
54
54
  if (user.passwordChangedAt && decoded.iat * 1000 < user.passwordChangedAt.getTime()) {
55
- res.status(401).json({ message: 'Token expired.' });
56
- return false;
55
+ throw new common_1.UnauthorizedException('Token expired due to password change. Please login again');
57
56
  }
58
57
  req.user = decoded;
59
58
  return true;
60
59
  }
61
- catch {
62
- res.status(401).json({ message: 'Invalid access token.' });
63
- return false;
60
+ catch (error) {
61
+ if (error instanceof common_1.UnauthorizedException || error instanceof common_1.ForbiddenException) {
62
+ throw error;
63
+ }
64
+ if (error.name === 'TokenExpiredError') {
65
+ throw new common_1.UnauthorizedException('Access token has expired');
66
+ }
67
+ if (error.name === 'JsonWebTokenError') {
68
+ throw new common_1.UnauthorizedException('Invalid access token');
69
+ }
70
+ if (error.name === 'NotBeforeError') {
71
+ throw new common_1.UnauthorizedException('Token not yet valid');
72
+ }
73
+ this.logger.error(`Authentication failed: ${error.message}`, error.stack, 'AuthenticateGuard');
74
+ throw new common_1.UnauthorizedException('Authentication failed');
64
75
  }
65
76
  }
66
77
  };
67
78
  exports.AuthenticateGuard = AuthenticateGuard;
68
79
  exports.AuthenticateGuard = AuthenticateGuard = __decorate([
69
80
  (0, common_1.Injectable)(),
70
- __metadata("design:paramtypes", [user_repository_1.UserRepository])
81
+ __metadata("design:paramtypes", [user_repository_1.UserRepository,
82
+ logger_service_1.LoggerService])
71
83
  ], AuthenticateGuard);
@@ -1,7 +1,9 @@
1
1
  import { RoleRepository } from '../repositories/role.repository';
2
+ import { LoggerService } from './logger.service';
2
3
  export declare class AdminRoleService {
3
4
  private readonly roles;
5
+ private readonly logger;
4
6
  private adminRoleId?;
5
- constructor(roles: RoleRepository);
7
+ constructor(roles: RoleRepository, logger: LoggerService);
6
8
  loadAdminRoleId(): Promise<string>;
7
9
  }
@@ -12,22 +12,36 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.AdminRoleService = void 0;
13
13
  const common_1 = require("@nestjs/common");
14
14
  const role_repository_1 = require("../repositories/role.repository");
15
+ const logger_service_1 = require("./logger.service");
15
16
  let AdminRoleService = class AdminRoleService {
16
- constructor(roles) {
17
+ constructor(roles, logger) {
17
18
  this.roles = roles;
19
+ this.logger = logger;
18
20
  }
19
21
  async loadAdminRoleId() {
20
- if (this.adminRoleId)
22
+ try {
23
+ if (this.adminRoleId)
24
+ return this.adminRoleId;
25
+ const admin = await this.roles.findByName('admin');
26
+ if (!admin) {
27
+ this.logger.error('Admin role not found - seed data may be missing', 'AdminRoleService');
28
+ throw new common_1.InternalServerErrorException('System configuration error');
29
+ }
30
+ this.adminRoleId = admin._id.toString();
21
31
  return this.adminRoleId;
22
- const admin = await this.roles.findByName('admin');
23
- if (!admin)
24
- throw new Error('Admin role not seeded.');
25
- this.adminRoleId = admin._id.toString();
26
- return this.adminRoleId;
32
+ }
33
+ catch (error) {
34
+ if (error instanceof common_1.InternalServerErrorException) {
35
+ throw error;
36
+ }
37
+ this.logger.error(`Failed to load admin role: ${error.message}`, error.stack, 'AdminRoleService');
38
+ throw new common_1.InternalServerErrorException('Failed to verify admin permissions');
39
+ }
27
40
  }
28
41
  };
29
42
  exports.AdminRoleService = AdminRoleService;
30
43
  exports.AdminRoleService = AdminRoleService = __decorate([
31
44
  (0, common_1.Injectable)(),
32
- __metadata("design:paramtypes", [role_repository_1.RoleRepository])
45
+ __metadata("design:paramtypes", [role_repository_1.RoleRepository,
46
+ logger_service_1.LoggerService])
33
47
  ], AdminRoleService);
@@ -3,11 +3,13 @@ import { RegisterDto } from '../dtos/auth/register.dto';
3
3
  import { LoginDto } from '../dtos/auth/login.dto';
4
4
  import { MailService } from './mail.service';
5
5
  import { RoleRepository } from '../repositories/role.repository';
6
+ import { LoggerService } from './logger.service';
6
7
  export declare class AuthService {
7
8
  private readonly users;
8
9
  private readonly mail;
9
10
  private readonly roles;
10
- constructor(users: UserRepository, mail: MailService, roles: RoleRepository);
11
+ private readonly logger;
12
+ constructor(users: UserRepository, mail: MailService, roles: RoleRepository, logger: LoggerService);
11
13
  private resolveExpiry;
12
14
  private signAccessToken;
13
15
  private signRefreshToken;
@@ -25,9 +27,11 @@ export declare class AuthService {
25
27
  }>;
26
28
  verifyEmail(token: string): Promise<{
27
29
  ok: boolean;
30
+ message: string;
28
31
  }>;
29
32
  resendVerification(email: string): Promise<{
30
33
  ok: boolean;
34
+ message: string;
31
35
  }>;
32
36
  login(dto: LoginDto): Promise<{
33
37
  accessToken: string;
@@ -39,11 +43,14 @@ export declare class AuthService {
39
43
  }>;
40
44
  forgotPassword(email: string): Promise<{
41
45
  ok: boolean;
46
+ message: string;
42
47
  }>;
43
48
  resetPassword(token: string, newPassword: string): Promise<{
44
49
  ok: boolean;
50
+ message: string;
45
51
  }>;
46
52
  deleteAccount(userId: string): Promise<{
47
53
  ok: boolean;
54
+ message: string;
48
55
  }>;
49
56
  }