지난 포스팅에서 로그인 기능을 구현하고 JWT 토큰까지 발급했다.
이번에는 유저 정보를 수정해보고자 한다.
그러기 위해선느 몇 가지 조건이 필요하다.
- 로그인 한 회원일 것
- 로그인 한 회원과 정보를 수정하고자 하는 유저가 일치할 것
이를 위해 context 정보를 이용할 예정이다.
NestJS에서 GraphQL의 context 정보를 어떻게 사용하는 지 알아보자
...
@Module({
imports: [
ConfigModule.forRoot({
envFilePath: ".env",
isGlobal: true
}),
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: "schema.gql",
context: ({ req, connection }) => {
if (req) {
const user = req.headers.authorization;
return { ...req, user };
} else {
return connection;
}
}
}),
UsersModule,
AuthModule],
controllers: [],
providers: []
})
export class AppModule {
}
💡 지난 포스팅에 작성한 코드를 보면 headers에 authorization 키를 context 정보를 저장하고 있다.
발급받은 JWT access_token을 headers에 전송하면 앱이 실행될 때 자동으로 context 정보를 저장한다.
그렇다면 login 해서 얻은 access_token을 헤더에 전송해보자.
💡 발급받은 access_token은 나중에 헤더에 전송할 예정이다.
import { Field, ID, ObjectType, ArgsType, InputType } from "@nestjs/graphql";
import * as mongoose from "mongoose";
import { Document } from "mongoose";
export const UserSchema = new mongoose.Schema({
_id: { type: mongoose.Schema.Types.ObjectId, auto: true },
displayName: String,
email: String,
photoURL: String,
password: String,
intro: String,
date_crated: String,
date_update: String // 추가
});
@ObjectType()
export class User extends Document {
@Field(() => ID)
uid: string;
@Field(() => String)
email: string;
@Field(() => String)
displayName: string;
@Field(() => String, { nullable: true })
photoURL: string;
@Field(() => String, { nullable: true })
intro: string;
@Field(() => String, { nullable: true })
date_crated: string;
@Field(() => String, { nullable: true }) // 추가
date_updated: string;
@Field(() => String, { nullable: true })
access_token: string;
password: string;
}
@ArgsType()
@InputType()
export class UserInputType {
@Field()
email: string;
@Field()
displayName: string;
@Field({ nullable: true })
photoURL: string;
@Field({ nullable: true })
intro: string;
@Field()
password: string;
}
@ArgsType()
@InputType()
export class LoginInputType {
@Field()
email: string;
@Field()
password: string;
}
// 추가
@ArgsType()
@InputType()
export class UserUpdateType {
@Field({ nullable: true })
displayName: string;
@Field({ nullable: true })
photoURL: string;
@Field({ nullable: true })
intro: string;
}
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
export const CurrentUser = createParamDecorator(
(data: unknown, context: ExecutionContext) => {
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req.user;
},
);
💡 현재 앱에 접속한 회원이 누군지 판단하여 context 정보를 사용할 수 있도록 만들어준다.
해당 코드가 없으면 앱이 제대로 작동 안 할 수 있으니 추가하도록 하자.
import { UseGuards } from "@nestjs/common"; //추가
import { Query, Resolver, Args, Mutation, ID } from "@nestjs/graphql"; // ID 추가
import { LoginInputType, User, UserInputType, UserUpdateType } from "../schemas/user.schema"; // UserUpdateType 추가
import { UsersService } from "./users.service";
import { ApolloError } from "apollo-server-express";
import { JwtAuthGuard } from "../auth/jwt-auth.guard"; // 추가
import { CurrentUser } from "../user.decorator"; // 추가
...
@UseGuards(JwtAuthGuard)
@Mutation(() => User)
async updateUser(
@CurrentUser() user: User,
@Args("uid", { type: () => ID }) uid: string,
@Args("input") input: UserUpdateType) {
try {
return await this.usersService.updateUser(user, uid, input);
} catch (e) {
throw new ApolloError(e);
}
}
💡 @UseGuards(JwtAuthGuard) 데코레이터를 설정 하여 토큰이 있는 유저만 접근할 수 있도록 설정한다.
moment-timezone
을 먼저 설치하겠다.npm install moment-timezone
import { Injectable, Inject } from "@nestjs/common";
import { LoginInputType, User, UserInputType, UserUpdateType } from "../schemas/user.schema"; // UserUpdateType 추가
import { Model } from "mongoose";
import * as bcrypt from "bcrypt";
import { AuthService } from "../auth/auth.service";
import { ApolloError } from "apollo-server-express";
import * as moment from "moment-timezone"; //추가
...
async updateUser(user: User, uid: string, input: UserUpdateType) {
try {
// NOTE - 1
if (user.uid !== uid) throw new ApolloError("You don't have to access.");
const check_user = await this.userModel.findById({ _id: uid }).exec();
if (!check_user) throw new ApolloError("There are no user information.");
const data = {
...input,
date_updated: moment().local().format()
};
await this.userModel.findOneAndUpdate(
{ _id: uid }, { ...data }, { new: true }
);
const result = {
uid: uid,
email: check_user.email,
displayName: input.displayName ? input.displayName : check_user.displayName,
...data
};
return result;
} catch (e) {
throw new ApolloError(e);
}
}
💡 Note - 1
현재 접속한 유저의 uid와 aggs로 받은 uid 값이 일치하는 지 확인한다.
유저 정보가 일치하는 유저만 정보를 수정할 수 있다.
GTTP HEADERS
를 클릭하여 아래의 내용 입력하기{"Authorization": "Bearer ${access_token}"}
예)
{"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiI2MzNiZDc1MTcwZDgyMTdmODUzY2FhNWUiLCJlbWFpbCI6ImNhdEB0ZXN0LmNvbSIsImRpc3BsYXlOYW1lIjoi64Ol7J2064u5IiwicGhvdG9VUkwiOiIiLCJpbnRybyI6IiIsImRhdGVfY3JhdGVkIjoiVHVlIE9jdCAwNCAyMDIyIDE1OjQ4OjQ5IEdNVCswOTAwICjrjIDtlZzrr7zqta0g7ZGc7KSA7IucKSIsImlhdCI6MTY2NDkyNjI1MSwiZXhwIjoxNjY1MDEyNjUxfQ.LujFDvLHMzbvFEL_sGJoEdjVgADkWpFREFOmRAdUCD4"}