From 751cd0911e2c732f4b9c411ba207806cec5eb9bb Mon Sep 17 00:00:00 2001 From: Antony Kurniawan Date: Sat, 11 Oct 2025 02:26:00 +0700 Subject: [PATCH] fix: auth --- controllers/auth.controller.js | 66 ++++++++++++------------------- services/auth.service.js | 71 ++++++++++++++-------------------- validate/auth.schema.js | 10 ++--- 3 files changed, 60 insertions(+), 87 deletions(-) diff --git a/controllers/auth.controller.js b/controllers/auth.controller.js index 2508de5..de1a9dd 100644 --- a/controllers/auth.controller.js +++ b/controllers/auth.controller.js @@ -1,6 +1,6 @@ const AuthService = require('../services/auth.service'); -const { registerSchema, loginSchema } = require('../validate/auth.schema'); const { setResponse, checkValidate } = require('../helpers/utils'); +const { registerSchema, loginSchema } = require('../validate/auth.schema'); const { createCaptcha } = require('../utils/captcha'); class AuthController { @@ -12,30 +12,21 @@ class AuthController { return res.status(400).json(setResponse(error, 'Validation failed', 400)); } - if (value.phone && value.phone.startsWith('0')) { - value.phone = '+62' + value.phone.slice(1); + // Format nomor HP Indonesia + if (value.user_phone && value.user_phone.startsWith('0')) { + value.user_phone = '+62' + value.user_phone.slice(1); } - const { user, tokens } = await AuthService.register(value); - - // Set refresh token di cookie - res.cookie('refreshToken', tokens.refreshToken, { - httpOnly: true, - secure: false, // masih dev - sameSite: 'lax', - maxAge: 7 * 24 * 60 * 60 * 1000 // 7 hari - }); + const results = await AuthService.register(value); const response = await setResponse( { - user: { ...user, approved: false }, - accessToken: tokens.accessToken + user: { ...results.user, approved: false }, }, - 'User registered successfully. Waiting for admin approval.', - 201 + 'User registered successfully. Waiting for admin approval.' ); - return res.status(response.statusCode).json(response); + res.status(response.statusCode).json(response); } // Login @@ -46,32 +37,25 @@ class AuthController { return res.status(400).json(setResponse(error, 'Validation failed', 400)); } - const { identifier, password, captcha, captchaText } = value; + const results = await AuthService.login(value); - if (!captcha || captcha.toLowerCase() !== captchaText.toLowerCase()) { - return res.status(400).json(setResponse([], 'Invalid captcha', 400)); - } - - const { user, tokens } = await AuthService.login({ identifier, password }); - - // Set refresh token di cookie - res.cookie('refreshToken', tokens.refreshToken, { + // Simpan refresh token di cookie + res.cookie('refreshToken', results.tokens.refreshToken, { httpOnly: true, - secure: false, // masih dev + secure: false, sameSite: 'lax', - maxAge: 7 * 24 * 60 * 60 * 1000 // 7 hari + maxAge: 7 * 24 * 60 * 60 * 1000 }); const response = await setResponse( { - user: { ...user, approved: true }, - accessToken: tokens.accessToken + user: { ...results.user, approved: true }, + accessToken: results.tokens.accessToken }, - 'Login successful', - 200 + 'Login successful' ); - return res.status(response.statusCode).json(response); + res.status(response.statusCode).json(response); } // Refresh Token @@ -82,10 +66,10 @@ class AuthController { return res.status(401).json(setResponse(null, 'Refresh token is required', 401)); } - const result = await AuthService.refreshToken(refreshToken); - const response = await setResponse(result, 'Token refreshed successfully', 200); + const results = await AuthService.refreshToken(refreshToken); + const response = await setResponse(results, 'Token refreshed successfully'); - return res.status(response.statusCode).json(response); + res.status(response.statusCode).json(response); } // Logout @@ -96,19 +80,19 @@ class AuthController { secure: true }); - const response = await setResponse(null, 'Logged out successfully', 200); - return res.status(response.statusCode).json(response); + const response = await setResponse(null, 'Logged out successfully'); + res.status(response.statusCode).json(response); } // Captcha static async generateCaptcha(req, res) { const { svg, text } = createCaptcha(); - // munculkan di header untuk keperluan dev + // Tampilkan captcha di header untuk dev res.setHeader('X-Captcha-Text', text); - const response = await setResponse({ svg, text }, 'Captcha generated', 200); - return res.status(response.statusCode).json(response); + const response = await setResponse({ svg, text }, 'Captcha generated'); + res.status(response.statusCode).json(response); } } diff --git a/services/auth.service.js b/services/auth.service.js index ff73748..c700bef 100644 --- a/services/auth.service.js +++ b/services/auth.service.js @@ -9,72 +9,66 @@ const JWTService = require('../utils/jwt'); class AuthService { // Register - static async register({ fullname, name, email, phone, password }) { + static async register(data) { try { - const existingUser = await getUserByUserEmailDb(email); - if (existingUser) { - throw new ErrorHandler(400, 'Email already registered'); + const existingEmail = await getUserByUserEmailDb(data.user_email); + const existingUsername = await getUserByUsernameDb(data.user_name); + + if (existingUsername) { + throw new ErrorHandler(400, 'Username is already taken'); + } + if (existingEmail) { + throw new ErrorHandler(400, 'Email is already taken'); } - const hashedPassword = await hashPassword(password); + const hashedPassword = await hashPassword(data.user_password); const userId = await createUserDb({ - user_fullname: fullname, - user_name: name, - user_email: email, - user_phone: phone, + user_fullname: data.user_fullname, + user_name: data.user_name, + user_email: data.user_email, + user_phone: data.user_phone, user_password: hashedPassword, - role_id: null, - is_sa: 0, - is_active: 1, - is_approve: 0, - approved_by: null, - approved_at: null }); const newUser = { user_id: userId, - user_fullname: fullname, - user_name: name, - user_email: email, - user_phone: phone + user_fullname: data.user_fullname, + user_name: data.user_name, + user_email: data.user_email, + user_phone: data.user_phone }; - const tokens = JWTService.generateTokenPair(newUser); - - return { user: newUser, tokens }; + return { user: newUser }; } catch (error) { throw new ErrorHandler(error.statusCode, error.message); } } // Login - static async login({ identifier, password }) { + static async login(data) { try { - let user; + const { identifier, password, captcha, captchaText } = data; + if (!captcha || captcha.toLowerCase() !== captchaText.toLowerCase()) { + throw new ErrorHandler(400, 'Invalid captcha'); + } + + let user; if (identifier.includes('@')) { user = await getUserByUserEmailDb(identifier); } else { user = await getUserByUsernameDb(identifier); } - if (!user) { - throw new ErrorHandler(401, 'Invalid credentials'); - } + 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 (!passwordMatch) throw new ErrorHandler(401, 'Invalid credentials'); - if (!user.is_active) { - throw new ErrorHandler(403, 'User is inactive'); - } - - if (!user.is_approve) { + if (!user.is_active) throw new ErrorHandler(403, 'User is inactive'); + if (!user.is_approve) throw new ErrorHandler(403, 'Your account has not been approved by admin yet.'); - } const payload = { user_id: user.user_id, @@ -88,7 +82,6 @@ class AuthService { }; const tokens = JWTService.generateTokenPair(payload); - return { user: payload, tokens }; } catch (error) { throw new ErrorHandler(error.statusCode, error.message); @@ -98,10 +91,6 @@ class AuthService { // Refresh Token static async refreshToken(refreshToken) { try { - if (!refreshToken) { - throw new ErrorHandler(401, 'Refresh token is required'); - } - let decoded; try { decoded = JWTService.verifyRefreshToken(refreshToken); diff --git a/validate/auth.schema.js b/validate/auth.schema.js index 11d834d..bba7312 100644 --- a/validate/auth.schema.js +++ b/validate/auth.schema.js @@ -4,17 +4,17 @@ const Joi = require("joi"); // Auth Validation // ======================== const registerSchema = Joi.object({ - fullname: Joi.string().min(3).max(100).required(), - name: Joi.string().alphanum().min(3).max(50).required(), - email: Joi.string().email().required(), - phone: Joi.string() + user_fullname: Joi.string().min(3).max(100).required(), + user_name: Joi.string().alphanum().min(3).max(50).required(), + user_email: Joi.string().email().required(), + user_phone: Joi.string() .pattern(/^(?:\+62|0)8\d{7,10}$/) .required() .messages({ 'string.pattern.base': 'Phone number must be a valid Indonesian number in format +628XXXXXXXXX' }), - password: Joi.string() + user_password: Joi.string() .min(8) .pattern(/[A-Z]/, 'uppercase letter') .pattern(/[a-z]/, 'lowercase letter')