생활 코딩 강의 목록 : https://opentutorials.org/module/3590
인프런 강의 목록 : https://www.inflearn.com/course/node-js-express/dashboard
이 수업은 CC 라이센스를 따르고 있으며, 아래 링크 에서도 볼 수 있습니다.
Node.js-Express https://opentutorials.org/course/3370
쿠키와 인증 https://opentutorials.org/course/3387
세션과 인증 https://opentutorials.org/course/3400 passpor.js
https://opentutorials.org/course/3402
다중 사용자 https://opentutorials.org/course/3411 google login https://opentutorials.org/course/3413 facebook login
https://opentutorials.org/course/3414
소스 : https://github.com/braverokmc79/E-Study-nodejs-WEB5-Express-multi-user
다중 사용자
67. 수업소개
68. 수업목적
69. 회원가입 UI
lib/auth.js
module.exports = { isOwner: function (request, response) { if (request.user) { return true; } else { return false; } }, statusUI: function (request, response) { var authStatusUI = '<a href="/auth/login">login</a> | <a href="/auth/register">Register</a>' if (this.isOwner(request, response)) { authStatusUI = `${request.user.nickname} | <a href="/auth/logout">logout</a>`; } return authStatusUI; } }
routes/auth.js
router.get('/register', function (request, response) { var fmsg = request.flash(); var feedback = ''; if (fmsg.error) { feedback = fmsg.error[0]; } var title = 'WEB - register'; var list = template.list(request.list); var html = template.HTML(title, list, ` <div style="color:red;">${feedback}</div> <form action="/auth/register_process" method="post"> <p><input type="text" name="email" placeholder="email"></p> <p><input type="password" name="pwd" placeholder="password"></p> <p><input type="password" name="pwd2" placeholder="password"></p> <p><input type="text" name="displayName" placeholder="display name"></p> <p> <input type="submit" value="register"> </p> </form> `, ''); response.send(html); });
70. 회원 정보 저장 1
lowdb 버전 업 으로 nodejs 다중 모듈 오류로 Simple JSONdb
Simple JSON db
https://www.npmjs.com/package/simple-json-db
https://github.com/nmaggioni/Simple-JSONdb
설치
$ npm install simple-json-db
사용법
const JSONdb = require('simple-json-db'); const db = new JSONdb('/path/to/your/storage.json');
routes/auth.js
var express = require('express'); var router = express.Router(); var template = require('../lib/template.js'); const JSONdb = require('simple-json-db'); const db= new JSONdb('./db_store/users.json'); router.post('/register_process', function (request, response) { var post = request.body; var email = post.email; var pwd = post.pwd; var pwd2 = post.pwd2; var displayName = post.displayName; const user = { email: email, pwd: pwd, displayName: displayName } db.set("user", user); response.redirect("/"); }); ~
db.json
{ "user": { "email": "egoing7777@gmail.com", "pwd": "1111", "displayName": "egoing" } }
목록 출력:
const JSONdb = require('simple-json-db'); const db = new JSONdb('./db_store/users.json'); const users = db.storage; for (var i in users) { console.log(users[i]); }
71. 회원 정보 저장 2
UUID 생성하기
npm install uuid
UUID 란?
UUID는 Universally Unique IDentifier의 약어로 범용 고유 식별자 라는 의미입니다.
RFC4122에 명시된 네트워크 상에서 교유성이 보장되는 id를 위한 표준 규약입니다.
DB를 다룰 때 PK를 주로 auto increment 값으로 사용하지만
URL이나 화면상에 노출 시키면 크롤링이나 인젝션 공격에 취약하다는 단점이 있습니다.
때문에 public한 화면단에서는 ramdom 한 UUID를 사용하는 것을 권장합니다.
UUID는 16진수 8자리-4자리-4자리-4자리-12자리 패턴으로 표현됩니다.
// UUID 패턴 예시 1604b772-adc0-4212-8a90-81186c57f598
UUID로 표현할 수 있는 객체의 갯수는 최대 340,282,366,920,938,463,463,374,607,431,768,211,456개 이며, 중복될 확률이 매우매우매우 낮다고 합니다.
종류
UUID 종류에는 크게 4가지가 있습니다.
- v1: 타임스탬프(시간) 기준
- v3: MD5 해시 기준
- v4: 랜덤값 기반
- v5: SHA-1 해시 기준
랜덤값 기반으로 생성되는 v4가 가장 많이 사용되고, 다음으로는 시간 기준인 v1이 많이 사용됩니다.
사용법
설치
# npm npm install uuid # yarn yarn add uuid # v4 만 설치 npm install uuid4
Index UUID
UUID를 그대로 id로 사용한다면 한가지 문제점이 있습니다.
UUID값는 16진수의 문자열과 '-'으로 이루어져 있기 때문에, string 형태로 저장됩니다.
하지만 DB에서 string 데이터를 인덱싱하면, 인덱스도 비정상적으로 커지며 검색 성능도 많이 떨어지게 됩니다.
아래 링크에 UUID 값을 인덱싱 가능하고 순서를 보장받는 체계로 변경하는 방법이 자세하게 설명되어있습니다.
https://www.percona.com/blog/2014/12/19/store-uuid-optimized-way/
요약하면 1-2-3-4-5 의 구조를 32145 로 변경하면 어느정도 보장받는 수 체계로 변환할 수 있다는 것입니다.
JS 코드로 작성하면 아래와 같이 사용할 수 있습니다.
const { v4 } = require('uuid'); const uuid = () => { const tokens = v4().split('-') return tokens[2] + tokens[1] + tokens[0] + tokens[3] + tokens[4]; } uuid();
출처 : https://jane-aeiou.tistory.com/59
routes/auth.js
여기서 email 을 키값으로 등록 처리
var express = require('express'); var router = express.Router(); var template = require('../lib/template.js'); const JSONdb = require('simple-json-db'); const db = new JSONdb('./db_store/users.json'); const { v4 } = require('uuid'); const uuid = () => { const tokens = v4().split('-') return tokens[2] + tokens[1] + tokens[0] + tokens[3] + tokens[4]; } module.exports = function (passport) { ~ router.get('/register', function (request, response) { var fmsg = request.flash(); console.log(fmsg); var feedback = ''; if (fmsg.error) { feedback = fmsg.error[0]; } var title = 'WEB - register'; var list = template.list(request.list); var html = template.HTML(title, list, ` <div style="color:red;">${feedback}</div> <form action="/auth/register_process" method="post"> <p><input type="text" name="email" placeholder="email" value="egoing7777@gmail.com"></p> <p><input type="password" name="pwd" placeholder="password" value="1111"></p> <p><input type="password" name="pwd2" placeholder="password" value="1111"></p> <p><input type="text" name="displayName" placeholder="display name" value="egoing"></p> <p> <input type="submit" value="register"> </p> </form> `, ''); response.send(html); }); router.post('/register_process', function (request, response) { var post = request.body; var email = post.email; var pwd = post.pwd; var pwd2 = post.pwd2; var displayName = post.displayName; if (pwd !== pwd2) { request.flash('error', 'Password must same!'); response.redirect("/auth/register"); } else { const getUser = db.users.get(email); if (getUser !== undefined) { console.log("이미 등록 처리된 이메일 입니다."); request.flash('error', '이미 등록 처리된 이메일 입니다.'); return response.redirect("/auth/register"); } const id = "user_" + uuid(); const user = { id: id, email: email, pwd: pwd, displayName: displayName } db.users.set(email, user); //passport login 적용 request.login(user, function (err) { return response.redirect("/"); }) } }); ~ return router; }
db_store/users.json
{ "egoing7777@gmail.com": { "id": "user_4020ed11250ba6518cde475c82fbbc7c", "email": "egoing7777@gmail.com", "pwd": "1111", "displayName": "egoing" } }
72. 세션 스토어에 인증 정보 저장
lib.db.js
const JSONdb = require('simple-json-db'); const dbUser = new JSONdb('./db_store/users.json'); const topics = new JSONdb('./db_store/topics.json'); const { v4 } = require('uuid'); const uuid = () => { const tokens = v4().split('-') return tokens[2] + tokens[1] + tokens[0] + tokens[3] + tokens[4]; } const db = { users: dbUser, topics: topics, uuid: uuid } module.exports = db
routes/auth.js
var express = require('express'); var router = express.Router(); var template = require('../lib/template.js'); const db = require("../lib/db"); const { v4 } = require('uuid'); const uuid = () => { const tokens = v4().split('-') return tokens[2] + tokens[1] + tokens[0] + tokens[3] + tokens[4]; } module.exports = function (passport) { router.get('/login', function (request, response) { var fmsg = request.flash(); var feedback = ''; if (fmsg.error) { feedback = fmsg.error[0]; } var title = 'WEB - login'; var list = template.list(request.list); var html = template.HTML(title, list, ` <div style="color:red;">${feedback}</div> <form action="/auth/login_process" method="post"> <p><input type="text" name="email" placeholder="email"></p> <p><input type="password" name="pwd" placeholder="password"></p> <p> <input type="submit" value="login"> </p> </form> `, ''); response.send(html); }); router.post('/login_process', passport.authenticate('local', { successRedirect: '/', failureRedirect: '/auth/login', failureFlash: true, successFlash: true })); router.get('/register', function (request, response) { var fmsg = request.flash(); console.log(fmsg); var feedback = ''; if (fmsg.error) { feedback = fmsg.error[0]; } var title = 'WEB - register'; var list = template.list(request.list); var html = template.HTML(title, list, ` <div style="color:red;">${feedback}</div> <form action="/auth/register_process" method="post"> <p><input type="text" name="email" placeholder="email" value="egoing7777@gmail.com"></p> <p><input type="password" name="pwd" placeholder="password" value="1111"></p> <p><input type="password" name="pwd2" placeholder="password" value="1111"></p> <p><input type="text" name="displayName" placeholder="display name" value="egoing"></p> <p> <input type="submit" value="register"> </p> </form> `, ''); response.send(html); }); router.post('/register_process', function (request, response) { var post = request.body; var email = post.email; var pwd = post.pwd; var pwd2 = post.pwd2; var displayName = post.displayName; if (pwd !== pwd2) { request.flash('error', 'Password must same!'); response.redirect("/auth/register"); } else { const getUser = db.users.get(email); if (getUser !== undefined) { console.log("이미 등록 처리된 이메일 입니다."); request.flash('error', '이미 등록 처리된 이메일 입니다.'); return response.redirect("/auth/register"); } const id = "user_" + db.uuid(); const user = { id: id, email: email, pwd: pwd, displayName: displayName } db.users.set(email, user); //passport login 적용 request.login(user, function (err) { return response.redirect("/"); }) } }); router.get('/logout', function (req, res, next) { req.logout(function (err) { if (err) { return next(err); } req.session.destroy(function (err) { res.redirect('/'); }) }); }); return router; }
lib/passport.js
const db = require("./db"); module.exports = function (app) { var passport = require('passport'), LocalStrategy = require('passport-local').Strategy; app.use(passport.initialize()); app.use(passport.session()); passport.serializeUser(function (user, cb) { process.nextTick(function () { cb(null, { id: user.id, displayName: user.displayName }); }); }); passport.deserializeUser(function (user, cb) { process.nextTick(function () { return cb(null, user); }); }); passport.use(new LocalStrategy({ usernameField: 'email', passwordField: 'pwd' }, function verify(email, password, cb) { const getUser = db.users.get(email); console.log("email getUser: ", getUser); if (getUser) { if (getUser.pwd === password) { return cb(null, getUser, { message: 'Welcome.' }); } else { return cb(null, false, { message: 'Incorrect password.' }); } } else { return cb(null, false, { message: 'Incorrect email.' }); } })); return passport; }
73. 로그인 구현
lib/passport.js
passport.use(new LocalStrategy({ usernameField: 'email', passwordField: 'pwd' }, function verify(email, password, cb) { const getUser = db.users.get(email); console.log("email getUser: ", getUser); if (getUser) { if (getUser.pwd === password) { return cb(null, getUser, { message: 'Welcome.' }); } else { return cb(null, false, { message: 'Incorrect password.' }); } } else { return cb(null, false, { message: 'Incorrect email.' }); } }));
74. 접근제어 - 글쓰기
routes/topic.js
~ router.post('/create_process', function (request, response) { if (!auth.isOwner(request, response)) { response.redirect('/'); return false; } var post = request.body; var title = post.title; var description = post.description; const id = "topic_" + db.uuid(); const topic = { id: id, title: title, description: description, user_id: request.user.email } db.topics.set(id, topic); response.redirect(`/topic/${id}`); }); router.get('/:pageId', function (request, response, next) { const topic = db.topics.get(request.params.pageId); const user = db.users.get(topic.user_id); if (topic) { var title = topic.title; var sanitizedTitle = sanitizeHtml(title); var sanitizedDescription = sanitizeHtml(topic.description, { allowedTags: ['h1'] }); var list = template.list(); var html = template.HTML(sanitizedTitle, list, `<h2>${sanitizedTitle}</h2>${sanitizedDescription} <p>by ${user.displayName}</p> `, ` <a href="/topic/create">create</a> <a href="/topic/update/${topic.id}">update</a> <form action="/topic/delete_process" method="post"> <input type="hidden" name="id" value="${topic.id}"> <input type="submit" value="delete"> </form>`, auth.statusUI(request, response) ); response.send(html); } else { next(err); } }); ~
lib/template.js
var db = require("../lib/db"); module.exports = { HTML: function (title, list, body, control, authStatusUI = '<a href="/auth/login">login</a>') { return ` <!doctype html> <html> <head> <title>WEB1 - ${title}</title> <meta charset="utf-8"> </head> <body> ${authStatusUI} <h1><a href="/">WEB</a></h1> ${list} ${control} ${body} </body> </html> `; }, list: function () { const topicList = db.topics.storage; var list = '<ul>'; for (let i in topicList) { list = list + `<li><a href="/topic/${topicList[i].id}">${topicList[i].title}</a></li>`; } list = list + '</ul>'; return list; } }
75. 글목록을 Simple JSON DB 로 전환
main.js
// app.get('*', function (request, response, next) { // fs.readdir('./data', function (error, filelist) { // request.list = filelist; // next(); // }); // });
lib/template.js
var db = require("../lib/db"); module.exports = { HTML: function (title, list, body, control, authStatusUI = '<a href="/auth/login">login</a>') { return ` <!doctype html> <html> <head> <title>WEB1 - ${title}</title> <meta charset="utf-8"> </head> <body> ${authStatusUI} <h1><a href="/">WEB</a></h1> ${list} ${control} ${body} </body> </html> `; }, list: function () { const topicList = db.topics.storage; var list = '<ul>'; for (let i in topicList) { list = list + `<li><a href="/topic/${topicList[i].id}">${topicList[i].title}</a></li>`; } list = list + '</ul>'; return list; } }
76. 접근제어 - 글 수정
routes/topics.js
router.get('/update/:pageId', function (request, response) { if (!auth.isOwner(request, response)) { response.redirect('/'); return false; } var topic = db.topics.get(request.params.pageId); if (topic.user_id !== request.user.email) { return response.redirect("/"); } var title = topic.title; var description = topic.description; var list = template.list(); var html = template.HTML(title, list, ` <form action="/topic/update_process" method="post"> <input type="hidden" name="id" value="${topic.id}"> <p><input type="text" name="title" placeholder="title" value="${title}"></p> <p><input type="text" name="user_id" placeholder="title" value="${topic.user_id}"></p> <p> <textarea name="description" placeholder="description">${description}</textarea> </p> <p> <input type="submit"> </p> </form> `, `<a href="/topic/create">create</a> <a href="/topic/update/${topic.id}">update</a>`, auth.statusUI(request, response) ); response.send(html); }); router.post('/update_process', function (request, response) { if (!auth.isOwner(request, response)) { response.redirect('/'); return false; } var post = request.body; var id = post.id; var title = post.title; var description = post.description; const topic = { id: id, title: title, description: description, user_id: request.user.email } for (let item in db.topics.storage) { console.log("imte ", item); if (item === id) { db.topics.delete(id); db.topics.set(id, topic); } } response.redirect(`/topic/${id}`); });
77. 접근제어 - 글 삭제
routes/topics.js
router.post('/delete_process', function (request, response) { if (!auth.isOwner(request, response)) { response.redirect('/'); return false; } const post = request.body; const id = post.id; const topic = db.topics.get(id); if (topic.user_id === request.user.email) { db.topics.delete(id); } return response.redirect("/"); });
78. 비밀번호 저장
bcrypt
https://www.npmjs.com/package/bcrypt
$ npm i bcrypt
lib/bcrypt.js 테스트
const bcrypt = require('bcrypt'); const saltRounds = 10; const myPlaintextPassword = 's0/\/\P4$$w0rD'; const someOtherPlaintextPassword = 'not_bacon'; bcrypt.hash(myPlaintextPassword, saltRounds, function (err, hash) { console.log("hash : ", hash); // Load hash from your password DB. bcrypt.compare(myPlaintextPassword, hash, function (err, result) { // result == true console.log("result :", result); }); bcrypt.compare(someOtherPlaintextPassword, hash, function (err, result) { // result == false console.log("result :", result); }); });
routes/auth.js
router.post('/register_process', function (request, response) { var post = request.body; var email = post.email; var pwd = post.pwd; var pwd2 = post.pwd2; var displayName = post.displayName; if (pwd !== pwd2) { request.flash('error', 'Password must same!'); response.redirect("/auth/register"); } else { const getUser = db.users.get(email); if (getUser !== undefined) { console.log("이미 등록 처리된 이메일 입니다."); request.flash('error', '이미 등록 처리된 이메일 입니다.'); return response.redirect("/auth/register"); } const id = "user_" + db.uuid(); const bcrypt = require('bcrypt'); const saltRounds = 10; bcrypt.hash(pwd, saltRounds, function (err, hash) { console.log("hash : ", hash); const user = { id: id, email: email, pwd: hash, displayName: displayName } db.users.set(email, user); //passport login 적용 request.login(user, function (err) { return response.redirect("/"); }) }); } });
lib/passport.js
const db = require("./db"); const bcrypt = require('bcrypt'); module.exports = function (app) { var passport = require('passport'), LocalStrategy = require('passport-local').Strategy; app.use(passport.initialize()); app.use(passport.session()); passport.serializeUser(function (user, cb) { process.nextTick(function () { cb(null, { id: user.id, email: user.email, displayName: user.displayName }); }); }); passport.deserializeUser(function (user, cb) { process.nextTick(function () { return cb(null, user); }); }); passport.use(new LocalStrategy({ usernameField: 'email', passwordField: 'pwd' }, function verify(email, password, cb) { const getUser = db.users.get(email); console.log("email getUser: ", getUser); if (getUser) { bcrypt.compare(password, getUser.pwd, function (err, result) { if (result) { return cb(null, getUser, { message: 'Welcome.' }); } else { return cb(null, false, { message: 'Incorrect password.' }); } }); } else { return cb(null, false, { message: 'Incorrect email.' }); } })); return passport; }
79. 수업을 마치며
댓글 ( 4)
댓글 남기기