Tampilan Layout sudah sesuai dengan roleIdnya
Sudah sessionnya Sudah disesuaikan juga semisal superadmin ngubah role admin, maka admin tersebut akan logOut dan diarahkan ke halama login sudah bisa logOut
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
// app/api/auth/_lib/session_create.ts
|
||||
import { cookies } from "next/headers";
|
||||
import { encrypt } from "./encrypt";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export async function sessionCreate({
|
||||
sessionKey,
|
||||
exp = "7 year",
|
||||
exp = "30 day",
|
||||
jwtSecret,
|
||||
user,
|
||||
}: {
|
||||
@@ -30,12 +32,59 @@ export async function sessionCreate({
|
||||
throw new Error("Token generation failed");
|
||||
}
|
||||
|
||||
// ✅ HYBRID: Simpan token ke database UserSession
|
||||
const userId = user.id as string;
|
||||
|
||||
if (userId) {
|
||||
try {
|
||||
// Hapus session lama user ini (logout device lain)
|
||||
await prisma.userSession.deleteMany({
|
||||
where: { userId },
|
||||
});
|
||||
|
||||
// Parse expiration
|
||||
const expiresDate = new Date();
|
||||
const expMatch = exp.match(/(\d+)\s*(day|year)/);
|
||||
|
||||
if (expMatch) {
|
||||
const [, num, unit] = expMatch;
|
||||
const amount = parseInt(num);
|
||||
|
||||
if (unit === 'year') {
|
||||
expiresDate.setFullYear(expiresDate.getFullYear() + amount);
|
||||
} else if (unit === 'day') {
|
||||
expiresDate.setDate(expiresDate.getDate() + amount);
|
||||
}
|
||||
} else {
|
||||
// Default 30 hari
|
||||
expiresDate.setDate(expiresDate.getDate() + 30);
|
||||
}
|
||||
|
||||
// Buat session baru di database
|
||||
await prisma.userSession.create({
|
||||
data: {
|
||||
userId,
|
||||
token, // JWT token disimpan
|
||||
expires: expiresDate,
|
||||
active: true,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`✅ Session created for user ${userId}`);
|
||||
} catch (dbError) {
|
||||
console.error("⚠️ Error menyimpan session ke database:", dbError);
|
||||
// Tetap lanjut meski gagal simpan ke DB (fallback ke JWT only)
|
||||
}
|
||||
}
|
||||
|
||||
// Set cookie
|
||||
const cookieStore = await cookies();
|
||||
cookieStore.set(sessionKey, token, {
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
path: "/",
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
maxAge: 30 * 24 * 60 * 60, // 30 hari dalam detik
|
||||
});
|
||||
|
||||
return token;
|
||||
|
||||
42
src/app/api/auth/_lib/session_delete.ts
Normal file
42
src/app/api/auth/_lib/session_delete.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
// app/api/auth/_lib/session_delete.ts
|
||||
import { cookies } from "next/headers";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
/**
|
||||
* Hapus session dari database dan cookie
|
||||
*/
|
||||
export async function sessionDelete({
|
||||
sessionKey,
|
||||
userId,
|
||||
}: {
|
||||
sessionKey: string;
|
||||
userId?: string;
|
||||
}): Promise<boolean> {
|
||||
try {
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get(sessionKey)?.value;
|
||||
|
||||
// Hapus dari database
|
||||
if (token) {
|
||||
const deleted = await prisma.userSession.deleteMany({
|
||||
where: { token },
|
||||
});
|
||||
console.log(`🗑️ Deleted ${deleted.count} session(s) by token`);
|
||||
} else if (userId) {
|
||||
// Fallback: hapus berdasarkan userId
|
||||
const deleted = await prisma.userSession.deleteMany({
|
||||
where: { userId },
|
||||
});
|
||||
console.log(`🗑️ Deleted ${deleted.count} session(s) for user ${userId}`);
|
||||
}
|
||||
|
||||
// Hapus cookie
|
||||
cookieStore.delete(sessionKey);
|
||||
console.log('✅ Session deleted successfully');
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("❌ Error deleting session:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
90
src/app/api/auth/_lib/session_verify.ts
Normal file
90
src/app/api/auth/_lib/session_verify.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
// app/api/auth/_lib/session_verify.ts
|
||||
import { cookies } from 'next/headers';
|
||||
import { decrypt } from './decrypt';
|
||||
import prisma from '@/lib/prisma';
|
||||
|
||||
/**
|
||||
* Verifikasi session hybrid:
|
||||
* 1. Decrypt JWT token
|
||||
* 2. Cek apakah token masih ada di database (untuk force logout)
|
||||
* 3. Return data user terbaru dari database
|
||||
*/
|
||||
export async function verifySession(): Promise<Record<string, unknown> | null> {
|
||||
try {
|
||||
const sessionKey = process.env.BASE_SESSION_KEY;
|
||||
if (!sessionKey) {
|
||||
throw new Error('BASE_SESSION_KEY tidak ditemukan di environment');
|
||||
}
|
||||
|
||||
const jwtSecret = process.env.BASE_TOKEN_KEY;
|
||||
if (!jwtSecret) {
|
||||
throw new Error('BASE_TOKEN_KEY tidak ditemukan di environment');
|
||||
}
|
||||
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get(sessionKey)?.value;
|
||||
|
||||
if (!token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Step 1: Decrypt JWT
|
||||
const jwtUser = await decrypt({ token, jwtSecret });
|
||||
|
||||
if (!jwtUser || !jwtUser.id) {
|
||||
console.log('⚠️ JWT decrypt failed atau tidak ada user ID');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Step 2: Cek database UserSession (untuk force logout)
|
||||
try {
|
||||
const dbSession = await prisma.userSession.findFirst({
|
||||
where: {
|
||||
userId: jwtUser.id as string,
|
||||
token: token,
|
||||
active: true,
|
||||
OR: [
|
||||
{ expires: null },
|
||||
{ expires: { gte: new Date() } },
|
||||
],
|
||||
},
|
||||
include: {
|
||||
User: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
nomor: true,
|
||||
roleId: true,
|
||||
isActive: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Token tidak ditemukan di database = sudah dihapus (force logout)
|
||||
if (!dbSession) {
|
||||
console.log('⚠️ Token valid tapi sudah dihapus dari database (force logout)');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Step 3: Return data user terbaru dari database
|
||||
// Ini penting agar roleId selalu update
|
||||
return {
|
||||
id: dbSession.User.id,
|
||||
username: dbSession.User.username,
|
||||
nomor: dbSession.User.nomor,
|
||||
roleId: dbSession.User.roleId,
|
||||
isActive: dbSession.User.isActive,
|
||||
};
|
||||
|
||||
} catch (dbError) {
|
||||
console.error("⚠️ Error cek database session:", dbError);
|
||||
// Fallback: jika database error, tetap pakai JWT
|
||||
return jwtUser;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.warn('❌ Session verification failed:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,47 +1,99 @@
|
||||
// app/api/auth/finalize-registration/route.ts
|
||||
import prisma from "@/lib/prisma";
|
||||
import { cookies } from "next/headers";
|
||||
import { NextResponse } from "next/server";
|
||||
// import { sessionCreate } from "../_lib/session_create";
|
||||
import { sessionCreate } from "../_lib/session_create";
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const { nomor, username, kodeId } = await req.json();
|
||||
const { nomor, username, kodeId, roleId } = await req.json();
|
||||
|
||||
// Verifikasi OTP (sama seperti verify-otp)
|
||||
const otpRecord = await prisma.kodeOtp.findUnique({ where: { id: kodeId } });
|
||||
if (!otpRecord?.isActive || otpRecord.nomor !== nomor) {
|
||||
return NextResponse.json({ success: false, message: 'OTP tidak valid' }, { status: 400 });
|
||||
// Validasi input
|
||||
if (!nomor || !username || !kodeId) {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Data tidak lengkap" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Buat user
|
||||
const user = await prisma.user.create({
|
||||
data: { username, nomor, isActive: false }
|
||||
// Verifikasi OTP
|
||||
const otpRecord = await prisma.kodeOtp.findUnique({
|
||||
where: { id: kodeId },
|
||||
});
|
||||
|
||||
if (!otpRecord?.isActive || otpRecord.nomor !== nomor) {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "OTP tidak valid" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Cek apakah username sudah dipakai
|
||||
const existingUser = await prisma.user.findUnique({
|
||||
where: { username },
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Username sudah digunakan" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Buat user baru
|
||||
const newUser = await prisma.user.create({
|
||||
data: {
|
||||
username,
|
||||
nomor,
|
||||
roleId: roleId || "1", // Default role
|
||||
isActive: false, // Menunggu approval
|
||||
},
|
||||
});
|
||||
|
||||
// Nonaktifkan OTP
|
||||
await prisma.kodeOtp.update({ where: { id: kodeId }, data: { isActive: false } });
|
||||
|
||||
// Buat session
|
||||
// const token = await sessionCreate({
|
||||
// sessionKey: process.env.BASE_SESSION_KEY!,
|
||||
// jwtSecret: process.env.BASE_TOKEN_KEY!,
|
||||
// user: { id: user.id, nomor: user.nomor, username: user.username, roleId: user.roleId, isActive: true },
|
||||
// });
|
||||
|
||||
(await cookies()).set('desadarmasaba_user_id', user.id, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
path: '/',
|
||||
maxAge: 30 * 24 * 60 * 60, // 30 hari
|
||||
await prisma.kodeOtp.update({
|
||||
where: { id: kodeId },
|
||||
data: { isActive: false },
|
||||
});
|
||||
|
||||
// ✅ CREATE SESSION (JWT + Database)
|
||||
try {
|
||||
await sessionCreate({
|
||||
sessionKey: process.env.BASE_SESSION_KEY!,
|
||||
jwtSecret: process.env.BASE_TOKEN_KEY!,
|
||||
exp: "30 day",
|
||||
user: {
|
||||
id: newUser.id,
|
||||
nomor: newUser.nomor,
|
||||
username: newUser.username,
|
||||
roleId: newUser.roleId,
|
||||
isActive: false, // User baru belum aktif
|
||||
},
|
||||
});
|
||||
} catch (sessionError) {
|
||||
console.error("❌ Error creating session:", sessionError);
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Gagal membuat session" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: "Registrasi berhasil. Menunggu persetujuan admin.",
|
||||
user: {
|
||||
id: newUser.id,
|
||||
name: newUser.username,
|
||||
roleId: newUser.roleId,
|
||||
isActive: false,
|
||||
},
|
||||
});
|
||||
|
||||
const response = NextResponse.json({ success: true, roleId: user.roleId });
|
||||
// response.cookies.set(process.env.BASE_SESSION_KEY!, token, { /* options */ });
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('Finalize Registration Error:', error);
|
||||
return NextResponse.json({ success: false, message: 'Registrasi gagal' }, { status: 500 });
|
||||
console.error("❌ Finalize Registration Error:", error);
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Registrasi gagal" },
|
||||
{ status: 500 }
|
||||
);
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
|
||||
35
src/app/api/auth/logout/route.ts
Normal file
35
src/app/api/auth/logout/route.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
// app/api/auth/logout/route.ts
|
||||
import { NextResponse } from "next/server";
|
||||
import { sessionDelete } from "../_lib/session_delete";
|
||||
|
||||
export async function POST() {
|
||||
try {
|
||||
const deleted = await sessionDelete({
|
||||
sessionKey: process.env.BASE_SESSION_KEY!,
|
||||
});
|
||||
|
||||
if (deleted) {
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: "Logout berhasil",
|
||||
});
|
||||
} else {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
message: "Gagal logout",
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("❌ Logout Error:", error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
message: "Terjadi kesalahan saat logout",
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,45 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { NextRequest } from "next/server";
|
||||
// Jika pakai custom session (bukan next-auth), ganti dengan logic session-mu
|
||||
// app/api/auth/me/route.ts
|
||||
import { NextResponse } from 'next/server';
|
||||
import { verifySession } from '../_lib/session_verify';
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
// 🔸 GANTI DENGAN LOGIC SESSION-MU
|
||||
// Contoh: jika kamu simpan user.id di cookie atau JWT
|
||||
const userId = req.cookies.get("desadarmasaba_user_id")?.value; // sesuaikan
|
||||
export async function GET() {
|
||||
try {
|
||||
// ✅ Verify session (hybrid: JWT + Database)
|
||||
const user = await verifySession();
|
||||
|
||||
if (!userId) {
|
||||
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
message: "Session tidak valid",
|
||||
user: null
|
||||
},
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
|
||||
// Data user sudah fresh dari database (via verifySession)
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
user: {
|
||||
id: user.id,
|
||||
name: user.username,
|
||||
username: user.username,
|
||||
nomor: user.nomor,
|
||||
roleId: user.roleId,
|
||||
isActive: user.isActive,
|
||||
},
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error("❌ Error in /api/auth/me:", error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
message: "Terjadi kesalahan",
|
||||
user: null
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
nomor: true,
|
||||
isActive: true,
|
||||
role: { select: { name: true } },
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return Response.json({ error: "User not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
return Response.json({ user });
|
||||
}
|
||||
@@ -4,13 +4,6 @@ import { NextResponse } from "next/server";
|
||||
import { sessionCreate } from "../_lib/session_create";
|
||||
|
||||
export async function POST(req: Request) {
|
||||
if (req.method !== "POST") {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Method Not Allowed" },
|
||||
{ status: 405 }
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const { nomor, otp, kodeId } = await req.json();
|
||||
|
||||
@@ -41,7 +34,7 @@ export async function POST(req: Request) {
|
||||
);
|
||||
}
|
||||
|
||||
// Pastikan tipe data cocok (OTP di DB = number)
|
||||
// Validasi OTP
|
||||
const receivedOtp = Number(otp);
|
||||
if (isNaN(receivedOtp) || otpRecord.otp !== receivedOtp) {
|
||||
return NextResponse.json(
|
||||
@@ -76,26 +69,22 @@ export async function POST(req: Request) {
|
||||
);
|
||||
}
|
||||
|
||||
if (!user.isActive) {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Akun belum disetujui oleh admin" },
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
// Buat session
|
||||
const token = await sessionCreate({
|
||||
sessionKey: process.env.BASE_SESSION_KEY!,
|
||||
jwtSecret: process.env.BASE_TOKEN_KEY!, // ✅
|
||||
user: {
|
||||
id: user.id,
|
||||
nomor: user.nomor,
|
||||
username: user.username,
|
||||
roleId: user.roleId,
|
||||
isActive: user.isActive,
|
||||
},
|
||||
});
|
||||
if (!token) {
|
||||
// ✅ CREATE SESSION (JWT + Database)
|
||||
try {
|
||||
await sessionCreate({
|
||||
sessionKey: process.env.BASE_SESSION_KEY!,
|
||||
jwtSecret: process.env.BASE_TOKEN_KEY!,
|
||||
exp: "30 day",
|
||||
user: {
|
||||
id: user.id,
|
||||
nomor: user.nomor,
|
||||
username: user.username,
|
||||
roleId: user.roleId,
|
||||
isActive: user.isActive,
|
||||
},
|
||||
});
|
||||
} catch (sessionError) {
|
||||
console.error("❌ Error creating session:", sessionError);
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Gagal membuat session" },
|
||||
{ status: 500 }
|
||||
@@ -108,34 +97,25 @@ export async function POST(req: Request) {
|
||||
data: { isActive: false },
|
||||
});
|
||||
|
||||
const userData = {
|
||||
id: user.id,
|
||||
name: user.username, // atau user.nama jika ada kolom nama
|
||||
roleId: user.roleId,
|
||||
};
|
||||
|
||||
// Set cookie & respons
|
||||
const response = NextResponse.json(
|
||||
{
|
||||
success: true,
|
||||
message: "Berhasil login",
|
||||
user: userData,
|
||||
roleId: user.roleId,
|
||||
},
|
||||
{ status: 200 }
|
||||
);
|
||||
|
||||
response.cookies.set(process.env.BASE_SESSION_KEY!, token, {
|
||||
path: "/",
|
||||
sameSite: "lax",
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
httpOnly: true, // 🔒 lebih aman
|
||||
maxAge: 30 * 24 * 60 * 60,
|
||||
// Update lastLogin
|
||||
await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: { lastLogin: new Date() },
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: user.isActive ? "Berhasil login" : "Menunggu persetujuan",
|
||||
user: {
|
||||
id: user.id,
|
||||
name: user.username,
|
||||
roleId: user.roleId,
|
||||
isActive: user.isActive,
|
||||
},
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("Verify OTP Error:", error);
|
||||
console.error("❌ Verify OTP Error:", error);
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Terjadi kesalahan saat verifikasi" },
|
||||
{ status: 500 }
|
||||
|
||||
Reference in New Issue
Block a user