This commit is contained in:
2025-09-04 11:46:08 +08:00
parent 2adf60f9eb
commit 8817b937b1
29 changed files with 593 additions and 374 deletions

View File

@@ -0,0 +1,50 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { jwtVerify } from "jose";
export async function decrypt({
token,
encodedKey,
}: {
token: string;
encodedKey: string;
}): Promise<Record<string, any> | null> {
if (!token || !encodedKey) {
console.error("Missing required parameters:", {
hasToken: !!token,
hasEncodedKey: !!encodedKey,
});
return null;
}
try {
const enc = new TextEncoder().encode(encodedKey);
const { payload } = await jwtVerify(token, enc, {
algorithms: ["HS256"],
});
if (!payload || !payload.user) {
console.error("Invalid payload structure:", {
hasPayload: !!payload,
hasUser: payload ? !!payload.user : false,
});
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>;
} 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),
});
return null;
}
}

View File

@@ -0,0 +1,26 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { SignJWT } from "jose";
export async function encrypt({
user,
exp = "7 year",
encodedKey,
}: {
user: Record<string, any>;
exp?: string;
encodedKey: string;
}): Promise<string | null> {
try {
const enc = new TextEncoder().encode(encodedKey);
return new SignJWT({ user })
.setProtectedHeader({ alg: "HS256" })
.setIssuedAt()
.setExpirationTime(exp)
.sign(enc);
} catch (error) {
console.error("Gagal mengenkripsi", error);
return null;
}
}
// wibu:0.2.82

View File

@@ -0,0 +1,13 @@
"use server";
import prisma from "@/lib/prisma";
export async function auth_getCodeOtpByNumber({kodeId}: {kodeId: string}) {
const data = await prisma.kodeOtp.findFirst({
where: {
id: kodeId,
},
});
return data;
}

View File

@@ -0,0 +1,4 @@
export function randomOTP() {
const random = Math.floor(Math.random() * (9000 - 1000 )) + 1000
return random;
}

View File

@@ -0,0 +1,36 @@
/* 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,
user,
}: {
sessionKey: string;
exp?: string;
encodedKey: string;
user: Record<string, unknown>;
}) {
const token = await encrypt({
exp,
encodedKey,
user,
});
const cookie: any = {
key: sessionKey,
value: token,
options: {
httpOnly: true,
sameSite: "lax",
path: "/",
},
};
(await cookies()).set(cookie.key, cookie.value, { ...cookie.options });
return token;
}
// wibu:0.2.82

View File

@@ -1,81 +0,0 @@
import { Context } from "elysia";
import prisma from "@/lib/prisma";
import bcrypt from "bcryptjs";
import jwt from "jsonwebtoken";
// ENV atau secret key untuk token
const JWT_SECRET = process.env.JWT_SECRET || "super-secret-key"; // ganti di env production
type LoginForm = {
email: string;
password: string;
};
export default async function userLogin(context: Context) {
const body = (await context.body) as LoginForm;
try {
// 1. Cari user berdasarkan email
const user = await prisma.user.findUnique({
where: { email: body.email },
include: { role: true }, // include role untuk otorisasi
});
// 2. Jika tidak ada user
if (!user) {
return {
success: false,
message: "Email tidak ditemukan",
};
}
// 3. Cek apakah user aktif
if (!user.isActive) {
return {
success: false,
message: "Akun tidak aktif",
};
}
// 4. Verifikasi password
const isMatch = await bcrypt.compare(body.password, user.password);
if (!isMatch) {
return {
success: false,
message: "Password salah",
};
}
// 5. Buat JWT token
const token = jwt.sign(
{
id: user.id,
email: user.email,
role: user.role.name,
},
JWT_SECRET,
{ expiresIn: "7d" } // expire 7 hari
);
// 6. Kirim response
return {
success: true,
message: "Login berhasil",
data: {
user: {
id: user.id,
nama: user.nama,
email: user.email,
role: user.role.name,
},
token,
},
};
} catch (error) {
console.error("Login error:", error);
return {
success: false,
message: "Terjadi kesalahan saat login",
};
}
}

View File

@@ -0,0 +1,63 @@
import prisma from "@/lib/prisma";
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 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 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 mengirim kode OTP" },
{ status: 400 }
);
return NextResponse.json(
{
success: true,
message: "Kode verifikasi terkirim",
kodeId: createOtpId.id,
},
{ status: 200 }
);
} catch (error) {
console.log("Error Login", error);
return NextResponse.json(
{ success: false, message: "Terjadi masalah saat login" , reason: error as Error },
{ status: 500 }
);
} finally {
await prisma.$disconnect();
}
}

View File

@@ -0,0 +1,38 @@
// import { cookies } from "next/headers";
// import { NextResponse } from "next/server";
// export const dynamic = "force-dynamic";
// export async function GET() {
// const sessionKey = process.env.NEXT_PUBLIC_BASE_SESSION_KEY!; // Gunakan environment variable yang tidak diekspos ke client-side
// if (!sessionKey) {
// return NextResponse.json(
// { success: false, message: "Session key tidak ditemukan" },
// { status: 500 }
// );
// }
// const cookieStore = cookies();
// const sessionCookie = cookieStore.get(sessionKey);
// if (!sessionCookie) {
// return NextResponse.json(
// { success: false, message: "Session tidak ditemukan" },
// { status: 400 }
// );
// }
// try {
// cookieStore.delete(sessionKey);
// return NextResponse.json(
// { success: true, message: "Logout berhasil" },
// { status: 200 }
// );
// } catch (error) {
// console.error("Gagal menghapus cookie:", error);
// return NextResponse.json(
// { success: false, message: "Gagal melakukan logout" },
// { status: 500 }
// );
// }
// }

View File

@@ -1,88 +0,0 @@
import prisma from "@/lib/prisma";
import bcrypt from "bcryptjs";
import { Context } from "elysia";
interface RegisterBody {
nama: string;
email: string;
password: string;
}
export default async function userRegister(context: Context) {
try {
const body = (await context.body) as RegisterBody;
// Validasi input
if (!body.nama || !body.email || !body.password) {
context.set.status = 400;
return {
success: false,
message: "Semua field harus diisi",
data: null
};
}
// Cek email sudah terdaftar
const existingUser = await prisma.user.findUnique({
where: { email: body.email },
});
if (existingUser) {
context.set.status = 400;
return {
success: false,
message: "Email sudah terdaftar",
data: null
};
}
// Dapatkan role warga
const role = await prisma.role.findFirst({
where: { name: "warga" }
});
if (!role) {
context.set.status = 500;
return {
success: false,
message: "Role warga tidak ditemukan",
data: null
};
}
// Hash password
const hashedPassword = await bcrypt.hash(body.password, 10);
// Buat user baru
const user = await prisma.user.create({
data: {
nama: body.nama,
email: body.email,
password: hashedPassword,
roleId: role.id,
},
select: {
id: true,
nama: true,
email: true,
roleId: true,
createdAt: true,
updatedAt: true
}
});
return {
success: true,
message: "Berhasil mendaftar",
data: user,
};
} catch (error) {
console.error("Registration error:", error);
context.set.status = 500;
return {
success: false,
message: "Terjadi kesalahan saat mendaftar",
data: null
};
}
}

View File

@@ -0,0 +1,62 @@
import prisma from "@/lib/prisma";
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 { data } = 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)
return NextResponse.json(
{ success: false, message: "Gagal Registrasi" },
{ status: 500 }
);
return NextResponse.json(
{
success: true,
message: "Registrasi Berhasil, Anda Sedang Login",
// data: createUser,
},
{ status: 201 }
);
} catch (error) {
console.error("Error registrasi:", error);
return NextResponse.json(
{
success: false,
message: "Maaf, Terjadi Keselahan",
reason: (error as Error).message,
},
{ status: 500 }
);
} finally {
await prisma.$disconnect();
}
}

View 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();
}
}

View 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.NEXT_PUBLIC_BASE_SESSION_KEY!,
encodedKey: process.env.NEXT_PUBLIC_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();
}
}