지난 포스팅에서 GrpahQL과 Mongoose를 통해 db에 데이터를 넣는 것까지 해봤다.
테스트 데이터를 유저로 만든김에 조금 더 코드를 다듬어 보기로 했다.
아무래도 비밀번호를 입력하니깐 암호화 처리도 해보고 내친김에 로그인 기능까지 구현해보고자 한다. 로그인도 그냥 하면 재미없으니깐 JWT Token을 발행해보고자 한다.
- 회원가입 할 때 입력받은 비밀번호를 bcryp를 사용하여 암호화 처리한다.
- 로그인을 하면 JWT Token을 발행한다.
npm install bcrypt
creteUser
에 password를 암호화해서 받도록 수정import * as bcrypt from "bcrypt";
...
async createUser(user: UserInputType) {
try {
const saltRounds = 10; // 추가
// CREATE DATA
const data = {
...user,
password: await bcrypt.hash(user.password, saltRounds), // 추가
date_crated: new Date()
};
const result = await this.userModel.create(data);
return {
uid: result._id,
...data
};
} catch (e) {
throw new ApolloError(e);
}
}
createUser
해보기{
email: 'cat@test.com',
displayName: '냥이😽',
password: '$2b$10$sy.g98/CHQVlSSLWy1nMDOfPd0GaQsbMgOlmeiREjIvoy4a.taz9q',
date_crated: 2022-10-01T02:26:18.399Z
}
LoginInputType
추가...
@ArgsType()
@InputType()
export class LoginInputType {
@Field()
email: string;
@Field()
password: string;
}
login()
추가하여 확인하기import { Injectable, Inject } from "@nestjs/common";
import { User, UserInputType, LoginInputType } from "../schemas/user.schema"; //LoginInputType 추가
import { Model } from "mongoose";
import * as bcrypt from "bcrypt";
import { ApolloError } from "apollo-server-express";
@Injectable()
export class UsersService {
constructor(
@Inject("USER_MODEL")
private readonly userModel: Model<User>
) {
}
...
async login(input: LoginInputType) {
try {
const user = await this.userModel.findOne({ email: input.email });
if (!user) throw new ApolloError("Please check your email");
const check_pw = await bcrypt.compare(input.password, user.password);
if (!check_pw) throw new ApolloError("Please check password");
user.uid = user._id;
return user;
} catch (e) {
throw new ApolloError(e);
}
}
}
import { Query, Resolver, Args, Mutation } from "@nestjs/graphql";
import { User, UserInputType, LoginInputType } from "../schemas/user.schema";
import { UsersService } from "./users.service";
import { ApolloError } from "apollo-server-express";
@Resolver()
export class UsersResolver {
constructor(private usersService: UsersService) {
}
...
@Mutation(() => User)
async login(@Args("input") input: LoginInputType) {
try {
return await this.usersService.login(input);
} catch (e) {
throw new ApolloError(e);
}
}
}
login
해보기💡 비밀전호를 복화하여 유저 정보를 반환하는 것을 볼 수 있다.
login()
삭제 .env
를 사용하기 위해 NestJS에서 제공하는 Congifue 패키지를 먼저 설치해보자npm i --save @nestjs/config
CongifueModule
import 하기GraphQLModule
에 context 정보를 작성해두자import { Module } from "@nestjs/common";
import { GraphQLModule } from "@nestjs/graphql";
import { ApolloDriver, ApolloDriverConfig } from "@nestjs/apollo";
import { UsersModule } from "./users/users.module";
import { ConfigModule } from "@nestjs/config";
@Module({
imports: [
ConfigModule.forRoot({
envFilePath: ".env",
isGlobal: true
}),
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: "schema.gql",
installSubscriptionHandlers: true,
context: ({ req, connection }) => {
if (req) {
const user = req.headers.authorization;
return { ...req, user };
} else {
return connection;
}
}
}),
UsersModule],
controllers: [],
providers: []
})
export class AppModule {
}
$ npm install --save @nestjs/jwt passport-jwt @nestjs/passport
$ npm install --save-dev @types/passport-jwt
$ nest g module auth
$ nest g service auth
findOneByEmail
함수 추가async findOneByEmail(email: string): Promise<User | undefined> {
try {
return await this.userModel.findOne({ email: email });
} catch (e) {
throw new ApolloError(e);
}
}
import { ExtractJwt, Strategy } from "passport-jwt";
import { PassportStrategy } from "@nestjs/passport";
import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, "jwt") {
constructor(
private readonly configService: ConfigService
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get<string>("JWT_TOKEN")
});
}
async validate(payload: any) {
return {
uid: payload.uid,
email: payload.email,
displayName: payload.displayName
};
}
}
import { ExecutionContext, Injectable } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
import { GqlExecutionContext } from "@nestjs/graphql";
@Injectable()
export class JwtAuthGuard extends AuthGuard("jwt") {
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req;
}
}
import { forwardRef, Module } from "@nestjs/common";
import { AuthService } from "./auth.service";
import { UsersModule } from "../users/users.module";
import { PassportModule } from "@nestjs/passport";
import { JwtModule } from "@nestjs/jwt";
import { JwtStrategy } from "./jwt.strategy";
import { ConfigService } from "@nestjs/config";
@Module({
imports: [
forwardRef(() => UsersModule),
PassportModule,
JwtModule.registerAsync({
useFactory: (configService: ConfigService) => ({
secret: configService.get<string>("JWT_TOKEN"),
signOptions: { expiresIn: "1 day" }
}),
inject: [ConfigService]
})
],
providers: [AuthService, JwtStrategy],
exports: [AuthService]
})
export class AuthModule {
}
import { Injectable, Inject, forwardRef } from "@nestjs/common";
import { UsersService } from "../users/users.service";
import { User } from "../schemas/user.schema";
import { JwtService } from "@nestjs/jwt";
import { JwtStrategy } from "./jwt.strategy";
import * as bcrypt from "bcrypt";
@Injectable()
export class AuthService {
constructor(
@Inject(forwardRef(() => UsersService))
private usersService: UsersService,
private jwtService: JwtService
) {
}
async validateUser(email: string, pass: string): Promise<any> {
try {
const user = await this.usersService.findOneByEmail(email);
if (user) {
if (await bcrypt.compare(pass, user.password)) {
delete user.password;
user.uid = user._id;
return user;
}
}
return null;
} catch (e) {
throw e;
}
}
async generateUserCredentials(user: User) {
try {
const payload = {
uid: user.uid,
email: user.email,
displayName: user.displayName,
photoURL: user.photoURL ? user.photoURL : "",
intro: user.intro ? user.intro : "",
date_crated: user.date_crated
};
return {
access_token: this.jwtService.sign(payload)
};
} catch (e) {
throw e;
}
}
}
AuthModule
추가import { Module } from "@nestjs/common";
import { UsersResolver } from "./users.resovler";
import { UsersService } from "./users.service";
import { UsersProviders } from "./users.providers";
import { DatabaseModule } from "../database.module";
import { AuthModule } from "../auth/auth.module"; // 추가
@Module({
imports: [DatabaseModule, AuthModule], //AuthModule 추가
providers: [UsersResolver, UsersService, ...UsersProviders],
exports: [UsersService]
})
export class UsersModule {
}
AuthService
추가login()
추가import { Injectable, Inject } from "@nestjs/common";
import { LoginInputType, User, UserInputType } from "../schemas/user.schema";
import { Model } from "mongoose";
import * as bcrypt from "bcrypt";
import { AuthService } from "../auth/auth.service"; //추가
import { ApolloError } from "apollo-server-express";
@Injectable()
export class UsersService {
constructor(
@Inject("USER_MODEL")
private readonly userModel: Model<User>,
private readonly authService: AuthService //추가
) {
}
async findAll(): Promise<User[]> {
try {
return this.userModel.find().exec();
} catch (e) {
throw new ApolloError(e);
}
}
async findOneByEmail(email: string): Promise<User | undefined> {
try {
return this.userModel.findOne({ email: email });
} catch (e) {
throw new ApolloError(e);
}
}
async createUser(user: UserInputType) {
try {
// CREATE DATA
const saltRounds = 10;
const data = {
...user,
password: await bcrypt.hash(user.password, saltRounds),
date_crated: new Date()
};
const result = await this.userModel.create(data);
return {
uid: result._id,
...data
};
} catch (e) {
throw new ApolloError(e);
}
}
async login(input: LoginInputType) {
try {
const user = await this.authService.validateUser(input.email, input.password);
if (!user) {
throw new ApolloError("Email or password are invalid");
} else {
const access_token = await this.authService.generateUserCredentials(user);
console.log("access_token", access_token);
user.uid = user._id;
user.access_token = access_token.access_token;
console.log(user);
return user;
}
return null;
} catch (e) {
throw new ApolloError(e);
}
}
}
login()
해보기💡 access_token이 제대로 발급된 것을 확인할 수 있다.