Files
football-next/app/api/auth/mobile/verify/route.ts
2026-05-06 16:28:13 +03:30

96 lines
2.7 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import bcrypt from "bcryptjs";
import crypto from "crypto";
import { db } from "@/lib/db";
const IRAN_MOBILE_REGEX = /^(\+98|98|0)?9\d{9}$/;
const SESSION_MAX_AGE_DAYS = 30;
function normalizeIranMobile(phone: string) {
const digits = phone.replace(/[^\d+]/g, "");
if (!IRAN_MOBILE_REGEX.test(digits)) return null;
if (digits.startsWith("+98")) return `0${digits.slice(3)}`;
if (digits.startsWith("98")) return `0${digits.slice(2)}`;
if (digits.startsWith("9")) return `0${digits}`;
return digits;
}
function buildPhoneEmail(phone: string) {
return `${phone}@mobile.local`;
}
export async function POST(req: NextRequest) {
const { phone, code, name } = await req.json().catch(() => ({}));
const normalizedPhone = typeof phone === "string" ? normalizeIranMobile(phone) : null;
const normalizedCode = typeof code === "string" ? code.trim() : "";
if (!normalizedPhone || !/^\d{6}$/.test(normalizedCode)) {
return NextResponse.json({ error: "شماره موبایل یا کد ورود معتبر نیست" }, { status: 400 });
}
const otp = await db.loginOtp.findFirst({
where: {
phone: normalizedPhone,
consumedAt: null,
expiresAt: { gt: new Date() },
},
orderBy: { createdAt: "desc" },
});
if (!otp || otp.attempts >= 5) {
return NextResponse.json({ error: "کد ورود نامعتبر یا منقضی شده است" }, { status: 400 });
}
const isValidCode = await bcrypt.compare(normalizedCode, otp.codeHash);
if (!isValidCode) {
await db.loginOtp.update({
where: { id: otp.id },
data: { attempts: { increment: 1 } },
});
return NextResponse.json({ error: "کد ورود نامعتبر است" }, { status: 400 });
}
const user = await db.user.upsert({
where: { phone: normalizedPhone },
update: {
name: typeof name === "string" && name.trim() ? name.trim() : undefined,
},
create: {
phone: normalizedPhone,
email: buildPhoneEmail(normalizedPhone),
name: typeof name === "string" && name.trim() ? name.trim() : null,
password: await bcrypt.hash(crypto.randomBytes(24).toString("hex"), 10),
},
});
await db.loginOtp.update({
where: { id: otp.id },
data: { consumedAt: new Date() },
});
const token = crypto.randomBytes(32).toString("hex");
const expires = new Date(Date.now() + SESSION_MAX_AGE_DAYS * 24 * 60 * 60 * 1000);
await db.session.create({
data: {
sessionToken: token,
userId: user.id,
expires,
},
});
return NextResponse.json({
token,
tokenType: "Bearer",
expiresAt: expires.toISOString(),
user: {
id: user.id,
name: user.name,
phone: user.phone,
role: user.role,
},
});
}