diff --git a/package.json b/package.json index 8f5f493a90cdbbcf47cf896927b7df07e5ee4616..ce7cb37cf1166ee2c63ff9e6c7f8888476489125 100644 --- a/package.json +++ b/package.json @@ -27,12 +27,18 @@ "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", + "@nestjs/jwt": "^9.0.0", "@nestjs/mongoose": "^9.2.1", + "@nestjs/passport": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.13.2", + "eslint-import-resolver-typescript": "^3.5.2", "fastify": "^4.9.2", "mongoose": "^6.7.2", + "passport": "^0.6.0", + "passport-jwt": "^4.0.0", + "passport-local": "^1.0.0", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^7.2.0" @@ -44,6 +50,8 @@ "@types/express": "^4.17.13", "@types/jest": "28.1.8", "@types/node": "^16.0.0", + "@types/passport-jwt": "^3.0.8", + "@types/passport-local": "^1.0.34", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", diff --git a/src/app.module.ts b/src/app.module.ts index e794431d67f12d769b4c16cfd6d8d637472b597e..b0ed19151deea8f71a8695e2c3ae05f300f3642f 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -3,6 +3,7 @@ import { MongooseModule } from '@nestjs/mongoose'; import config from './config'; import { PeopleModule } from './people/people.module'; import { GroupsModule } from './groups/groups.module'; +import { LoginModule } from './login/login.module'; import { InternshipsModule } from './internships/internships.module'; @Module({ @@ -11,6 +12,8 @@ import { InternshipsModule } from './internships/internships.module'; GroupsModule, InternshipsModule, MongooseModule.forRoot(config.mongodb.uri), + LoginModule ], + }) export class AppModule {} diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000000000000000000000000000000000000..70fd993f27a2084f8237ccfb660e5f7449c4ad49 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,3 @@ +export const jwtConstants = { + secret: 'secretKey', +} \ No newline at end of file diff --git a/src/login/jwt.strategy.ts b/src/login/jwt.strategy.ts new file mode 100644 index 0000000000000000000000000000000000000000..f7edbefbb555ace2d3a353537385630e84155784 --- /dev/null +++ b/src/login/jwt.strategy.ts @@ -0,0 +1,17 @@ +import { PassportStrategy } from "@nestjs/passport"; +import { ExtractJwt, Strategy } from "passport-jwt"; +import { jwtConstants } from '../constants'; + +export class JwtStrategy extends PassportStrategy(Strategy) { + constructor() { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + secretOrKey: jwtConstants.secret + }) + } + + async validate(payload: any) { + return {userId: payload.sub, email: payload.email} + } +} \ No newline at end of file diff --git a/src/login/local.strategy.ts b/src/login/local.strategy.ts new file mode 100644 index 0000000000000000000000000000000000000000..de96605a5c27eb76f91fa49f1f61070f1c474374 --- /dev/null +++ b/src/login/local.strategy.ts @@ -0,0 +1,19 @@ +import { Injectable, UnauthorizedException } from "@nestjs/common"; +import { PassportStrategy } from "@nestjs/passport"; +import { Strategy } from "passport-local"; +import { LoginService } from "./login.service"; + +@Injectable() +export class LocalStrategy extends PassportStrategy(Strategy) { + constructor(private loginService: LoginService) { + super(); + } + + async validate(username: string, password: string): Promise<any> { + const user = await this.loginService.validateUser(username, password); + if (!user) { + throw new UnauthorizedException(); + } + return user; + } + } \ No newline at end of file diff --git a/src/login/login.controller.ts b/src/login/login.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..3722154a8ee47b218a44efe18f13273448d6e028 --- /dev/null +++ b/src/login/login.controller.ts @@ -0,0 +1,17 @@ +import { Controller, Post, Request, UseGuards } from '@nestjs/common'; +import { Body } from '@nestjs/common/decorators/http/route-params.decorator'; +import { AuthGuard } from '@nestjs/passport'; +import { Login } from 'src/types/login'; +import { LoginService } from './login.service'; + +@Controller('login') +export class LoginController { + constructor(private loginService: LoginService) {} + +// @UseGuards(AuthGuard('local')) + @Post() + async login(@Body() req: Login) { + console.log('controller ' + req.email + req.passwordHash); + return this.loginService.login(req); + } +} diff --git a/src/login/login.module.ts b/src/login/login.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..d7ab1bb84d9ed57786230c2a120fa9ad470b937e --- /dev/null +++ b/src/login/login.module.ts @@ -0,0 +1,25 @@ +import { Module } from '@nestjs/common'; +import { JwtModule } from '@nestjs/jwt'; +import { PassportModule } from '@nestjs/passport'; +import { PeopleModule } from 'src/people/people.module'; +import { LoginService } from './login.service'; +import { LoginController } from './login.controller'; +import { jwtConstants } from '../constants'; +// import { PeopleService } from 'src/people/people.service'; +import { LocalStrategy } from './local.strategy'; +import { JwtStrategy } from './jwt.strategy'; + +@Module({ + imports: [ + PeopleModule, + PassportModule, + JwtModule.register({ + secret: jwtConstants.secret, + signOptions: { expiresIn: '300s' }, + }), + ], + controllers: [LoginController], + providers: [LoginService, LocalStrategy, JwtStrategy], + exports: [LoginService], +}) +export class LoginModule {} diff --git a/src/login/login.service.ts b/src/login/login.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..e756721f87be1e49f71587c23a975fa381fbaafa --- /dev/null +++ b/src/login/login.service.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@nestjs/common'; +import { JwtService } from '@nestjs/jwt'; +import { PeopleService } from 'src/people/people.service'; + +@Injectable() +export class LoginService { + constructor( + private peopleService: PeopleService, + private jwtService: JwtService, + ) {} + + async validateUser(username: string, pass: string): Promise<any> { + const user = await this.peopleService.login(username, pass); + if (user && user.passwordHash == pass) { + const { passwordHash, ...result } = user; + return result; + } + return null; + } + + async login(user: any) { + const req = await this.validateUser(user.email, user.passwordHash); + if(req != null) { + const payload = { email: user.email, sub: user.userId }; + return { + access_token: this.jwtService.sign(payload), + }; + } + } +} diff --git a/src/people/dao/people.dao.ts b/src/people/dao/people.dao.ts index 39dc137ba81a5cdcdacf215cbe1a9c38ba04313d..61f707a6c3c945f467e674b20e255bb3d1967009 100644 --- a/src/people/dao/people.dao.ts +++ b/src/people/dao/people.dao.ts @@ -17,6 +17,15 @@ export class PeopleDao { private readonly _peopleModel: Model<People>, ) {} + login = (email: string, password: string): Promise<People | void> => + new Promise((resolve, reject) => { + this._peopleModel.findOne({email : email, passwordHash : password}, (err, value) => { + if (err) reject(err.message); + if (!value) reject(new NotFoundException('Email or password is incorrect!')); + resolve(value); + }); + }); + find = (): Promise<People[]> => new Promise((resolve, reject) => { this._peopleModel.find({}, {}, {}, (err, value) => { diff --git a/src/people/people.controller.ts b/src/people/people.controller.ts index 8d40c28a36275093db88ba1b57c7a03ca670e677..0837bc2eb3c625217a86ae0585cfe4440bf604d3 100644 --- a/src/people/people.controller.ts +++ b/src/people/people.controller.ts @@ -7,6 +7,7 @@ import { Param, Body, UseInterceptors, + UseGuards } from '@nestjs/common'; import { CreatePeopleDto } from './dto/create-people.dto'; import { UpdatePeopleDto } from './dto/update-people.dto'; @@ -15,16 +16,26 @@ import { HttpInterceptor } from '../interceptors/http.interceptor'; // import { UpdatePeopleDto } from './dto/update-people.dto'; import { PeopleEntity } from './entities/people.entity'; import { PeopleService } from './people.service'; +import { AuthGuard } from '@nestjs/passport'; + + interface Login { + email: string; + password: string; +} @Controller('people') @UseInterceptors(HttpInterceptor) export class PeopleController { constructor(private readonly _peopleService: PeopleService) {} + + @UseGuards(AuthGuard('jwt')) @Get() findAll(): Promise<PeopleEntity[] | void> { return this._peopleService.findAll(); } + + @UseGuards(AuthGuard('jwt')) @Get(':id') findOne(@Param() params: { id: string }): Promise<PeopleEntity | void> { return this._peopleService.findOne(params.id); diff --git a/src/people/people.module.ts b/src/people/people.module.ts index e11d14169bdb3af7eedc37dc2419c5dad35a2849..78b11e07eadc091633ad3dc99eb8d305de564742 100644 --- a/src/people/people.module.ts +++ b/src/people/people.module.ts @@ -6,11 +6,11 @@ import { PeopleService } from './people.service'; import { People, PeopleSchema } from './schemas/people.schema'; @Module({ - imports: [ - MongooseModule.forFeature([{ name: People.name, schema: PeopleSchema }]), - ], - controllers: [PeopleController], - providers: [PeopleService, PeopleDao, Logger], - }) - - export class PeopleModule {} \ No newline at end of file + imports: [ + MongooseModule.forFeature([{ name: People.name, schema: PeopleSchema }]), + ], + controllers: [PeopleController], + providers: [PeopleService, PeopleDao, Logger], + exports: [PeopleService], +}) +export class PeopleModule {} diff --git a/src/people/people.service.ts b/src/people/people.service.ts index e97344a5d049104065cd802bd97b5ba6e7bb5b4b..8f386160981d90dbc239c5b09d66a545e5fb69ee 100644 --- a/src/people/people.service.ts +++ b/src/people/people.service.ts @@ -4,10 +4,16 @@ import { CreatePeopleDto } from './dto/create-people.dto'; import { UpdatePeopleDto } from './dto/update-people.dto'; import { PeopleEntity } from './entities/people.entity'; + + @Injectable() export class PeopleService { + constructor(private readonly _peopleDao: PeopleDao) {} + login = (email: string, password: string): Promise<PeopleEntity | void> => + this._peopleDao.login(email, password); + findAll = (): Promise<PeopleEntity[] | void> => this._peopleDao.find(); findOne = (id: string): Promise<PeopleEntity | void> => diff --git a/src/types/login.ts b/src/types/login.ts new file mode 100644 index 0000000000000000000000000000000000000000..f58a23841e7bfe53b9d43c16d329705900658ae0 --- /dev/null +++ b/src/types/login.ts @@ -0,0 +1,4 @@ +export interface Login { + email: string; + passwordHash: string; +}