user authentication - join, login
bcrypt.hash() / form validation / status code / bcrypt.compare()
+ėėíęļ° ė ė globalRouterëĨž rootRouterëĄ ëŠĻë ėė íīėĢžėëĪ.
models íīë ėė User.js íėžė ë§ë í userScheamaė User ëŠĻëļė ë§ë ëĪ.
init.js íėžėė User.js íėžė import íëĪ.
emailęģž usernameė ėĪëģĩė ë°Đė§íęļ° ėíī unique: true ëĨž ėķę°íëĪ.
export const getJoin = (req, res) => {
return res.render("join", { pageTitle: "Join" });
};
ėīëĐėžęģž ëđë°ëēíļë 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")
userRouter.route("/join").get(getJoin).post(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");
};
ė§ęļė ë°ėīí°ëē ėīėĪė íĻėĪėëę° ę·ļëëĄ ë
ļėķëėī ėëĪ.
ëģīėė ėíī íĻėĪėëëĨž ëīë íīėí ė ėëëĄ ėēëĶŽíīėž íëĪ.
íĻėĪėë íīėą(password hashing)ė ėīėĐíëĐī, userę° ė
ë Ĩí ė íí íĻėĪėëëĨž ëŠĻëĨīëëžë íĻėĪėë ėžėđ ėŽëķëĨž íėļí ė ėëĪ.
'ë ļë§ë ė―ë' ė íëļ äļ ëđë°ëēíļ íļë ļëĪęģ ? ėíļí. íīėíĻė. 5ëķ ėĪëŠ . ė°ļęģ
ė
ë Ĩę°(ëđë°ëēíļ) -> íīė íĻė(hashing) -> ėķë Ĩę°(íīėąë ëđë°ëēíļ)
ę·ļëŽë, ęē°ęģžę°ė ėëĐī ë ėļëģīė° í
ėīëļė ėīėĐíī ė
ë Ĩę°ė ėėëž ė ėëĪ.
ë ėļëģīė° í
ėīëļėīë ė
ë Ĩę°ęģž ėķë Ĩę°ė ė°ęē°íīëė ęēė ë§íëĪ.
ėīëĨž ë°Đė§íęļ° ėíī salting ęģžė ė ęą°ėģėž íëĪ.
saltë ëëĪ í
ėĪíļëĨž ë§íëĪ.
ė
ë Ĩę°(ëđë°ëēíļ+salt) -> íīė íĻė(hashing) -> ėķë Ĩę°(íīėąë ëđë°ëēíļ)
ėīė ęē°ęģžę°ė ėėë ë ėļëģīė° í ėīëļė ėīėĐíī ė ë Ĩę°ė ėėëž ė ėëĪ.
bcryptë ėíļę° íŽíĻë ėíļí íīėą íĻėėīëĪ.
ėīëĨž ėīėĐíëĐī ë ėļëģīė° í
ėīëļ ęģĩęēĐėžëĄ ëđë°ëēíļę° íīíđëđíë ęēė ë°Đė§í ė ėëĪ.
ėŽėĐ ë°Đëē
bcrypt.hash(myPlaintextPassword, saltRounds, function(err, hash) {
// Store hash in your password DB.
});
User.js
userSchema.pre("save", async function() {
this.password = await bcrypt.hash(this.password, 5);
});
ėŽęļ°ė thisë ė ėĨíë Īë user documentëĨž ë§íëĪ.
async & awaitė ėŽėĐíėžëŊëĄ ė―ë°ą íĻėë ė ėīėĪ íė ėëĪ.
fromėė emailęģž usernameė unique ėėąė ėĢžėęļ° ëëŽļė ėīëŊļ ë°ėīí°ëē ėīėĪė ė ėĨëėī ėë emailęģž usernameė ė ėĨíë Īęģ íëĐī ėëŽę° ëŽëĪ.
ė―ë ė°Ļėėė userę° ė ë Ĩí ę°ėī ë°ėīí°ëē ėīėĪė ė ėĨë ę°ęģž ėĪëģĩëëė§ ėēīíŽí í ėë§ė ėëĩė ëģīëīëëĄ ėė íėŽ ėą ėĪíė ëŽļė ę° ėëëĄ íīėž íëĪ.
ė―ë ėĪëģĩė ížíęļ° ėíī $or
ė ėŽėĐíëĪ.
íĻėĪėëëĨž í ëē ë íėļíë inputė ë§ë ëĪ.
ėėė ė ë Ĩí íĻėĪėëė ę°ė ę°ė ę°ė§ëė§ íėļíīėž íëĪ.
ė ëīėĐė ë°ėíī ė―ëëĨž ėė íëĪ.
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});
}
};
ėēė ęģė ė ėėąí ë ėĪė í ėëŽ ëŽļęĩŽę° ëīëëžë ëļëžė°ė ë usernameęģž passwordëĨž ė ėĨí ęēėļė§ëĨž ëŽŧëëĪ.
ëļëžė°ė ë usernameęģž passwordëĨž ę°ė§ęģ ėėēė ëģīëļ í ėëĩėžëĄ 200ė ë°ėžëĐī ęģė ėī ėąęģĩė ėžëĄ ë§ëĪėīėĄëĪęģ íëĻíėŽ ėīëĨž ė ėĨí ęēėļė§ ëŽŧë ęēėīëĪ.
ë°ëžė, ëļëžė°ė ėęē ë ëë§ė ė ëė§ë§ ėëŽę° ėėëĪęģ ėë ĪėĪėž íëĪ.
ėĶ, ëļëžė°ė ėęē 200(ėąęģĩ)ėī ėëëž 400(ėëŠŧë ėėē)ė ėëĩėžëĄ ëģīëīėž íëĪ.
res.status(400).render();
username, email ėĪëģĩ ęēėŽė password íėļ ëķëķė ėė ę°ėī ėė íëĪ.
ëëķėī videoController.js íėžėėë ę°ę°ė ėëŽ ëķëķė 400(ėëēę° ėėēė ęĩŽëŽļė ėļėíė§ ëŠŧíĻ) ëë 404(ė°ūė ė ėė)ëĨž ėķę°íëĪ.
ëļëžė°ė ę° ėđ ėŽėīíļëĨž ë°ĐëŽļíī ėí ė―ë 2xxė ë°ėžëĐī íīëđ urlė íėĪí ëĶŽė ëĻęļ°ė§ë§, ėí ė―ë 4xxė ë°ėžëĐī íīëđ urlė íėĪí ëĶŽė ëĻęļ°ė§ ėëëĪ.
ę·ļëŽëŊëĄ userëĨž ėíī ëļëžė°ė ėęē ėë§ė ėí ė―ëëĨž ëģīëīë ėžė ėĪėíëĪ.
export const getLogin = (req, res) => {
return res.render("login", { pageTitle: "Login" });
};
ëĻžė , userę° ė
ë Ĩí usernameėī ë°ėīí°ëē ėīėĪė ėëė§ íėļíëĪ. (ë°ėė íīëđ user objectëĨž ę°ė ļėėž íëŊëĄ ėŽęļ°ė ėė exists()ę° ėëëž findOne()ė ėŽėĐíëĪ.)
ėëĪëĐī ėëŽ ëĐėė§ė íĻęŧ ëĪė login íėīė§ëĨž render íëĪ.
ėëĪëĐī userę° ė
ë Ĩí passwordëĨž íīėąíėŽ ėŧė ę°ėī ë°ėīí°ëē ėīėĪė ėëė§ íėļíëĪ.
ėëĪëĐī ėëŽ ëĐėė§ė íĻęŧ ëĪė login íėīė§ëĨž render íëĪ.
ėëĪëĐī homeėžëĄ redirect íëĪ.
bcrypt.compare(myPlaintextPassword, hash, function(err, result) {
// result == true
});
myPlaintextPassword : userę° ė
ë Ĩí password
hash : ë°ėīí°ëē ėīėĪė ėë password íīė ę°
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ę° ëęĩŽėļė§ë ėęģ ėëĪ.
ėĪė ëĄ ëĄę·ļėļė ęĩŽííęļ° ėíīė ėŋ íĪė ėļė
ė ëíī ėėėž íëĪ.