[TIL] 211129

Lee Syong·2021년 11ė›” 29ėž
0

TIL

ëŠĐ록 ëģīęļ°
103/204
post-thumbnail

📝 ė˜Ī늘 한 ęēƒ

  1. user authentication - join, login

  2. bcrypt.hash() / form validation / status code / bcrypt.compare()


📚 ë°°ėšī ęēƒ

User Authentication

1. ęģ„ė •(accout) ėƒė„ą

+ė‹œėž‘하ęļ° ė „ė— globalRouterëĨž rootRouter로 ëŠĻ두 ėˆ˜ė •í•īėĢžė—ˆë‹Ī.

1) userSchema & User ëŠĻëļ 만ë“Īęļ°

  • models íī더 ė•ˆė— User.js 파ėžė„ 만든 후 userScheamaė™€ User ëŠĻëļė„ 만든ë‹Ī.

  • init.js 파ėžė—ė„œ User.js 파ėžė„ import 한ë‹Ī.

  • emailęģž usernameė€ ėĪ‘ëģĩė„ ë°Đė§€í•˜ęļ° ėœ„í•ī unique: true ëĨž ėķ”ę°€í•œë‹Ī.

2) getjoin ėŧĻíŠļëĄĪ럮

export const getJoin = (req, res) => {
  return res.render("join", { pageTitle: "Join" });
};

3) join.pug 파ėž ėƒė„ą

ėīëДėžęģž ëđ„ë°€ëēˆí˜ļ는 type ė†ė„ą 값ė„ 각각 emailęģž password로 ė§€ė •í•īėĪ€ë‹Ī.

extends base

block content 
  form(method="POST")
    input(name="name", type="text", placeholder="Name", required)
    input(name="email", type="email", placeholder="Email", required)
    input(name="username", type="text", placeholder="Username", required)
    input(name="password", type="password", placeholder="Password", required)
    input(name="location", type="text", placeholder="Location")
    input(value="Join", type="submit")

4) userRouterė— route ėķ”ę°€

userRouter.route("/join").get(getJoin).post(postJoin);

5) postJoin ėŧĻíŠļëĄĪ럮

join(회ė›ę°€ėž…)ė„ ė™„ëĢŒí•˜ëĐī login 페ėīė§€ëĄœ ėī동하도록 한ë‹Ī.

import User from "../modules/User";

export const postJoin = async (req, res) => {
  const { email, username, password, name, location } = req.body;
  await User.create({
    eamil,
    username,
    password,
    name,
    location
  });
  return res.redirect("/login");
};

6) password hashing(íŒĻėŠĪė›Œë“œ í•īė‹ą)

ė§€ęļˆė€ 데ėī터ëē ėīėŠĪė— íŒĻėŠĪė›Œë“œę°€ ę·ļ대로 ë…ļėķœë˜ė–ī ėžˆë‹Ī.
ëģīė•ˆė„ ėœ„í•ī íŒĻėŠĪė›Œë“œëĨž ëīë„ í•īė„í•  ėˆ˜ ė—†ë„록 ėē˜ëĶŽí•īė•ž 한ë‹Ī.
íŒĻėŠĪė›Œë“œ í•īė‹ą(password hashing)ė„ ėīėšĐ하ëĐī, user가 ėž…ë Ĩ한 ė •í™•í•œ íŒĻėŠĪė›Œë“œëĨž ëŠĻëĨī더띾도 íŒĻėŠĪė›Œë“œ ėžėđ˜ ė—Žëķ€ëĨž 확ėļ할 ėˆ˜ ėžˆë‹Ī.

(1) í•īė‹œ í•Ļėˆ˜

'ë…ļ마드 ė―”더' ėœ íŠœëļŒ äļ­ ëđ„ë°€ëēˆí˜ļ í„ļë ļë‹Īęģ ? ė•”í˜ļ화. í•īė‹œí•Ļėˆ˜. 5ëķ„ ė„Ī멅. ė°ļęģ 

ėž…ë Ĩ값(ëđ„ë°€ëēˆí˜ļ) -> í•īė‹œ í•Ļėˆ˜(hashing) -> ėķœë Ĩ값(í•īė‹ąëœ ëđ„ë°€ëēˆí˜ļ)
  • 동ėží•œ ėž…ë Ĩ값 -> 동ėží•œ ėķœë Ĩ값
  • ėžë°Đí–Ĩ í•Ļėˆ˜ : ėž…ë Ĩ값 -> ėķœë Ĩ값 (o) / ėķœë Ĩ값 -> ėž…ë Ĩ값 (x)

ę·ļ럮나, ęē°ęģžę°’ė„ ė•ŒëĐī 레ėļëģīėš° 테ėīëļ”ė„ ėīėšĐí•ī ėž…ë Ĩ값ė„ ė•Œė•„ë‚ž ėˆ˜ ėžˆë‹Ī.
레ėļëģīėš° 테ėīëļ”ėī란 ėž…ë Ĩ값ęģž ėķœë Ĩ값ė„ ė—°ęē°í•ī놓ė€ ęēƒė„ 말한ë‹Ī.

ėīëĨž ë°Đė§€í•˜ęļ° ėœ„í•ī salting ęģžė •ė„ ęą°ėģė•ž 한ë‹Ī.
salt란 랜ëĪ 텍ėŠĪíŠļëĨž 말한ë‹Ī.

ėž…ë Ĩ값(ëđ„ë°€ëēˆí˜ļ+salt) -> í•īė‹œ í•Ļėˆ˜(hashing) -> ėķœë Ĩ값(í•īė‹ąëœ ëđ„ë°€ëēˆí˜ļ)

ėīė œ ęē°ęģžę°’ė„ ė•Œė•„도 레ėļëģīėš° 테ėīëļ”ė„ ėīėšĐí•ī ėž…ë Ĩ값ė„ ė•Œė•„ë‚ž ėˆ˜ ė—†ë‹Ī.

(2) bcrypt.hash()

bcrypt란 ė†”íŠļ가 폎í•Ļ된 ė•”í˜ļ화 í•īė‹ą í•Ļėˆ˜ėīë‹Ī.
ėīëĨž ėīėšĐ하ëĐī 레ėļëģīėš° 테ėīëļ” ęģĩęēĐėœžëĄœ ëđ„ë°€ëēˆí˜ļ가 í•īí‚đë‹đ하는 ęēƒė„ ë°Đė§€í•  ėˆ˜ ėžˆë‹Ī.

ė‚ŽėšĐ ë°Đëē•

bcrypt.hash(myPlaintextPassword, saltRounds, function(err, hash) {
    // Store hash in your password DB.
});
  • myPlaintextPassword : user가 ėž…ë Ĩ한 password
  • saltRouds : í•īė‹ą 횟ėˆ˜

User.js

userSchema.pre("save", async function() {
  this.password = await bcrypt.hash(this.password, 5);
});

ė—Žęļ°ė„œ this는 ė €ėžĨ하ë Ī는 user documentëĨž 말한ë‹Ī.
async & awaitė„ ė‚ŽėšĐ했ėœžëŊ€ëĄœ ė―œë°ą í•Ļėˆ˜ëŠ” ė ė–īėĪ„ 필ėš” ė—†ë‹Ī.

7) 폞 ėœ íšĻė„ą ęē€ė‚Ž(form validation)

(1) username, email ėĪ‘ëģĩ ęē€ė‚Ž

  • fromė—ė„œ emailęģž usernameė— unique ė†ė„ąė„ ėĢžė—ˆęļ° 때ëŽļė— ėīëŊļ 데ėī터ëē ėīėŠĪė— ė €ėžĨ되ė–ī ėžˆëŠ” emailęģž usernameė„ ė €ėžĨ하ë Īęģ  하ëĐī ė—ëŸŽę°€ 뜮ë‹Ī.

  • ė―”ë“œ ė°Ļė›ė—ė„œ user가 ėž…ë Ĩ한 값ėī 데ėī터ëē ėīėŠĪė— ė €ėžĨ된 값ęģž ėĪ‘ëģĩ되는ė§€ ėēī큮한 후 ė•Œë§žė€ ė‘ë‹ĩė„ ëģīë‚ī도록 ėˆ˜ė •í•˜ė—Ž ė•ą ė‹Ī행ė— ëŽļė œę°€ ė—†ë„록 í•īė•ž 한ë‹Ī.

  • ė―”ë“œ ėĪ‘ëģĩė„ 픾하ęļ° ėœ„í•ī $orė„ ė‚ŽėšĐ했ë‹Ī.

(2) password 확ėļ

  • íŒĻėŠĪė›Œë“œëĨž 한 ëēˆ 더 확ėļ하는 inputė„ 만든ë‹Ī.

  • ėœ„ė—ė„œ ėž…ë Ĩ한 íŒĻėŠĪė›Œë“œė™€ 같ė€ 값ė„ 가ė§€ëŠ”ė§€ 확ėļí•īė•ž 한ë‹Ī.

(3) postJoin ėŧĻíŠļëĄĪ럮 ėˆ˜ė •

ėœ„ ë‚īėšĐė„ 반ė˜í•ī ė―”ë“œëĨž ėˆ˜ė •í–ˆë‹Ī.

user 데ėī터ëĨž ėƒė„ąí•˜ęģ  ė €ėžĨ하는 ëķ€ëķ„ė— try ~ catch ęĩŽëŽļė„ ėķ”ę°€í–ˆë‹Ī.
ė―”ë“œ ė°Ļė›ė—ė„œ ëŊļëĶŽ ėē˜ëĶŽí•œ ė—ëŸŽ 말ęģ ë„ ë‹ĪëĨļ ė—ëŸŽëĨž 대ëđ„하ęļ° ėœ„í•Ļėīë‹Ī.

export const postJoin = async (req, res) => {
  const { email, username, password, password2, name, location } = req.body;
  // password 확ėļ
  if (password !== password2) {
    return res.render("join", { pageTitle: "Join", errorMessage: "Password confirmation does not match." });
  }
  // username, email ėĪ‘ëģĩ ęē€ė‚Ž
  const exists = await User.exists({ $or : [{username}, {email}] });
  if (exists) {
    return res.render("join", { pageTitle: "Join", errorMessage: "This username/email is already taken."});
  }
  // user 데ėī터 ėƒė„ą 및 ė €ėžĨ (try ~ catch ęĩŽëŽļ ėķ”ę°€)
  try {
    await User.create({
      email,
      username,
      password,
      name,
      location,
    });
    return res.redirect("/login");
  } catch(error) {
    return render("join", { pageTitle: "Join", errorMessage: error._message});
  }
}; 

8) ėƒíƒœ ė―”ë“œ(status code)

ėē˜ėŒ ęģ„ė •ė„ ėƒė„ąí•  때 ė„Īė •í•œ ė—ëŸŽ ëŽļęĩŽę°€ ë–ī더띾도 ëļŒëžėš°ė €ëŠ” usernameęģž passwordëĨž ė €ėžĨ할 ęēƒėļė§€ëĨž ëŽŧ는ë‹Ī.

ëļŒëžėš°ė €ëŠ” usernameęģž passwordëĨž 가ė§€ęģ  ėš”ėē­ė„ ëģīë‚ļ 후 ė‘ë‹ĩėœžëĄœ 200ė„ 받ėœžëĐī ęģ„ė •ėī ė„ąęģĩė ėœžëĄœ 만ë“Īė–īėĄŒë‹Īęģ  판ë‹Ļ하ė—Ž ėīëĨž ė €ėžĨ할 ęēƒėļė§€ ëŽŧ는 ęēƒėīë‹Ī.

따띾ė„œ, ëļŒëžėš°ė €ė—ęēŒ 렌더링ė€ ėž˜ 됐ė§€ë§Œ ė—ëŸŽę°€ ėžˆė—ˆë‹Īęģ  ė•Œë ĪėĪ˜ė•ž 한ë‹Ī.
ėĶ‰, ëļŒëžėš°ė €ė—ęēŒ 200(ė„ąęģĩ)ėī ė•„니띞 400(ėž˜ëŠŧ된 ėš”ėē­)ė„ ė‘ë‹ĩėœžëĄœ ëģīë‚īė•ž 한ë‹Ī.

res.status(400).render();

username, email ėĪ‘ëģĩ ęē€ė‚Žė™€ password 확ėļ ëķ€ëķ„ė„ ėœ„ė™€ 같ėī ėˆ˜ė •í•œë‹Ī.
더ëķˆė–ī videoController.js 파ėžė—ė„œë„ 각각ė˜ ė—ëŸŽ ëķ€ëķ„ė— 400(ė„œëē„ę°€ ėš”ėē­ė˜ ęĩŽëŽļė„ ėļė‹í•˜ė§€ ëŠŧí•Ļ) 또는 404(ė°ūė„ ėˆ˜ ė—†ėŒ)ëĨž ėķ”ę°€í•œë‹Ī.

ëļŒëžėš°ė €ę°€ ė›đ ė‚ŽėīíŠļëĨž ë°ĐëŽļí•ī ėƒíƒœ ė―”ë“œ 2xxė„ 받ėœžëĐī í•īë‹đ urlė„ 히ėŠĪ토ëĶŽė— ë‚Ļęļ°ė§€ë§Œ, ėƒíƒœ ė―”ë“œ 4xxė„ 받ėœžëĐī í•īë‹đ urlė„ 히ėŠĪ토ëĶŽė— ë‚Ļęļ°ė§€ ė•ŠëŠ”ë‹Ī.
ę·ļ럮ëŊ€ëĄœ userëĨž ėœ„í•ī ëļŒëžėš°ė €ė—ęēŒ ė•Œë§žė€ ėƒíƒœ ė―”ë“œëĨž ëģīë‚ī는 ėžė€ ėĪ‘ėš”하ë‹Ī.


2. 로ę·ļėļ(Login)

1) getLogin ėŧĻíŠļëĄĪ럮

export const getLogin = (req, res) => {
  return res.render("login", { pageTitle: "Login" });
};

2) postLogin ėŧĻíŠļëĄĪ럮

ëĻžė €, user가 ėž…ë Ĩ한 usernameėī 데ėī터ëē ėīėŠĪė— ėžˆëŠ”ė§€ 확ėļ한ë‹Ī. (밑ė—ė„œ í•īë‹đ user objectëĨž 가ė ļė™€ė•ž 하ëŊ€ëĄœ ė—Žęļ°ė„œ ė•„ė˜ˆ exists()가 ė•„니띞 findOne()ė„ ė‚ŽėšĐ한ë‹Ī.)
ė—†ë‹ĪëĐī ė—ëŸŽ ëДė‹œė§€ė™€ í•Ļęŧ˜ ë‹Īė‹œ login 페ėīė§€ëĨž render 한ë‹Ī.

ėžˆë‹ĪëĐī user가 ėž…ë Ĩ한 passwordëĨž í•īė‹ąí•˜ė—Ž ė–ŧė€ 값ėī 데ėī터ëē ėīėŠĪė— ėžˆëŠ”ė§€ 확ėļ한ë‹Ī.
ė—†ë‹ĪëĐī ė—ëŸŽ ëДė‹œė§€ė™€ í•Ļęŧ˜ ë‹Īė‹œ login 페ėīė§€ëĨž render 한ë‹Ī.
ėžˆë‹ĪëĐī homeėœžëĄœ redirect 한ë‹Ī.

(1) bcrypt.compare()

bcrypt.compare(myPlaintextPassword, hash, function(err, result) {
    // result == true
});

myPlaintextPassword : user가 ėž…ë Ĩ한 password
hash : 데ėī터ëē ėīėŠĪė— ėžˆëŠ” password í•īė‹œ 값

(2) ė―”ë“œ ėž‘ė„ą

import bcrypt from "bcrypt"; // bcryptëĨž import í•īėĪ˜ė•ž 한ë‹Ī!

export const postLogin = async (req, res) => {
  const { username, password } = req.body;
  // user(username)가 ėĄīėžŽí•˜ëŠ”ė§€ 확ėļ
  const user = await User.findOne({ username });
  if (!user) {
    return res.status(400).render("login", {
      pageTitle: "Login",
      errorMessage: "An accout with this username does not exist.",
    });
  }
  // user가 ėž…ë Ĩ한 password가 passwordė˜ í•īė‹œ 값ęģž ėžėđ˜í•˜ëŠ”ė§€ 확ėļ
  const ok = await bcrypt.compare(password, user.password);
  if (!ok) {
    return res.status(400).render("login", {
      pageTitle: "Login",
      errorMessage: "Wrong password",
    });
  }
  // userė—ęēŒ 로ę·ļėļė— ė„ąęģĩ했ėŒė„ ė•Œë Īė•ž 한ë‹Ī
  // ...
  return res.redirect("/");
};

ėīė œ ė ė–ī도 로ę·ļėļė„ ė‹œë„í•œ user가 누ęĩŽėļė§€ëŠ” ė•Œęģ  ėžˆë‹Ī.
ė‹Īė œëĄœ 로ę·ļėļė„ ęĩŽí˜„하ęļ° ėœ„í•īė„  ėŋ í‚Īė™€ ė„ļė…˜ė— 대í•ī ė•Œė•„ė•ž 한ë‹Ī.


âœĻ ë‚īėž 할 ęēƒ

  1. 강ė˜ ęģ„ė† ë“Ģęļ°
profile
ëŠĨ동ė ėœžëĄœ ė‚īėž, 행ëģĩ하ęēŒðŸ˜

0개ė˜ 댓ęļ€