diff --git a/routes/index.js b/routes/index.js index 92d5909..ff85afd 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,8 +1,8 @@ const router = require("express").Router(); -const auth = require("./auth"); -const users = require("./users"); +const auth = require("./auth.route"); +const users = require("./users.route"); router.use("/auth", auth); -router.use("/users", users); +router.use("/user", users); module.exports = router; diff --git a/routes/users.js b/routes/users.route.js similarity index 55% rename from routes/users.js rename to routes/users.route.js index 3cbf210..3f8a329 100644 --- a/routes/users.js +++ b/routes/users.route.js @@ -14,20 +14,19 @@ const verifyToken = require("../middleware/verifyToken"); router.get("/roles", getAllRoles); -router.route("/profile") - .get(getUserProfile); +router.get('/profile', verifyToken.verifyAccessToken, getUserProfile); router.route("/") - .get(verifyToken, getAllUsers) - .post(verifyToken, createUser); + .get(verifyToken.verifyAccessToken, getAllUsers) + .post(verifyToken.verifyAccessToken, createUser); router .route("/status") - .get(verifyToken, getAllStatusUsers); + .get(verifyToken.verifyAccessToken, getAllStatusUsers); router.route("/:id") - .get(verifyToken, getUserById) - .put(verifyToken, updateUser) - .delete(verifyToken, deleteUser); + .get(verifyToken.verifyAccessToken, getUserById) + .put(verifyToken.verifyAccessToken, updateUser) + .delete(verifyToken.verifyAccessToken, deleteUser); module.exports = router; diff --git a/services/auth.service.js b/services/auth.service.js index 583d300..fa25709 100644 --- a/services/auth.service.js +++ b/services/auth.service.js @@ -1,77 +1,101 @@ -const bcrypt = require("bcrypt"); -const jwt = require("jsonwebtoken"); -const validateUser = require("../helpers/validateUser"); -const { ErrorHandler } = require("../helpers/error"); -const { - getUserByUsernameDb -} = require("../db/user.db"); -const { logger } = require("../utils/logger"); +const { + getUserByUserEmailDb, + createUserDb +} = require('../db/user.db'); +const JWTService = require('../utils/jwt'); +const { hashPassword, comparePassword } = require('../helpers/hashPassword'); +const { ErrorHandler } = require('../helpers/error'); class AuthService { - async login(username, password, tenantId) { - try { - // if (!validateUser(username, password)) { - // throw new ErrorHandler(403, "Invalid login"); - // } - - const user = await getUserByUsernameDb(username, tenantId); - console.log(user); - - if (!user) { - throw new ErrorHandler(403, "Username not found."); - } - - const isCorrectPassword = password === user.password - if (!isCorrectPassword) { - throw new ErrorHandler(403, "Username or password incorrect."); - } - - const dataToken = { - tenant_id: tenantId, - user_id: user.user_id, - username, - fullname: user.full_name, - role_id: user.role_id - } - - const token = await this.signToken(dataToken); - const refreshToken = await this.signRefreshToken(dataToken); - - return { - token, - refreshToken, - role_id: dataToken.role_id, - tenant_id: tenantId, - user: { - user_id: dataToken.user_id, - fullname: dataToken.fullname, - username: dataToken.username, - }, - }; - } catch (error) { - throw new ErrorHandler(error.statusCode, error.message); + // Register + static async register({ fullname, username, email, phone, password }) { + const existingUser = await getUserByUserEmailDb(email); + if (existingUser) { + throw new ErrorHandler(400, 'Email already registered'); } + + const hashedPassword = await hashPassword(password); + + const userId = await createUserDb({ + user_fullname: fullname, + user_name: username, + user_email: email, + user_phone: phone, + user_password: hashedPassword, + role_id: 3, + is_sa: 0, + is_active: 1 + }); + + // ambil user baru + const newUser = { + user_id: userId, + user_fullname: fullname, + user_name: username, + user_email: email, + user_phone: phone, + role_id: 3, + }; + + // generate token pair + const tokens = JWTService.generateTokenPair(newUser); + + return { user: newUser, tokens }; } - async signToken(data) { - try { - // console.log("signToken process.env.SECRET", process.env.SECRET) - return jwt.sign(data, process.env.SECRET, { expiresIn: "23h" }); - } catch (error) { - logger.error(error); - throw new ErrorHandler(500, "An error occurred"); + // Login + static async login({ email, password }) { + const user = await getUserByUserEmailDb(email); + if (!user) { + throw new ErrorHandler(401, 'Invalid credentials'); } + + const passwordMatch = await comparePassword(password, user.user_password); + if (!passwordMatch) { + throw new ErrorHandler(401, 'Invalid credentials'); + } + + if (!user.is_active) { + throw new ErrorHandler(403, 'User is inactive'); + } + + const payload = { + user_id: user.user_id, + user_fullname: user.user_fullname, + user_name: user.user_name, + user_email: user.user_email, + phone: user.phone, + role_id: user.role_id, + role_name: user.role_name, + is_sa: user.is_sa + }; + + const tokens = JWTService.generateTokenPair(payload); + return { user: payload, tokens }; } - async signRefreshToken(data) { - try { - return jwt.sign(data, process.env.REFRESH_SECRET, { expiresIn: "23h" }); - } catch (error) { - logger.error(error); - throw new ErrorHandler(500, error.message); + // Refresh token + static async refreshToken(refreshToken) { + if (!refreshToken) { + throw new ErrorHandler(401, 'Refresh token is required'); } + + const decoded = JWTService.verifyRefreshToken(refreshToken); + + const payload = { + user_id: decoded.user_id, + user_fullname: decoded.user_fullname, + user_name: decoded.user_name, + user_email: decoded.user_email, + role_id: decoded.role_id, + role_name: decoded.role_name + }; + + const accessToken = JWTService.generateAccessToken(payload); + return { accessToken, tokenType: 'Bearer', expiresIn: 900 }; } + } -module.exports = new AuthService(); +module.exports = AuthService; diff --git a/utils/captcha.js b/utils/captcha.js new file mode 100644 index 0000000..50aee24 --- /dev/null +++ b/utils/captcha.js @@ -0,0 +1,8 @@ +const svgCaptcha = require('svg-captcha'); + +function createCaptcha() { + const captcha = svgCaptcha.create({ size: 5, noise: 2, color: true }); + return { svg: captcha.data, text: captcha.text }; +} + +module.exports = { createCaptcha }; \ No newline at end of file diff --git a/utils/jwt.js b/utils/jwt.js new file mode 100644 index 0000000..d131e6b --- /dev/null +++ b/utils/jwt.js @@ -0,0 +1,81 @@ +const jwt = require('jsonwebtoken'); +const crypto = require('crypto'); + +const tokenSettings = { + access: { + expiresIn: '15m', + type: 'access', + secret: process.env.SECRET + }, + refresh: { + expiresIn: '7d', + type: 'refresh', + secret: process.env.REFRESH_SECRET + } +}; + +function generateTokenId() { + return crypto.randomBytes(32).toString('hex'); +} + +function generateToken(payload, type) { + const settings = tokenSettings[type]; + if (!settings) throw new Error(`Invalid token type: ${type}`); + + const tokenPayload = { ...payload, type: settings.type }; + + return jwt.sign(tokenPayload, settings.secret, { + expiresIn: settings.expiresIn, + jwtid: generateTokenId() + }); +} + +function verifyTokenType(token, type) { + const settings = tokenSettings[type]; + try { + const decoded = jwt.verify(token, settings.secret); + if (decoded.type !== type) throw new Error('Invalid token type'); + return decoded; + } catch (error) { + if (error.name === 'TokenExpiredError') throw new Error(`${type} token has expired`); + if (error.name === 'JsonWebTokenError') throw new Error(`Invalid ${type} token`); + throw error; + } +} + +function generateAccessToken(payload) { + return generateToken(payload, 'access'); +} + +function generateRefreshToken(payload) { + return generateToken(payload, 'refresh'); +} + +function verifyToken(token) { + return verifyTokenType(token, 'access'); +} + +function verifyRefreshToken(token) { + return verifyTokenType(token, 'refresh'); +} + +function generateTokenPair(payload) { + const accessToken = generateAccessToken(payload); + const refreshToken = generateRefreshToken(payload); + + return { + accessToken, + refreshToken, + tokenType: 'Bearer', + expiresIn: 900, + refreshExpiresIn: 604800 + }; +} + +module.exports = { + generateAccessToken, + generateRefreshToken, + verifyToken, + verifyRefreshToken, + generateTokenPair, +};