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:
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
|
||||
101
src/app/api/auth/verify-otp-login/route.ts
Normal file
101
src/app/api/auth/verify-otp-login/route.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
61
src/app/api/auth/verify-otp-register/route.ts
Normal file
61
src/app/api/auth/verify-otp-register/route.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user