Node JS #12

Edit profilePermalink

프로필을 수정할 수 있는 기능을 구현하자.

Edit profile : GET methodPermalink

링크 제작

로그인 되어 있는 경우에만 이 링크가 보이게 한다.

if loggedIn
                       li
                           a(href="/users/edit") Edit Profile
                       li
                           a(href="/users/logout")  Log Out

Form 제작

폼에 현재 유저의 정보가 자동 기입되게 하기 위해 locals 변수를 이용한다.

extends base


block content

   form(method="POST")

       input(placeholder="Name", name="name", type="text", required, value=loggedInUser.name)

       input(placeholder="Email", name="email", type="email", required, value=loggedInUser.email)

       input(placeholder="Username", name="username", type="text", required, value=loggedInUser.username)

       input(placeholder="Location", name="location", type="text", required, value=loggedInUser.location)

       input(type="submit", value="Update Profile")

미들웨어 수정하여 로그인된 유저의 정보를 접근가능하게 만들기 (locals 변수)

export const localsMiddleware = (req, res, next) => {
 res.locals.loggedIn = Boolean(req.session.loggedIn);
 res.locals.siteName = "Wetube";
 res.locals.loggedInUser = req.session.user || {}; // undefined 에러를 방지하기 위해 {} 빈 객체 삽입
 next();
};

컨트롤러 구현

export const getEdit = (req, res) => {
   return res.render("edit-profile", {
       pageTitle: "Edit Profile",
   });
};

export const postEdit = async (req, res) => {
   return res.render("edit-profile");
};

라우팅 처리

userRouter.route("/edit").get(getEdit).post(postEdit);

그러면 이제 로그인 하지 않은 사람이 localhost:4000/users/edit URL을 타고 들어오면 이 페이지가 로딩된다는 문제가 생긴다.

이를 해결하기 위해 미들웨어를 이용하자.


Protector and Public M/WPermalink

로그인의 유무에 따라 request를 허용하는 미들웨어를 만들자.

export const protectorMiddleware = (req, res, next) => {
  if (req.session.loggedIn) {
    return next();
  } else {
    return res.redirect("/login");
  }
};

export const publicOnlyMiddleware = (req, res, next) => {
  if (!req.session.loggedIn) {
    return next();
  } else {
    return res.redirect("/");
  }
};

그리고 이 미들웨어를 라우팅에 적용해주면 된다.

rootRouter.route("/join").all(publicOnlyMiddleware).get(getJoin).post(postJoin);
rootRouter
  .route("/login")
  .all(publicOnlyMiddleware)
  .get(getLogin)
  .post(postLogin);

userRouter.get("/logout", protectorMiddleware, logout);
userRouter.route("/edit").all(protectorMiddleware).get(getEdit).post(postEdit);
userRouter.get("/github/start", publicOnlyMiddleware, startGithubLogin);
userRouter.get("/github/finish", publicOnlyMiddleware, finishGithubLogin);

videoRouter
  .route("/:id([0-9a-f]{24})/edit")
  .all(protectorMiddleware)
  .get(getEdit)
  .post(postEdit);
videoRouter
  .route("/:id([0-9a-f]{24})/delete")
  .all(protectorMiddleware)
  .get(deleteVideo);
videoRouter
  .route("/upload")
  .all(protectorMiddleware)
  .get(getUpload)
  .post(postUpload);

이때 .all(M/W)는 모든 종류의 method 요청에 대해 M/W를 등록하는 함수라 생각하면 된다.


Edit Profile : POST MethodPermalink


export const postEdit = async (req, res) => {
    const {
        session: {
            user: { _id }, //id가 아닌 _id임을 주의(세선을 console.log 찍어서 확인)
        },
        body: { name, email, username, location },
    } = req;

    const updatedUser = await User.findByIdAndUpdate(
        _id,
        {
            name,
            email,
            username,
            location,
        },
        { new: true } // 옵션 줘서 업데이트 이후의 객체 반환
    );
    req.session.user = updatedUser;
    return res.redirect("/users/edit");
};

여기서 DB를 수정했으면, 세션도 수정해서 FE에도 수정사항이 반영되도록 해야한다.

세션 또한 웹서버에 저장되므로 이를 변경시켜 주지 않으면 FE에는 변경사항이 표현되지 않는다.

(우린 다음과 같이 Form의 내용을 기입해주었다. 그리고 이는 세션에 저장된 정보)

input(placeholder="Name", name="name", type="text", required, value=loggedInUser.name)

그리고 User.findByIdAndUpdate()에서 새로운 객체를 반환받아서 이를 세션에 저장하자.

  • [options.new=false] «Boolean» if true, return the modified document rather than the original

아니면 아래와 같은 방식으로도 세션에 저장할 수 있다.

req.session.user={
	...req.session.user,
	name,
	email,
	username,
	location
}

그리고 사용자가 만일 이미 존재하는 이름이나 이메일로 변경하려는 경우에 막는 조치가 필요한데, 이는 추후에 구현하도록 하자.


Change PasswordPermalink

이제는 사용자가 자신의 비밀번호를 변경할 수 있게 하는 기능을 구현해보자

임시방편으로 컨트롤러, 라우터 처리

export const getChangePassword = (req, res) => {
  if (req.session.user.socialOnly === true) {
    return res.redirect("/");
  }
  return res.render("users/change-password", { pageTitle: "Change Password" });
};
export const postChangePassword = (req, res) => {
  // send notification
  return res.redirect("/");
};
userRouter
  .route("/change-password")
  .all(protectorMiddleware)
  .get(getChangePassword)
  .post(postChangePassword);

그리고 view 파일이 많으니 view 파일을 users, videos 서브 파일로 분류해서 정리하자.

extends ../base

block content
    form(method="post")
        input(placeholder="Old Password")
        input(placeholder="New Password")

그리고 깃허브 계정으로 로그인 되었을 경우는 비번이 없으므로 이 경우에는 비번 변경 버튼이 보이지 않게 하자.

...
input(type="submit", value="Update Profile")
        if !loggedInUser.socialOnly
            hr
            a(href="change-password") Change Password →

그리고 컨트롤러를 제대로 다시 작성해보자.

export const postChangePassword = async (req, res) => {
  const {
    session: {
      user: { _id },
    },
    body: { oldPassword, newPassword, newPasswordConfirmation },
  } = req;
  const user = await User.findById(_id);
  const ok = await bcrypt.compare(oldPassword, user.password);
  if (!ok) {
     // 브라우저는 status code만을 인식하므로 변경 실패를 status code로 알림
    return res.status(400).render("users/change-password", {
      pageTitle: "Change Password",
      errorMessage: "The current password is incorrect",
    });
  }
  if (newPassword !== newPasswordConfirmation) {
    return res.status(400).render("users/change-password", {
      pageTitle: "Change Password",
      errorMessage: "The password does not match the confirmation",
    });
  }
  user.password = newPassword;
  await user.save(); // 이후 pre M/W가 작동되어 비번 해시화 한 뒤에 DB에 저장
  //send notification
  
  return res.redirect("/users/logout");
};


// 참고로 pre("save")M/W는 Create()이나 Save()으로 트리거 시킬수 있다.
// findByIdAndUpdate는 pre("save")를 트리거 시키지 않음
/*uerSchema.pre("save", async function (err, result) {
    this.password = await bcrypt.hash(this.password, 5);
});*/

그리고 이때 Form의 Input은 반드시 이름을 가져야 한다는 것을 잊지말자.

그래야 input의 value를 req.body로 가져올 수 있다.

extends ../base

block content 
    if errorMessage
        span=errorMessage
    form(method="POST")
        input(placeholder="Old password",type="password",name="oldPassword")
        input(placeholder="New password",type="password",name="newPassword")
        input(placeholder="New password Confirmation",type="password",name="newPasswordConfirmation")
        input(value="Change Password", type="submit")

그리고 우리는 DB가 2개이다.

유저 DB 하나와 세션 DB가 그것이다.

따라서 변경된 정보를 유저 DB뿐 아니라 세션 DB에도 저장해야 한다는 것을 주의하자. (이 경우에는 세션의 pw정보가 필요하지 않아 세션을 업데이트 해주지 않았다.)

Categories:

Updated:

Leave a comment