diff --git a/custom.d.ts b/custom.d.ts new file mode 100644 index 0000000..c4b240b --- /dev/null +++ b/custom.d.ts @@ -0,0 +1,7 @@ +import { TokenPayload } from "./src/shared/modules/auth/types/TokenPayload.js"; + +declare module "express-serve-static-core" { + export interface Request { + tokenPayload: TokenPayload; + } +} diff --git a/package-lock.json b/package-lock.json index d53377f..fd79d5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "got": "^14.6.6", "http-status-codes": "^2.3.0", "inversify": "^6.0.1", + "jose": "^4.15.4", "mime-types": "3.0.2", "mongoose": "^7.5.3", "multer": "2.1.1", @@ -4159,6 +4160,15 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jose": { + "version": "4.15.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.4.tgz", + "integrity": "sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/joycon": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", @@ -9766,6 +9776,11 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "jose": { + "version": "4.15.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.4.tgz", + "integrity": "sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==" + }, "joycon": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", diff --git a/package.json b/package.json index 9267412..b41e93b 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "got": "^14.6.6", "http-status-codes": "^2.3.0", "inversify": "^6.0.1", + "jose": "^4.15.4", "mime-types": "3.0.2", "mongoose": "^7.5.3", "multer": "2.1.1", diff --git a/src/main.rest.ts b/src/main.rest.ts index f973b8f..9dafe1f 100644 --- a/src/main.rest.ts +++ b/src/main.rest.ts @@ -6,6 +6,7 @@ import { createRestContainer } from './rest/rest.container.js'; import { createUserContainer } from './shared/modules/user/user.container.js'; import { createOfferContainer } from './shared/modules/offer/offer.container.js'; import { createCommentContainer } from './shared/modules/comment/comment.container.js'; +import { createAuthContainer } from './shared/modules/auth/auth-container.js'; async function bootstrap() { const appContainer = Container.merge( @@ -13,6 +14,7 @@ async function bootstrap() { createUserContainer(), createOfferContainer(), createCommentContainer(), + createAuthContainer(), ); const application = appContainer.get( diff --git a/src/rest/rest.application.ts b/src/rest/rest.application.ts index 017d029..d23f675 100644 --- a/src/rest/rest.application.ts +++ b/src/rest/rest.application.ts @@ -7,6 +7,7 @@ import { getMongoUri } from '../shared/helpers/database-client.js'; import express, { Express } from 'express'; import { Controller } from '../shared/libs/rest/controller/controller.interface.js'; import { ExceptionFilter } from '../shared/libs/rest/exception-filter/exception-filter.interface.js'; +import { ParseTokenMiddleware } from '../shared/libs/rest/middleware/parse-token.middleware.js'; @injectable() export class RestApplication { @@ -25,6 +26,8 @@ export class RestApplication { private readonly exceptionFilter: ExceptionFilter, @inject(Component.CommentController) private readonly commentController: Controller, + @inject(Component.AuthExceptionFilter) + private readonly authExceptionFilter: ExceptionFilter, ) { this.server = express(); } @@ -42,6 +45,13 @@ export class RestApplication { } private async _initMiddleware() { + const authenticateMiddleware = new ParseTokenMiddleware( + this.config.get('JWT_SECRET'), + ); + + this.server.use( + authenticateMiddleware.execute.bind(authenticateMiddleware), + ); this.server.use(express.json()); this.server.use( '/upload', @@ -61,6 +71,9 @@ export class RestApplication { } private async __initExceptionFilters() { + this.server.use( + this.authExceptionFilter.catch.bind(this.authExceptionFilter), + ); this.server.use(this.exceptionFilter.catch.bind(this.exceptionFilter)); } diff --git a/src/shared/libs/config/rest.config.schema.ts b/src/shared/libs/config/rest.config.schema.ts index 64cc4db..a318da0 100644 --- a/src/shared/libs/config/rest.config.schema.ts +++ b/src/shared/libs/config/rest.config.schema.ts @@ -10,6 +10,7 @@ export type RestSchema = { DB_PORT: string; DB_NAME: string; UPLOAD_FILE_DIRECTORY: string; + JWT_SECRET: string; }; convict.addFormats(validator); @@ -63,4 +64,10 @@ export const restSchema = convict({ env: 'UPLOAD_FILE_DIRECTORY', default: null, }, + JWT_SECRET: { + doc: 'JWT secret', + format: String, + env: 'JWT_SECRET', + default: null, + }, }); diff --git a/src/shared/libs/rest/middleware/parse-token.middleware.ts b/src/shared/libs/rest/middleware/parse-token.middleware.ts new file mode 100644 index 0000000..e71f643 --- /dev/null +++ b/src/shared/libs/rest/middleware/parse-token.middleware.ts @@ -0,0 +1,61 @@ +import { NextFunction, Request, Response } from 'express'; +import { Middleware } from '../index.js'; +import crypto from 'node:crypto'; +import { jwtVerify } from 'jose'; +import { HttpError } from '../errors/index.js'; +import { StatusCodes } from 'http-status-codes'; +import { TokenPayload } from '../../../modules/auth/types/TokenPayload.js'; + +function isTokenPayload(payload: unknown): payload is TokenPayload { + return ( + typeof payload === 'object' && + payload !== null && + 'email' in payload && + typeof payload.email === 'string' && + 'name' in payload && + typeof payload.name === 'string' && + 'id' in payload && + typeof payload.id === 'string' + ); +} + +export class ParseTokenMiddleware implements Middleware { + constructor(private readonly jwtSecret: string) {} + + async execute( + req: Request, + _res: Response, + next: NextFunction, + ): Promise { + const authorizationHeader = req.headers?.authorization?.split(' '); + + if ( + !authorizationHeader || + authorizationHeader?.[0] !== 'Bearer' || + !authorizationHeader?.[1] + ) { + return next(); + } + + const [, token] = authorizationHeader; + try { + const { payload } = await jwtVerify( + token, + crypto.createSecretKey(this.jwtSecret, 'utf-8'), + ); + + if (isTokenPayload(payload)) { + req.tokenPayload = { ...payload }; + } + return next(); + } catch (err) { + return next( + new HttpError( + StatusCodes.UNAUTHORIZED, + 'Invalid token', + 'AuthenticateMiddleware', + ), + ); + } + } +} diff --git a/src/shared/libs/rest/middleware/private-route.middleware.ts b/src/shared/libs/rest/middleware/private-route.middleware.ts new file mode 100644 index 0000000..3d9f247 --- /dev/null +++ b/src/shared/libs/rest/middleware/private-route.middleware.ts @@ -0,0 +1,18 @@ +import { NextFunction, Request, Response } from 'express'; +import { Middleware } from '../index.js'; +import { HttpError } from '../errors/index.js'; +import { StatusCodes } from 'http-status-codes'; + +export class PrivateRouteMiddleware implements Middleware { + execute(req: Request, _res: Response, next: NextFunction): void { + if (!req.tokenPayload) { + throw new HttpError( + StatusCodes.UNAUTHORIZED, + 'Unauthorized', + 'PrivateRouteMiddleware', + ); + } + + return next(); + } +} diff --git a/src/shared/modules/auth/auth-container.ts b/src/shared/modules/auth/auth-container.ts new file mode 100644 index 0000000..3f71fc2 --- /dev/null +++ b/src/shared/modules/auth/auth-container.ts @@ -0,0 +1,19 @@ +import { Container } from 'inversify'; +import { Component } from '../../types/container.js'; +import { DefaultAuthService } from './auth-service.js'; +import { AuthExceptionFilter } from './auth.exception-filter.js'; + +export function createAuthContainer() { + const authContainer = new Container(); + + authContainer + .bind(Component.AuthService) + .to(DefaultAuthService) + .inSingletonScope(); + authContainer + .bind(Component.AuthExceptionFilter) + .to(AuthExceptionFilter) + .inSingletonScope(); + + return authContainer; +} diff --git a/src/shared/modules/auth/auth-service.interface.ts b/src/shared/modules/auth/auth-service.interface.ts new file mode 100644 index 0000000..66098ab --- /dev/null +++ b/src/shared/modules/auth/auth-service.interface.ts @@ -0,0 +1,6 @@ +import { LoginUserDTO, UserEntity } from '../user/index.js'; + +export interface AuthServiceInterface { + authenticate(user: UserEntity): Promise; + verify(dto: LoginUserDTO): Promise; +} diff --git a/src/shared/modules/auth/auth-service.ts b/src/shared/modules/auth/auth-service.ts new file mode 100644 index 0000000..dae5c63 --- /dev/null +++ b/src/shared/modules/auth/auth-service.ts @@ -0,0 +1,59 @@ +import { inject, injectable } from 'inversify'; +import { LoginUserDTO, UserEntity } from '../user/index.js'; +import { AuthServiceInterface } from './auth-service.interface.js'; +import { Component } from '../../types/container.js'; +import { Logger } from '../../libs/logger/index.js'; +import { UserService } from '../user/user-service.js'; +import { Config, RestSchema } from '../../libs/config/index.js'; +import * as crypto from 'node:crypto'; +import { TokenPayload } from './types/TokenPayload.js'; +import { SignJWT } from 'jose'; +import { JWT_ALGORITHM, JWT_EXPIRED } from './auth.constant.js'; +import { + UserNotFoundException, + UserPasswordIncorrectException, +} from './errors/index.js'; + +@injectable() +export class DefaultAuthService implements AuthServiceInterface { + constructor( + @inject(Component.Logger) private readonly logger: Logger, + @inject(Component.UserService) private readonly userService: UserService, + @inject(Component.Config) private readonly config: Config, + ) {} + + public async authenticate(user: UserEntity): Promise { + const secret = this.config.get('JWT_SECRET'); + const secretKey = crypto.createSecretKey(secret, 'utf-8'); + + const tokenPayload: TokenPayload = { + email: user.email, + name: user.name, + id: user._id.toString(), + }; + + this.logger.info(`Create token for ${user.email}`); + + return new SignJWT(tokenPayload) + .setProtectedHeader({ alg: JWT_ALGORITHM }) + .setIssuedAt() + .setExpirationTime(JWT_EXPIRED) + .sign(secretKey); + } + + public async verify(dto: LoginUserDTO): Promise { + const user = await this.userService.findByEmail(dto.email); + + if (!user) { + this.logger.warn(`User with ${dto.email} not found`); + throw new UserNotFoundException(); + } + + if (!user.verifyPassword(dto.password, this.config.get('SALT'))) { + this.logger.warn(`Incorrect password for user ${dto.email}`); + throw new UserPasswordIncorrectException(); + } + + return user; + } +} diff --git a/src/shared/modules/auth/auth.constant.ts b/src/shared/modules/auth/auth.constant.ts new file mode 100644 index 0000000..269747d --- /dev/null +++ b/src/shared/modules/auth/auth.constant.ts @@ -0,0 +1,2 @@ +export const JWT_ALGORITHM = 'HS256'; +export const JWT_EXPIRED = '2d'; diff --git a/src/shared/modules/auth/auth.exception-filter.ts b/src/shared/modules/auth/auth.exception-filter.ts new file mode 100644 index 0000000..ef8fc3c --- /dev/null +++ b/src/shared/modules/auth/auth.exception-filter.ts @@ -0,0 +1,31 @@ +import { NextFunction, Request, Response } from 'express'; +import { ExceptionFilter } from '../../libs/rest/exception-filter/exception-filter.interface.js'; +import { BaseUserException } from './index.js'; +import { Logger } from '../../libs/logger/index.js'; +import { Component } from '../../types/container.js'; +import { inject, injectable } from 'inversify'; + +@injectable() +export class AuthExceptionFilter implements ExceptionFilter { + constructor(@inject(Component.Logger) private readonly logger: Logger) { + this.logger.info('AuthExceptionFilter was created'); + } + + public catch( + error: unknown, + _req: Request, + res: Response, + next: NextFunction, + ): void { + if (!(error instanceof BaseUserException)) { + return next(error); + } + + this.logger.error(`[AuthModule] ${error.message}`, error); + + res.status(error.httpStatusCode).json({ + type: 'AUTHORIZATION', + error: error.message, + }); + } +} diff --git a/src/shared/modules/auth/errors/base-user.exception.ts b/src/shared/modules/auth/errors/base-user.exception.ts new file mode 100644 index 0000000..c9eb2a2 --- /dev/null +++ b/src/shared/modules/auth/errors/base-user.exception.ts @@ -0,0 +1,7 @@ +import { HttpError } from '../../../libs/rest/errors/index.js'; + +export class BaseUserException extends HttpError { + constructor(httpStatusCode: number, message: string) { + super(httpStatusCode, message); + } +} diff --git a/src/shared/modules/auth/errors/index.ts b/src/shared/modules/auth/errors/index.ts new file mode 100644 index 0000000..4aa1752 --- /dev/null +++ b/src/shared/modules/auth/errors/index.ts @@ -0,0 +1,3 @@ +export * from './base-user.exception.js'; +export * from './user-password-incorrect.exception.js'; +export * from './user-not-found.exception.js'; diff --git a/src/shared/modules/auth/errors/user-not-found.exception.ts b/src/shared/modules/auth/errors/user-not-found.exception.ts new file mode 100644 index 0000000..d5833d0 --- /dev/null +++ b/src/shared/modules/auth/errors/user-not-found.exception.ts @@ -0,0 +1,8 @@ +import { StatusCodes } from 'http-status-codes'; +import { BaseUserException } from './base-user.exception.js'; + +export class UserNotFoundException extends BaseUserException { + constructor() { + super(StatusCodes.NOT_FOUND, 'User not found'); + } +} diff --git a/src/shared/modules/auth/errors/user-password-incorrect.exception.ts b/src/shared/modules/auth/errors/user-password-incorrect.exception.ts new file mode 100644 index 0000000..fa2e994 --- /dev/null +++ b/src/shared/modules/auth/errors/user-password-incorrect.exception.ts @@ -0,0 +1,8 @@ +import { StatusCodes } from 'http-status-codes'; +import { BaseUserException } from './base-user.exception.js'; + +export class UserPasswordIncorrectException extends BaseUserException { + constructor() { + super(StatusCodes.UNAUTHORIZED, 'User password incorrect'); + } +} diff --git a/src/shared/modules/auth/index.ts b/src/shared/modules/auth/index.ts new file mode 100644 index 0000000..935ea1f --- /dev/null +++ b/src/shared/modules/auth/index.ts @@ -0,0 +1,3 @@ +export * from './auth-container.js'; +export * from './auth-service.interface.js'; +export * from './errors/index.js'; diff --git a/src/shared/modules/auth/types/TokenPayload.ts b/src/shared/modules/auth/types/TokenPayload.ts new file mode 100644 index 0000000..648388e --- /dev/null +++ b/src/shared/modules/auth/types/TokenPayload.ts @@ -0,0 +1,5 @@ +export type TokenPayload = { + email: string; + name: string; + id: string; +}; diff --git a/src/shared/modules/comment/comment-controller.ts b/src/shared/modules/comment/comment-controller.ts index bdf4c74..8e67cec 100644 --- a/src/shared/modules/comment/comment-controller.ts +++ b/src/shared/modules/comment/comment-controller.ts @@ -9,6 +9,7 @@ import { Request, Response } from 'express'; import { CommentRDO } from './rdo/comment-rdo.js'; import { CommentDto } from './dto/comment-dto.js'; import { ValidateDTOMiddleware } from '../../libs/rest/middleware/validate-object.middleware.js'; +import { PrivateRouteMiddleware } from '../../libs/rest/middleware/private-route.middleware.js'; export class CommentController extends BaseController { constructor( @@ -22,7 +23,10 @@ export class CommentController extends BaseController { path: '/', method: HttpMethod.Post, handler: this.create, - middlewares: [new ValidateDTOMiddleware(CommentDto)], + middlewares: [ + new ValidateDTOMiddleware(CommentDto), + new PrivateRouteMiddleware(), + ], }); } diff --git a/src/shared/modules/offer/offer-controller.ts b/src/shared/modules/offer/offer-controller.ts index be41068..0c67e7d 100644 --- a/src/shared/modules/offer/offer-controller.ts +++ b/src/shared/modules/offer/offer-controller.ts @@ -16,6 +16,8 @@ import { CreateOfferDto } from './dto/offer-dto.js'; import { UpdateOfferDto } from './dto/update-dto.js'; import { DocumentExistsMiddleware } from '../../libs/rest/middleware/document-exists.middleware.js'; import { DocumentExists } from '../../libs/rest/types/document-exists.interface.js'; +import { PrivateRouteMiddleware } from '../../libs/rest/middleware/private-route.middleware.js'; +import { UserServiceInterface } from '../user/user-service.interface.js'; export class OfferController extends BaseController { constructor( @@ -24,6 +26,8 @@ export class OfferController extends BaseController { private readonly offerService: OfferServiceInterface & DocumentExists, @inject(Component.CommentService) private readonly commentService: CommentServiceInterface, + @inject(Component.UserService) + private readonly userService: UserServiceInterface, ) { super(logger); @@ -32,13 +36,17 @@ export class OfferController extends BaseController { path: '/', method: HttpMethod.Post, handler: this.create, - middlewares: [new ValidateDTOMiddleware(CreateOfferDto)], + middlewares: [ + new PrivateRouteMiddleware(), + new ValidateDTOMiddleware(CreateOfferDto), + ], }); this.addRoute({ path: '/:offerId', method: HttpMethod.Put, handler: this.update, middlewares: [ + new PrivateRouteMiddleware(), new ValidateObjectIdMiddleware('offerId'), new ValidateDTOMiddleware(UpdateOfferDto), new DocumentExistsMiddleware('offerId', 'offer', this.offerService), @@ -49,6 +57,7 @@ export class OfferController extends BaseController { method: HttpMethod.Delete, handler: this.delete, middlewares: [ + new PrivateRouteMiddleware(), new ValidateObjectIdMiddleware('offerId'), new DocumentExistsMiddleware('offerId', 'offer', this.offerService), ], @@ -65,9 +74,20 @@ export class OfferController extends BaseController { } public async index(req: Request, res: Response) { - console.log(req.body); const offers = await this.offerService.find(); - const responseData = fillDTO(OfferRDO, offers); + let favoritesIds: string[] = []; + + if (req.tokenPayload) { + const user = await this.userService.findById(req.tokenPayload.id); + favoritesIds = + user?.favorites.map((offer) => offer.toString()) ?? ([] as string[]); + } + + const offersWithStatus = offers.map((offer) => ({ + ...offer.toObject(), + isFavorite: favoritesIds.includes(offer.id), + })); + const responseData = fillDTO(OfferRDO, offersWithStatus); this.ok(res, responseData); } @@ -83,7 +103,10 @@ export class OfferController extends BaseController { return this.logger.error(error.message, error); } - const result = await this.offerService.createOffer(req.body); + const result = await this.offerService.createOffer({ + ...req.body, + userId: req.tokenPayload.id, + }); this.created(res, fillDTO(OfferRDO, result)); } diff --git a/src/shared/modules/user/rdo/user.rdo.ts b/src/shared/modules/user/rdo/user.rdo.ts index 6da44a2..95f044d 100644 --- a/src/shared/modules/user/rdo/user.rdo.ts +++ b/src/shared/modules/user/rdo/user.rdo.ts @@ -16,3 +16,11 @@ export class UserRDO { @Expose() favorites: string[]; } + +export class LoggedUserRDO { + @Expose() + public email: string; + + @Expose() + public token: string; +} diff --git a/src/shared/modules/user/user-controller.ts b/src/shared/modules/user/user-controller.ts index d60315a..6979ca3 100644 --- a/src/shared/modules/user/user-controller.ts +++ b/src/shared/modules/user/user-controller.ts @@ -7,11 +7,13 @@ import { Logger } from '../../libs/logger/index.js'; import { UserServiceInterface } from './user-service.interface.js'; import { StatusCodes } from 'http-status-codes'; import { Config, RestSchema } from '../../libs/config/index.js'; -import { UserRDO } from './rdo/user.rdo.js'; +import { LoggedUserRDO, UserRDO } from './rdo/user.rdo.js'; import { fillDTO } from '../../helpers/common.js'; import { HttpError } from '../../libs/rest/errors/http-error.js'; import { ValidateDTOMiddleware } from '../../libs/rest/middleware/validate-object.middleware.js'; import { CreateUserDto, LoginUserDTO } from './dto/user-dto.js'; +import { AuthServiceInterface, UserNotFoundException } from '../auth/index.js'; +import { PrivateRouteMiddleware } from '../../libs/rest/middleware/private-route.middleware.js'; export class UserController extends BaseController { constructor( @@ -19,6 +21,8 @@ export class UserController extends BaseController { @inject(Component.UserService) private readonly userService: UserServiceInterface, @inject(Component.Config) private readonly config: Config, + @inject(Component.AuthService) + private readonly authService: AuthServiceInterface, ) { super(logger); this.addRoute({ @@ -40,6 +44,13 @@ export class UserController extends BaseController { method: HttpMethod.Post, handler: this.uploadAvatar, }); + + this.addRoute({ + path: '/login', + method: HttpMethod.Get, + handler: this.checkAuthenticate, + middlewares: [new PrivateRouteMiddleware()], + }); } public async create(req: Request, res: Response) { @@ -59,21 +70,18 @@ export class UserController extends BaseController { this.created(res, fillDTO(UserRDO, result)); } - public async auth(req: Request, _res: Response) { - const existsUser = await this.userService.findByEmail(req.body.email); + public async auth(req: Request, res: Response) { + const user = await this.authService.verify(req.body as LoginUserDTO); - if (!existsUser) { - throw new HttpError( - StatusCodes.NOT_FOUND, - `User with email ${req.body.email} not found`, - ); + if (!user) { + throw new UserNotFoundException(); } - throw new HttpError( - StatusCodes.NOT_IMPLEMENTED, - 'Not implemented', - 'UserController', - ); + const token = await this.authService.authenticate(user); + + const response = fillDTO(LoggedUserRDO, { email: user.email, token }); + + this.ok(res, response); } public uploadAvatar(req: Request, res: Response) { @@ -81,4 +89,17 @@ export class UserController extends BaseController { file: req.file?.path, }); } + + public async checkAuthenticate(req: Request, res: Response) { + const user = await this.userService.findByEmail(req.tokenPayload.email); + + if (!user) { + throw new HttpError( + StatusCodes.UNAUTHORIZED, + 'Unauthorized', + 'UserController', + ); + } + this.ok(res, fillDTO(LoggedUserRDO, user)); + } } diff --git a/src/shared/modules/user/user-service.interface.ts b/src/shared/modules/user/user-service.interface.ts index 065559c..9cb64c4 100644 --- a/src/shared/modules/user/user-service.interface.ts +++ b/src/shared/modules/user/user-service.interface.ts @@ -6,7 +6,7 @@ import { OfferEntity } from '../offer/offer.entity.js'; export interface UserServiceInterface { register(dto: CreateUserDto, salt: string): Promise>; findByEmail(email: string): Promise | null>; - findById(id: number): Promise | null>; + findById(id: string): Promise | null>; findFavoriteOffers(userId: number): Promise[]>; toggleFavoriteOffer(offerId: string, userId: number): Promise; } diff --git a/src/shared/modules/user/user-service.ts b/src/shared/modules/user/user-service.ts index f2f7851..9412d7f 100644 --- a/src/shared/modules/user/user-service.ts +++ b/src/shared/modules/user/user-service.ts @@ -31,7 +31,7 @@ export class UserService implements UserServiceInterface { return this.userModel.findOne({ email }).exec(); } - async findById(id: number): Promise | null> { + async findById(id: string): Promise | null> { return this.userModel.findById(id).exec(); } diff --git a/src/shared/modules/user/user.entity.ts b/src/shared/modules/user/user.entity.ts index f0fb7ef..925341c 100644 --- a/src/shared/modules/user/user.entity.ts +++ b/src/shared/modules/user/user.entity.ts @@ -53,6 +53,11 @@ export class UserEntity extends defaultClasses.TimeStamps implements User { this.password = createSha256(password, salt); } + public verifyPassword(password: string, salt: string): boolean { + const hash = createSha256(password, salt); + return this.password === hash; + } + public getPassword(): string { return this.password; } diff --git a/src/shared/types/container.ts b/src/shared/types/container.ts index 8a3a25f..7125145 100644 --- a/src/shared/types/container.ts +++ b/src/shared/types/container.ts @@ -13,4 +13,6 @@ export const Component = { UserController: Symbol.for('UserController'), ExceptionFilter: Symbol.for('ExceptionFilter'), CommentController: Symbol.for('CommentController'), + AuthService: Symbol.for('AuthService'), + AuthExceptionFilter: Symbol.for('AuthExceptionFilter'), } as const; diff --git a/tsconfig.json b/tsconfig.json index 54102f0..8686727 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -37,5 +37,6 @@ ], "ts-node": { "esm": true - } + }, + "files": ["custom.d.ts"] }