created API For Aplication Absensi
This commit is contained in:
49
app/core/controllers/auth.controller.js
Normal file
49
app/core/controllers/auth.controller.js
Normal file
@@ -0,0 +1,49 @@
|
||||
const service = require('../services/auth.service')
|
||||
const servicegoogle = require('../services/authGoogle.service')
|
||||
|
||||
exports.signIn = async (req, res) => {
|
||||
const response = await service.signIn(req,res)
|
||||
return response
|
||||
}
|
||||
|
||||
exports.sendOtp = async (req, res) => {
|
||||
const response = await service.sendOtp(req, res)
|
||||
return response
|
||||
}
|
||||
|
||||
exports.checkOtp = async (req, res) => {
|
||||
const response = await service.checkOtp(req, res)
|
||||
return response
|
||||
}
|
||||
|
||||
exports.signUp = async (req, res) => {
|
||||
const response = await service.signUp(req, res)
|
||||
return response
|
||||
}
|
||||
|
||||
exports.getUserlogin = async (req, res) => {
|
||||
const token = req.headers.authorization?.split(" ")[1]
|
||||
const response = await service.getUserLogin(token, res)
|
||||
return response
|
||||
}
|
||||
|
||||
exports.forgotPassword = async(req, res) => {
|
||||
var body = req.body
|
||||
const response = await service.forgotPassword(body, res)
|
||||
return response
|
||||
}
|
||||
|
||||
exports.resetPassword = async(req, res, token) => {
|
||||
var body = req.body
|
||||
var token = token
|
||||
const response = await service.resetPassword(body, token, res)
|
||||
return response
|
||||
}
|
||||
exports.loginWithGoogle = async (req, res) => {
|
||||
const response = await servicegoogle.loginWithGoogle(req, res)
|
||||
return response
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
0
app/core/models/index.js
Normal file
0
app/core/models/index.js
Normal file
17
app/core/resources/user.resource.js
Normal file
17
app/core/resources/user.resource.js
Normal file
@@ -0,0 +1,17 @@
|
||||
class UserResource {
|
||||
constructor(user) {
|
||||
this.id = user?.id ?? null
|
||||
this.name = user?.name ?? null
|
||||
this.email = user?.email ?? null
|
||||
this.phone = user?.phone ?? null
|
||||
//this.role = user?.role ?? null
|
||||
this.via = user?.login_via ?? null
|
||||
this.google_id = user?.google_id ?? null
|
||||
this.last_login = user?.last_login ?? null
|
||||
this.created_at = user?.created_at ?? null
|
||||
this.updated_at = user?.updated_at ?? null
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = UserResource
|
||||
19
app/core/routes/api.route.js
Normal file
19
app/core/routes/api.route.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const express = require('express')
|
||||
|
||||
const router = express.Router()
|
||||
const authRouter = require('./auth.route')
|
||||
const ProfileRouter = require('../../modules/profile/routes/profile.route')
|
||||
const AbsensRouter = require('../../modules/absensi/routes/absensi.route')
|
||||
const BranchRouter = require('../../modules/branch/routes/branch.route')
|
||||
|
||||
router.use('/auth', authRouter)
|
||||
router.use('/profiles', ProfileRouter)
|
||||
router.use('/attedances', AbsensRouter)
|
||||
router.use('/branches', BranchRouter)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
module.exports = router
|
||||
41
app/core/routes/auth.route.js
Normal file
41
app/core/routes/auth.route.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const controller = require('../controllers/auth.controller')
|
||||
const authentication = require("../../middlewares/authentication.js");
|
||||
const apiKey = require("../../middlewares/apiKey.js");
|
||||
|
||||
router.post('/signIn', apiKey, (req, res) => {
|
||||
controller.signIn(req, res)
|
||||
})
|
||||
|
||||
router.post('/signUp', (req, res) => {
|
||||
controller.signUp(req, res)
|
||||
})
|
||||
|
||||
router.get('/me', authentication,apiKey, (req, res) => {
|
||||
controller.getUserlogin(req,res)
|
||||
})
|
||||
|
||||
router.post('/send-otp',apiKey, (req, res) => {
|
||||
controller.sendOtp(req, res)
|
||||
})
|
||||
|
||||
router.post('/check-otp', apiKey, (req, res) => {
|
||||
controller.checkOtp(req, res)
|
||||
})
|
||||
|
||||
router.post("/forgot-password", apiKey, function name(req, res) {
|
||||
controller.forgotPassword(req, res);
|
||||
});
|
||||
|
||||
router.post("/reset/:token", apiKey, function name(req, res) {
|
||||
const { token } = req.params;
|
||||
controller.resetPassword(req, res, token);
|
||||
});
|
||||
|
||||
|
||||
|
||||
module.exports = router
|
||||
|
||||
|
||||
|
||||
12
app/core/routes/authGoogle.route.js
Normal file
12
app/core/routes/authGoogle.route.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const express = require('express');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
const apiKey = require('../../middlewares/apiKey')
|
||||
const controller = require('../controllers/auth.controller')
|
||||
|
||||
router.post('/google', apiKey, controller.loginWithGoogle);
|
||||
|
||||
|
||||
|
||||
module.exports = router;
|
||||
390
app/core/services/auth.service.js
Normal file
390
app/core/services/auth.service.js
Normal file
@@ -0,0 +1,390 @@
|
||||
require('dotenv').config()
|
||||
const bcrypt = require('bcrypt')
|
||||
const jwt = require('jsonwebtoken')
|
||||
const moment = require('moment')
|
||||
const crypto = require('crypto')
|
||||
const { Sequelize } = require('sequelize')
|
||||
const { sequelize } = require('../../../models/migration.js')
|
||||
const response = require('../../helpers/responses')
|
||||
const { sendOTP, generateOTP, sendForgotPassword, normalizePhone } = require('../../helpers/helpers')
|
||||
const UserResource = require('../resources/user.resource')
|
||||
const { sendMail } = require('../../config/mail.config')
|
||||
const db = require('../../../models/migration.js')
|
||||
const { password } = require('../../config/db.config.js')
|
||||
const errorHandler = require('../../middlewares/errorHandler.js')
|
||||
const { v4: uuidv4 } = require("uuid");
|
||||
const User = db.User
|
||||
const Branch = db.Branch
|
||||
const UserOtp = db.UserOtp
|
||||
const PasswordReset = db.PasswordReset
|
||||
const { Op } = require('sequelize')
|
||||
const { OAuth2Client } = require("google-auth-library");
|
||||
|
||||
const signIn = async (req, res) => {
|
||||
|
||||
const client = new OAuth2Client("GOOGLE_CLIENT_ID");
|
||||
|
||||
try {
|
||||
const { email, phone, password, token: tokenGoogle, login_via } = req.body;
|
||||
let user;
|
||||
|
||||
|
||||
if (email) {
|
||||
user = await User.findOne({ where: { email } });
|
||||
|
||||
if (login_via == 'GOOGLE') {
|
||||
// Verify Google token
|
||||
const ticket = await client.verifyIdToken({
|
||||
idToken: tokenGoogle, // Changed from tokenGoogle to idToken
|
||||
audience: process.env.GOOGLE_CLIENT_ID,
|
||||
});
|
||||
|
||||
const payload = ticket.getPayload();
|
||||
const googleEmail = payload.email;
|
||||
|
||||
// Find or create user with Google credentials
|
||||
user = await User.findOne({
|
||||
where: {
|
||||
email: googleEmail
|
||||
}
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
// Create new user if doesn't exist
|
||||
user = await User.create({
|
||||
email: googleEmail,
|
||||
name: payload.name,
|
||||
login_via: 'GOOGLE',
|
||||
token: tokenGoogle,
|
||||
google_id: payload.sub,
|
||||
avatar_url: payload.picture,
|
||||
});
|
||||
}else{
|
||||
// Update user's Google token
|
||||
user.google_id = payload.sub;
|
||||
user.avatar_url = payload.picture;
|
||||
user.token = tokenGoogle;
|
||||
await user.save();
|
||||
}
|
||||
}
|
||||
|
||||
} else if (phone) {
|
||||
const normalizedPhone = normalizePhone(phone);
|
||||
user = await User.findOne({ where: { phone: normalizedPhone } });
|
||||
}
|
||||
|
||||
if (login_via === 'GOOGLE') {
|
||||
} else {
|
||||
if (!user || !(await bcrypt.compare(password, user.password))) {
|
||||
return res.status(400).json({ error: "Email / Phone atau Password salah" });
|
||||
}
|
||||
}
|
||||
|
||||
if (user.is_suspended) {
|
||||
return res.status(403).json({ error: "Akun Anda telah ditangguhkan" });
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
|
||||
// === Generate JWT mirip Supabase ===
|
||||
|
||||
const token = jwt.sign(
|
||||
{
|
||||
name: user.name,
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
phone: user.phone || "",
|
||||
role: user.role || "user",
|
||||
|
||||
},
|
||||
process.env.JWT_SECRET_KEY
|
||||
);
|
||||
|
||||
|
||||
// Update last login
|
||||
user.last_login = now;
|
||||
user.is_first_login = false;
|
||||
await user.save();
|
||||
|
||||
return res.json({
|
||||
access_token: token,
|
||||
token_type: "bearer",
|
||||
user: {
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
role: user.role || "user",
|
||||
created_at: user.created_at.toISOString(),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
errorHandler(error, req, res)
|
||||
return response.failed(res, 500, error.message)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
const sendOtp = async (req, res) => {
|
||||
try {
|
||||
const { email, phone, via } = req.body
|
||||
const otp = generateOTP(6)
|
||||
const hash = await bcrypt.hash(otp, 10)
|
||||
const data = via === 'WHATSAPP' ? email : phone
|
||||
const otpData = await UserOtp.create({ data, otp, token: hash, expire_in: 60, via })
|
||||
|
||||
await sendOTP('OTP', otp, via, phone, email)
|
||||
|
||||
return response.success(res, { otpData }, `OTP dikirim ${via}`)
|
||||
} catch (error) {
|
||||
return response.failed(res, 500, error.message)
|
||||
}
|
||||
}
|
||||
|
||||
const checkOtp = async (req, res) => {
|
||||
try {
|
||||
const { otp, token } = req.body
|
||||
const data = await UserOtp.findOne({ where: { otp, token } })
|
||||
|
||||
if (!data) {
|
||||
return response.failed(res, 404, 'OTP tidak valid')
|
||||
}
|
||||
|
||||
const userCondition = data.via === 'WHATSAPP' ? { email: data.data } : { phone: data.data }
|
||||
let user = await User.findOne({ where: userCondition })
|
||||
|
||||
if (!user) {
|
||||
user = await User.create(userCondition)
|
||||
}
|
||||
|
||||
const jwtToken = jwt.sign({ id: user.id }, process.env.JWT_SECRET_KEY, { expiresIn: '1d' })
|
||||
return response.success(res, { user: new UserResource(user), token: jwtToken }, 'Login OTP berhasil')
|
||||
|
||||
|
||||
} catch (error) {
|
||||
return response.failed(res, 500, error.message)
|
||||
}
|
||||
}
|
||||
|
||||
const signUp = async (req, res) => {
|
||||
try {
|
||||
const { name, email, password, role, branch_id } = req.body;
|
||||
|
||||
|
||||
// Cek apakah email / phone sudah ada
|
||||
const existingUser = await User.findOne({
|
||||
where: {
|
||||
[Sequelize.Op.or]: [{ email }],
|
||||
},
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
return res.status(400).json({ error: "Email sudah terdaftar" });
|
||||
}
|
||||
|
||||
const branch = await Branch.findOne({ where: { id: branch_id } });
|
||||
if (!branch) {
|
||||
await t.rollback();
|
||||
return response.failed(res, 404, "Branch tidak ditemukan");
|
||||
}
|
||||
|
||||
// Hash password
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
|
||||
// Buat user baru dalam transaksi
|
||||
const user = await sequelize.transaction(async (t) => {
|
||||
const newUser = await User.create(
|
||||
{
|
||||
name,
|
||||
email,
|
||||
branch_id,
|
||||
password: hashedPassword,
|
||||
role: role || "user",
|
||||
},
|
||||
{ transaction: t }
|
||||
);
|
||||
|
||||
return newUser;
|
||||
});
|
||||
|
||||
// Generate token JWT
|
||||
|
||||
|
||||
const token = jwt.sign(
|
||||
{
|
||||
name: user.name,
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
phone: user.phone || "",
|
||||
role: user.role || "user",
|
||||
|
||||
},
|
||||
process.env.JWT_SECRET_KEY
|
||||
);
|
||||
|
||||
const refreshToken = uuidv4();
|
||||
const now = new Date();
|
||||
|
||||
// Update last_login setelah register
|
||||
user.last_login = now;
|
||||
user.is_first_login = false;
|
||||
await user.save();
|
||||
|
||||
return res.json({
|
||||
access_token: token,
|
||||
token_type: "bearer",
|
||||
user: {
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
branch: branch.name,
|
||||
role: user.role || "user",
|
||||
created_at: user.created_at.toISOString(),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
errorHandler(error, req, res)
|
||||
return response.failed(res, 500, error.message)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const getUserLogin = async (token, res) => {
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET_KEY)
|
||||
|
||||
const user = await User.findByPk(decoded.id, {
|
||||
attributes: { exclude: ['password'] }
|
||||
})
|
||||
|
||||
if (!user) {
|
||||
return response.failed(res, 404, 'User Tidak Ditemukan')
|
||||
}
|
||||
|
||||
return response.success(res, new UserResource(user), 'User ditemukan')
|
||||
} catch (error) {
|
||||
return response.failed(res, 401, 'Token Tidak Valid atau expired')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const forgotPassword = async (body, res) => {
|
||||
const [user, resetPassword] = await Promise.all([
|
||||
User.findOne({
|
||||
where: {
|
||||
email: body.email,
|
||||
},
|
||||
}),
|
||||
PasswordReset.findOne({
|
||||
where: {
|
||||
email: body.email,
|
||||
is_used: false,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
if (!user) {
|
||||
return response.error(res, 1307, 400);
|
||||
}
|
||||
|
||||
if (resetPassword) {
|
||||
resetPassword.token = crypto.randomBytes(20).toString('hex');
|
||||
resetPassword.expires_at = moment().add(15, 'minutes').toISOString();
|
||||
|
||||
await resetPassword.save();
|
||||
|
||||
await sequelize.transaction(async () => {
|
||||
await sendForgotPassword(body.email, resetPassword.token);
|
||||
});
|
||||
|
||||
const tokenUpdated = {
|
||||
user,
|
||||
forgot_password: resetPassword,
|
||||
};
|
||||
|
||||
return response.success(res, tokenUpdated, 'Token Updated');
|
||||
}
|
||||
|
||||
const emailToken = crypto.randomBytes(20).toString('hex');
|
||||
const expiresAt = moment().add(15, 'minutes').toISOString();
|
||||
|
||||
const dataPasswordReset = await PasswordReset.create({
|
||||
email: body.email,
|
||||
token: emailToken,
|
||||
expires_at: expiresAt,
|
||||
});
|
||||
|
||||
const tokenCreated = {
|
||||
user,
|
||||
forgot_password: dataPasswordReset,
|
||||
};
|
||||
|
||||
return response.success(res, tokenCreated, 'Token Created');
|
||||
};
|
||||
|
||||
|
||||
const resetPassword = async (body, token, res) => {
|
||||
try {
|
||||
const resetPassword = await PasswordReset.findOne({
|
||||
where: {
|
||||
is_used: false,
|
||||
token: token,
|
||||
},
|
||||
});
|
||||
console.log('resetPassword data:', resetPassword);
|
||||
|
||||
if (!resetPassword) {
|
||||
return response.error(res, 1306, 400);
|
||||
}
|
||||
|
||||
const now = moment();
|
||||
const expiresAt = moment(resetPassword.expires_at);
|
||||
if (now.isAfter(expiresAt)) {
|
||||
return response.error(res, 1306, 400);
|
||||
}
|
||||
|
||||
const password = body.new_password;
|
||||
const confirm_password = body.confirm_password;
|
||||
|
||||
if (password !== confirm_password) {
|
||||
return response.failed(res, 1105, 400);
|
||||
}
|
||||
|
||||
const salt = await bcrypt.genSalt(10);
|
||||
const hash = await bcrypt.hash(password, salt);
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: resetPassword.email,
|
||||
},
|
||||
});
|
||||
user.password = hash;
|
||||
user.is_default = false;
|
||||
await user.save();
|
||||
|
||||
resetPassword.is_used = true;
|
||||
await resetPassword.save();
|
||||
|
||||
return response.success(res, null, 'Password reset successfully');
|
||||
} catch (error) {
|
||||
console.error('Reset password error:', error);
|
||||
return response.failed(res, 500, error.message || 'Something went wrong');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
module.exports = {
|
||||
signIn,
|
||||
sendOtp,
|
||||
checkOtp,
|
||||
signUp,
|
||||
getUserLogin,
|
||||
resetPassword,
|
||||
forgotPassword
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
128
app/core/services/authGoogle.service.js
Normal file
128
app/core/services/authGoogle.service.js
Normal file
@@ -0,0 +1,128 @@
|
||||
const db = require('../../../models/migration')
|
||||
|
||||
const { OAuth2Client } = require("google-auth-library");
|
||||
const dotenv = require('dotenv');
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const googleClientId = process.env.GOOGLE_CLIENT_ID;
|
||||
const jwt = require('jsonwebtoken')
|
||||
|
||||
exports.loginWithGoogle = async (req, res) => {
|
||||
const t = await db.sequelize.transaction();
|
||||
try {
|
||||
const {name, email, google_token } = req.body;
|
||||
|
||||
if (!email || !google_token) {
|
||||
await t.rollback();
|
||||
return responses.failed(res, 400, 'Email and Google token are required');
|
||||
}
|
||||
|
||||
const client = new OAuth2Client(googleClientId);
|
||||
const ticket = await client.verifyIdToken({
|
||||
idToken: google_token,
|
||||
audience: googleClientId,
|
||||
});
|
||||
|
||||
const payload = ticket.getPayload();
|
||||
|
||||
const googleName = payload.name;
|
||||
const googleAvatar = payload.picture;
|
||||
|
||||
if (
|
||||
payload.iss !== 'accounts.google.com' &&
|
||||
payload.iss !== 'https://accounts.google.com'
|
||||
) {
|
||||
await t.rollback();
|
||||
return responses.failed(res, 400, 'Invalid token issuer');
|
||||
}
|
||||
|
||||
if (payload.email !== email) {
|
||||
await t.rollback();
|
||||
return responses.failed(res, 400, 'Email mismatch');
|
||||
}
|
||||
|
||||
let findUser = await db.User.findOne({
|
||||
where: { email: email },
|
||||
});
|
||||
|
||||
let isFirstLogin = false;
|
||||
|
||||
if (!findUser) {
|
||||
findUser = await db.User.create({
|
||||
email: email,
|
||||
name: googleName,
|
||||
avatar: googleAvatar,
|
||||
status: 'ACTIVE',
|
||||
login_via: 'GOOGLE',
|
||||
}, { transaction: t });
|
||||
|
||||
const user_id = findUser.id;
|
||||
|
||||
await db.Branch.create({
|
||||
name: 'Personal',
|
||||
initial_balance: 0,
|
||||
current_balance: 0,
|
||||
created_by: user_id,
|
||||
user_id: user_id
|
||||
}, { transaction: t });
|
||||
|
||||
await db.Category.create({
|
||||
name: 'Umum',
|
||||
transaction_type: 'income',
|
||||
icons: '💵',
|
||||
user_id: user_id
|
||||
}, { transaction: t });
|
||||
|
||||
await db.Wallet.create({
|
||||
name: 'Dompet',
|
||||
current_balance: 0,
|
||||
type: 'Cash',
|
||||
user_id: user_id
|
||||
}, { transaction: t });
|
||||
|
||||
|
||||
isFirstLogin = true;
|
||||
} else {
|
||||
isFirstLogin = findUser.last_login === null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const tokenJwt = jwt.sign(
|
||||
{
|
||||
id: findUser.id,
|
||||
},
|
||||
process.env.JWT_SECRET_KEY
|
||||
);
|
||||
|
||||
|
||||
const data = {
|
||||
is_google_login: true,
|
||||
token: tokenJwt,
|
||||
user_data: {
|
||||
id: findUser.id,
|
||||
name: findUser.name || googleName,
|
||||
avatar: findUser.avatar || googleAvatar,
|
||||
username: findUser.username,
|
||||
email: findUser.email,
|
||||
|
||||
},
|
||||
google_ticket: ticket,
|
||||
};
|
||||
|
||||
await t.commit();
|
||||
|
||||
return res
|
||||
.status(200)
|
||||
.json({ success: true, message: 'Login successful', data });
|
||||
} catch (error) {
|
||||
await t.rollback()
|
||||
console.error('Error logging in with Google:', error);
|
||||
return res
|
||||
.status(500)
|
||||
.json({ success: false, message: error.message });
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user