Nodejs

 

생활 코딩 강의 목록 :  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

 

 

WEB2 - Node.js 수업의 예제 코드 바로가기

 

 

소스 :  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/

 

Storing UUID Values in MySQL

Karthik Appigatla revisits a post Peter Zaitsev wrote on UUIDs (Universal Unique IDs), rearranging the timestamp and talks about storing UUID Values.

www.percona.com

 

요약하면 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. 수업을 마치며

 

 

 

 

 

 

 

 

 

 

 

about author

PHRASE

Level 60  라이트

일승일패는 병가 상사(兵家常事) , [전쟁에서 이기고 지는 일은 흔히 있을 수 있는 일이라는 뜻으로] 실패한 사람을 위로하거나, 실패한 사람이 스스로를 변명할 때 흔히 쓰는 말.

댓글 ( 4)

댓글 남기기

작성