@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.
package/README.md CHANGED
@@ -188,14 +188,22 @@ Content-Type: application/json
188
188
  "fname": "Test",
189
189
  "lname": "User"
190
190
  },
191
- "username": "Userrr",
191
+ "username": "custom-username",
192
192
  "email": "user@example.com",
193
193
  "password": "Pa$$word!",
194
194
  "phoneNumber": "+1234567890",
195
- "avatar": "https://example.com/avatar.jpg"
195
+ "avatar": "https://example.com/avatar.jpg",
196
+ "jobTitle": "Software Engineer",
197
+ "company": "Ciscode"
196
198
  }
197
199
  ```
198
200
 
201
+ **Notes:**
202
+
203
+ - `username` is now **optional**. If not provided, it will be auto-generated as `fname-lname` (e.g., `test-user`)
204
+ - `jobTitle` and `company` are **optional** profile fields
205
+ - All other fields work as before
206
+
199
207
  **Response:**
200
208
 
201
209
  ```json
@@ -387,10 +395,12 @@ All permissions are assigned to the `admin` role.
387
395
  fname: string,
388
396
  lname: string
389
397
  },
390
- username: string (unique, 3-30 chars),
398
+ username: string (unique, 3-30 chars, auto-generated as fname-lname if not provided),
391
399
  email: string (unique, validated),
392
400
  phoneNumber?: string (unique, 10-14 digits),
393
401
  avatar?: string (default: 'default.jpg'),
402
+ jobTitle?: string,
403
+ company?: string,
394
404
  password: string (hashed, min 6 chars),
395
405
  roles: ObjectId[] (references Role),
396
406
  isVerified: boolean (default: false),
@@ -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,
@@ -4,10 +4,12 @@ declare class FullNameDto {
4
4
  }
5
5
  export declare class RegisterDto {
6
6
  fullname: FullNameDto;
7
- username: string;
7
+ username?: string;
8
8
  email: string;
9
9
  password: string;
10
10
  phoneNumber?: string;
11
11
  avatar?: string;
12
+ jobTitle?: string;
13
+ company?: string;
12
14
  }
13
15
  export {};
@@ -31,6 +31,7 @@ __decorate([
31
31
  __metadata("design:type", FullNameDto)
32
32
  ], RegisterDto.prototype, "fullname", void 0);
33
33
  __decorate([
34
+ (0, class_validator_1.IsOptional)(),
34
35
  (0, class_validator_1.IsString)(),
35
36
  (0, class_validator_1.MinLength)(3),
36
37
  __metadata("design:type", String)
@@ -54,3 +55,13 @@ __decorate([
54
55
  (0, class_validator_1.IsString)(),
55
56
  __metadata("design:type", String)
56
57
  ], RegisterDto.prototype, "avatar", void 0);
58
+ __decorate([
59
+ (0, class_validator_1.IsOptional)(),
60
+ (0, class_validator_1.IsString)(),
61
+ __metadata("design:type", String)
62
+ ], RegisterDto.prototype, "jobTitle", void 0);
63
+ __decorate([
64
+ (0, class_validator_1.IsOptional)(),
65
+ (0, class_validator_1.IsString)(),
66
+ __metadata("design:type", String)
67
+ ], RegisterDto.prototype, "company", void 0);
@@ -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);
@@ -15,6 +15,8 @@ export declare class User {
15
15
  roles: Types.ObjectId[];
16
16
  isVerified: boolean;
17
17
  isBanned: boolean;
18
+ jobTitle?: string;
19
+ company?: string;
18
20
  }
19
21
  export declare const UserSchema: import("mongoose").Schema<User, import("mongoose").Model<User, any, any, any, Document<unknown, any, User> & User & {
20
22
  _id: Types.ObjectId;
@@ -80,6 +80,14 @@ __decorate([
80
80
  (0, mongoose_1.Prop)({ default: false }),
81
81
  __metadata("design:type", Boolean)
82
82
  ], User.prototype, "isBanned", void 0);
83
+ __decorate([
84
+ (0, mongoose_1.Prop)({ trim: true, sparse: true }),
85
+ __metadata("design:type", String)
86
+ ], User.prototype, "jobTitle", void 0);
87
+ __decorate([
88
+ (0, mongoose_1.Prop)({ trim: true, sparse: true }),
89
+ __metadata("design:type", String)
90
+ ], User.prototype, "company", void 0);
83
91
  exports.User = User = __decorate([
84
92
  (0, mongoose_1.Schema)({ timestamps: true })
85
93
  ], User);
@@ -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
  }