From e14b2ff46c96c3e70999d36ebc69f4c501ed374f Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 9 Jun 2025 16:28:28 +0700 Subject: [PATCH] add modified function login anda logout --- backend/controller/authController.js | 137 ++++++++++++-- backend/routes/authRoutes.js | 187 +++++++++++++++++++- frontend/lib/api_services/api_services.dart | 3 + frontend/lib/user/home_page.dart | 35 +++- 4 files changed, 339 insertions(+), 23 deletions(-) diff --git a/backend/controller/authController.js b/backend/controller/authController.js index ebb10c9..0ecc325 100644 --- a/backend/controller/authController.js +++ b/backend/controller/authController.js @@ -41,9 +41,9 @@ exports.register = async (req, res) => { } }; - // Penyimpanan sesi login (in-memory) - const activeSessions = {}; - const sessionTimeouts = {}; + // Inisialisasi global variables di awal file +const activeSessions = {}; // { userId: true } +const sessionTimeouts = {}; // { userId: timeoutId } // Login exports.login = async (req, res) => { @@ -58,7 +58,10 @@ exports.login = async (req, res) => { // 🔹 Cek apakah user sudah login di device lain if (activeSessions[user.id]) { - return res.status(403).json({ message: "Akun ini sedang digunakan di perangkat lain." }); + return res.status(403).json({ + message: "Akun ini sedang digunakan di perangkat lain.", + debug: `User ${user.id} masih dalam sesi aktif` + }); } // 🔹 Verifikasi password @@ -75,42 +78,144 @@ exports.login = async (req, res) => { ); // 🔹 Tandai user sedang login (aktif) - activeSessions[user.id] = true; + activeSessions[user.id] = { + loginTime: new Date(), + email: user.email + }; - // 🔹 Atur timer logout otomatis setelah 5 menit (300000 ms) + // 🔹 Bersihkan timeout lama jika ada if (sessionTimeouts[user.id]) { - clearTimeout(sessionTimeouts[user.id]); // Bersihkan timer lama jika ada + clearTimeout(sessionTimeouts[user.id]); + delete sessionTimeouts[user.id]; } + + // 🔹 Atur timer logout otomatis setelah 5 menit sessionTimeouts[user.id] = setTimeout(() => { + console.log(`User ID ${user.id} (${user.email}) otomatis logout karena timeout`); delete activeSessions[user.id]; delete sessionTimeouts[user.id]; - console.log(`User ID ${user.id} otomatis logout karena timeout`); }, 5 * 60 * 1000); // 5 menit - console.log("User ID dari backend:", user.id); + console.log("Login berhasil - User ID:", user.id, "Email:", user.email); + console.log("Active sessions:", Object.keys(activeSessions)); // 🔹 Kirim response res.status(200).json({ message: "Login berhasil", token, - role: user.role + role: user.role, + userId: user.id }); } catch (error) { - res.status(500).json({ message: "Terjadi kesalahan", error }); + console.error("Login error:", error); + res.status(500).json({ message: "Terjadi kesalahan", error: error.message }); } }; -//logout +// Logout (Versi yang lebih robust) exports.logout = async (req, res) => { - const userId = req.user?.id; + try { + // Coba ambil userId dari berbagai sumber + let userId = req.user?.id; + + // Jika tidak ada dari middleware, coba ambil dari body atau query + if (!userId) { + userId = req.body?.userId || req.query?.userId; + } + + // Jika masih tidak ada, coba decode token manual + if (!userId && req.headers.authorization) { + try { + const token = req.headers.authorization.split(' ')[1]; + const decoded = jwt.verify(token, process.env.JWT_SECRET); + userId = decoded.id; + } catch (tokenError) { + console.log("Token decode error:", tokenError.message); + } + } + + console.log("Logout attempt - User ID:", userId); + console.log("Active sessions before logout:", Object.keys(activeSessions)); + + if (userId && activeSessions[userId]) { + // Hapus sesi aktif + delete activeSessions[userId]; + + // Bersihkan timeout + if (sessionTimeouts[userId]) { + clearTimeout(sessionTimeouts[userId]); + delete sessionTimeouts[userId]; + } + + console.log("Logout berhasil - User ID:", userId); + console.log("Active sessions after logout:", Object.keys(activeSessions)); + + return res.status(200).json({ + message: "Logout berhasil", + userId: userId + }); + } + + console.log("Logout gagal - Tidak ada sesi aktif untuk User ID:", userId); + res.status(400).json({ + message: "Tidak ada sesi login aktif", + userId: userId, + activeSessions: Object.keys(activeSessions) + }); + + } catch (error) { + console.error("Logout error:", error); + res.status(500).json({ message: "Terjadi kesalahan saat logout", error: error.message }); + } +}; + +// Tambahkan endpoint untuk debugging (opsional - hapus di production) +exports.debugSessions = (req, res) => { + res.json({ + activeSessions: activeSessions, + sessionTimeouts: Object.keys(sessionTimeouts), + message: "Debug info - hapus endpoint ini di production" + }); +}; + +// Force logout (untuk admin atau debugging) +exports.forceLogout = (req, res) => { + const { userId } = req.body; + if (userId && activeSessions[userId]) { delete activeSessions[userId]; + if (sessionTimeouts[userId]) { + clearTimeout(sessionTimeouts[userId]); + delete sessionTimeouts[userId]; + } + return res.json({ message: `User ${userId} berhasil di-force logout` }); + } + + res.status(404).json({ message: "User tidak ditemukan dalam sesi aktif" }); +}; + +// Clear semua sessions (untuk debugging) +exports.clearAllSessions = (req, res) => { + const sessionCount = Object.keys(activeSessions).length; + + // Bersihkan semua timeout + Object.keys(sessionTimeouts).forEach(userId => { clearTimeout(sessionTimeouts[userId]); delete sessionTimeouts[userId]; - return res.status(200).json({ message: "Logout berhasil" }); - } - res.status(400).json({ message: "Tidak ada sesi login aktif" }); + }); + + // Bersihkan semua active sessions + Object.keys(activeSessions).forEach(userId => { + delete activeSessions[userId]; + }); + + console.log(`Cleared ${sessionCount} active sessions`); + + res.json({ + message: "Semua sesi aktif berhasil dibersihkan", + clearedSessions: sessionCount + }); }; diff --git a/backend/routes/authRoutes.js b/backend/routes/authRoutes.js index 653ea85..4132031 100644 --- a/backend/routes/authRoutes.js +++ b/backend/routes/authRoutes.js @@ -74,17 +74,196 @@ router.post('/register', authController.register); * responses: * 200: * description: Login berhasil + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: Login berhasil + * token: + * type: string + * example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... + * role: + * type: string + * example: user + * userId: + * type: integer + * example: 1 * 401: - * description: Password salah + * description: Email atau password salah * 403: - * description: Akun sedang digunakan di perangkat lain - * 404: - * description: User tidak ditemukan + * description: Akun sedang digunakan di perangkat lain + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: Akun ini sedang digunakan di perangkat lain. + * debug: + * type: string + * example: User 1 masih dalam sesi aktif * 500: * description: Terjadi kesalahan server */ router.post('/login', authController.login); +/** + * @swagger + * /api/auth/logout: + * post: + * summary: Logout dari akun + * tags: [Authentication] + * security: + * - bearerAuth: [] + * requestBody: + * content: + * application/json: + * schema: + * type: object + * properties: + * userId: + * type: integer + * description: ID user (opsional, diambil dari token jika tidak disediakan) + * example: 1 + * responses: + * 200: + * description: Logout berhasil + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: Logout berhasil + * userId: + * type: integer + * example: 1 + * 400: + * description: Tidak ada sesi login aktif + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: Tidak ada sesi login aktif + * userId: + * type: integer + * example: 1 + * activeSessions: + * type: array + * items: + * type: string + * example: ["2", "3"] + * 500: + * description: Terjadi kesalahan server + */ +router.post('/logout', authController.logout); + +/** + * @swagger + * /api/auth/debug-sessions: + * get: + * summary: Debug - Lihat semua sesi aktif (HAPUS DI PRODUCTION!) + * tags: [Debug] + * responses: + * 200: + * description: Informasi debug sesi + * content: + * application/json: + * schema: + * type: object + * properties: + * activeSessions: + * type: object + * description: Objek berisi semua sesi aktif + * example: + * "1": + * loginTime: "2024-01-01T10:00:00.000Z" + * email: "johndoe@gmail.com" + * sessionTimeouts: + * type: array + * items: + * type: string + * description: Array ID user yang memiliki timeout aktif + * example: ["1", "2"] + * message: + * type: string + * example: Debug info - hapus endpoint ini di production + */ +router.get('/debug-sessions', authController.debugSessions); + +/** + * @swagger + * /api/auth/force-logout: + * post: + * summary: Debug - Force logout user tertentu (HAPUS DI PRODUCTION!) + * tags: [Debug] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - userId + * properties: + * userId: + * type: integer + * description: ID user yang akan di-force logout + * example: 1 + * responses: + * 200: + * description: Force logout berhasil + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: User 1 berhasil di-force logout + * 404: + * description: User tidak ditemukan dalam sesi aktif + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: User tidak ditemukan dalam sesi aktif + */ +router.post('/force-logout', authController.forceLogout); + +/** + * @swagger + * /api/auth/clear-all-sessions: + * post: + * summary: Debug - Bersihkan semua sesi aktif (HAPUS DI PRODUCTION!) + * tags: [Debug] + * responses: + * 200: + * description: Semua sesi berhasil dibersihkan + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: Semua sesi aktif berhasil dibersihkan + * clearedSessions: + * type: integer + * example: 3 + */ +router.post('/clear-all-sessions', authController.clearAllSessions); /** * @swagger diff --git a/frontend/lib/api_services/api_services.dart b/frontend/lib/api_services/api_services.dart index e89b708..078f221 100644 --- a/frontend/lib/api_services/api_services.dart +++ b/frontend/lib/api_services/api_services.dart @@ -223,6 +223,8 @@ Future>> getAllHistori() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); print("User ID dari respons login: ${responseData['userId']}"); // Tambahkan log await prefs.setString('userId', responseData['userId'].toString()); + await prefs.setString('token', responseData['token']); + await prefs.setString('role', responseData['role']); return responseData; } else { @@ -239,6 +241,7 @@ Future>> getAllHistori() async { SharedPreferences prefs = await SharedPreferences.getInstance(); await prefs.remove('token'); await prefs.remove('role'); + await prefs.remove('userId'); } // Fungsi Cek Login diff --git a/frontend/lib/user/home_page.dart b/frontend/lib/user/home_page.dart index b2dce45..e952886 100644 --- a/frontend/lib/user/home_page.dart +++ b/frontend/lib/user/home_page.dart @@ -5,19 +5,48 @@ import 'riwayat_diagnosa_page.dart'; import 'profile_page.dart'; import 'basis_pengetahuan_page.dart'; import 'package:shared_preferences/shared_preferences.dart'; - +import 'login_page.dart'; +import 'dart:async'; class HomePage extends StatefulWidget { + + const HomePage({Key? key}) : super(key: key); + @override _HomePageState createState() => _HomePageState(); } class _HomePageState extends State { String userId = ''; // Variabel untuk menyimpan userId + Timer? _logoutTimer; @override void initState() { - super.initState(); + super.initState(); + _startAutoLogoutTimer(); + } + + void _startAutoLogoutTimer() { + _logoutTimer?.cancel(); // Pastikan timer sebelumnya di-cancel + + _logoutTimer = Timer(const Duration(minutes: 5), () async { + // Hapus semua data login + final prefs = await SharedPreferences.getInstance(); + await prefs.clear(); + + // Redirect ke LoginPage + if (!mounted) return; + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute(builder: (context) => LoginPage()), + (route) => false, // Menghapus semua halaman sebelumnya + ); + }); + } + + @override + void dispose() { + _logoutTimer?.cancel(); + super.dispose(); } Future navigateToRiwayatDiagnosaPage(BuildContext context) async { @@ -32,8 +61,8 @@ class _HomePageState extends State { return; } } - + @override Widget build(BuildContext context) { return Scaffold(