Login, Register, Verifkasi Code Admin V1
This commit is contained in:
152
src/app/api/auth/_lib/api_fetch_auth.ts
Normal file
152
src/app/api/auth/_lib/api_fetch_auth.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
// app/api/auth/_lib/api_fetch_auth.ts
|
||||
|
||||
// app/api/auth/_lib/api_fetch_auth.ts
|
||||
|
||||
export const apiFetchLogin = async ({ nomor }: { nomor: string }) => {
|
||||
if (!nomor || nomor.replace(/\D/g, '').length < 10) {
|
||||
throw new Error('Nomor tidak valid');
|
||||
}
|
||||
|
||||
const cleanPhone = nomor.replace(/\D/g, '');
|
||||
|
||||
const response = await fetch("/api/auth/login", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ nomor: cleanPhone }),
|
||||
});
|
||||
|
||||
// Pastikan respons bisa di-parse sebagai JSON
|
||||
let data;
|
||||
try {
|
||||
data = await response.json();
|
||||
} catch (e) {
|
||||
console.error("Non-JSON response from /api/auth/login:", await response.text());
|
||||
throw new Error('Respons server tidak valid');
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.message || 'Gagal memproses login');
|
||||
}
|
||||
|
||||
// Validasi minimal respons
|
||||
if (typeof data.success !== 'boolean' || typeof data.isRegistered !== 'boolean') {
|
||||
throw new Error('Respons tidak sesuai format');
|
||||
}
|
||||
|
||||
if (data.success) {
|
||||
if (data.isRegistered && !data.kodeId) {
|
||||
throw new Error('Kode verifikasi tidak ditemukan untuk user terdaftar');
|
||||
}
|
||||
return data; // { success, isRegistered, kodeId? }
|
||||
} else {
|
||||
throw new Error(data.message || 'Login gagal');
|
||||
}
|
||||
};
|
||||
|
||||
export const apiFetchRegister = async ({
|
||||
username,
|
||||
nomor,
|
||||
}: {
|
||||
username: string;
|
||||
nomor: string;
|
||||
}) => {
|
||||
const cleanPhone = nomor.replace(/\D/g, '');
|
||||
if (cleanPhone.length < 10) throw new Error('Nomor tidak valid');
|
||||
|
||||
const response = await fetch("/api/auth/send-otp-register", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ username: username.trim(), nomor: cleanPhone }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (!response.ok) throw new Error(data.message || 'Gagal mengirim OTP');
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const apiFetchOtpData = async ({ kodeId }: { kodeId: string }) => {
|
||||
if (!kodeId) {
|
||||
throw new Error('Kode ID tidak valid');
|
||||
}
|
||||
|
||||
const response = await fetch("/api/auth/otp-data", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ kodeId }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.message || 'Gagal memuat data OTP');
|
||||
}
|
||||
|
||||
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', {
|
||||
// method: 'POST',
|
||||
// headers: { 'Content-Type': 'application/json' },
|
||||
// body: JSON.stringify({ nomor, otp, kodeId }),
|
||||
// });
|
||||
|
||||
// const data = await response.json();
|
||||
|
||||
// if (!response.ok) {
|
||||
// throw new Error(data.message || 'Verifikasi OTP gagal');
|
||||
// }
|
||||
|
||||
// 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', {
|
||||
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,
|
||||
status: response.status,
|
||||
};
|
||||
};
|
||||
@@ -1,50 +1,32 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { jwtVerify } from "jose";
|
||||
|
||||
export async function decrypt({
|
||||
token,
|
||||
encodedKey,
|
||||
jwtSecret,
|
||||
}: {
|
||||
token: string;
|
||||
encodedKey: string;
|
||||
}): Promise<Record<string, any> | null> {
|
||||
if (!token || !encodedKey) {
|
||||
console.error("Missing required parameters:", {
|
||||
hasToken: !!token,
|
||||
hasEncodedKey: !!encodedKey,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
jwtSecret: string;
|
||||
}): Promise<Record<string, unknown> | null> {
|
||||
if (!token || !jwtSecret) return null;
|
||||
|
||||
try {
|
||||
const enc = new TextEncoder().encode(encodedKey);
|
||||
const { payload } = await jwtVerify(token, enc, {
|
||||
const secret = new TextEncoder().encode(jwtSecret);
|
||||
const { payload } = await jwtVerify(token, secret, {
|
||||
algorithms: ["HS256"],
|
||||
});
|
||||
|
||||
if (!payload || !payload.user) {
|
||||
console.error("Invalid payload structure:", {
|
||||
hasPayload: !!payload,
|
||||
hasUser: payload ? !!payload.user : false,
|
||||
});
|
||||
if (
|
||||
typeof payload !== "object" ||
|
||||
payload === null ||
|
||||
!("user" in payload) ||
|
||||
typeof payload.user !== "object"
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Logging untuk debug
|
||||
// console.log("Decrypt successful:", {
|
||||
// payloadExists: !!payload,
|
||||
// userExists: !!payload.user,
|
||||
// tokenPreview: token.substring(0, 10) + "...",
|
||||
// });
|
||||
|
||||
return payload.user as Record<string, any>;
|
||||
return payload.user as Record<string, unknown>;
|
||||
} catch (error) {
|
||||
console.error("Token verification failed:", {
|
||||
error,
|
||||
tokenLength: token?.length,
|
||||
errorName: error instanceof Error ? error.name : "Unknown error",
|
||||
errorMessage: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
console.error("JWT Decrypt failed:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,23 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { SignJWT } from "jose";
|
||||
|
||||
export async function encrypt({
|
||||
user,
|
||||
exp = "7 year",
|
||||
encodedKey,
|
||||
exp = "7d",
|
||||
jwtSecret,
|
||||
}: {
|
||||
user: Record<string, any>;
|
||||
exp?: string;
|
||||
encodedKey: string;
|
||||
user: Record<string, unknown>;
|
||||
exp?: string | number;
|
||||
jwtSecret: string;
|
||||
}): Promise<string | null> {
|
||||
try {
|
||||
const enc = new TextEncoder().encode(encodedKey);
|
||||
const secret = new TextEncoder().encode(jwtSecret);
|
||||
return new SignJWT({ user })
|
||||
.setProtectedHeader({ alg: "HS256" })
|
||||
.setIssuedAt()
|
||||
.setExpirationTime(exp)
|
||||
.sign(enc);
|
||||
.sign(secret);
|
||||
} catch (error) {
|
||||
console.error("Gagal mengenkripsi", error);
|
||||
console.error("JWT Encrypt failed:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// wibu:0.2.82
|
||||
}
|
||||
@@ -1,36 +1,42 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { cookies } from "next/headers";
|
||||
import { encrypt } from "./encrypt";
|
||||
|
||||
export async function sessionCreate({
|
||||
sessionKey,
|
||||
exp = "7 year",
|
||||
encodedKey,
|
||||
jwtSecret,
|
||||
user,
|
||||
}: {
|
||||
sessionKey: string;
|
||||
exp?: string;
|
||||
encodedKey: string;
|
||||
jwtSecret: string;
|
||||
user: Record<string, unknown>;
|
||||
}) {
|
||||
// 🔒 Validasi kunci tidak kosong
|
||||
if (!sessionKey || sessionKey.length === 0) {
|
||||
throw new Error("sessionKey tidak boleh kosong");
|
||||
}
|
||||
if (!jwtSecret || jwtSecret.length === 0) {
|
||||
throw new Error("jwtSecret tidak boleh kosong");
|
||||
}
|
||||
|
||||
const token = await encrypt({
|
||||
exp,
|
||||
encodedKey,
|
||||
jwtSecret,
|
||||
user,
|
||||
});
|
||||
|
||||
const cookie: any = {
|
||||
key: sessionKey,
|
||||
value: token,
|
||||
options: {
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
path: "/",
|
||||
},
|
||||
};
|
||||
if (token === null) {
|
||||
throw new Error("Token generation failed");
|
||||
}
|
||||
|
||||
const cookieStore = await cookies();
|
||||
cookieStore.set(sessionKey, token, {
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
path: "/",
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
});
|
||||
|
||||
(await cookies()).set(cookie.key, cookie.value, { ...cookie.options });
|
||||
return token;
|
||||
}
|
||||
|
||||
// wibu:0.2.82
|
||||
}
|
||||
40
src/app/api/auth/finalize-registration/route.ts
Normal file
40
src/app/api/auth/finalize-registration/route.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
// 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 } = 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 });
|
||||
}
|
||||
|
||||
// Buat user
|
||||
const user = await prisma.user.create({
|
||||
data: { username, nomor, isActive: true }
|
||||
});
|
||||
|
||||
// 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 },
|
||||
});
|
||||
|
||||
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 });
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// app/api/auth/login/route.ts
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
import { NextResponse } from "next/server";
|
||||
import { randomOTP } from "../_lib/randomOTP";
|
||||
|
||||
@@ -12,52 +12,66 @@ export async function POST(req: Request) {
|
||||
}
|
||||
|
||||
try {
|
||||
const codeOtp = randomOTP();
|
||||
const body = await req.json();
|
||||
const { nomor } = body;
|
||||
const res = await fetch(
|
||||
`https://wa.wibudev.com/code?nom=${nomor}&text=Website Desa Darmasaba - Kode ini bersifat RAHASIA dan JANGAN DI BAGIKAN KEPADA SIAPAPUN, termasuk anggota ataupun Admin lainnya.
|
||||
\n
|
||||
>> Kode OTP anda: ${codeOtp}.
|
||||
`
|
||||
);
|
||||
const { nomor } = await req.json();
|
||||
|
||||
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({
|
||||
data: {
|
||||
nomor: nomor,
|
||||
otp: codeOtp,
|
||||
},
|
||||
const existingUser = await prisma.user.findUnique({
|
||||
where: { nomor },
|
||||
select: { id: true, isActive: true },
|
||||
});
|
||||
|
||||
if (!createOtpId)
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Gagal mengirim kode OTP" },
|
||||
{ status: 400 }
|
||||
);
|
||||
const isRegistered = !!existingUser;
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
if (isRegistered) {
|
||||
// ✅ User terdaftar → kirim OTP
|
||||
const codeOtp = randomOTP();
|
||||
const otpNumber = Number(codeOtp);
|
||||
|
||||
const waMessage = `Website Desa Darmasaba - Kode verifikasi Anda: ${codeOtp}`;
|
||||
const waUrl = `https://wa.wibudev.com/code?nom=${encodeURIComponent(nomor)}&text=${encodeURIComponent(waMessage)}`;
|
||||
|
||||
const res = await fetch(waUrl);
|
||||
const sendWa = await res.json();
|
||||
|
||||
if (sendWa.status !== "success") {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Gagal mengirim OTP via WhatsApp" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const createOtpId = await prisma.kodeOtp.create({
|
||||
data: { nomor, otp: otpNumber, isActive: true },
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: "Kode verifikasi terkirim",
|
||||
message: "Kode verifikasi dikirim",
|
||||
kodeId: createOtpId.id,
|
||||
},
|
||||
{ status: 200 }
|
||||
);
|
||||
isRegistered: true,
|
||||
});
|
||||
} else {
|
||||
// ❌ User belum terdaftar → JANGAN kirim OTP
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: "Nomor belum terdaftar",
|
||||
isRegistered: false,
|
||||
// Tidak ada kodeId
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error Login", error);
|
||||
console.error("Error Login:", error);
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Terjadi masalah saat login" , reason: error as Error },
|
||||
{ success: false, message: "Terjadi kesalahan saat login" },
|
||||
{ status: 500 }
|
||||
);
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
30
src/app/api/auth/me/route.ts
Normal file
30
src/app/api/auth/me/route.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { NextRequest } from "next/server";
|
||||
// Jika pakai custom session (bukan next-auth), ganti dengan logic session-mu
|
||||
|
||||
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
|
||||
|
||||
if (!userId) {
|
||||
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
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 });
|
||||
}
|
||||
41
src/app/api/auth/otp-data/route.ts
Normal file
41
src/app/api/auth/otp-data/route.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
// app/api/auth/otp-data/route.ts
|
||||
import { NextResponse } from "next/server";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const { kodeId } = await req.json();
|
||||
|
||||
if (!kodeId) {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Kode ID tidak diberikan" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const otpRecord = await prisma.kodeOtp.findUnique({
|
||||
where: { id: kodeId },
|
||||
select: { id: true, nomor: true, isActive: true, createdAt: true },
|
||||
});
|
||||
|
||||
if (!otpRecord || !otpRecord.isActive) {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Kode verifikasi tidak valid atau sudah digunakan" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: otpRecord,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error fetching OTP data:", error);
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Gagal mengambil data OTP" },
|
||||
{ status: 500 }
|
||||
);
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
@@ -1,62 +1,122 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { NextResponse } from "next/server";
|
||||
// app/api/auth/register/route.ts
|
||||
import { NextResponse } from 'next/server';
|
||||
import prisma from '@/lib/prisma';
|
||||
|
||||
export async function POST(req: Request) {
|
||||
if (req.method !== "POST") {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Method Not Allowed" },
|
||||
{ status: 405 }
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const { data } = await req.json();
|
||||
// Terima langsung properti, bukan { data: { ... } }
|
||||
const { username, nomor } = await req.json();
|
||||
|
||||
const cekUsername = await prisma.user.findUnique({
|
||||
where: {
|
||||
username: data.username,
|
||||
nomor: data.nomor,
|
||||
},
|
||||
});
|
||||
|
||||
if (cekUsername)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
message: "Username sudah digunakan",
|
||||
});
|
||||
|
||||
const createUser = await prisma.user.create({
|
||||
data: {
|
||||
username: data.username,
|
||||
nomor: data.nomor,
|
||||
},
|
||||
});
|
||||
|
||||
if (!createUser)
|
||||
// Validasi input
|
||||
if (!username || !nomor) {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Gagal Registrasi" },
|
||||
{ status: 500 }
|
||||
{ success: false, message: 'Data tidak lengkap' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: true,
|
||||
message: "Registrasi Berhasil, Anda Sedang Login",
|
||||
// data: createUser,
|
||||
// // Validasi OTP: pastikan berisi digit saja
|
||||
// const cleanOtp = otp.toString().trim();
|
||||
// if (!/^\d{4,6}$/.test(cleanOtp)) {
|
||||
// return NextResponse.json(
|
||||
// { success: false, message: 'Kode OTP tidak valid' },
|
||||
// { status: 400 }
|
||||
// );
|
||||
// }
|
||||
|
||||
// const receivedOtp = parseInt(cleanOtp, 10);
|
||||
// if (isNaN(receivedOtp)) {
|
||||
// return NextResponse.json(
|
||||
// { success: false, message: 'Kode OTP tidak valid' },
|
||||
// { 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 kadaluarsa' },
|
||||
// { status: 400 }
|
||||
// );
|
||||
// }
|
||||
|
||||
// if (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 duplikat nomor
|
||||
const existingUser = await prisma.user.findUnique({
|
||||
where: { nomor },
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: 'Nomor sudah terdaftar' },
|
||||
{ status: 409 }
|
||||
);
|
||||
}
|
||||
|
||||
// Cek username unik (pastikan ada @unique di schema!)
|
||||
const existingByUsername = await prisma.user.findUnique({
|
||||
where: { username },
|
||||
});
|
||||
|
||||
if (existingByUsername) {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: 'Username sudah digunakan' },
|
||||
{ status: 409 }
|
||||
);
|
||||
}
|
||||
|
||||
// Buat user
|
||||
const newUser = await prisma.user.create({
|
||||
data: {
|
||||
username: username.trim(),
|
||||
nomor,
|
||||
isActive: false,
|
||||
// roleId default "1"
|
||||
},
|
||||
{ status: 201 }
|
||||
);
|
||||
});
|
||||
|
||||
// // Nonaktifkan OTP
|
||||
// await prisma.kodeOtp.update({
|
||||
// where: { id: kodeId },
|
||||
// data: { isActive: false },
|
||||
// });
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'Pendaftaran berhasil. Menunggu persetujuan admin.',
|
||||
userId: newUser.id,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error registrasi:", error);
|
||||
console.error('Registration error:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
message: "Maaf, Terjadi Keselahan",
|
||||
reason: (error as Error).message,
|
||||
},
|
||||
{ success: false, message: 'Terjadi kesalahan saat pendaftaran' },
|
||||
{ status: 500 }
|
||||
);
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
71
src/app/api/auth/resend/route.ts
Normal file
71
src/app/api/auth/resend/route.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { randomOTP } from "../_lib/randomOTP";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
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 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")
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
message: "Nomor Whatsapp Tidak Aktif",
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
|
||||
const createOtpId = await prisma.kodeOtp.create({
|
||||
data: {
|
||||
nomor: nomor,
|
||||
otp: codeOtp,
|
||||
},
|
||||
});
|
||||
|
||||
if (!createOtpId)
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
message: "Gagal Membuat Kode OTP",
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: true,
|
||||
message: "Kode Verifikasi Dikirim",
|
||||
kodeId: createOtpId.id,
|
||||
},
|
||||
{ status: 200 }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(" Error Resend OTP", error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
message: "Server Whatsapp Error !!",
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
51
src/app/api/auth/send-otp-register/route.ts
Normal file
51
src/app/api/auth/send-otp-register/route.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { NextResponse } from "next/server";
|
||||
import { randomOTP } from "../_lib/randomOTP";
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const { username, nomor } = await req.json();
|
||||
|
||||
if (!username || !nomor) {
|
||||
return NextResponse.json({ success: false, message: 'Data tidak lengkap' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Cek duplikat
|
||||
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 } })) {
|
||||
return NextResponse.json({ success: false, message: 'Username sudah digunakan' }, { status: 409 });
|
||||
}
|
||||
|
||||
// Generate OTP
|
||||
const codeOtp = randomOTP();
|
||||
const otpNumber = Number(codeOtp);
|
||||
|
||||
// Kirim WA
|
||||
const waUrl = `https://wa.wibudev.com/code?nom=${encodeURIComponent(nomor)}&text=Website Desa Darmasaba - Kode verifikasi Anda: ${codeOtp}`;
|
||||
const res = await fetch(waUrl);
|
||||
const sendWa = await res.json();
|
||||
|
||||
if (sendWa.status !== "success") {
|
||||
return NextResponse.json({ success: false, message: 'Gagal mengirim OTP' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Simpan OTP
|
||||
const otpRecord = await prisma.kodeOtp.create({
|
||||
data: { nomor, otp: otpNumber, isActive: true }
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'Kode verifikasi dikirim',
|
||||
kodeId: otpRecord.id,
|
||||
nomor,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Send OTP for Register Error:', error);
|
||||
return NextResponse.json({ success: false, message: 'Gagal mengirim OTP' }, { status: 500 });
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
78
src/app/api/auth/validasi/route.ts
Normal file
78
src/app/api/auth/validasi/route.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import prisma from "@/lib/prisma";
|
||||
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 } = await req.json();
|
||||
const dataUser = await prisma.user.findUnique({
|
||||
where: {
|
||||
nomor: nomor,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
nomor: true,
|
||||
username: true,
|
||||
roleId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (dataUser == null)
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Nomor Belum Terdaftar" },
|
||||
{ status: 200 }
|
||||
);
|
||||
|
||||
const token = await sessionCreate({
|
||||
sessionKey: process.env.BASE_SESSION_KEY!,
|
||||
jwtSecret: process.env.BASE_TOKEN_KEY!,
|
||||
user: dataUser as any,
|
||||
});
|
||||
|
||||
if (!token) {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Gagal membuat session" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
// Buat response dengan token dalam cookie
|
||||
const response = NextResponse.json(
|
||||
{
|
||||
success: true,
|
||||
message: "Berhasil Login",
|
||||
roleId: dataUser.roleId,
|
||||
},
|
||||
{ status: 200 }
|
||||
);
|
||||
|
||||
// Set cookie dengan token yang sudah dipastikan tidak null
|
||||
response.cookies.set(process.env.NEXT_PUBLIC_BASE_SESSION_KEY!, token, {
|
||||
path: "/",
|
||||
sameSite: "lax",
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
maxAge: 30 * 24 * 60 * 60, // 30 hari dalam detik (1 bulan)
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("API Error or Server Error", error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
message: "Maaf, Terjadi Keselahan",
|
||||
reason: (error as Error).message,
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
139
src/app/api/auth/verify-otp/route.ts
Normal file
139
src/app/api/auth/verify-otp/route.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
// 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) {
|
||||
if (req.method !== "POST") {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Method Not Allowed" },
|
||||
{ status: 405 }
|
||||
);
|
||||
}
|
||||
|
||||
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 }
|
||||
);
|
||||
}
|
||||
|
||||
// Pastikan tipe data cocok (OTP di DB = number)
|
||||
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 }
|
||||
);
|
||||
}
|
||||
|
||||
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) {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Gagal membuat session" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
// Nonaktifkan OTP
|
||||
await prisma.kodeOtp.update({
|
||||
where: { id: kodeId },
|
||||
data: { isActive: false },
|
||||
});
|
||||
|
||||
// Set cookie & respons
|
||||
const response = NextResponse.json(
|
||||
{
|
||||
success: true,
|
||||
message: "Berhasil login",
|
||||
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,
|
||||
});
|
||||
|
||||
return response;
|
||||
} 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