hashing
npm i bcrypt
import bcrypt from "bcrypt";
import mongoose from "mongoose";
userSchema.pre("save", async function() {
//this는 create되는 User을 가리킴
this.password = await bcrypt.hash(this.password, 5);
})
bcrypt.hash(this.password, 5)
→ 1번째 인자에는 hash할 비밀번호가 들어감.
→ 2번째 인자에는 hash를 몇번 할 것인지 Number으로 들어감.
hashing한 비밀번호 login할때 맞는지 비교하기
bcrypt.compare(password, user.password)
- 첫번째 인자는 사용자가 login할때 적은 password
- 2번째 인자는 사용자가 가입할때 적은 비밀번호(hash된 비밀번호)
const match = await bcrypt.compare(password, user.password);
if(!match) {
return res.status(400).render("login",{
pageTitle,
errorMessage: "Worng Password"
});
}
이미 create한 user인지 확인하는 법
username과 email 둘중에 하나만 있어도 true를 return한다.
const exits = await User.exists({$or: [{ username }, { email }]});
confirm password
if(password !== password2){
return res.render("join", {
pageTitle: "join",
errorMessage : "Password confirmation does not match"
});
}
status 400/404
if(password !== password2){
return res.status(400).render("join", {
pageTitle: "join",
errorMessage : "Password confirmation does not match"
});
}
session
server.js에 session을 middleware로 설정
app.use(session({
secret: "Hello!",
resave: true,
saveUninitialized: true,
}))
위에 작성된 session 미들웨어를 통해 req.session을 사용 할 수 있다.
user가 로그인 할때 req.session에 user내용을 저장하게 한다.
export const postLogin = async(req, res) => {
const { username, password } = req.body;
const pageTitle = "Login"
const user = await User.findOne({username});
if(!user){
return res.status(400).render("login", {pageTitle, errorMessage:"존재하지 않는 사용자입니다."})
}
const match = await bcrypt.compare(password, user.password);
if(!match) {
return res.status(400).render("login",{
pageTitle,
errorMessage: "Worng Password"
});
}
req.session.loggedIn = true;
req.session.user = user;
return res.redirect("/");
}
위의코드로만 ssesion을 쓴다면 서버가 재시작 할 때 마다 session이 사라진다. 따라서 session을 mongodb에 저장 해 주어야 한다.
npm i connect-mongo
import session from "express-session";
import MongoStore from "connect-mongo";;
app.use(session({
secret: "Hello!",
resave: true,
saveUninitialized: true,
store: MongoStore.create({
mongoUrl: "mongodb://127.0.0.1:27017/wetube",
})
}))
위의 코드로 session을 mongodb에 저장하면 로그인을 안한 유저까지 session이 저장되어 용량을 어마어마 하게 차지 할 수 있다.
이를 방지하기위해 밑의 코드와 같이 session설정을 해주면 된다.
- 아래와 같은 코드로 작성시 session을 초기화 할때마다 session을 mongodb에 저장한다.
→ 우리의 코드에서는 login할때 세션이 초기화됨! req.session에 값을 넣어주기 때문.
app.use(
session({
secret: "Hello!",
resave: false,
saveUninitialized: false,
store: MongoStore.create({
mongoUrl: "mongodb://127.0.0.1:27017/wetube",
}),
})
.env파일 만들어서 secrit와 MONGODB_URL 변수로 두기
- .env파일은 .gitignore에 들어가게한다.
- 관습적으로 .env파일에 들어가는 변수는 대문자로 하는게 원칙.
COOKIE_SECRET=dawdawdhkauwdhkuasdjldsgijdilsglosdgdskflaksd;
DB_URL=mongodb://127.0.0.1:27017/wetube
사용할때는 process.env.변수명으로 사용하기
app.use(
session({
secret: process.env.COOKIE_SECRET,
resave: false,
saveUninitialized: false,
store: MongoStore.create({
mongoUrl: process.env.DB_URL,
}),
})
);
npm i dotenv
서버가 시작되는 첫 지점인 init.js에 import "dotenv/config"해주기
Github Login
https://github.com/settings/developers로 접속하여 새로운 앱을 만든다.
pug에 github로그인 창 만들기
a(href="/users/github/start") Continue with Github
/users/github/start로 갔을때 controller를 만든다.
scope: "read:user user:email"을 설정 안해주면 나중에 user정보를 받을때 user정보를 얻을 수 없음.
export const startGithubLogin = (req, res) => {
const baseUrl = "https://github.com/login/oauth/authorize";
const config = {
client_id: "b247bfa518854515df95",
allow_signup: false,
scope: "read:user user:email",
};
const params = new URLSearchParams(config).toString();
const finalUrl = `${baseUrl}?${params}`;
return res.redirect(finalUrl);
};
return res.redirect(finalUrl)으로 finalUrl로 가면 github에 로그인하는 창이나온다. 로그인하면 생성한 앱을 만들때 쓴 callback url로 페이지가 이동한다.
받은 code를 이용해 github에게 token요청하기
export const finishGithubLogin = async (req, res) => {
const baseUrl = "https://github.com/login/oauth/access_token";
const config = {
client_id: process.env.GH_CLIENT,
client_secret: process.env.GH_SECRET,
code: req.query.code,
};
const params = new URLSearchParams(config).toString();
const finalUrl = `${baseUrl}?${params}`;
const tokenRequest = await (
await fetch(finalUrl, {
method: "POST",
headers: {
Accept: "application/json",
},
})
).json();
};
{
access_token: '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',
token_type: 'bearer',
scope: 'read:user,user:email'
}
export const finishGithubLogin = async (req, res) => {
const baseUrl = "https://github.com/login/oauth/access_token";
const config = {
client_id: process.env.GH_CLIENT,
client_secret: process.env.GH_SECRET,
code: req.query.code,
};
const params = new URLSearchParams(config).toString();
const finalUrl = `${baseUrl}?${params}`;
const tokenRequest = await (
await fetch(finalUrl, {
method: "POST",
headers: {
Accept: "application/json",
},
})
).json();
if ("access_token" in tokenRequest) {
const { access_token } = tokenRequest;
const userData = await (
await fetch("https://api.github.com/user", {
headers: {
Authorization: `token ${access_token}`,
},
})
).json();
console.log(userData);
} else {
return res.redirect("/login");
}
};
{
login: 'abc5259',
id: 62169861,
node_id: 'MDQ6VXNlcjYyMTY5ODYx',
avatar_url: 'https://avatars.githubusercontent.com/u/62169861?v=4',
gravatar_id: '',
url: 'https://api.github.com/users/abc5259',
html_url: 'https://github.com/abc5259',
followers_url: 'https://api.github.com/users/abc5259/followers',
following_url: 'https://api.github.com/users/abc5259/following{/other_user}',
gists_url: 'https://api.github.com/users/abc5259/gists{/gist_id}',
starred_url: 'https://api.github.com/users/abc5259/starred{/owner}{/repo}',
subscriptions_url: 'https://api.github.com/users/abc5259/subscriptions',
organizations_url: 'https://api.github.com/users/abc5259/orgs',
repos_url: 'https://api.github.com/users/abc5259/repos',
events_url: 'https://api.github.com/users/abc5259/events{/privacy}',
received_events_url: 'https://api.github.com/users/abc5259/received_events',
type: 'User',
site_admin: false,
name: 'LeeJaeHoon',
company: null,
blog: '',
location: null,
email: 'dlwogns3413@naver.com',
hireable: null,
bio: null,
twitter_username: null,
public_repos: 18,
public_gists: 0,
followers: 0,
following: 0,
created_at: '2020-03-14T07:58:41Z',
updated_at: '2021-10-19T23:28:59Z',
private_gists: 0,
total_private_repos: 1,
owned_private_repos: 1,
disk_usage: 73130,
collaborators: 0,
two_factor_authentication: false,
plan: {
name: 'free',
space: 976562499,
collaborators: 0,
private_repos: 10000
}
}
export const finishGithubLogin = async (req, res) => {
const baseUrl = "https://github.com/login/oauth/access_token";
const config = {
client_id: process.env.GH_CLIENT,
client_secret: process.env.GH_SECRET,
code: req.query.code,
};
const params = new URLSearchParams(config).toString();
const finalUrl = `${baseUrl}?${params}`;
const tokenRequest = await (
await fetch(finalUrl, {
method: "POST",
headers: {
Accept: "application/json",
},
})
).json();
if ("access_token" in tokenRequest) {
const { access_token } = tokenRequest;
const apiUrl = "https://api.github.com";
const userData = await (
await fetch(`${apiUrl}/user`, {
headers: {
Authorization: `token ${access_token}`,
},
})
).json();
const emailData = await (
await fetch(`${apiUrl}/user/emails`, {
headers: {
Authorization: `token ${access_token}`,
},
})
).json();
const email = emailData.find(
email => email.primary === true && email.verified === true
);
if (!email) {
return res.redirect("/login");
}
} else {
return res.redirect("/login");
}
};
[
{
email: 'dlwogns3413@naver.com',
primary: true,
verified: true,
visibility: 'public'
},
{
email: '62169861+abc5259@users.noreply.github.com',
primary: false,
verified: true,
visibility: null
}
]
export const finishGithubLogin = async (req, res) => {
const baseUrl = "https://github.com/login/oauth/access_token";
const config = {
client_id: process.env.GH_CLIENT,
client_secret: process.env.GH_SECRET,
code: req.query.code,
};
const params = new URLSearchParams(config).toString();
const finalUrl = `${baseUrl}?${params}`;
const tokenRequest = await (
await fetch(finalUrl, {
method: "POST",
headers: {
Accept: "application/json",
},
})
).json();
if ("access_token" in tokenRequest) {
const { access_token } = tokenRequest;
const apiUrl = "https://api.github.com";
const userData = await (
await fetch(`${apiUrl}/user`, {
headers: {
Authorization: `token ${access_token}`,
},
})
).json();
const emailData = await (
await fetch(`${apiUrl}/user/emails`, {
headers: {
Authorization: `token ${access_token}`,
},
})
).json();
const emailObj = emailData.find(
email => email.primary === true && email.verified === true
);
if (!emailObj) {
return res.redirect("/login");
}
let user = await User.findOne({ email: emailObj.email });
if (!user) {
user = await User.create({
avatarUrl: userData.avatar_url,
name: userData.name,
email: emailObj.email,
username: userData.login,
password: "",
location: userData.location,
socialOnly: true,
});
}
req.session.loggedIn = true;
req.session.user = user;
return res.redirect("/");
} else {
return res.redirect("/login");
}
LogOut
export const logout = (req, res) => {
req.session.destroy();
return res.redirect("/");
};
protector/public middleware
protector middleware
export const protectedMiddleware = (req, res, next) => {
if (req.session.loggedIn) {
next();
} else {
return res.redirect("/login");
}
};
public middleware
export const publicOnlyMiddleware = (req, res, next) => {
if (!req.session.loggedIn) {
next();
} else {
return res.redirect("/");
}
};
userRouter.get("/logout", protectedMiddleware, logout);
userRouter.route("/edit").all(protectedMiddleware).get(getEdit).post(postEdit);
edit profile
export const postEdit = async (req, res) => {
const {
session: {
user: { _id },
},
body: { name, email, username, location },
} = req;
const user = await User.findByIdAndUpdate(_id, {
name,
email,
username,
location,
});
return res.render("edit-profile", { pageTitle: "Edit Profile" });
};
export const getEdit = (req, res) => {
return res.render("edit-profile", { pageTitle: "Edit Profile" });
};
export const postEdit = async (req, res) => {
const pagetitle = "Edit Profile";
const {
session: {
user: { _id },
},
body: { name, email, username, location },
} = req;
const updateUser = await User.findByIdAndUpdate(
_id,
{
name,
email,
username,
location,
},
{ new: true }
);
req.session.user = updateUser;
return res.redirect("/users/edit");
};
export const getEdit = (req, res) => {
return res.render("edit-profile", { pageTitle: "Edit Profile" });
};
export const postEdit = async (req, res) => {
const pagetitle = "Edit Profile";
const {
session: {
user: { _id },
},
body: { name, email, username, location },
} = req;
if (req.session.user.email !== email) {
const existEmail = await User.exists({ email });
if (existEmail) {
return res.status(400).render("edit-profile", {
pagetitle,
errorMessage: "이미 있는 이메일 입니다.",
});
}
}
if (req.session.user.username !== username) {
const existUsername = await User.exists({ username });
if (existUsername) {
return res.status(400).render("edit-profile", {
pagetitle,
errorMessage: "이미 있는 username 입니다.",
});
}
}
const updateUser = await User.findByIdAndUpdate(
_id,
{
name,
email,
username,
location,
},
{ new: true }
);
req.session.user = updateUser;
return res.redirect("/users/edit");
};
Change Password
export const postChangePassword = async (req, res) => {
const {
session: {
user: { _id, password },
},
body: { OldPassword, newPassword, newPasswordConfirmation },
} = req;
const match = await bcrypt.compare(OldPassword, password);
if (!match) {
return res.status(400).render("change-password", {
pageTitle: "Change Password",
errorMessage: "현재 비밀번호가 일치하지 않습니다",
});
}
if (newPassword !== newPasswordConfirmation) {
return res.status(400).render("change-password", {
pageTitle: "Change Password",
errorMessage: "새로운 비밀번호가 일치하지 않습니다.",
});
}
const user = await User.findById(_id);
user.password = newPassword;
await user.save();
req.session.user.password = user.password;
return res.redirect("/users/logout");
};