From d936324f5b93d3c4402152bcf8402abed502ff56 Mon Sep 17 00:00:00 2001
From: Nabilsenko <96882497+Nabilsenko@users.noreply.github.com>
Date: Fri, 16 Dec 2022 01:01:06 +0100
Subject: [PATCH] feat: added JWT to login controller

---
 package.json                    |  8 ++++++++
 src/app.module.ts               |  4 +++-
 src/constants.ts                |  3 +++
 src/login/jwt.strategy.ts       | 17 +++++++++++++++++
 src/login/local.strategy.ts     | 19 +++++++++++++++++++
 src/login/login.controller.ts   | 17 +++++++++++++++++
 src/login/login.module.ts       | 25 +++++++++++++++++++++++++
 src/login/login.service.ts      | 30 ++++++++++++++++++++++++++++++
 src/people/people.controller.ts | 11 ++++++-----
 src/people/people.module.ts     | 16 ++++++++--------
 src/people/people.service.ts    |  3 +++
 src/types/login.ts              |  4 ++++
 12 files changed, 143 insertions(+), 14 deletions(-)
 create mode 100644 src/constants.ts
 create mode 100644 src/login/jwt.strategy.ts
 create mode 100644 src/login/local.strategy.ts
 create mode 100644 src/login/login.controller.ts
 create mode 100644 src/login/login.module.ts
 create mode 100644 src/login/login.service.ts
 create mode 100644 src/types/login.ts

diff --git a/package.json b/package.json
index 37f66f7..dcb39ff 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 83454b3..7bfd69e 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 0000000..70fd993
--- /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 0000000..f7edbef
--- /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 0000000..de96605
--- /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 0000000..3722154
--- /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 0000000..d7ab1bb
--- /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 0000000..e756721
--- /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 4b382b4..0837bc2 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 e11d141..78b11e0 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 5ea05b6..8f38616 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 0000000..f58a238
--- /dev/null
+++ b/src/types/login.ts
@@ -0,0 +1,4 @@
+export interface Login {
+  email: string;
+  passwordHash: string;
+}
-- 
GitLab