Files
MIS-Contro-Tower/app/api/invites/[token]/route.ts
2026-01-03 20:18:39 +00:00

141 lines
3.6 KiB
TypeScript

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import bcrypt from "bcrypt";
import { prisma } from "@/lib/prisma";
import { buildSessionCookieOptions, COOKIE_NAME, SESSION_DAYS } from "@/lib/auth/sessionCookie";
async function loadInvite(token: string) {
return prisma.orgInvite.findFirst({
where: {
token,
revokedAt: null,
acceptedAt: null,
expiresAt: { gt: new Date() },
},
include: {
org: { select: { id: true, name: true, slug: true } },
},
});
}
export async function GET(
_req: NextRequest,
{ params }: { params: Promise<{ token: string }> }
) {
const { token } = await params;
const invite = await loadInvite(token);
if (!invite) {
return NextResponse.json({ ok: false, error: "Invite not found" }, { status: 404 });
}
return NextResponse.json({
ok: true,
invite: {
email: invite.email,
role: invite.role,
org: invite.org,
expiresAt: invite.expiresAt,
},
});
}
export async function POST(
req: NextRequest,
{ params }: { params: Promise<{ token: string }> }
) {
const { token } = await params;
const invite = await loadInvite(token);
if (!invite) {
return NextResponse.json({ ok: false, error: "Invite not found" }, { status: 404 });
}
const body = await req.json().catch(() => ({}));
const name = String(body.name || "").trim();
const password = String(body.password || "");
const existingUser = await prisma.user.findUnique({
where: { email: invite.email },
});
if (!password || password.length < 8) {
return NextResponse.json({ ok: false, error: "Password must be at least 8 characters" }, { status: 400 });
}
if (!existingUser && !name) {
return NextResponse.json({ ok: false, error: "Name is required" }, { status: 400 });
}
let userId = existingUser?.id ?? null;
if (existingUser) {
if (!existingUser.isActive) {
return NextResponse.json({ ok: false, error: "User is inactive" }, { status: 403 });
}
const ok = await bcrypt.compare(password, existingUser.passwordHash);
if (!ok) {
return NextResponse.json({ ok: false, error: "Invalid credentials" }, { status: 401 });
}
userId = existingUser.id;
} else {
const passwordHash = await bcrypt.hash(password, 10);
const created = await prisma.user.create({
data: {
email: invite.email,
name,
passwordHash,
emailVerifiedAt: new Date(),
},
});
userId = created.id;
}
const expiresAt = new Date(Date.now() + SESSION_DAYS * 24 * 60 * 60 * 1000);
const session = await prisma.$transaction(async (tx) => {
if (existingUser && !existingUser.emailVerifiedAt) {
await tx.user.update({
where: { id: existingUser.id },
data: {
emailVerifiedAt: new Date(),
emailVerificationToken: null,
emailVerificationExpiresAt: null,
},
});
}
await tx.orgUser.upsert({
where: {
orgId_userId: {
orgId: invite.orgId,
userId,
},
},
update: {
role: invite.role,
},
create: {
orgId: invite.orgId,
userId,
role: invite.role,
},
});
await tx.orgInvite.update({
where: { id: invite.id },
data: { acceptedAt: new Date() },
});
return tx.session.create({
data: {
userId,
orgId: invite.orgId,
expiresAt,
},
});
});
const res = NextResponse.json({ ok: true, next: "/machines" });
res.cookies.set(COOKIE_NAME, session.id, buildSessionCookieOptions(req));
return res;
}