@ciscode/authentication-kit 1.2.1 → 1.4.1

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:**
@@ -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,
@@ -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,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;
@@ -19,15 +21,21 @@ export declare class AuthService {
19
21
  accessToken: string;
20
22
  refreshToken: string;
21
23
  }>;
24
+ getMe(userId: string): Promise<{
25
+ ok: boolean;
26
+ data: any;
27
+ }>;
22
28
  register(dto: RegisterDto): Promise<{
23
29
  id: any;
24
30
  email: string;
25
31
  }>;
26
32
  verifyEmail(token: string): Promise<{
27
33
  ok: boolean;
34
+ message: string;
28
35
  }>;
29
36
  resendVerification(email: string): Promise<{
30
37
  ok: boolean;
38
+ message: string;
31
39
  }>;
32
40
  login(dto: LoginDto): Promise<{
33
41
  accessToken: string;
@@ -39,11 +47,14 @@ export declare class AuthService {
39
47
  }>;
40
48
  forgotPassword(email: string): Promise<{
41
49
  ok: boolean;
50
+ message: string;
42
51
  }>;
43
52
  resetPassword(token: string, newPassword: string): Promise<{
44
53
  ok: boolean;
54
+ message: string;
45
55
  }>;
46
56
  deleteAccount(userId: string): Promise<{
47
57
  ok: boolean;
58
+ message: string;
48
59
  }>;
49
60
  }