Nodejs: Signin, Login, User Information
2023. 11. 22. 00:23ㆍJavascript Node.js
Signin and Login code
// ./routers/auth.router.js
import { Router } from 'express';
import db from '../models/index.cjs';
import bcrypt from 'bcrypt';
import {
PASSWORD_HASH_SALT_ROUNDS,
JWT_ACCESS_TOKEN_SECRET,
JWT_ACCESS_TOKEN_EXPIRES_IN,
} from '../constants/security.constant.js';
import jwt from 'jsonwebtoken';
const { Users } = db;
const authRouter = Router();
// 회원가입 /api/auth/siginup
authRouter.post('/signup', async (req, res) => {
try {
const { email, password, passwordConfirm, name } = req.body;
if (!email) {
return res.status(400).json({
success: false,
message: '이메일 형식이 맞지 않습니다.',
});
}
if (!password) {
return res.status(400).json({
success: false,
message: '비밀번호 입력이 필요합니다.',
});
}
if (!passwordConfirm) {
return res.status(400).json({
success: false,
message: '비밀번호 확인 입력이 필요합니다.',
});
}
if (!name) {
return res.status(400).json({
success: false,
message: '이름입력이 필요합니다.',
});
}
if (password !== passwordConfirm) {
return res.status(400).json({
success: false,
message: '입력한 비밀번호가 서로 일치하지 않습니다.',
});
}
if (password.length < 6) {
return res.status(400).json({
success: false,
message: '비밀번호는 최소 6자리 이상입니다.',
});
}
let emailValidationRegex = new RegExp('[a-z0-9._]+@[a-z]+.[a-z]{2,3}');
const isValidEmail = emailValidationRegex.test(email);
if (!isValidEmail) {
return res.status(400).json({
success: false,
message: '올바른 이메일 형식이 아닙니다.',
});
}
const existedUser = await Users.findOne({ where: { email } });
if (existedUser) {
return res.status(400).json({
success: false,
message: '이미 가입된 이메일 입니다.',
});
}
const hashedPassword = bcrypt.hashSync(password, PASSWORD_HASH_SALT_ROUNDS);
const newUser = (
await Users.create({ email, password: hashedPassword, name })
).toJSON();
delete newUser.password;
return res.status(201).json({
success: true,
message: '회원가입에 성공했습니다.',
data: newUser,
});
} catch (error) {
console.error(error);
return res.status(500).json({
success: true,
message: '예상치 못한 에러가 발생하였습니다. 관리자에게 문의하세요',
});
}
});
// 로그인
authRouter.post('/signin', async (req, res) => {
try {
const { email, password } = req.body;
if (!email) {
return res.status(400).json({
success: false,
message: '이메일 입력이 필요합니다.',
});
}
if (!password) {
return res.status(400).json({
success: false,
message: '비밀번호 입력이 필요합니다.',
});
}
const user = (await Users.findOne({ where: { email } }))?.toJSON();
const hashedPassword = user?.password;
// isPsswordMatched 의 bcrypt.compareSync(password, hashedPassword); 값이 같으면 true를 반환
// 반대로 반대 값이 나오면 false 즉 같지 않다는 의미
const isPasswordMatched = bcrypt.compareSync(password, hashedPassword);
const isCorrectUser = user && isPasswordMatched;
if (!isCorrectUser) {
return res.status(401).json({
success: false,
message: '일치하는 인증 정보가 없습니다.',
});
}
// accessToken 발급
// 회원가입시 사용했던 id를 userid이름으로 변경
// jwt.sign()메서드는 별도의 promise를 반환하지 않고 바로 string을 반환하기 때문에 await을 붙일 필요가 없다.
const accessToken = jwt.sign(
{ /* payload값 */ userId: user.id },
/* secret key */ JWT_ACCESS_TOKEN_SECRET,
{ /* options-시간설정 */ expiresIn: JWT_ACCESS_TOKEN_EXPIRES_IN },
);
return res.status(200).json({
success: true,
message: '로그인에 성공했습니다.',
data: { accessToken },
});
} catch (error) {
console.error(error);
return res.status(500).json({
success: true,
message: '예상치 못한 에러가 발생하였습니다. 관리자에게 문의하세요',
});
}
});
export { authRouter };
User Infromation code
// ./middleware/need-signin.middleware.js
import jwt from 'jsonwebtoken';
import { JWT_ACCESS_TOKEN_SECRET } from '../constants/security.constant.js';
import db from '../models/index.cjs';
const { Users } = db;
export const needSignIn = async (req, res, next) => {
try {
const authorizationHeader = req.headers.authorization;
// 인증 정보가 아예 없는 경우
if (!authorizationHeader) {
return res.status(400).json({
success: true,
message: '인증정보가 없습니다.',
});
}
// JWT 기본적인 형태 -> Authorization: Bearer <token>
const [tokenType, accessToken] = authorizationHeader?.split(' ');
// 토큰형식이 일치하지 않는 경우
if (tokenType !== 'Bearer') {
return res.status(400).json({
success: true,
message: '지원하지 않는 인증 방식입니다.',
});
}
// AccessToken이 존재하지 않는 경우
if (!accessToken) {
return res.status(400).json({
success: true,
message: 'AccessToken이 없습니다.',
});
}
const decodedPayload = jwt.verify(
accessToken,
JWT_ACCESS_TOKEN_SECRET + '1',
);
const { userId } = decodedPayload;
console.log({ decodedPayload });
// 일치하는 userId가 없는 경우
const user = (await Users.findByPk(userId)).toJSON();
if (!user) {
return res.status(400).json({
success: true,
message: '존재하지 않는 사용자 입니다.',
});
}
delete user.password;
res.locals.user = user;
next();
} catch (error) {
console.error(error);
let statusCode = 500;
let errorMessage = '';
// switch case 로 분기처리
switch (error.message) {
// JWT 유효기간이 지난 경우
case 'jwt expired':
statusCode = 401;
errorMessage = '인증 정보 유효기간이 지났습니다.';
break;
// 검증에 실패한 경우
case 'invalid signature':
statusCode = 401;
errorMessage = '유효하지 않는 인증정보입니다.';
break;
default:
statusCode = 500;
errorMessage =
'예상치 못한 에러가 발생하였습니다. 관리자에게 문의하세요';
break;
}
return res.status(statusCode).json({
success: true,
message: errorMessage,
});
}
};
Associated modules
// ./models/index.cjs
// Sequelize ORM 구조
// sequelize는 ORM(Object-Relational Mapping)로 분류
// ORM이란 객체와 관계형 데이터베이스의 관계를 매핑 해주는 도구이다.
'use strict';
const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const process = require('process');
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || 'development';
// config 파일을 .cjs로 변경한 점이 특이점이다. 이유는 현재 프로젝트는 model type으로 진행
const config = require(__dirname + '/../config/config.cjs')[env];
const db = {};
let sequelize;
if (config.use_env_variable) {
sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
sequelize = new Sequelize(
config.database,
config.username,
config.password,
config,
);
}
fs.readdirSync(__dirname)
.filter((file) => {
return (
file.indexOf('.') !== 0 &&
file !== basename &&
file.slice(-4) === '.cjs' &&
file.indexOf('.test.js') === -1
);
})
.forEach((file) => {
const model = require(path.join(__dirname, file))(
sequelize,
Sequelize.DataTypes,
);
db[model.name] = model;
});
Object.keys(db).forEach((modelName) => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;
// ./constants/security.constant.js
import 'dotenv/config';
// 환경변수의 값을 불러올 때에는 기본적으로 String으로 불러오게 된다
// 따라서 강제적으로 항변환을 해줘야한다.
export const PASSWORD_HASH_SALT_ROUNDS = Number.parseInt(
process.env.PASSWORD_HASH_SALT_ROUNDS,
10,
);
export const JWT_ACCESS_TOKEN_SECRET = process.env.JWT_ACCESS_TOKEN_SECRET;
export const JWT_ACCESS_TOKEN_EXPIRES_IN = '12h';
Additional webpage information
https://velog.io/@from_numpy/NestJS-How-to-implement-Refresh-Token-with-JWT
https://sequelize.org/docs/v6/core-concepts/model-querying-finders/
https://developer.mozilla.org/ko/docs/Web/HTTP/Status
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Optional_chaining
https://www.avast.com/random-password-generator#pc
https://www.npmjs.com/package/jsonwebtoken
'Javascript Node.js' 카테고리의 다른 글
Javascript: Optional chaing (?.) (0) | 2023.12.11 |
---|---|
Project: present a web page about the blog. (0) | 2023.12.08 |
How to use Access Token and Refresh Token? (0) | 2023.11.27 |
Nodejs: Signing up and signing in with a refresh token and an access token (1) | 2023.11.22 |
Nodejs: The order of web development (0) | 2023.11.20 |