Error Handling
const { isEmail } = require('validator');
const userSchema = new mongoose.Schema({
email: {
type: String,
unique: true,
required: true,
validate: [isEmail, 'Please enter a valid email']
}
})
const handleError = (err) => {
console.log(err.message, err.code);
let errors = { email: '', password: '' };
if(err.code == 11000) {
errors.email = 'That email is aleady registered.'
return errors;
}
if(err.message.includes('user validation failed')) {
Object.values(err.errors).forEach(({ properties }) => {
errors[properties.path] = properties.message;
})
}
if(...) {...}
return errors;
}
module.exports.signup_post = async (req, res) => {
const { email, password } = req.body
try {
const user = await.User.create({ email, password });
res.status(201).json(user);
} catch(error) {
const errors = handleError(errors)
res.status(400).json({ errors });
}
}
- validate가 실패했을 때, validate의 second arg의 메세지가 반환됨
unique
의 경우, [isEmail, '...']
와 같이 에러메세지를 할당할 수가 없다. 대신 duplicate error code인 11000
을 사용할 수 있다. (console.log)
Hash a password
Mongoose Hook
- Mongoose Hook을 사용해서 document가 저장되기 전(pre) 혹은 저장된 후(post)에 특정한 작업을 할 수 있음
- doc은 db에 저장되는 document를 의미함
const userSchema = {...};
userSchema.post('save', function(doc, next){
console.log(doc)
next();
})
userSchema.pre('save', function(next){
console.log(this)
next();
})
Hasing
- salt를 추가하지 않을 경우, hasing algorithm을 reverse해서 해킹을 할 수 있음
const bycript = require('bycript')
UserSchema.pre('save', async function (next) {
const user = this;
const saltRounds = 10;
if (user.isModified('password')) {
user.password = await bcrypt.hash(user.password, saltRounds);
}
next();
});
Cookie
- cookie의 default 만료기간(expire)은
session
으로 설정되어 있음(탭을 닫으면 쿠키 삭제됨)
app.get('/set-cookie', (req, res) => {
res.setHeader('Set-CooKie', 'newCookie=true');
res.send('You got a cookie');
})
Cookie-parser
Cookie-parser
라이브러리를 사용하면, cookie object의 method를 더욱 간편하게 사용할 수 있음
res.cookie(name, value, {options})
option을 통해서 쿠키 만료 기간, secure, httpOnly등을 설정할 수 있음
- production에서는 항상
https
에서 cookie를 사용할 것 (secure: true
)
httpOnly: true
: 프론트엔드에서 쿠키에 접근할 수 없음
const cookieParser = require(cookie-parser);
app.use(cookieParser());
app.get('/set-cookie', (req, res) => {
res.cookie('newCookie', true, { maxAge: 60, secure: true, httpOnly: true });
res.send('You got a cookie');
})
app.get('/get-cookie', (req, res) => {
const cookies = req.cookies;
res.json(cookies);
})
JSON Web Token
cookie
안에 jwt
을 저장해서 브라우저와 서버간에 주고 받음(request, response)
Signup
import { useHistory } from "react-router-dom";
const handleSubmit = async(e) => {
e.preventDefault();
try {
const res = await fetch('/signup', {
method: 'POST',
body: JSON.stringify({ email, password }),
headers: { 'Content-Type': 'application/json' }
});
const data = await res.json();
if(data.user) {
let history = useHistory();
history.push('/')
}
} catch(err) {
console.log(err)
}
}
const jwt = require('jsonwebtoken')
const cookieParser = require('cookie-parser');
const MAX_AGE = 3 * 24 * 60 * 60;
const createToken = id => {
return jwt.sign({ id }, 'suyeon jwt secret', {
expiresIn: MAX_AGE
})
}
module.exports.signup_post = async (req, res) => {
const { email, password } = req.body
try {
const user = await.User.create({ email, password });
const token = createToken(user._id);
res.cookie('jwt', token, { httpOnly: true, maxAge: MAX_AGE * 1000 });
res.status(201).json({ user: user._id });
} catch(error) {
const errors = handleError(errors)
res.status(400).json({ errors });
}
}
Login
schema
에서 static method
를 정의할 수 있음
module.exports.login_post = async (req, res) => {
const { email, password } = req.body
try {
const user = await User.login(email, password);
const token = createToken(user._id);
res.cookie('jwt', token, { httpOnly: true, maxAge: MAX_AGE * 1000 });
res.status(200).json({ user: user._id });
} catch(err) {
const errors = handleError(errors)
res.status(400).json({ errors });
}
}
userSchema.statics.login = async function(email, password) {
const user = await this.findOne({ email });
if(user) {
const auth = await bcyrpt.compare(password, user.password);
if(auth) {
return user;
}
throw Error('Incorrect password')
}
throw Error('Incorrect eamil')
}
Logout
router.get('/logout', authController.logout_get);
module.exports.logout_get = (req, res) => {
res.cookie('jwt', '', { maxAge: 1 });
history.push('/');
};
Protected Routes
auth
를 체크하는 미들웨어를 생성한 후, 각 route에 미들웨어를 적용한다.
app.get('url', middleware, (res, req) ⇒ {...})
const jwt = require('jsonwebtoken');
const requireAuth = (req, res, next) => {
const token = req.cookie.jwt;
if(token) {
jwt.verify(token, 'Suyeon secret', (error, decodedToken) => {
if(error) {
res.redirect('/login');
} else {
next();
}
})
} else {
res.redirect('/login');
}
}
module.exports = { requireAuth }
app.get('/smoothies', requireAuth, (res, req) => res.render(...))
Check Current User
current user
를 체크해서 user
의 정보(이메일등)을 view
에 디스플레이할 수 있음
*
과 middleware
를 가장 먼저 위치시키면, 모든 request마다 middleware가 적용됨
const checkUser = async (req, res, next) => {
const token = req.cookie.jwt;
if(token) {
jwt.verify(token, 'Suyeon secret', (error, decodedToken) => {
if(error) {
res.locals.user = null;
console.log(error);
next();
} else {
let user = await User.findById(decodedToken.id);
res.locals.user = user;
next();
}
})
} else {
res.locals.user = null;
next();
}
}
app.get('*', checkUser);
app.get('/', (req, res) => ...);
app.get('/blog', (req, res) => ...);
app.use(authRoutes);
user.email