diff --git a/controllers/auth.controller.js b/controllers/auth.controller.js index 386f1f3..2508de5 100644 --- a/controllers/auth.controller.js +++ b/controllers/auth.controller.js @@ -1,139 +1,114 @@ const AuthService = require('../services/auth.service'); -const { registerSchema, loginSchema } = require('../validate/auth.schema') -const { setResponse } = require('../helpers/utils'); +const { registerSchema, loginSchema } = require('../validate/auth.schema'); +const { setResponse, checkValidate } = require('../helpers/utils'); const { createCaptcha } = require('../utils/captcha'); class AuthController { - - // Registration + // Register static async register(req, res) { - try { - const { error, value } = registerSchema.validate(req.body, { abortEarly: false }); + const { error, value } = await checkValidate(registerSchema, req); - if (error) { - const errors = error.details.reduce((acc, cur) => { - const field = Array.isArray(cur.path) ? cur.path.join('.') : String(cur.path); - if (!acc[field]) acc[field] = []; - acc[field].push(cur.message); - return acc; - }, {}); - return res.status(400).json(setResponse(errors, 'Validation failed', 400)); - } - - if (value.phone && value.phone.startsWith('0')) { - value.phone = '+62' + value.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 - }); - - return res.status(201).json( - setResponse( - { - user: { ...user, approved: false }, - accessToken: tokens.accessToken - }, - 'User registered successfully. Waiting for admin approval.', - 201 - ) - ); - } catch (err) { - return res.status(err.statusCode || 500).json( - setResponse([], err.message || 'Register failed', err.statusCode || 500) - ); + if (error) { + return res.status(400).json(setResponse(error, 'Validation failed', 400)); } - } - // Captcha - static async generateCaptcha(req, res) { - try { - const { svg, text } = createCaptcha(); - - res.setHeader('X-Captcha-Text', text); - - return res.status(200).json({ data: { svg, text } }); - } catch (err) { - return res.status(500).json(setResponse([], 'Captcha failed', 500)); + if (value.phone && value.phone.startsWith('0')) { + value.phone = '+62' + value.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 response = await setResponse( + { + user: { ...user, approved: false }, + accessToken: tokens.accessToken + }, + 'User registered successfully. Waiting for admin approval.', + 201 + ); + + return res.status(response.statusCode).json(response); } // Login static async login(req, res) { - try { - const { error, value } = loginSchema.validate(req.body, { abortEarly: false }); - if (error) return res.status(400).json(setResponse([], 'Validation failed', 400)); + const { error, value } = await checkValidate(loginSchema, req); - const { identifier, password, captcha, captchaText } = 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, { - httpOnly: true, - secure: false, // masih dev - sameSite: 'lax', - maxAge: 7 * 24 * 60 * 60 * 1000 // 7 hari - }); - - return res.status(200).json( - setResponse( - { - user: { ...user, approved: true }, - accessToken: tokens.accessToken - }, - 'Login successful', - 200 - ) - ); - } catch (err) { - return res.status(err.statusCode || 500).json( - setResponse([], err.message || 'Login failed', err.statusCode || 500) - ); + if (error) { + return res.status(400).json(setResponse(error, 'Validation failed', 400)); } + + const { identifier, password, captcha, captchaText } = 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, { + httpOnly: true, + secure: false, // masih dev + sameSite: 'lax', + maxAge: 7 * 24 * 60 * 60 * 1000 // 7 hari + }); + + const response = await setResponse( + { + user: { ...user, approved: true }, + accessToken: tokens.accessToken + }, + 'Login successful', + 200 + ); + + return res.status(response.statusCode).json(response); } // Refresh Token static async refreshToken(req, res) { - try { - const refreshToken = req.cookies?.refreshToken; - if (!refreshToken) { - return res.status(401).json(setResponse(null, 'Refresh token is required', 401)); - } + const refreshToken = req.cookies?.refreshToken; - const result = await AuthService.refreshToken(refreshToken); - - return res.status(200).json(setResponse(result, 'Token refreshed successfully', 200)); - } catch (err) { - const status = err.statusCode && err.statusCode < 500 ? err.statusCode : 401; - return res.status(status).json( - setResponse(null, err.message || 'Refresh token invalid', status) - ); + if (!refreshToken) { + 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); + + return res.status(response.statusCode).json(response); } // Logout static async logout(req, res) { - try { - res.clearCookie('refreshToken', { - httpOnly: true, - sameSite: 'none', - secure: true - }); - return res.status(200).json(setResponse(null, 'Logged out successfully', 200)); - } catch (err) { - return res.status(500).json(setResponse(null, 'Logout failed', 500)); - } + res.clearCookie('refreshToken', { + httpOnly: true, + sameSite: 'none', + secure: true + }); + + const response = await setResponse(null, 'Logged out successfully', 200); + return res.status(response.statusCode).json(response); + } + + // Captcha + static async generateCaptcha(req, res) { + const { svg, text } = createCaptcha(); + + // munculkan di header untuk keperluan dev + res.setHeader('X-Captcha-Text', text); + + const response = await setResponse({ svg, text }, 'Captcha generated', 200); + return res.status(response.statusCode).json(response); } } diff --git a/services/auth.service.js b/services/auth.service.js index 6f084a9..ff73748 100644 --- a/services/auth.service.js +++ b/services/auth.service.js @@ -1,5 +1,5 @@ -const { - getUserByUserEmailDb, +const { + getUserByUserEmailDb, createUserDb, getUserByUsernameDb } = require('../db/user.db'); @@ -8,116 +8,131 @@ const { ErrorHandler } = require('../helpers/error'); const JWTService = require('../utils/jwt'); class AuthService { - // Register static async register({ fullname, name, email, phone, password }) { - const existingUser = await getUserByUserEmailDb(email); - if (existingUser) { - throw new ErrorHandler(400, 'Email already registered'); + try { + 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: name, + user_email: email, + user_phone: 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 + }; + + const tokens = JWTService.generateTokenPair(newUser); + + return { user: newUser, tokens }; + } catch (error) { + throw new ErrorHandler(error.statusCode, error.message); } - - const hashedPassword = await hashPassword(password); - - const userId = await createUserDb({ - user_fullname: fullname, - user_name: name, - user_email: email, - user_phone: 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 - }; - - // generate token pair - const tokens = JWTService.generateTokenPair(newUser); - - return { user: newUser, tokens }; } // Login static async login({ identifier, password }) { - let user; + try { + let user; - if (identifier.includes('@')) { - user = await getUserByUserEmailDb(identifier); - } else { - user = await getUserByUsernameDb(identifier); + if (identifier.includes('@')) { + user = await getUserByUserEmailDb(identifier); + } else { + user = await getUserByUsernameDb(identifier); + } + + 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'); + } + + if (!user.is_approve) { + throw new ErrorHandler(403, 'Your account has not been approved by admin yet.'); + } + + const payload = { + user_id: user.user_id, + user_fullname: user.user_fullname, + user_name: user.user_name, + user_email: user.user_email, + user_phone: user.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 }; + } catch (error) { + throw new ErrorHandler(error.statusCode, error.message); } - - 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'); - } - - if (!user.is_approve) { - throw new ErrorHandler(403, 'Your account has not been approved by admin yet.'); - } - - const payload = { - user_id: user.user_id, - user_fullname: user.user_fullname, - user_name: user.user_name, - user_email: user.user_email, - user_phone: user.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 }; } // Refresh Token static async refreshToken(refreshToken) { - if (!refreshToken) throw new ErrorHandler(401, 'Refresh token is required'); - - let decoded; try { - decoded = JWTService.verifyRefreshToken(refreshToken); - } catch (err) { - if (err.message.includes('expired')) throw new ErrorHandler(401, 'Refresh token expired'); - throw new ErrorHandler(401, 'Invalid refresh token'); + if (!refreshToken) { + throw new ErrorHandler(401, 'Refresh token is required'); + } + + let decoded; + try { + decoded = JWTService.verifyRefreshToken(refreshToken); + } catch (err) { + if (err.message.includes('expired')) { + throw new ErrorHandler(401, 'Refresh token expired'); + } + throw new ErrorHandler(401, 'Invalid refresh token'); + } + + const payload = { + user_id: decoded.user_id, + user_fullname: decoded.user_fullname, + user_name: decoded.user_name, + user_email: decoded.user_email, + user_phone: decoded.user_phone, + role_id: decoded.role_id, + role_name: decoded.role_name, + is_sa: decoded.is_sa + }; + + const accessToken = JWTService.generateAccessToken(payload); + + return { + accessToken, + tokenType: 'Bearer', + expiresIn: 900 + }; + } catch (error) { + throw new ErrorHandler(error.statusCode, error.message); } - - const payload = { - user_id: decoded.user_id, - user_fullname: decoded.user_fullname, - user_name: decoded.user_name, - user_email: decoded.user_email, - user_phone: decoded.user_phone, - role_id: decoded.role_id, - role_name: decoded.role_name, - is_sa: decoded.is_sa - }; - - const accessToken = JWTService.generateAccessToken(payload); - - return { - accessToken, - tokenType: 'Bearer', - expiresIn: 900 - }; } }