728x90
반응형

윈도우 하위시스템에서 우분투 컨테이너를 사용할 때 기존 mongo 사용법과 달리

sudo service mongod start 명령어가 안먹힐 때가 있다.

 

그럴 땐 먼저 아래 링크로 들어가 복사를 한다.

https://github.com/mongodb/mongo/blob/master/debian/init.d

 

GitHub - mongodb/mongo: The MongoDB Database

The MongoDB Database. Contribute to mongodb/mongo development by creating an account on GitHub.

github.com

 

데비안 계열의 우분투에서 쓰는 init script 내용을 아래 명령어로 들어가 붙여넣기한다.

$ sudo vi /etc/init.d/mongod

 

그 다음 권한을 설정해준다.

$ chmod 755 mongod

 

이제는 명령어가 잘 먹히는 것을 확인할 수 있다.

 $ sudo service mongod start
728x90
반응형
728x90
반응형

swagger에 관한 자세한 내용은 아래 링크에서 확인 가능합니다.

https://docs.nestjs.com/openapi/introduction#bootstrap

 

Documentation | NestJS - A progressive Node.js framework

Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Progamming), FP (Functional Programming), and FRP (Functional Reac

docs.nestjs.com


 

먼저 Swagger를 설치해주자.

$ npm install --save @nestjs/swagger swagger-ui-express

 

 

main.ts 파일에 swagger를 추가해준다.

main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as cookieParser from 'cookie-parser';
import { ValidationPipe } from '@nestjs/common';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.use(cookieParser());

  const config = new DocumentBuilder()
  .setTitle('User example')
  .setDescription('The user API description')
  .setVersion('1.0')
  .build();

  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('swagger', app, document);

  
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist : true, 
      forbidNonWhitelisted : true,
      transform : true
    })
  )
    await app.listen(3001);
  }
bootstrap();

 

그 다음 UserController와 EmailController를 바꿔준다.

 

user.controller.ts

import { Controller, Request, Response, Get, Post, Body, Patch, Param, Delete, Res, Req, UseGuards } from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

//auth
import { LocalAuthGuard } from '../auth/local-auth.guard';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { AuthService } from '../auth/auth.service';

//swagger
import { ApiCreatedResponse, ApiOperation, ApiTags } from '@nestjs/swagger';

@Controller('user')
@ApiTags('유저 API')
export class UserController {
  constructor(
    private readonly userService: UserService,
    private authService: AuthService
    ) {}

  @Get(':id')
  @ApiOperation({summary : '유저 확인 API', description: '유저를 찾는다.' })
  @ApiCreatedResponse({description: '유저를 찾는다'})
  findOne(@Param('id') id: string) {
    return this.userService.findOne(id);
  }

  @Post('signup')
  @ApiOperation({summary : '회원가입 API', description: '회원가입' })
  @ApiCreatedResponse({description: '회원가입을 한다'})
  async signUp(@Res({ passthrough: true}) res : any, @Req() req : any) {
    return this.userService.signUp(req.body.userInfo);
  }

  @UseGuards(LocalAuthGuard)
  @Post('login')
  @ApiOperation({summary : '로그인 API', description: '로그인' })
  @ApiCreatedResponse({description: '로그인을 한다'})
  async login(@Request() req, @Res({ passthrough: true }) res : any) {
    if(req.user.result === "success") { // ID,PW 둘다 확인 됐을 경우
      return this.authService.login(req.user.user, res);
    }
    else {
      return {result : req.user.result}; // 둘 중 하나 이상이 틀릴 경우
    }
  }

  @UseGuards(JwtAuthGuard)
  @Get('profile')
  @ApiOperation({summary : '토큰확인 API', description: '토큰확인' })
  @ApiCreatedResponse({description: '토큰확인을 한다'})
  getProfile(@Request() req) {
    return req.user;
  }
}

 

email.controller.ts

import { Controller, Get, Post, Body, Patch, Param, Delete, Req, Res } from '@nestjs/common';
import { EmailService } from './email.service';
import { CreateEmailDto } from './dto/create-email.dto';
import { UpdateEmailDto } from './dto/update-email.dto';

//swagger
import { ApiCreatedResponse, ApiOperation, ApiTags } from '@nestjs/swagger';

@Controller('email')
@ApiTags('이메일 API')
export class EmailController {
  constructor(private readonly emailService: EmailService) {}

  @Get(':id')
  @ApiOperation({summary : '이메일 확인 API', description: '이메일로 유저를 찾는다.' })
  @ApiCreatedResponse({description: '이메일로 유저를 찾는다'})
  findOne(@Param('id') id: string) {
    return this.emailService.findOne(id);
  }

  @Post('send')
  @ApiOperation({summary : '이메일 전송 API', description: '이메일을 전송한다.' })
  @ApiCreatedResponse({description: '이메일을 전송한다.'})
  async emailsend(@Res({ passthrough: true }) res : any, @Req() req : any) {
    return await this.emailService.emailSend(req.body.email,res);
  }

  @Post('cert')
  @ApiOperation({summary : '이메일 인증 API', description: '인증번호를 확인한다.' })
  @ApiCreatedResponse({description: '인증번호를 확인한다.'})
  async emailCert(@Res({ passthrough: true}) res : any, @Req() req : any) {
    return await this.emailService.emailCert(req);
  }
}

 

swagger 화면(localhost:3001/swagger)으로 접속해본다.

 

 

정상적으로 뜨는 것을 확인했다.

728x90
반응형
728x90
반응형

이 포스트는 전 포스트에서 이어집니다.

 

 

 

https://docs.nestjs.kr/middleware

 

네스트JS 한국어 매뉴얼 사이트

네스트JS 한국, 네스트JS Korea 한국어 매뉴얼

docs.nestjs.kr


 

Nest 미들웨어는 기본적으로 express 미들웨어와 동일하다. 

 

  • 모든 코드를 실행하십시오.
  • 요청 및 응답 객체를 변경합니다.
  • 요청-응답주기를 종료합니다.
  • 스택의 next 미들웨어 함수를 호출합니다.
  • 현재 미들웨어 함수가 요청-응답주기를 종료하지 않으면 next()를 호출하여 next 미들웨어 기능에 제어를 전달합니다. 그렇지 않으면 요청이 중단됩니다.

 


Request가 들어올 때 로그로 확인하기 위한 미들웨어를 작성해보자.

 

/src/common/middleware/logger.middleware.ts

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log('Request...');
    next();
  }
}

 

 

app.module에서 전 포스트에서 작성한 UserModule을 import하고 다음과 같이 수정하자.

app.module.ts

import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';
import { LoggerMiddleware } from './common/middleware/logger.middleware';

@Module({
  imports: [UserModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('user');
  }
}

 

이제 Swagger에서 요청을 보내보자.

 

 

콘솔을 확인해보면 미들웨어에서 작성한 내용이 잘 뜨는 것을 확인할 수 있다.

 

미들웨어에서 한 번 들어온 데이터를 검증하고 next()로 넘길 수 있다. 

( 토큰이나 쿠키를 확인하는 것 등)

 

 

logger.middleware.ts

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log(req.body)
    next();
  }
}

 

swagger로 요청을 보내보면

 

 

잘 확인되는 것을 볼 수 있다.

 

참고로 이렇게 예외 상황도 만들 수 있다.

consumer
  .apply(LoggerMiddleware)
  .exclude(
    { path: 'cats', method: RequestMethod.GET },
    { path: 'cats', method: RequestMethod.POST },
    'cats/(.*)',
  )
  .forRoutes(CatsController);
728x90
반응형

'Back-End > Nest.js' 카테고리의 다른 글

Nest.js | Swagger  (0) 2021.10.28
Nest.js | MongoDB | PATCH and DELETE  (0) 2021.10.06
Nest.js | MongoDB | Create  (0) 2021.10.06
Nest.js | MongoDB | Find  (0) 2021.10.06
Nest.js | MongoDB | Model,Schema,Controller  (0) 2021.10.06
728x90
반응형

swagger에 관한 자세한 사항은 아래 링크

https://docs.nestjs.com/openapi/introduction#bootstrap

 

Documentation | NestJS - A progressive Node.js framework

Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Progamming), FP (Functional Programming), and FRP (Functional Reac

docs.nestjs.com


 

NestJS에서 swagger를 사용하면 아래와 같이 API를 테스트 할 수 있는 GUI 환경을 제공해준다.

 

 

 

일단 NestJS와 Swagger를 설치한다.

$ npm i -g @nestjs/cli
$ nest new teepo
$ npm install --save @nestjs/swagger swagger-ui-express

 

그다음 main.ts에 swagger를 추가해준다.

main.ts

import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const config = new DocumentBuilder()
    .setTitle('User example')
    .setDescription('The user API description')
    .setVersion('1.0')
    .build();
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('swagger', app, document);

  await app.listen(3000);
}
bootstrap();

SwaggerModule.setup() 메소드 안에서 Swagger UI를 마운트하는 경로를 설정할 수 있다.

 

 

이제 npm run start:dev를 하고 화면(:3000/swagger)을 확인해보자.

$ npm run start:dev

 

 

정상적으로 출력되었다. 이제 api에 문서화를 진행해보자.

 

임의로 user라는 resource를 만들어보겠다.

$ nest g res user

 

 

간단하게 dto 파일을 작성한다.

create-user.dto.ts

export class CreateUserDto {
    name : string;
    age : number;
}

 

 

userController 파일을 다음과 같이 수정해보자.

user.controller.ts

import { Controller, Response, Get, Post, Body, Patch, Param, Delete, Res, HttpStatus } from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { ApiCreatedResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
import { User } from './entities/user.entity';

@Controller('user')
@ApiTags('유저 API')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Post()
  @ApiOperation({summary : '유저 생성 API', description: '유저를 생성한다.' })
  @ApiCreatedResponse({description: '유저를 생성한다', type: User})
  async create(@Body() createUserDto: CreateUserDto) {
    const user: User = await this.userService.createUser(createUserDto);
    return user;
  }
}

 

userController에서 쓰일 userService를 정의한다.

user.service.ts

import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

@Injectable()
export class UserService {
  createUser(createUserDto: CreateUserDto) {
    return {
      name : 'teepo',
      age : 28
    };
  }

}

 

 

이제 실행 화면을 보면

 

 

이렇게 우리가 추가한 '유저 API'가 잘 생성된 것을 볼 수 있다. 오른쪽 Try it out 버튼을 클릭하면 테스트 화면이 나온다.

일단 아무것도 입력하지 않고 바로 보내보자.

 

 

결과화면 : 

 

 

Service에 작성했던 정보가 잘 넘어온 것을 확인할 수 있다. 

 

dto와 클래스 또한 Swagger를 사용해 정의할 수 있다. 타입도 지정해주고 확인해주자.

class-validator와 class-transformer를 설치한다.

$ npm i class-validator class-transformer

 

 

dto 파일에 타입과 swagger 데코레이터를 추가한다.

create-user.dto

import {IsString, IsNumber} from 'class-validator'
import { ApiProperty } from '@nestjs/swagger';

export class CreateUserDto {
    @ApiProperty({ description: '이름' })
    @IsString()
    name : string;

    @ApiProperty({ description: '나이' })
    @IsNumber()
    age : number;
}

 

 

main.ts에 파이프를 설정해준다.

main.ts

import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const config = new DocumentBuilder()
    .setTitle('User example')
    .setDescription('The user API description')
    .setVersion('1.0')
    .build();
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('swagger', app, document);

  app.useGlobalPipes(
    new ValidationPipe({
      whitelist : true, 
      forbidNonWhitelisted : true,
      transform : true
    })
  )

  await app.listen(3000);
}
bootstrap();

 

Service부분에 내가 입력한 값 그대로를 반환하게끔 수정해준다.

user.service.ts

import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

@Injectable()
export class UserService {
  createUser(createUserDto: CreateUserDto) {
    return createUserDto
  }

}

 

 

이제 다시 화면을 보면 DTO에 설정해두었던 타입들이 잘 들어가는 것을 확인할 수 있다.

 

 

 

맨 아래쪽에 스키마 부분도 설명이 잘 되어있다.

 

 

 

이번엔 타입과 다른 값을 보내보자. name에 string대신 number를 보내줄 것이다.

 

 

 

결과를 확인해보면

 

 

"name must be a string" 이라는 문구를 확인할 수 있다.

728x90
반응형

'Back-End > Nest.js' 카테고리의 다른 글

Nest.js | Middleware  (0) 2021.10.28
Nest.js | MongoDB | PATCH and DELETE  (0) 2021.10.06
Nest.js | MongoDB | Create  (0) 2021.10.06
Nest.js | MongoDB | Find  (0) 2021.10.06
Nest.js | MongoDB | Model,Schema,Controller  (0) 2021.10.06
728x90
반응형

윈도우에서 도커에 우분투를 설치했을 때 sudo 명령어가 안먹히면 아래 방법을 써보자.

 

1. 도커 컨테이너 ID 확인

# docker ps -a

 

2. 확인한 ID로 컨테이너 접속

# docker exec -u root -t -i 컨테이너ID /bin/bash

 

3. 아래 명령어 입력

apt-get update && \
      apt-get -y install sudo

 

728x90
반응형
728x90
반응형

전 포스트에서 올바른 ID, PW가 전달됐을 경우 토큰을 생성하고 반환하는 것까지 해보았다.

 

이번 포스트에서는 쿠키에있는 JWT를 복호화하는 과정을 구현해볼 것이다.

 

NextJS JWT

먼저 쿠키를 읽어오기 위해 next-cookies를 설치한다. (NextJS)

$ npm i next-cookies

 

복호화 하기위해 아래도 설치한다.

$ npm i jwt-decode
$ npm i @types/jwt-decode --save-dev

 

dashboard/index 에 import 해주고 getServerSideProps 쪽에 확인하는 코드를 작성해준다.

src/dashboard/index.tsx

import axios from 'axios'
import type { GetServerSideProps, NextPage } from 'next'
import Link from 'next/link'
import cookies from 'next-cookies'
import jwtDecode, { JwtPayload } from 'jwt-decode'


interface Props {
    data : any
}


const Dashboard:NextPage<Props> = ({data}) => {
    return (
        <div>
            <div className='dashboard'>
                Dashboard
            </div>
        </div>
        )
}


export default Dashboard;

export const getServerSideProps:GetServerSideProps = async (context) => { // SSR
    const token = cookies(context).token;
    const decodedToken: JwtPayload = jwtDecode<JwtPayload>(token ? token : '');
    console.log(decodedToken)
    
    return {
        props: {
        }
    }
}

 

 

로그인에 성공해서 쿠키에 담긴 토큰을 decode해서 콘솔에 찍어 확인해보자.

 

 

로그인 성공 :

 

NextJS 서버 콘솔 ( 서버사이드라 브라우저에서 뜨지 않습니다. )

 

추가로 시간이 지나거나 토큰이 읽히지 않는경우 다시 첫 페이지로 가게끔 코딩한다.

src/dashboard/index.tsx

export const getServerSideProps:GetServerSideProps = async (context) => { // SSR
    try {
        const token = cookies(context).token;
        const decodedToken: JwtPayload = jwtDecode<JwtPayload>(token ? token : '');
        
        if(decodedToken) {
            return {
                props: {
                }
            }
        }
        else {
            return {
                redirect: {
                destination: '/',
                permanent: false,
                },
            }
        }
    }
    catch(e) {
        console.log(e)
        return {
            redirect: {
            destination: '/',
            permanent: false,
            },
        }
    }
}
728x90
반응형
728x90
반응형

로그인 시 인증관련을 위해 Passport와 JWT에 대해 다루어보겠다.


passport에 관한 내용

https://velog.io/@kdo0129/Passport%EB%A1%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0

 

Passport로 로그인 구현하기

passport는 Node 용 인증 미들웨어다. 요청 인증이라는 단일 목적을 제공하기 위해 설계되었다.이번 surf 프로젝트에서는 다양한 인증 방식을 기획했다. (통합해서 로그인이라고 부르겠다.)종류를 살

velog.io

Passport는 커뮤니티에 잘 알려져 있고 성공적으로 많은 app 제품에 사용된 nodeJS의 가장 인기있는 인증관련 라이브러리이다. passport는 @nestjs/passport 모듈에 통합되었습니다.

JWT(Json Web Token)에 관한 내용은 아래 링크를 확인하자. 구글링해서도 금방 찾을 수 있다.

http://www.opennaru.com/opennaru-blog/jwt-json-web-token/

 

JWT (JSON Web Token) 이해하기와 활용 방안 - Opennaru, Inc.

JWT 는 JSON Web Token의 약자로 전자 서명 된 URL-safe (URL로 이용할 수있는 문자 만 구성된)의 JSON입니다.JWT는 속성 정보 (Claim)를 JSON 데이터 구조로 표현한 토큰으로 RFC7519 표준 입니다.

www.opennaru.com


Nest Authentication 공식문서

https://docs.nestjs.com/security/authentication

 

Documentation | NestJS - A progressive Node.js framework

Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Progamming), FP (Functional Programming), and FRP (Functional Reac

docs.nestjs.com


Local Strategy Passport

 

이제 우린 NestJS에서 지원해주는 Passport, JWT 모듈을 사용할 것이기 때문에
이전에 작성해두었던 user.controller.ts의 login과 user.service.ts의 login 함수를 삭제해도 된다. 

 

 

필요한 패키지를 설치한다.

$ npm install --save @nestjs/passport passport passport-local
$ npm install --save-dev @types/passport-local

$ npm install --save @nestjs/jwt passport-jwt
$ npm install --save-dev @types/passport-jwt

 

1. 인증관련 모듈을 만들기 위해 auth 모듈, 서비스를 생성한다.

$ nest g module auth
$ nest g service auth

 

 

2. auth.service.ts를 다음과 같이 수정한다.

/auth/auth.service.ts

import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { CreateUserDto } from 'src/user/dto/create-user.dto';
import { User, UserDocument } from 'src/user/schemas/user.schema';
//bcrypt
import * as bcrypt from 'bcrypt'

@Injectable()
export class AuthService {
    constructor(@InjectModel(User.name) private userModel: Model<UserDocument>,
    private jwtService: JwtService) {}

  async validateUser(username: string, password : string): Promise<any> {
    const user = await this.userModel.findOne({id : username}); // ID가 존재할 경우
    if (user) {
      const result = await bcrypt.compare(password,user.pw); 
      if(result) { // ID와 비밀번호가 일치할 경우
        return {result : "success",user : user};
      }
      else { // ID는 맞지만 비밀번호가 틀릴 경우
        return {result : "Pwfailed"}; 
      }
    }
    else { // ID 자체가 없는 경우
      return {result : "Idfailed"};
    }
  }

  async login(user : any, res : any) {
    const payload = { id: user.id };
    const token = await this.jwtService.sign(payload);
    res.cookie('token', token, {path: '/', expires: new Date(Date.now()+10000)}); // 쿠키생성
    return {
      result : "success",
      access_token: token
    };
  }
}

- 나중에 local.strategy.ts 파일에서 쓰일 validateUser 함수를 구현했다.

- 처음 api를 타기 전 클라이언트에서 보낸 usernamepasswordDB에 존재하는지 확인한다.

- 로그인 할 때 쓰일 login 함수를 선언했다. (user.controller에서 쓰임)

- 성공 시 쿠키를 생성하고 토큰과 결과값을 반환한다.

 

 

3. authModule에 User 모델을 가져온다.

/auth/auth.module.ts

import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { MongooseModule } from '@nestjs/mongoose';
import { PassportModule } from '@nestjs/passport';
import { User, UserSchema } from 'src/user/schemas/user.schema';
import { AuthService } from './auth.service';
import { LocalStrategy } from './local.strategy';
import { JwtStrategy } from './jwt.strategy';
import { jwtConstants } from './constants';

@Module({
  imports : [MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]),
],
  providers: [AuthService],
})
export class AuthModule {}

 

 

4. local.strategy.ts 파일과 jwt.strategy.ts 파일을 작성한다.

/auth/local.strategy.ts

import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
  constructor(private authService: AuthService) {
    super({
      username : "",
      password : ""
    });
  }

  async validate(username: string, password: string): Promise<any> {
    const result = await this.authService.validateUser(username,password); // ID,PW 검증
    // if (result.result !== "success") { // 검증 안될 경우 에러메세지 띄어줌
    //   throw new UnauthorizedException();
    // }
    return result;
  }
}

- 들어올 데이터인 username, password를 정의했다 ( 나중에 속성을 바꾸고 싶으면 super() 부분을 수정한다. )

- 그대로 authServicevalidateUser 함수로 넘긴다.

 

 

/auth/jwt.strategy.ts

import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { jwtConstants } from './constants';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: jwtConstants.secret,
    });
  }

  async validate(payload: any) {
    return {  username: payload.username };
  }
}

- 차후에 validate 부분을 바꿀 것이다. 

 

 

5. jwt 비밀키로 쓰일 contants파일을 생성하고 authModule를 수정해준다.

/auth/auth.module.ts

import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { MongooseModule } from '@nestjs/mongoose';
import { PassportModule } from '@nestjs/passport';
import { User, UserSchema } from 'src/user/schemas/user.schema';
import { AuthService } from './auth.service';
import { LocalStrategy } from './local.strategy';
import { JwtStrategy } from './jwt.strategy';
import { jwtConstants } from './constants';

@Module({
  imports : [MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]),
  PassportModule,
  JwtModule.register({
    secret: jwtConstants.secret,
    signOptions: { expiresIn: '60s' },
  }),
],
  providers: [AuthService,LocalStrategy,JwtStrategy],
  exports: [AuthService],
})
export class AuthModule {}

- PassportModuleJwt 모듈을 가져왔고, 비밀키와 시간을 설정해주었다.

- 작성한 Strategy파일들을 providers에 담아줬다.

- 나중에 user Controller에서 Service를 쓰기위해 export 해주었다.

 

 

6. local-auth.guard.tsjwt-auth.guard.ts 파일들을 작성한다. (user controller 에서 쓰인다.)

/auth/local-auth.guard.ts

import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}

 

/auth/jwt-auth.guard.ts

import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}

 

 

7. userModule에서 AuthModuleimport 한다.

user.module.ts

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { User, UserSchema } from './schemas/user.schema';
import { PassportModule } from '@nestjs/passport';
import { AuthModule } from 'src/auth/auth.module';


@Module({
  imports: [
    MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]),
    AuthModule
],
  controllers: [UserController],
  providers: [UserService],
  exports: [UserService]
})
export class UserModule {}

 

 

8. userController에서 api를 다시 지정해준다. 하는김에 토큰 확인하는 profile도 만들었다.

user.controller.ts

import { Controller, Request, Response, Get, Post, Body, Patch, Param, Delete, Res, Req, UseGuards } from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

//auth
import { LocalAuthGuard } from '../auth/local-auth.guard';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { AuthService } from '../auth/auth.service';

@Controller('user')
export class UserController {
  constructor(
    private readonly userService: UserService,
    private authService: AuthService
    ) {}

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.userService.findOne(id);
  }

  @Post('signup')
  async signUp(@Res({ passthrough: true}) res : any, @Req() req : any) {
    return this.userService.signUp(req.body.userInfo);
  }

  @UseGuards(LocalAuthGuard)
  @Post('login')
  async login(@Request() req, @Res({ passthrough: true }) res : any) {
    if(req.user.result === "success") { // ID,PW 둘다 확인 됐을 경우
      return this.authService.login(req.user.user, res);
    }
    else {
      return {result : req.user.result}; // 둘 중 하나 이상이 틀릴 경우
    }
  }

  @UseGuards(JwtAuthGuard)
  @Get('profile')
  getProfile(@Request() req) {
    return req.user;
  }
}

 

  1. api 요청이 들어온다
  2. UseGuards('local')에서 유저의 정보가 잘 들어왔는지 local-strategy 파일의 validate까지 확인한다.
  3. authServicevalidateUser에서 DB를 조회하고, 상황에 맞는 값을 반환한다.
  4. local-strategy 파일에서 다시 Controller로 값을 req.user에 담아서 반환한다.
  5. ID, PW 중 한 개 이상이 틀릴 경우 validateUser 함수에서 반환했던 실패 반환값을 그대로 반환한다.
  6. 성공했을 경우 authServicelogin함수로 들어가 쿠키와 토큰을 생성하고, 토큰을 반환한다.

 

이제 원래 작성했었던 LoginComponent와 user_action 을 수정하자.

 

LoginComponent.tsx

    const LoginHandler = (event : any) => {
        event.preventDefault();
        dispatch(userLogin(userState.userInfo.id,userState.userInfo.pw)).then((req : any) => {
            if(req.payload.result === "success") { // 로그인 성공 시 
                alert('로그인 성공입니다.');
                router.push(`/dashboard`)
            }
            else if(req.payload.result === "IdFailed") { // 아이디가 없을 경우
                alert('존재하지 않는 아이디입니다.');
            }else if(req.payload.result === "PwFailed"){ // 비밀번호가 틀릴경우
                alert('비밀번호가 틀렸습니다.');
            }else { // 그 외의 경우
                alert('로그인 실패입니다.');
            }
        })
    }

dispatch의 userLogin 함수에 전달값을 수정했다.

 

 

user_action.ts

export async function userLogin (id : string, pw : string) {
    const request = Axios.post('/api/user/login',{"username": id, "password": pw})
    .then(response => response.data)

    return {
        type : actionTypesUser.USER_LOGIN,
        payload: request
    }
}

전달받는 값과 보내는 값을 백엔드에 보내는 요청에 맞게 수정했다.

 

이제 화면에서 다시 로그인을 해보자. ( 헤더 부분은 신경 안쓰셔도 됩니다. )

 

 

로그인이 성공했을 때 토큰이 쿠키에 담긴 것을 확인할 수 있다.

 

 

 

728x90
반응형
728x90
반응형

회원가입 완료 후 회원가입된 아이디 비밀번호로 로그인이 성공하면 대쉬보드 페이지로 넘어가는 서비스를 구현해보자.

 

먼저 SignupComponent와 LoginComponent에 useEffect로 해당 컴포넌트에 처음 들어갈 때 userState를 초기화하는 구문을 넣어준다. 이 구문을 넣지 않으면 회원가입에서 진행했던 내용들이 그대로 로그인 화면에도 쓰이게 된다.

 

    useEffect(()=> {
        dispatch({type: actionTypesUser.USER_INIT})
    },[])

 

[cate].tsx에서 SignupComponent에 전달했던 값과 똑같은 값을 LoginComponent에 넘겨준다.

 

[cate].tsx

            <LoginComponent
                userChangeHandler={userChangeHandler} // 로그인에 props로 Handler함수를 보낸다.
                userState={userState} /> : ""}

 

로그인 화면을 다음과같이 구성해준다.

 

LoginComponent.tsx

import type { NextPage } from 'next'
import { actionTypesUser, UserState } from "../../store/interfaces/";
import { useEffect } from 'react'
import { useDispatch } from 'react-redux';
import Link from 'next/link'

interface Props {
    userChangeHandler : (event : any) => void;
    userState : UserState;
}

const Login:NextPage<Props> = ({userChangeHandler,userState}) => {
    const dispatch = useDispatch();

    useEffect(()=> {
        dispatch({type: actionTypesUser.USER_INIT});
    },[])

    const LoginHandler = (event : any) => {
        event.preventDefault();
    }
    
    return (
        <div className='login'>
            <form onSubmit={LoginHandler}>
                <div>
                    <input 
                        name="id"
                        type="text"
                        placeholder="Enter ID"
                        onChange={userChangeHandler} />
                </div>
                <div>
                    <input 
                        name="pw"
                        type="password"
                        placeholder="Enter PW"
                        onChange={userChangeHandler} />
                </div>
                <div>
                    <button
                        type="submit">
                    Login
                    </button>
                </div>
                <Link href={`/auth/signup`}>
                    <a> 회원가입 화면으로 가기 </a>
                </Link>
            </form>
        </div>
        )
}


export default Login

아이디 입력창과 비밀번호 입력창, 로그인 버튼과 Link를 사용해 화면을 구성했다. 회원가입에서 이미 다뤘던 내용들이라 자세한 설명은 하지 않겠다. 

 


next/link에 관한 자세한 내용은 다음 링크에서 확인할 수 있다.

https://nextjs.org/docs/api-reference/next/link

 

next/link | Next.js

Enable client-side transitions between routes with the built-in Link component.

nextjs.org


화면을 확인해보면 

 

 

이제 로그인 버튼을 눌렀을 경우 DB를 조회하고 해당 아이디 비밀번호가 올바르면 로그인 성공 메세지가 뜨게 할 것이다.

 

먼저 로그인에 필요한 액트, 타입, 리듀서, 액션을 만들어주자.

 

userAct.interfaces.ts

(...)

USER_LOGIN = "USER_LOGIN", // 로그인

(...)
    
export type ActionsUser = UserInit | UserState | UserInfo | UserIdDuplicate
                         | UserEmailDuplicate | UserEmailSend | UserEmailCert
                         | UserSignup | UserLogin
                         
(...)
                         
export interface UserLogin {
    type : actionTypesUser.USER_LOGIN;
    payload : any;
}

 

user_reducer.ts

        case actionTypesUser.USER_LOGIN:
                return {
                    ...state, data : action.payload
                }

 

user_action.ts

export async function userLogin (userInfo : UserInfo) {
    const request = Axios.post('/api/user/login',{userInfo})
    .then(response => response.data)

    return {
        type : actionTypesUser.USER_LOGIN,
        payload: request
    }
}

 

 

그다음 LoginHandler 함수를 다음과 같이 조건식을 세워서 만든다.

 

LoginComponent.tsx

    const LoginHandler = (event : any) => {
        event.preventdefault();
        dispatch(userLogin(userState.userInfo)).then((req : any) => {
            if(req.payload.result === "success") { // 로그인 성공 시 
                alert('로그인 성공입니다.');
            }
            else if(req.payload.result === "IdFailed") { // 아이디가 없을 경우
                alert('존재하지 않는 아이디입니다.');
            }else if(req.payload.result === "PwFailed"){ // 비밀번호가 틀릴경우
                alert('비밀번호가 틀렸습니다.');
            }else { // 그 외의 경우
                alert('로그인 실패입니다.');
            }
        })
    }

 

이제 프론트에서 할 일은 끝났다. 백엔드로 넘어가보자. 먼저 컨트롤러 부분에 login Post데코레이터를 만들어준다.

 

user.controller.ts

  @Post('login')
  async logIn(@Res({ passthrough: true}) res : any, @Req() req : any) {
    return await this.userService.logIn(req.body.userInfo,res);
  }

 

user.service.ts

  async logIn(userInfo : CreateUserDto, res : any) {
    try {
      const userOne = await this.userModel.findOne({id : userInfo.id});
      if(userOne) { // ID가 존재할 경우
        const result = await bcrypt.compare(userInfo.pw,userOne.pw); 
        if(result) { // 비밀번호가 일치할 경우
          res.cookie('isLogined',userInfo.id, {path: '/', expires: new Date(Date.now()+86400000)});
          return { result : "success"};
        }
        else { // 비밀번호가 틀릴 경우
          return { result : "PwFailed"};
        }
      }else { // ID가 없을 경우
        return { result : 'IdFailed'};
      }
    }catch(e) {
      return false;
    }
  }

 

이제 화면에서 로그인이 성공했을 때 쿠키가 제대로 생기는지 확인해보자.

 

- 존재하지 않는 ID일 경우

 

- 비밀번호가 틀릴 경우

 

- 로그인이 성공할 경우

 

쿠키까지 확인 : 

 

로그인 성공 시 쿠키가 잘 생성되는것을 확인했다.

 

마지막으로 로그인이 완료되면 넘어갈 페이지인 대시보드 페이지를 만들어보자.

 

/pages/dashboard.tsx

import type { NextPage } from 'next'
import Link from 'next/link'


const Dashboard:NextPage = () => {
    return (
        <div className='dashboard'>
            Dashboard
        </div>
        )
}


export default Dashboard

 

LoginComponent.tsx

    const LoginHandler = (event : any) => {
        event.preventDefault();
        dispatch(userLogin(userState.userInfo)).then((req : any) => {
            if(req.payload.result === "success") { // 로그인 성공 시 
                alert('로그인 성공입니다.');
                router.push(`/dashboard`)
            }
            else if(req.payload.result === "IdFailed") { // 아이디가 없을 경우
                alert('존재하지 않는 아이디입니다.');
            }else if(req.payload.result === "PwFailed"){ // 비밀번호가 틀릴경우
                alert('비밀번호가 틀렸습니다.');
            }else { // 그 외의 경우
                alert('로그인 실패입니다.');
            }
        })
    }

 

router.push를 이용해 성공시 대쉬보드 페이지로 넘어가게끔 했다.

728x90
반응형
728x90
반응형

지금까지 구현했던 회원가입창에 모든 데이터를 넣고 회원가입은 완료시켜보자.

 

회원가입을 눌렀을 경우 실행되는 함수는 submitHandler이다. 다음과 같이 수정해주자.

    const submitHandler = (event : any) => {
        event.preventDefault();
        if(!userState.idDuple) {
            alert('아이디 중복여부를 체크해주세요.');
        }else {
            if(!userState.emailDuple) {
                alert('이메일 중복여부를 확인해주세요.');
            }else {
                if(!userState.emailAuth) {
                    alert('이메일 인증을 확인해주세요.');
                }else {
                    if(userState.userInfo.pw.length < 4) {
                        alert('비밀번호를 4자이상 입력해주세요.')
                    } else {
                        if(userState.userInfo.pw !== pwdCertText) {
                            alert('비밀번호와 비밀번호 확인 문자가 다릅니다.')
                        }
                        else {
                            // signup dispatch
                        }
                    }
                }
            }
        }
    }

 

지금까지 실행했던 것들로 인증을 하고 만약 다를경우 alert창을 띄워줄 것이다

 

이제부터 액션을 만들고 주석처리된 부분에 dispatch를 실행할 것이다. 액트와 타입을 지정해준다.

 

userAct.interfaces.ts

(...)

USER_SIGNUP = "USER_SIGNUP", // 회원가입

(...)

export type ActionsUser = UserInit | UserState | UserInfo | UserIdDuplicate
                         | UserEmailDuplicate | UserEmailSend | UserEmailCert
                         | UserSignup

(...)

export interface UserSignup {
    type : actionTypesUser.USER_SIGNUP;
    payload : any;
}

 

이번엔 리듀서 부분을 수정해준다.

 

user_reducer.ts

        case actionTypesUser.USER_SIGNUP:
                return {
                    ...state, data : action.payload
                }

 

 

클라이언트에서 사용할 dispatch 액션을 정의해준다. post메소드로 데이터를 전송할 것이다.

interfaces에서 userInfo 클래스를 가져오고 다음 함수를 만들어주자.

 

user_action.ts

export async function userSignup (userInfo : UserInfo) {
    const request = Axios.post('/api/user/signup',{userInfo})
    .then(response => response.data)

    return {
        type : actionTypesUser.USER_SIGNUP,
        payload: request
    }
}

 

다시 SignupComponent로 넘어와서 NextJS의 라우터를 사용해 회원가입 성공 시 로그인 화면으로 넘어가게끔 하자.

 

SignupComponent.tsx

import { useRouter } from 'next/router'

(...)

const Signup:NextPage<Props> = ({userChangeHandler,userState}) => {

(...)

    // router
    const router = useRouter()
    
(...)

    const submitHandler = (event : any) => {
        event.preventDefault();
        if(!userState.idDuple) {
            alert('아이디 중복여부를 체크해주세요.');
        }else {
            if(!userState.emailDuple) {
                alert('이메일 중복여부를 확인해주세요.');
            }else {
                if(!userState.emailAuth) {
                    alert('이메일 인증을 확인해주세요.');
                }else {
                    if(userState.userInfo.pw.length < 4) {
                        alert('비밀번호를 4자이상 입력해주세요.');
                    } else {
                        if(userState.userInfo.pw !== pwdCertText) {
                            alert('비밀번호와 비밀번호 확인 문자가 다릅니다.');
                        }
                        else {
                            // add address detail
                            dispatch({type : actionTypesUser.USER_INFO, data: ['address',userState.userInfo.address + addressDetail]})
                            // signup dispatch
                            dispatch(userSignup(userState.userInfo)).then((req: any) => {
                                if(req.payload.result) {
                                    alert('회원가입 성공입니다.')
                                    router.push('/auth/login');
                                }
                                else {
                                    alert('회원가입 오류입니다.');
                                }
                            })
                        }
                    }
                }
            }
        }
    }

 

이제 프론트에서 할 일은 끝났다. 백엔드에서 값이 잘 들어왔는지 확인하고 올바른 결과값을 반환해주자.

 

먼저 필요한 것들을 설치하고 dto를 설정해준다.

$ npm i class-validator class-transformer

 

create-user.dto.ts

import {IsString, IsNumber} from 'class-validator'

export class CreateUserDto {
    @IsString()
    id : string;

    @IsString()
    pw : string;

    @IsString()
    email : string;

    @IsString()
    address : string;
}

 

main.ts 파일에서 ValidationPipe를 사용한다.

maints

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as cookieParser from 'cookie-parser';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.use(cookieParser());

  app.useGlobalPipes(
    new ValidationPipe({
      whitelist : true, 
      forbidNonWhitelisted : true,
      transform : true
    })
  )

  await app.listen(3001);
}
bootstrap();

 

 

 

컨트롤러에 signup 함수를 지정해준다.

 

user.controller.ts

import { Controller, Get, Post, Body, Patch, Param, Delete, Res, Req } from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.userService.findOne(id);
  }

  @Post('signup')
  async signUp(@Res({ passthrough: true}) res : any, @Req() req : any) {
    return this.userService.signUp(req.body.userInfo);
  }

}

 

서비스 부분에서 비밀번호를 암호화하고 DB에 저장하는 함수를 선언한다.

 

user.service.ts

import { Injectable } from '@nestjs/common';
//mongoose
import { Model } from 'mongoose';
import { InjectModel } from '@nestjs/mongoose';
import { User, UserDocument } from './schemas/user.schema';
//dto
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
//bcrypt
import * as bcrypt from 'bcrypt'

@Injectable()
export class UserService {
  constructor(@InjectModel(User.name) private userModel: Model<UserDocument>) {}

  async findOne(id: string) {
    const userOne = await this.userModel.findOne({id});
    if(userOne)
      return {result : false, user : userOne};
    else
      return {result : true};
  }

  async signUp(userInfo : CreateUserDto) {
    try{
      const authPW = await bcrypt.hash(userInfo.pw,parseInt(process.env.saltOrRounds));
      const createUser = await this.userModel.create({
        id : userInfo.id,
        pw : authPW,
        email : userInfo.email,
        address : userInfo.address
      })
      return {result : true};
    }catch(e) {
      return {result : false};
    }
  }
}

 

 

이제 실행을 해본다!

 

 

회원가입 버튼 클릭 후 :

 

alert 창의 확인버튼 누른 후 : 

 

정상적으로 로그인 페이지로 넘어오는것을 확인할 수 있다.

참고로 robo 3t라는 프로그램으로 들어온 mongoDB 데이터를 확인할 수 있다.

 

728x90
반응형
728x90
반응형

 

회원가입에 필요한 주소검색을 구현해보자. 먼저 다음 명령어를 실행한다.

$ npm i react-daum-postcode

 

그다음 상단에서 import 하고 

import DaumPostcode from 'react-daum-postcode';

 

다음과 같이 컴포넌트를 사용할 것이다.

<DaumPostcode onComplete={AddressComplete} />

 

 

일단 필요한 State 먼저 선언한다.

    //address
    const [addressOpen,setAddressOpen] = useState<boolean>(false); // 주소검색 오픈
    const [addressDetail,setAddressDetail] = useState<string>(''); // 자세한 주소 입력칸

 

그리고 HTML부분을 다음과같이 바꾸고 화면을 보자.

<div>
    <input 
        name="address"
        type="text"
        placeholder="Enter Address"
        value={userState.userInfo.address}
        disabled />
    <button
    	type="button"
        onClick={()=> { setAddressOpen(true) }}>
    주소검색    
    </button>
</div>
{ addressOpen ?
<DaumPostcode onComplete={AddressComplete} />
: ""}
<div>
    <input 
            name="address"
            type="text"
            placeholder="Enter Address2"
            value={addressDetail}
            onChange={(event)=> {
                setAddressDetail(event.target.value)
            }} />
</div>

 

그다음 주소검색 버튼을 누르고 원하는 주소를 클릭하면 실행되는 AddressComplete 함수를 구현한다.

const AddressComplete = (data : any) => {
        let fullAddress = data.address;
        let extraAddress = '';
        if (data.addressType === 'R') {
            if (data.bname !== '') {
                extraAddress += data.bname;
            }
            if (data.buildingName !== '') {
                extraAddress += (extraAddress !== '' ? `, ${data.buildingName}` : data.buildingName);
            }
            fullAddress += (extraAddress !== '' ? ` (${extraAddress})` : '');
        }
        //fullAddress -> 전체 주소반환
        dispatch({type : actionTypesUser.USER_INFO, data : ['address',fullAddress]})
        setAddressOpen(false);
    }

 

  • 기본적으로 검색한 주소가 들어가는 칸과 주소검색 버튼, 상세 주소 입력 칸이 있다.
  • 검색한 주소는 검색한 내용이 들어가야되므로 disabled 처리한다. value는 userInfo의 address 이다.
  • 주소검색 버튼을 누르면 하단에 주소검색칸이 생긴다.
  • 원하는 주소를 클릭하면 setAddressOpen 함수가 실행된다.
  • 상세주소까지 입력하면 끝!

 

화면을 확인해보자

 

1. 첫 화면

 

 

2. 주소검색을 클릭할 경우

 

 

3. 원하는 주소 클릭 후 상세주소까지 입력

 

 

현재 주소검색 결과값과 상세주소가 합쳐져있지 않기 때문에 회원가입 버튼을 눌렀을 경우에

합쳐줄 것이다.

728x90
반응형

+ Recent posts