feat(auth): membuat user registration, login, sama pengecekan profile user yang sudah dilogin
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
import { PrismaClient } from '../generated/prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export default prisma;
|
||||
@@ -0,0 +1,67 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import prisma from '../lib/prisma';
|
||||
import { CustomError } from './errorHandler';
|
||||
|
||||
export interface AuthenticatedRequest extends Request {
|
||||
user?: {
|
||||
id: string;
|
||||
email: string;
|
||||
role: 'ADMIN' | 'STUDENT';
|
||||
};
|
||||
}
|
||||
|
||||
export const protect = async (req: AuthenticatedRequest, _res: Response, next: NextFunction) => {
|
||||
let token: string | undefined;
|
||||
|
||||
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) {
|
||||
token = req.headers.authorization.split(' ')[1];
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
const error: CustomError = new Error('Not authorized to access this route, token is missing');
|
||||
error.statusCode = 401;
|
||||
return next(error);
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'internify_secret_key_2026_dev') as {
|
||||
id: string;
|
||||
email: string;
|
||||
role: 'ADMIN' | 'STUDENT';
|
||||
};
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: decoded.id },
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
role: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
const error: CustomError = new Error('User belonging to this token no longer exists');
|
||||
error.statusCode = 401;
|
||||
return next(error);
|
||||
}
|
||||
|
||||
req.user = user as { id: string; email: string; role: 'ADMIN' | 'STUDENT' };
|
||||
next();
|
||||
} catch (err) {
|
||||
const error: CustomError = new Error('Not authorized to access this route, token is invalid or expired');
|
||||
error.statusCode = 401;
|
||||
return next(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const restrictTo = (...roles: ('ADMIN' | 'STUDENT')[]) => {
|
||||
return (req: AuthenticatedRequest, _res: Response, next: NextFunction) => {
|
||||
if (!req.user || !roles.includes(req.user.role)) {
|
||||
const error: CustomError = new Error('You do not have permission to perform this action');
|
||||
error.statusCode = 403;
|
||||
return next(error);
|
||||
}
|
||||
next();
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,200 @@
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import prisma from '../lib/prisma';
|
||||
import { protect, AuthenticatedRequest } from '../middleware/auth';
|
||||
import { CustomError } from '../middleware/errorHandler';
|
||||
|
||||
const router = Router();
|
||||
|
||||
const generateToken = (payload: { id: string; email: string; role: string }) => {
|
||||
return jwt.sign(
|
||||
payload,
|
||||
process.env.JWT_SECRET || 'internify_secret_key_2026_dev',
|
||||
{ expiresIn: (process.env.JWT_EXPIRES_IN || '7d') as any }
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @route POST /api/auth/register
|
||||
* @desc Mendaftarkan user baru dengan role STUDENT (Mahasiswa)
|
||||
* @access Public
|
||||
*/
|
||||
router.post('/register', async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const {
|
||||
email,
|
||||
password,
|
||||
namaDepan,
|
||||
namaBelakang,
|
||||
kontak,
|
||||
jurusan,
|
||||
universitas,
|
||||
negara,
|
||||
cvPath,
|
||||
portofolioPath,
|
||||
motivasi,
|
||||
relevantSkills,
|
||||
} = req.body;
|
||||
|
||||
if (!email || !password || !namaDepan || !kontak || !jurusan || !universitas || !negara) {
|
||||
const error: CustomError = new Error('Mohon lengkapi semua field wajib yang diperlukan');
|
||||
error.statusCode = 400;
|
||||
return next(error);
|
||||
}
|
||||
|
||||
const existingUser = await prisma.user.findUnique({
|
||||
where: { email },
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
const error: CustomError = new Error('Email sudah terdaftar');
|
||||
error.statusCode = 400;
|
||||
return next(error);
|
||||
}
|
||||
|
||||
const salt = await bcrypt.genSalt(10);
|
||||
const passwordHash = await bcrypt.hash(password, salt);
|
||||
|
||||
const newUser = await prisma.$transaction(async (tx) => {
|
||||
const user = await tx.user.create({
|
||||
data: {
|
||||
email,
|
||||
passwordHash,
|
||||
role: 'STUDENT',
|
||||
},
|
||||
});
|
||||
|
||||
await tx.mahasiswa.create({
|
||||
data: {
|
||||
idUser: user.id,
|
||||
namaDepan,
|
||||
namaBelakang: namaBelakang || null,
|
||||
kontak,
|
||||
jurusan,
|
||||
universitas,
|
||||
negara,
|
||||
cvPath: cvPath || '',
|
||||
portofolioPath: portofolioPath || '',
|
||||
motivasi: motivasi || '',
|
||||
relevantSkills: relevantSkills || '',
|
||||
},
|
||||
});
|
||||
|
||||
return user;
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: 'Registrasi mahasiswa berhasil',
|
||||
data: {
|
||||
id: newUser.id,
|
||||
email: newUser.email,
|
||||
role: newUser.role,
|
||||
createdAt: newUser.createdAt,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @route POST /api/auth/login
|
||||
* @desc Autentikasi user dan dapatin token
|
||||
* @access Public
|
||||
*/
|
||||
router.post('/login', async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
if (!email || !password) {
|
||||
const error: CustomError = new Error('Mohon masukkan email dan password');
|
||||
error.statusCode = 400;
|
||||
return next(error);
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email },
|
||||
include: {
|
||||
admin: true,
|
||||
mahasiswa: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
const error: CustomError = new Error('Email atau password salah');
|
||||
error.statusCode = 401;
|
||||
return next(error);
|
||||
}
|
||||
|
||||
const isMatch = await bcrypt.compare(password, user.passwordHash);
|
||||
if (!isMatch) {
|
||||
const error: CustomError = new Error('Email atau password salah');
|
||||
error.statusCode = 401;
|
||||
return next(error);
|
||||
}
|
||||
|
||||
const profile = user.role === 'ADMIN' ? user.admin : user.mahasiswa;
|
||||
|
||||
const token = generateToken({
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
role: user.role,
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
token,
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
role: user.role,
|
||||
profile,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @route GET /api/auth/profile
|
||||
* @desc Dapatin profile user yang sedang login
|
||||
* @access Private
|
||||
*/
|
||||
router.get('/profile', protect, async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const userId = req.user?.id;
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
include: {
|
||||
admin: true,
|
||||
mahasiswa: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
const error: CustomError = new Error('User tidak ditemukan');
|
||||
error.statusCode = 404;
|
||||
return next(error);
|
||||
}
|
||||
|
||||
const profile = user.role === 'ADMIN' ? user.admin : user.mahasiswa;
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
role: user.role,
|
||||
profile,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -1,7 +1,11 @@
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import prisma from '../lib/prisma';
|
||||
import authRouter from './auth';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.use('/auth', authRouter);
|
||||
|
||||
/**
|
||||
* @route GET /api/health
|
||||
* @desc Endpoint untuk memeriksa kesehatan/status server
|
||||
@@ -15,6 +19,31 @@ router.get('/health', (_req: Request, res: Response) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @route GET /api/users
|
||||
* @desc Mendapatkan daftar semua user dari database menggunakan Prisma
|
||||
* @access Public
|
||||
*/
|
||||
router.get('/users', async (_req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const users = await prisma.user.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
role: true,
|
||||
createdAt: true,
|
||||
},
|
||||
});
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
count: users.length,
|
||||
data: users,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @route GET /api/error-test
|
||||
* @desc Mensimulasikan error server internal untuk memverifikasi middleware penanganan error global
|
||||
|
||||
Reference in New Issue
Block a user