diff --git a/package.json b/package.json index 37f66f78cbed369f51f01c858c2c9eb87d2f9fc5..dcb39ffbccc83ced9a2dacc88b72b3249abc0d46 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 83454b396fbe455e342ac10495c8d9d035e20455..7bfd69e0ad3ee60b6bd1167b8b1af736477bb1b9 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -3,8 +3,10 @@ import { MongooseModule } from '@nestjs/mongoose'; import { mongodb } from './config'; import { PeopleModule } from './people/people.module'; import { GroupsModule } from './groups/groups.module'; +import { LoginModule } from './login/login.module'; + @Module({ - imports: [PeopleModule, GroupsModule, MongooseModule.forRoot(mongodb.uri)], + imports: [PeopleModule, GroupsModule, MongooseModule.forRoot(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/people.controller.ts b/src/people/people.controller.ts index 4b382b4aba84cb4f27e6531200d28515ab9bd054..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,6 +16,7 @@ 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; @@ -26,15 +28,14 @@ import { PeopleService } from './people.service'; export class PeopleController { constructor(private readonly _peopleService: PeopleService) {} - @Post('/login') - login(@Body() login: Login): Promise<PeopleEntity | void> { - return this._peopleService.login(login.email, login.password); - } - + + @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 5ea05b6f47a55bde346071bc7ec1c5ffd67fd223..8f386160981d90dbc239c5b09d66a545e5fb69ee 100644 --- a/src/people/people.service.ts +++ b/src/people/people.service.ts @@ -4,8 +4,11 @@ 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> => 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; +}