fix(auth): improve OTP error handling and add health check endpoint
Option 2 - Improve Error Handling: - Track WA success status and error messages in login route - Return debug info (including OTP code) only in non-production - Show descriptive message when WhatsApp fails to send - Better error categorization (HTTP error vs logic error vs connection) Option 3 - Health Check Endpoint: - Create /api/health/otp endpoint for OTP service diagnostics - Support test mode with query params: ?test=true&number=6281234567890 - Check token configuration (configured vs placeholder) - Measure response time and validate service response - Return comprehensive status for debugging OTP issues Usage: - Basic check: GET /api/health/otp - Test send: GET /api/health/otp?test=true&number=6281234567890 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
@@ -29,6 +29,9 @@ export async function POST(req: Request) {
|
|||||||
|
|
||||||
console.log(`🔑 DEBUG OTP [${nomor}]: ${codeOtp}`);
|
console.log(`🔑 DEBUG OTP [${nomor}]: ${codeOtp}`);
|
||||||
|
|
||||||
|
let waSuccess = false;
|
||||||
|
let waErrorMessage = "";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const waResponse = await sendCodeOtp({
|
const waResponse = await sendCodeOtp({
|
||||||
nomor,
|
nomor,
|
||||||
@@ -36,25 +39,29 @@ export async function POST(req: Request) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!waResponse.ok) {
|
if (!waResponse.ok) {
|
||||||
console.error(
|
waErrorMessage = `WA Service HTTP Error: ${waResponse.status} ${waResponse.statusText}`;
|
||||||
`⚠️ WA Service HTTP Error: ${waResponse.status} ${waResponse.statusText}. Continuing since OTP is logged.`
|
console.error(`⚠️ ${waErrorMessage}. Continuing since OTP is logged.`);
|
||||||
);
|
|
||||||
console.log(`💡 Use this OTP to login: ${codeOtp}`);
|
console.log(`💡 Use this OTP to login: ${codeOtp}`);
|
||||||
} else {
|
} else {
|
||||||
const sendWa = await waResponse.json();
|
const sendWa = await waResponse.json();
|
||||||
console.log("📱 WA Response:", sendWa);
|
console.log("📱 WA Response:", sendWa);
|
||||||
|
|
||||||
if (sendWa.status !== "success") {
|
if (sendWa.status !== "success") {
|
||||||
console.error("⚠️ WA Service Logic Error:", sendWa);
|
waErrorMessage = `WA Service Logic Error: ${JSON.stringify(sendWa)}`;
|
||||||
|
console.error("⚠️", waErrorMessage);
|
||||||
|
} else {
|
||||||
|
waSuccess = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (waError: unknown) {
|
} catch (waError: unknown) {
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
waError instanceof Error ? waError.message : String(waError);
|
waError instanceof Error ? waError.message : String(waError);
|
||||||
|
|
||||||
|
waErrorMessage = `WA Connection Exception: ${errorMessage}`;
|
||||||
console.error(
|
console.error(
|
||||||
"⚠️ WA Connection Exception. Continuing since OTP is logged.",
|
"⚠️",
|
||||||
errorMessage
|
waErrorMessage,
|
||||||
|
". Continuing since OTP is logged."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,11 +78,25 @@ export async function POST(req: Request) {
|
|||||||
path: "/",
|
path: "/",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Include debug info for non-production environments when WA fails
|
||||||
|
const isNonProd = process.env.NODE_ENV !== "production";
|
||||||
|
const includeDebug = isNonProd && !waSuccess;
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
message: "Kode verifikasi dikirim",
|
message: waSuccess
|
||||||
|
? "Kode verifikasi dikirim"
|
||||||
|
: "Kode verifikasi dibuat (WhatsApp gagal terkirim)",
|
||||||
kodeId: createOtpId.id,
|
kodeId: createOtpId.id,
|
||||||
isRegistered: true,
|
isRegistered: true,
|
||||||
|
waSuccess,
|
||||||
|
...(includeDebug && {
|
||||||
|
debug: {
|
||||||
|
codeOtp,
|
||||||
|
waErrorMessage,
|
||||||
|
note: "Hanya muncul di development/staging. Hapus di production.",
|
||||||
|
},
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
|
|||||||
160
src/app/api/health/otp/route.ts
Normal file
160
src/app/api/health/otp/route.ts
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
// app/api/health/otp/route.ts
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { randomOTP } from "@/app/api/auth/_lib/randomOTP";
|
||||||
|
import { sendCodeOtp } from "@/app/api/auth/_lib/sendCodeOtp";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Health check endpoint untuk OTP WhatsApp service
|
||||||
|
*
|
||||||
|
* GET /api/health/otp?test=true&number=6281234567890
|
||||||
|
*
|
||||||
|
* Query parameters:
|
||||||
|
* - test: Set "true" untuk kirim test OTP (optional)
|
||||||
|
* - number: Nomor tujuan test (required jika test=true)
|
||||||
|
*
|
||||||
|
* Response:
|
||||||
|
* - Status OTP service (available/unavailable)
|
||||||
|
* - Token configuration status
|
||||||
|
* - Test message results (jika test=true)
|
||||||
|
*/
|
||||||
|
export async function GET(req: Request) {
|
||||||
|
try {
|
||||||
|
const { searchParams } = new URL(req.url);
|
||||||
|
const isTest = searchParams.get("test") === "true";
|
||||||
|
const testNumber = searchParams.get("number");
|
||||||
|
|
||||||
|
// Check token configuration
|
||||||
|
const tokenConfigured = Boolean(process.env.WA_SERVER_TOKEN);
|
||||||
|
const isPlaceholder =
|
||||||
|
process.env.WA_SERVER_TOKEN === "your_whatsapp_server_token";
|
||||||
|
|
||||||
|
const healthCheck = {
|
||||||
|
service: "OTP WhatsApp",
|
||||||
|
status: "ok" as "ok" | "degraded" | "unavailable",
|
||||||
|
token: {
|
||||||
|
configured: tokenConfigured,
|
||||||
|
isPlaceholder,
|
||||||
|
},
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// If test mode, actually try to send OTP
|
||||||
|
if (isTest) {
|
||||||
|
if (!testNumber) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
message: "Nomor diperlukan untuk test mode",
|
||||||
|
error: "Tambahkan query parameter: ?number=6281234567890",
|
||||||
|
},
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const testOtp = randomOTP();
|
||||||
|
|
||||||
|
console.log(`🧪 OTP HEALTH CHECK - Testing with number: ${testNumber}`);
|
||||||
|
console.log(`🧪 Test OTP code: ${testOtp}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const startTime = Date.now();
|
||||||
|
const waResponse = await sendCodeOtp({
|
||||||
|
nomor: testNumber,
|
||||||
|
codeOtp: testOtp,
|
||||||
|
});
|
||||||
|
const responseTime = Date.now() - startTime;
|
||||||
|
|
||||||
|
if (!waResponse.ok) {
|
||||||
|
healthCheck.status = "unavailable";
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "Gagal mengirim OTP test",
|
||||||
|
health: healthCheck,
|
||||||
|
test: {
|
||||||
|
number: testNumber,
|
||||||
|
httpStatus: waResponse.status,
|
||||||
|
statusText: waResponse.statusText,
|
||||||
|
responseTime: `${responseTime}ms`,
|
||||||
|
note: "Cek apakah WA_SERVER_TOKEN sudah benar di Portainer",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseData = await waResponse.json();
|
||||||
|
|
||||||
|
if (responseData.status !== "success") {
|
||||||
|
healthCheck.status = "degraded";
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "OTP test terkirim tapi service merespon error",
|
||||||
|
health: healthCheck,
|
||||||
|
test: {
|
||||||
|
number: testNumber,
|
||||||
|
httpStatus: waResponse.status,
|
||||||
|
responseTime: `${responseTime}ms`,
|
||||||
|
serviceResponse: responseData,
|
||||||
|
note: "Cek apakah WA_SERVER_TOKEN sudah benar di Portainer",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success
|
||||||
|
healthCheck.status = "ok";
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "OTP test berhasil dikirim",
|
||||||
|
health: healthCheck,
|
||||||
|
test: {
|
||||||
|
number: testNumber,
|
||||||
|
httpStatus: waResponse.status,
|
||||||
|
responseTime: `${responseTime}ms`,
|
||||||
|
serviceResponse: responseData,
|
||||||
|
otpCode: testOtp,
|
||||||
|
note: "Gunakan kode ini untuk verifikasi test",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
healthCheck.status = "unavailable";
|
||||||
|
const errorMessage =
|
||||||
|
error instanceof Error ? error.message : String(error);
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "Exception saat mengirim OTP test",
|
||||||
|
health: healthCheck,
|
||||||
|
test: {
|
||||||
|
number: testNumber,
|
||||||
|
error: errorMessage,
|
||||||
|
note: "Cek network connectivity ke otp.wibudev.com",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic health check without sending OTP
|
||||||
|
if (!tokenConfigured || isPlaceholder) {
|
||||||
|
healthCheck.status = "unavailable";
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message:
|
||||||
|
healthCheck.status === "ok"
|
||||||
|
? "OTP service tersedia"
|
||||||
|
: "OTP service tidak tersedia - cek WA_SERVER_TOKEN",
|
||||||
|
health: healthCheck,
|
||||||
|
usage: "Tambahkan ?test=true&number=6281234567890 untuk test kirim OTP",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ OTP Health Check Error:", error);
|
||||||
|
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
message: "Health check gagal",
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
},
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user