블로그 이미지
Peter Note
Web & LLM FullStacker, Application Architecter, KnowHow Dispenser and Bike Rider

Publication

Category

Recent Post

2013. 7. 10. 15:42 MongoDB/Prototyping

Mongoose를 사용할 경우 몇가지 유용한 API를 제공하고 있다. 이중 virtual, method, pre에 대한 사용법을 알아보자. 예제는 로그인시 패스워드를 암호화 하는 코드를 가지고 파악해 본다



1. Schemas

  - http://mongoosejs.com/docs/guide.html 의 Schemas에 대한 정의와 플러그인 확장이 가능하다

  - 스키마 인스턴스를 만들었다면 여기에 methods, statics, indexes, virtuals, options등을 설정할 수 있다

  - 플로그인들은 http://plugins.mongoose.com에서 검색가능하다. 현재 나는 _id에 대한 auto increase 기능을 잘 쓰고 있다 

// 1) 스키마 만들기 : 도큐먼트 구조의 정의

var ComponentSchema = new mongoose.Schema({
    _id         : { type: Number },
    name        : { type: String },
    type        : { type: String },
    config      : { type: String },
    updated_on  : { type: Date, default: Date.now }
  });

  // 2) 플러그인 확장 & methods, statics, indexes, virtuals, options등 설정
  ComponentSchema.plugin(autoinc.plugin, {
    model : 'sd_component',
    field : '_id',
    start : 200000,
    step  : 1
  });


// 3) 모델은 스키마 정의에 대한 컴파일된 생성자이다. Model 인스턴스를 통해서 몽고디비로부터 데이터를 CRUD한다

var Component = mongoose.model('sd_component', ComponentSchema);


  - 샘플로 UserSchema 생성하기

  var UserSchema = new mongoose.Schema({
    username : {type:String, required:true , unique:true},
    hashed_password : {type:String, required:true},
    salt : {type:String, required:true}
  });



2. Instance Methods

  - 스키마가 만들어지고 난후 기본적으로 설정된 메소드를 상속받아 사용한다 예) findById

  - 사용자 커스텀 메소드를 정의할 수 있다. 처리 결과를 return 한다.

  - statics는 callback을 파라미터로 넘기고, return이 없다. (참조)

// 메소드안에서 메소드 호출이 가능하다  

UserSchema.method('authenticate', function(plainText) {
    console.log('authenticate called:')
    console.log('plain text = ' + plainText)
    console.log('hashed = ' + this.encryptPassword(plainText))
    console.log('db password= ' + this.hashed_password)
    return this.encryptPassword(plainText) === this.hashed_password;
  });

  UserSchema.method('makeSalt', function() {
    return Math.round((new Date().valueOf() * Math.random())) + '';
  });


  // crypto 모듈 사용하여 암호화  

  UserSchema.method('encryptPassword', function(password) {
    return crypto.createHmac('sha1', this.salt).update(password).digest('hex');
  });

  UserSchema.method('generateToken', function() {
    return crypto.createHash('md5').update(this.username + Date().toString()).digest("hex");
  });



3. Virtual

  - MongoDB에 저장되지 않는 편리한 Attribute이다 (참조)

  - set/get 펑션을 정의한다

  UserSchema.virtual('password')
    .set(function(password) {
      this._password = password;
      this.salt = this.makeSalt(); // 사용자정의 메소드 호출
      this.hashed_password = this.encryptPassword(password); // 사용자정의 메소드 호출
    })
    .get(function() { return this._password; });


// 모델 생성

var User = mongoose.model('User', UserSchema);

User.password = '1234';     // set

console.log(User.password); // get



4. Pre

  - 몽구스의 middleware기능이다

  - init, validate, save, remove 메소드 수행시 처리되는 미들웨어 펑션이다 

  - 복잡한 유효성검사, 트리거 이벤트 처리등. 예로 사용자를 삭제하면 사용자 관련 블로그포스트도 삭제하기같은 경우 사용

     또는 에러 핸들링

  UserSchema.pre('save', function(next) {
    this.token = this.generateToken();
    if (!validatePresenceOf(this.password || this.hashed_password)) {
      next(new Error('Invalid password'));
    } else {
      next();
    }
  });


function validatePresenceOf(value) {
  return value && value.length;
}


// ex)

User.save(function(err) { console.log(err.message); } );


  - post : 사후처리



5. 전체 소스 코드

  - User Model을 모듈로 만든다.  User.js

  - 애플리케이션에서는 User.js를 Model 인스턴스를 리턴받아서 사용한다 

  - Express의 Middleware에서 사용자 로그인시에 User.authenticate을 호출하여 패스워드를 암호화 한다

var mongoose = require('mongoose'),
  crypto = require('crypto');

module.exports = function () {

  var UserSchema = new mongoose.Schema({
    username : {type:String, required:true , unique:true},
    hashed_password : {type:String, required:true},
    salt : {type:String, required:true}
  });

  UserSchema.virtual('password')
    .set(function(password) {
      this._password = password;
      this.salt = this.makeSalt();
      this.hashed_password = this.encryptPassword(password);
    })
    .get(function() { return this._password; });

  UserSchema.method('authenticate', function(plainText) {
    console.log('authenticate called:')
    console.log('plain text = ' + plainText)
    console.log('hashed = ' + this.encryptPassword(plainText))
    console.log('db password= ' + this.hashed_password)
    return this.encryptPassword(plainText) === this.hashed_password;
  });

  UserSchema.method('makeSalt', function() {
    return Math.round((new Date().valueOf() * Math.random())) + '';
  });

  UserSchema.method('encryptPassword', function(password) {
    return crypto.createHmac('sha1', this.salt).update(password).digest('hex');
  });

  UserSchema.method('generateToken', function() {
    return crypto.createHash('md5').update(this.username + Date().toString()).digest("hex");
  });

  UserSchema.pre('save', function(next) {
    this.token = this.generateToken();
    if (!validatePresenceOf(this.password || this.hashed_password)) {
      next(new Error('Invalid password'));
    } else {
      next();
    }
  });

  return mongoose.model('User', UserSchema);
}

function validatePresenceOf(value) {
  return value && value.length;
}



<참조>

  - mongoosse virtual 사용법

  - Action function과 Callback function의 이해 

  - 샘플소스의 GitHub 저장소

posted by Peter Note