Fix Kondisi Verify Otp Registrasi dan Login

Next mau fix eror saat user sudah terdaftar tetapi di redirect ke login, seharusnya redirect sesuai roleIdnya
This commit is contained in:
2025-11-25 15:03:27 +08:00
parent 716db0adca
commit ace5aff1b6
24 changed files with 1069 additions and 788 deletions

View File

@@ -86,32 +86,14 @@ export const apiFetchOtpData = async ({ kodeId }: { kodeId: string }) => {
return data;
};
export const apiFetchVerifyOtp = async ({
nomor,
otp,
kodeId
}: {
nomor: string;
otp: string;
kodeId: string;
}) => {
if (!nomor || !otp || !kodeId) {
throw new Error('Data verifikasi tidak lengkap');
}
if (!/^\d{4,6}$/.test(otp)) {
throw new Error('Kode OTP harus 4-6 digit angka');
}
const response = await fetch('/api/auth/verify-otp', {
// Ganti endpoint ke verify-otp-login
export const apiFetchVerifyOtp = async ({ nomor, otp, kodeId }: { nomor: string; otp: string; kodeId: string }) => {
const response = await fetch('/api/auth/verify-otp-login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ nomor, otp, kodeId }),
});
const data = await response.json();
// ✅ Jangan throw error untuk status 4xx — biarkan frontend handle
return {
success: response.ok,
...data,

View File

@@ -1,7 +1,6 @@
// 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,
@@ -14,78 +13,30 @@ export async function sessionCreate({
jwtSecret: string;
user: Record<string, unknown>;
}) {
// 🔒 Validasi kunci tidak kosong
// Validasi env vars
if (!sessionKey || sessionKey.length === 0) {
throw new Error("sessionKey tidak boleh kosong");
}
if (!jwtSecret || jwtSecret.length === 0) {
throw new Error("jwtSecret tidak boleh kosong");
if (!jwtSecret || jwtSecret.length < 32) {
throw new Error("jwtSecret minimal 32 karakter");
}
const token = await encrypt({
exp,
jwtSecret,
user,
});
if (token === null) {
const token = await encrypt({ exp, jwtSecret, user });
if (!token) {
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, {
(await cookies()).set(sessionKey, token, {
httpOnly: true,
sameSite: "lax",
path: "/",
secure: process.env.NODE_ENV === "production",
maxAge: 30 * 24 * 60 * 60, // 30 hari dalam detik
maxAge: 30 * 24 * 60 * 60,
});
console.log("✅ BASE_SESSION_KEY loaded:", !!process.env.BASE_SESSION_KEY);
console.log("✅ BASE_TOKEN_KEY loaded:", !!process.env.BASE_TOKEN_KEY);
return token;
}
}

View File

@@ -1,134 +1,43 @@
// // 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;
// }
// }
// src/app/api/auth/_lib/session_verify.ts
import { cookies } from 'next/headers';
import { decrypt } from './decrypt';
import prisma from '@/lib/prisma';
// app/api/auth/_lib/session_verify.ts
import { cookies } from "next/headers";
import { decrypt } from "./decrypt";
import prisma from "@/lib/prisma";
export async function verifySession() {
try {
const sessionKey = process.env.BASE_SESSION_KEY;
const jwtSecret = process.env.BASE_TOKEN_KEY;
if (!sessionKey || !jwtSecret) {
throw new Error('Environment variables tidak lengkap');
}
if (!sessionKey || !jwtSecret) throw new Error('Env tidak lengkap');
const token = (await cookies()).get(sessionKey)?.value;
if (!token) return null;
// Decrypt JWT
const jwtUser = await decrypt({ token, jwtSecret });
if (!jwtUser || !jwtUser.id) return null;
if (!jwtUser?.id) return null;
// ✅ Cek apakah session di-invalidate
const user = await prisma.user.findUnique({
where: { id: jwtUser.id as string },
select: {
id: true,
username: true,
nomor: true,
roleId: true,
isActive: true,
sessionInvalid: true, // ← Tambahkan field ini
// Cari session di DB berdasarkan token
const dbSession = await prisma.userSession.findFirst({
where: {
token,
active: true,
expiresAt: { gte: new Date() }
},
include: { user: true }
});
if (!user || user.sessionInvalid) {
console.log('⚠️ Session tidak valid (force logout)');
if (!dbSession) {
console.log('⚠️ Session tidak ditemukan di DB');
return null;
}
return user;
// ❌ Hanya tolak jika sessionInvalid = true
if (dbSession.user.sessionInvalid) {
console.log('⚠️ Session di-invalidate');
return null;
}
// ✅ Return user, meskipun isActive = false
return dbSession.user;
} catch (error) {
console.warn('Session verification failed:', error);
return null;

View File

@@ -1,51 +1,64 @@
// app/api/auth/finalize-registration/route.ts
// src/app/api/auth/finalize-registration/route.ts
import prisma from "@/lib/prisma";
import { NextResponse } from "next/server";
import { sessionCreate } from "../_lib/session_create";
export async function POST(req: Request) {
try {
const { nomor, username, kodeId, roleId } = await req.json();
const { nomor, username, kodeId } = await req.json();
// Validasi input
if (!nomor || !username || !kodeId) {
const cleanNomor = nomor.replace(/\D/g, "");
if (!cleanNomor || !username || !kodeId) {
return NextResponse.json(
{ success: false, message: "Data tidak lengkap" },
{ status: 400 }
);
}
// Verifikasi OTP
// Di awal fungsi POST
console.log("📦 Received payload:", { nomor, username, kodeId });
// Validasi OTP
const otpRecord = await prisma.kodeOtp.findUnique({
where: { id: kodeId },
});
if (!otpRecord?.isActive || otpRecord.nomor !== nomor) {
if (!otpRecord?.isActive || otpRecord.nomor !== cleanNomor) {
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) {
// Cek duplikat username
if (await prisma.user.findFirst({ where: { username } })) {
return NextResponse.json(
{ success: false, message: "Username sudah digunakan" },
{ status: 400 }
{ status: 409 }
);
}
// Buat user baru
// ✅ Gunakan username dari input user
const defaultRole = await prisma.role.findFirst({
where: { name: "ADMIN DESA" },
select: { id: true },
});
if (!defaultRole) {
return NextResponse.json(
{ success: false, message: "Role default tidak ditemukan" },
{ status: 500 }
);
}
// ✅ Buat user dengan username yang diinput
const newUser = await prisma.user.create({
data: {
username,
username, // ✅ Ini yang benar
nomor,
roleId: roleId || "1", // Default role
isActive: false, // Menunggu approval
roleId: defaultRole.id,
isActive: false,
},
});
@@ -55,29 +68,22 @@ export async function POST(req: Request) {
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 }
);
}
// ✅ BUAT SESI untuk user baru (meski isActive = false)
const token = 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, // ✅ Pastikan sesuai
roleId: newUser.roleId,
isActive: false,
},
});
return NextResponse.json({
// Set cookie
const response = NextResponse.json({
success: true,
message: "Registrasi berhasil. Menunggu persetujuan admin.",
user: {
@@ -88,6 +94,14 @@ export async function POST(req: Request) {
},
});
response.cookies.set(process.env.BASE_SESSION_KEY!, token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
path: "/",
maxAge: 30 * 24 * 60 * 60,
});
return response;
} catch (error) {
console.error("❌ Finalize Registration Error:", error);
return NextResponse.json(
@@ -97,4 +111,4 @@ export async function POST(req: Request) {
} finally {
await prisma.$disconnect();
}
}
}

View File

@@ -5,36 +5,54 @@ import prisma from '@/lib/prisma';
export async function GET() {
try {
const user = await verifySession();
if (!user) {
const sessionUser = await verifySession();
if (!sessionUser) {
return NextResponse.json(
{ success: false, message: "Session tidak valid", user: null },
{ success: false, message: "Unauthorized", user: null },
{ status: 401 }
);
}
// ✅ Ambil menu akses kustom
const menuAccess = await prisma.userMenuAccess.findMany({
where: { userId: user.id },
select: { menuId: true },
});
const [dbUser, menuAccess] = await Promise.all([
prisma.user.findUnique({
where: { id: sessionUser.id },
select: {
id: true,
username: true,
nomor: true,
roleId: true, // STRING!
isActive: true, // BOOLEAN!
},
}),
prisma.userMenuAccess.findMany({
where: { userId: sessionUser.id },
select: { menuId: true },
}),
]);
if (!dbUser) {
return NextResponse.json(
{ success: false, message: "User not found", user: null },
{ status: 404 }
);
}
return NextResponse.json({
success: true,
user: {
id: user.id,
name: user.username,
username: user.username,
nomor: user.nomor,
roleId: user.roleId,
isActive: user.isActive,
menuIds: menuAccess.map(m => m.menuId), // ✅ tambahkan ini
id: dbUser.id,
name: dbUser.username,
username: dbUser.username,
nomor: dbUser.nomor,
roleId: dbUser.roleId, // STRING!
isActive: dbUser.isActive, // BOOLEAN!
menuIds: menuAccess.map(m => m.menuId),
},
});
} catch (error) {
console.error("❌ Error in /api/auth/me:", error);
return NextResponse.json(
{ success: false, message: "Terjadi kesalahan", user: null },
{ success: false, message: "Internal server error", user: null },
{ status: 500 }
);
}

View File

@@ -2,9 +2,10 @@
import { NextResponse } from "next/server";
import prisma from "@/lib/prisma";
export async function POST(req: Request) {
export async function GET(request: Request) {
try {
const { kodeId } = await req.json();
const { searchParams } = new URL(request.url);
const kodeId = searchParams.get("kodeId");
if (!kodeId) {
return NextResponse.json(
@@ -15,7 +16,7 @@ export async function POST(req: Request) {
const otpRecord = await prisma.kodeOtp.findUnique({
where: { id: kodeId },
select: { id: true, nomor: true, isActive: true, createdAt: true },
select: { nomor: true, isActive: true },
});
if (!otpRecord || !otpRecord.isActive) {
@@ -27,12 +28,12 @@ export async function POST(req: Request) {
return NextResponse.json({
success: true,
data: otpRecord,
data: { nomor: otpRecord.nomor },
});
} catch (error) {
console.error("Error fetching OTP data:", error);
console.error("❌ Gagal mengambil data OTP:", error);
return NextResponse.json(
{ success: false, message: "Gagal mengambil data OTP" },
{ success: false, message: "Terjadi kesalahan internal" },
{ status: 500 }
);
} finally {

View File

@@ -14,7 +14,7 @@ export async function POST(req: Request) {
if (await prisma.user.findUnique({ where: { nomor } })) {
return NextResponse.json({ success: false, message: 'Nomor sudah terdaftar' }, { status: 409 });
}
if (await prisma.user.findUnique({ where: { username } })) {
if (await prisma.user.findFirst({ where: { username } })) {
return NextResponse.json({ success: false, message: 'Username sudah digunakan' }, { status: 409 });
}

View File

@@ -1,71 +1,58 @@
// src/app/api/auth/resend-otp/route.ts
import prisma from "@/lib/prisma";
import { randomOTP } from "../_lib/randomOTP";
import { NextResponse } from "next/server";
import { randomOTP } from "../_lib/randomOTP";
export async function POST(req: Request) {
if (req.method !== "POST") {
return NextResponse.json(
{ success: false, message: "Method Not Allowed" },
{ status: 405 }
);
}
try {
const codeOtp = randomOTP();
const body = await req.json();
const { nomor } = body;
const { nomor } = await req.json();
const res = await fetch(
`https://wa.wibudev.com/code?nom=${nomor}&text=HIPMI - Kode ini bersifat RAHASIA dan JANGAN DI BAGIKAN KEPADA SIAPAPUN, termasuk anggota ataupun pengurus HIPMI lainnya.
\n
>> Kode OTP anda: ${codeOtp}.
`
);
const sendWa = await res.json();
if (sendWa.status !== "success")
if (!nomor || typeof nomor !== 'string') {
return NextResponse.json(
{
success: false,
message: "Nomor Whatsapp Tidak Aktif",
},
{ success: false, message: "Nomor tidak valid" },
{ status: 400 }
);
}
const createOtpId = await prisma.kodeOtp.create({
const codeOtp = randomOTP();
const otpNumber = Number(codeOtp);
// Kirim OTP via WhatsApp
const waMessage = `Kode verifikasi Anda: ${codeOtp}`;
const waUrl = `https://wa.wibudev.com/code?nom=${encodeURIComponent(nomor)}&text=${encodeURIComponent(waMessage)}`;
const waRes = await fetch(waUrl);
const waData = await waRes.json();
if (waData.status !== "success") {
return NextResponse.json(
{ success: false, message: "Gagal mengirim OTP via WhatsApp" },
{ status: 400 }
);
}
// Simpan OTP ke database
const otpRecord = await prisma.kodeOtp.create({
data: {
nomor: nomor,
otp: codeOtp,
nomor,
otp: otpNumber,
isActive: true,
},
});
if (!createOtpId)
return NextResponse.json(
{
success: false,
message: "Gagal Membuat Kode OTP",
},
{ status: 400 }
);
return NextResponse.json({
success: true,
message: "OTP baru dikirim",
kodeId: otpRecord.id,
});
return NextResponse.json(
{
success: true,
message: "Kode Verifikasi Dikirim",
kodeId: createOtpId.id,
},
{ status: 200 }
);
} catch (error) {
console.error(" Error Resend OTP", error);
console.error("Error Resend OTP:", error);
return NextResponse.json(
{
success: false,
message: "Server Whatsapp Error !!",
},
{ success: false, message: "Gagal mengirim ulang OTP" },
{ status: 500 }
);
} finally {
await prisma.$disconnect();
}
}
}

View File

@@ -14,7 +14,7 @@ export async function POST(req: Request) {
if (await prisma.user.findUnique({ where: { nomor } })) {
return NextResponse.json({ success: false, message: 'Nomor sudah terdaftar' }, { status: 409 });
}
if (await prisma.user.findUnique({ where: { username } })) {
if (await prisma.user.findFirst({ where: { username } })) {
return NextResponse.json({ success: false, message: 'Username sudah digunakan' }, { status: 409 });
}

View File

@@ -0,0 +1,101 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// src/app/api/auth/verify-otp-login/route.ts
import prisma from "@/lib/prisma";
import { NextResponse } from "next/server";
import { sessionCreate } from "../_lib/session_create";
export async function POST(req: Request) {
try {
const { nomor, otp, kodeId } = await req.json();
if (!nomor || !otp || !kodeId) {
return NextResponse.json(
{ success: false, message: "Data tidak lengkap" },
{ status: 400 }
);
}
const otpRecord = await prisma.kodeOtp.findUnique({ where: { id: kodeId } });
if (!otpRecord || !otpRecord.isActive || otpRecord.nomor !== nomor) {
return NextResponse.json(
{ success: false, message: "Kode verifikasi tidak valid" },
{ status: 400 }
);
}
const receivedOtp = Number(otp);
if (isNaN(receivedOtp) || otpRecord.otp !== receivedOtp) {
return NextResponse.json(
{ success: false, message: "Kode OTP salah" },
{ status: 400 }
);
}
// 🔍 CARI USER — JANGAN BUAT BARU!
const user = await prisma.user.findUnique({
where: { nomor },
select: { id: true, nomor: true, username: true, roleId: true, isActive: true },
});
if (!user) {
// ❌ Nomor belum terdaftar → suruh registrasi
return NextResponse.json(
{ success: false, message: "Akun tidak ditemukan. Silakan registrasi terlebih dahulu." },
{ status: 404 }
);
}
// ✅ Buat sesi
const token = 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,
},
});
await prisma.$transaction([
prisma.kodeOtp.update({ where: { id: kodeId }, data: { isActive: false } }),
prisma.user.update({ where: { id: user.id }, data: { lastLogin: new Date() } }),
]);
const response = NextResponse.json({
success: true,
message: user.isActive ? "Berhasil login" : "Menunggu persetujuan",
user: {
id: user.id,
name: user.username,
roleId: user.roleId,
isActive: user.isActive,
},
});
response.cookies.set(process.env.BASE_SESSION_KEY!, token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
path: "/",
maxAge: 30 * 24 * 60 * 60,
});
return response;
} catch (error: any) {
console.error("❌ Verify OTP Login Error:", error);
if (error.message.includes("sessionKey") || error.message.includes("jwtSecret")) {
return NextResponse.json(
{ success: false, message: "Konfigurasi server tidak lengkap" },
{ status: 500 }
);
}
return NextResponse.json(
{ success: false, message: "Terjadi kesalahan saat login" },
{ status: 500 }
);
} finally {
await prisma.$disconnect();
}
}

View File

@@ -0,0 +1,61 @@
// src/app/api/auth/verify-otp-register/route.ts
import prisma from "@/lib/prisma";
import { NextResponse } from "next/server";
export async function POST(req: Request) {
try {
const { nomor, otp, kodeId } = await req.json();
if (!nomor || !otp || !kodeId) {
return NextResponse.json(
{ success: false, message: "Data tidak lengkap" },
{ status: 400 }
);
}
const otpRecord = await prisma.kodeOtp.findUnique({
where: { id: kodeId },
});
if (!otpRecord || !otpRecord.isActive) {
return NextResponse.json(
{ success: false, message: "Kode verifikasi tidak valid atau sudah kadaluarsa" },
{ status: 400 }
);
}
if (otpRecord.nomor !== nomor) {
return NextResponse.json(
{ success: false, message: "Nomor tidak sesuai dengan kode verifikasi" },
{ status: 400 }
);
}
const receivedOtp = Number(otp);
if (isNaN(receivedOtp) || otpRecord.otp !== receivedOtp) {
return NextResponse.json(
{ success: false, message: "Kode OTP salah" },
{ status: 400 }
);
}
// ✅ Hanya validasi — jangan update isActive!
return NextResponse.json({
success: true,
message: "OTP valid. Lanjutkan ke finalisasi registrasi.",
data: {
nomor,
kodeId,
},
});
} catch (error) {
console.error("❌ Verify OTP Register Error:", error);
return NextResponse.json(
{ success: false, message: "Terjadi kesalahan saat verifikasi OTP" },
{ status: 500 }
);
} finally {
await prisma.$disconnect();
}
}

View File

@@ -1,126 +0,0 @@
// app/api/auth/verify-otp/route.ts
import prisma from "@/lib/prisma";
import { NextResponse } from "next/server";
import { sessionCreate } from "../_lib/session_create";
export async function POST(req: Request) {
try {
const { nomor, otp, kodeId } = await req.json();
// Validasi input
if (!nomor || !otp || !kodeId) {
return NextResponse.json(
{ success: false, message: "Data tidak lengkap" },
{ status: 400 }
);
}
// Cari OTP record
const otpRecord = await prisma.kodeOtp.findUnique({
where: { id: kodeId },
});
if (!otpRecord) {
return NextResponse.json(
{ success: false, message: "Kode verifikasi tidak valid" },
{ status: 400 }
);
}
if (!otpRecord.isActive) {
return NextResponse.json(
{ success: false, message: "Kode verifikasi sudah digunakan" },
{ status: 400 }
);
}
// Validasi OTP
const receivedOtp = Number(otp);
if (isNaN(receivedOtp) || otpRecord.otp !== receivedOtp) {
return NextResponse.json(
{ success: false, message: "Kode OTP salah" },
{ status: 400 }
);
}
if (otpRecord.nomor !== nomor) {
return NextResponse.json(
{ success: false, message: "Nomor tidak sesuai" },
{ status: 400 }
);
}
// Cek user berdasarkan nomor
const user = await prisma.user.findUnique({
where: { nomor },
select: {
id: true,
nomor: true,
username: true,
roleId: true,
isActive: true,
},
});
if (!user) {
return NextResponse.json(
{ success: false, message: "Akun tidak ditemukan" },
{ status: 404 }
);
}
// ✅ 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 }
);
}
// Nonaktifkan OTP
await prisma.kodeOtp.update({
where: { id: kodeId },
data: { isActive: false },
});
// 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,
},
});
} catch (error) {
console.error("❌ Verify OTP Error:", error);
return NextResponse.json(
{ success: false, message: "Terjadi kesalahan saat verifikasi" },
{ status: 500 }
);
} finally {
await prisma.$disconnect();
}
}