mirror of
https://github.com/hanzo-apps/dataroom.git
synced 2026-11-02 22:04:20 +00:00
108 files: README, marketing copy, page titles, meta tags, footer text, email templates, .env.example, UI strings. URLs swapped from papermark.com/papermark.io to dataroom.hanzo.ai. Internal identifiers left intact per repo policy: localStorage keys (papermark.email/papermark.name/last_papermark_login — changing them logs users out), type field names (papermarkUserId), internal helper symbols (PAPERMARK_HEADERS, PapermarkSparkle component, isPapermarkUrl helper), internal-only filenames. LLM.md retains 'Upstream: Papermark' attribution line. Per-locale .po translations regenerate from source on the next lingui-extract CI run. DB migrations untouched (append-only history; rewriting them breaks installed schemas). LICENSE stays AGPL-3.0.
142 lines
3.5 KiB
TypeScript
142 lines
3.5 KiB
TypeScript
import { NextApiRequest, NextApiResponse } from "next";
|
||
|
||
import dns from "dns/promises";
|
||
import { getServerSession } from "next-auth/next";
|
||
|
||
import { validDomainRegex } from "@/lib/domains";
|
||
import { errorhandler } from "@/lib/errorHandler";
|
||
import prisma from "@/lib/prisma";
|
||
import { CustomUser } from "@/lib/types";
|
||
|
||
import { authOptions } from "../../../../auth/[...nextauth]";
|
||
|
||
type DomainValidationStatus = "invalid" | "has site" | "available";
|
||
|
||
const sanitizeDomain = (value: string) =>
|
||
value
|
||
.trim()
|
||
.toLowerCase()
|
||
.replace(/^(?:https?:\/\/)?(?:www\.)?/i, "")
|
||
.split("/")[0];
|
||
|
||
export default async function handle(
|
||
req: NextApiRequest,
|
||
res: NextApiResponse,
|
||
) {
|
||
if (req.method !== "GET") {
|
||
res.setHeader("Allow", ["GET"]);
|
||
return res.status(405).end(`Method ${req.method} Not Allowed`);
|
||
}
|
||
|
||
const session = await getServerSession(req, res, authOptions);
|
||
if (!session) {
|
||
return res.status(401).end("Unauthorized");
|
||
}
|
||
|
||
const { teamId } = req.query as { teamId: string };
|
||
const rawDomain = Array.isArray(req.query.domain)
|
||
? req.query.domain[0]
|
||
: req.query.domain;
|
||
|
||
if (!teamId || !rawDomain) {
|
||
return res.status(400).json({ status: "invalid" });
|
||
}
|
||
|
||
const userId = (session.user as CustomUser).id;
|
||
|
||
try {
|
||
const hasTeamAccess = await prisma.userTeam.findUnique({
|
||
where: {
|
||
userId_teamId: {
|
||
userId,
|
||
teamId,
|
||
},
|
||
},
|
||
});
|
||
if (!hasTeamAccess) {
|
||
return res.status(401).end("Unauthorized");
|
||
}
|
||
|
||
const sanitizedDomain = sanitizeDomain(rawDomain);
|
||
|
||
if (
|
||
!sanitizedDomain ||
|
||
!validDomainRegex.test(sanitizedDomain) ||
|
||
sanitizedDomain.includes("hanzo")
|
||
) {
|
||
return res.status(200).json({
|
||
status: "invalid" as DomainValidationStatus,
|
||
});
|
||
}
|
||
|
||
const existingDomain = await prisma.domain.findFirst({
|
||
where: {
|
||
slug: sanitizedDomain,
|
||
},
|
||
select: {
|
||
id: true,
|
||
},
|
||
});
|
||
|
||
if (existingDomain) {
|
||
return res.status(200).json({
|
||
status: "has site" as DomainValidationStatus,
|
||
});
|
||
}
|
||
|
||
const hasSite = await hasSiteConfigured(sanitizedDomain);
|
||
if (hasSite) {
|
||
return res.status(200).json({
|
||
status: "has site" as DomainValidationStatus,
|
||
});
|
||
}
|
||
|
||
return res.status(200).json({
|
||
status: "available" as DomainValidationStatus,
|
||
});
|
||
} catch (error) {
|
||
errorhandler(error, res);
|
||
}
|
||
}
|
||
|
||
async function hasSiteConfigured(domain: string): Promise<boolean> {
|
||
try {
|
||
const urls = [`https://${domain}`, `http://${domain}`];
|
||
|
||
for (const url of urls) {
|
||
try {
|
||
const controller = new AbortController();
|
||
const timeoutId = setTimeout(() => controller.abort(), 3000);
|
||
const response = await fetch(url, {
|
||
method: "HEAD",
|
||
signal: controller.signal,
|
||
redirect: "manual",
|
||
});
|
||
clearTimeout(timeoutId);
|
||
// Any response (including redirects) means a site is configured
|
||
if (response.status < 500) return true;
|
||
} catch {
|
||
// Timeout, network error, or certificate error – try next URL
|
||
continue;
|
||
}
|
||
}
|
||
|
||
const dnsPromise = dns.resolve(domain);
|
||
const timeoutPromise = new Promise((_, reject) =>
|
||
setTimeout(() => reject(new Error("DNS Timeout")), 3000),
|
||
);
|
||
|
||
try {
|
||
const records = (await Promise.race([
|
||
dnsPromise,
|
||
timeoutPromise,
|
||
])) as string[];
|
||
return records.length > 0;
|
||
} catch (error) {
|
||
return false;
|
||
}
|
||
} catch (error) {
|
||
return false;
|
||
}
|
||
}
|