const mongoose = require('mongoose');
// mongoDB를 node.js에서 사용할 수 있도록 만든 것이 mongoose 모듈
const bcrypt = require('bcrypt');
// Blowfish를 기반으로 만들어진 단방향 해시 함수, 흔히 사용되는 해시 알고리즘인
// SHA-256을 사용해서 데이터를 해싱한다. 단방향 암호화이기 때문에 복호화 불가능.
const saltRounds = 10;
// bcrypt 해시를 계산하는 데 필요한 시간을 제어하며
// saltRounds가 높을수록 더 많은 해싱 라운드가 수행되게 된다.
/*
라운드란 ?
블록체인이나 해시그래프 등 탈중앙화 분산형 시스템에서 다수의 노드들이 일정한 합의 알고 리즘에 따라 의살결정을 내리는 최소환의 시간 단위를 말한다.
*/
const jwt = require('jsonwebtoken');
스키마는 데이터베이스의 테이블, 컬렉션의 타입과 속성을 정의해 각각의 필드에 저장하는 값에 의존성을 부여합니다. mongoDB에는 스키마가 없기 때문에 몽구스를 사용해서 스키마를 생성합니다.
const userSchema = mongoose.Schema({
email: { type: String, trim: true, unique: 1 },
password: { type: String, minlength: 10 },
name: { type: String, maxlength: 100 },
github: { type: String, maxlength: 100 },
gender: { type: String, maxlength: 10 },
university: { type: String, maxlength: 10 },
major: { type: String, maxlength: 10 },
role: { type: Number, default: 0 },
title: String,
image: String,
token: { type: String },
tokenExp: { type: Number }
});
String
: 문자열Number
: 정수Schema.Types.ObjectId
: 명시적으로 id 타입을 사용할 때는 이렇게 사용해야 한다. 스키마에 정의를 안해도 자동적으로 몽고에서는 생성하며 행을 구분하는 아이디 값으로 사용된다.Date
: 날짜Buffer
: 바이너리 타입Boolean
: 참과 거짓만 값을 저장하고 나타낸다.Schema.Types.Mixed
: 이름 그대로 다양한 타입을 저장할 수 있다. 객첸 배열 JSON으로 사용합니다. 타입이 없다고 생각해도 된다.Array
:[]
사용해서 배열을 표시 ex)[Number]
정수 배열
required
: 필수 입력unique
: 다른 행과 중복되면 안됨.trim
: 공백 제거(문자열 타입에 사용)default
: 문서가 생성되면 기본값으로 저장된다.lowercase
: 대문자를 소문자로 저장match
: 정규시긍로 저장하려는 값과 비교validate
: 함수로 개발자가 조건을 만듦.set
: 값을 입력할 때 함수로 조건을 만듦.get
: 값을 출력할 때 함수로 조건을 만듦.ref
: 해당하는 모델을 참조할 때 사용.
// 회원가입 시 비밀번호 암호화
// .pre()를 통해 해당 스키마에 데이터가 저장(.save)되기 전 수행할 작업들을 지정한다.
userSchema.pre('save', function(next) {
var user = this;
// 패스워드가 변경될 때만 해싱작업 처리
if(user.isModified('password')) {
// geSalt()를 사용해 salt값 생성
// salt: 공격자가 암호를 유추할 수 없도록, 평문 데이터에 의미 없는 데이터를 뿌려 넣는데 이것을 salt라고 한다.
// salt값 생성
bcrypt.genSalt(saltRounds, function(err, salt) {
if(err) return next(err);
// 생성된 salt값과 비밀번호를 인자로 넘겨준다.
// hash 생성
bcrypt.hash(user.password, salt, function(err, hash) {
if(err) return next(err);
// hash값을 user.password에 저장
user.password = hash
next() // save() 처리
});
});
} else {
next();
}
}, {});
// 몽구스 메서드 생성 (methods) // 로그인 시 비밀번호 암호화 -> 디비에 저장된 비밀번호와 비교 userSchema.methods.comparePassword = function(plainPassword, cb) { // 입력한 값: plainPassword가 this.password와 같습니까 ? // 같은지 비교하려면 입력한 값을 암호화해서 이미 해싱된 db의 비밀번호와 같은지 확인 // bcypt.compare(plainPassword, this.password, function(err, isMatch){ if(err) return cb(err); // cb(callback) => err cb(null, isMatch); // err은 null, isMatch는 true를 반환 }); }
// 몽구스 메서드 생성 (methods) // 로그인 시 토큰 생성 userSchema.methods.generateToken = function(cb) { var user = this; // 만들어진 인스턴스를 user 변수에 저장 ` // jsonwebtoken을 이용해서 토큰 생성 (토큰발급) // user._id는 db에 이미 저장된 _id var token = jwt.sign(user._id.toHexString(), 'secretToken'); ` // 생성된 토큰을 user.token에 저장 user.token = token; user.save(function(err, user) { if(err) return cb(err); cb(null, user) }); }
// 몽구스 메서드 생성 (statics) // 인증 시 토큰과 디비의 토큰을 복호화하여 비교 userSchema.statics.findByToken = function(token, cb) { var user = this; // 토큰 인증(확인) jwt.verify(token, 'secretToken', function(err, decoded) { user.findOne({ "_id": decoded, "token": token }, function(err, user) { if(err) return cb(err); cb(null, user); }); }); } const User = mongoose.model('User', userSchema); module.exports = { User }
- find(조건, 필드, 옵션, 콜백);
- findOne(조건, 필드, 옵션, 콜백);
- create(조건, 콜백);
- save(콜백);
- update(조건, 수정, 옵션, 콜백);
- findOneAndUpdate(조건, 수정, 옵션, 콜백);
- findByIdAndUpdate(아이디, 수정, 옵션, 콜백);
- remove(조건, 콜백);
- findOneAndRemove(조건, 옵션, 콜백);
- findByIdAndRemove(아이디, 옵션, 콜백);
정리가 깔끔하게 잘 되있네요 종종와서 참고하겠습니다.
감사합니다.