feat: check custom domain before redirection and save

This commit is contained in:
DIYgod 2023-03-22 00:43:55 +00:00
parent a311911e09
commit 23494d1941
No known key found for this signature in database
6 changed files with 172 additions and 121 deletions

View File

@ -1,112 +1,12 @@
[
"xlog",
"diygod",
"rss3",
"crossbell-blog",
"hey",
"song",
"atlas-thinking",
"joshua",
"jeff",
"crossbell-io-updates",
"kanikig",
"isheep",
"henryqw",
"hominsu",
"stars",
"somehacker",
"novonine",
"qfdk",
"fblauer",
"anxiete",
"kumasanki",
"jingfelix",
"tcj",
"atlas",
"meow-7218",
"erection",
"jowno",
"walt",
"adazz",
"ark",
"hans",
"isolcat",
"henryq",
"duoyu",
"candinya",
"shang",
"toast",
"pseudoyu",
"caos",
"nanami",
"xbox",
"vidorra",
"ivyheretochill",
"wiming",
"zsakvo",
"wuhang2003",
"bladedragon",
"qiuxuanluo",
"saltad",
"arthurwu",
"laoyan",
"baxx",
"kmkmkm",
"fuyuan",
"ming5ming",
"jameshih",
"cryptogirls",
"daidr",
"mekal",
"tommy-1329",
"jackyoung-5042",
"moojok",
"maji-may-7766",
"bocaibocaieth-1274",
"soptq-6165",
"yeungyeah",
"xnor",
"chenyyds",
"0xcavin",
"shuxinfeng",
"tea-24",
"revery",
"hsinyau",
"rootless",
"haydenull",
"walk2future",
"liuyejinghong",
"sion",
"magren",
"lolik",
"yuhangch",
"ssshooter",
"baiyi-5736",
"kendrickzou-8",
"nafidurmus",
"dagora",
"aiirobyte",
"onweb3",
"anghunk",
"agrislis",
"jigsaw",
"sakura-6601",
"bai-2800",
"rustynail",
"ximu-3371",
"xn--37q595dihas5a-4315",
"batgirl",
"exodus-exodus",
"xn--psu263e-5088",
"cryptonerdcn",
"fzdwx-2094",
"ovler",
"stz",
"monkey-1593",
"hoodrh",
"outtime",
"bs10081-7290",
"huazidev",
"character-4568",
"lyunvy"
10, 33446, 572, 45, 153, 12, 506, 30, 595, 45899, 22033, 51494, 51124, 51232,
40669, 45828, 26375, 49041, 48952, 51408, 51470, 51072, 496, 38034, 47359,
33335, 50701, 50161, 470, 50312, 21052, 32022, 274, 77, 50168, 51145, 47328,
630, 383, 51383, 723, 51172, 4583, 50472, 32179, 51316, 33210, 45089, 51094,
31132, 50140, 37137, 42839, 50114, 51006, 51191, 50843, 46648, 33398, 887,
51126, 51197, 32085, 1407, 51154, 51104, 40675, 4381, 43051, 50622, 51056,
33483, 19, 50877, 33420, 50094, 49213, 33276, 50351, 42993, 50216, 50132,
45763, 50143, 43754, 32168, 500, 47399, 33538, 48951, 33471, 33396, 37223,
33458, 39133, 20004, 43625, 42787, 43258, 33462, 47109, 46939, 45701, 33233,
37346, 40671, 33870, 33421
]

View File

@ -138,8 +138,8 @@ export const getSites = async (input: string[]) => {
const result = await client
.query(
`
query getCharacters($identities: [String!], $limit: Int) {
characters( where: { handle: { in: $identities } }, orderBy: [{ updatedAt: desc }], take: $limit ) {
query getCharacters($identities: [Int!], $limit: Int) {
characters( where: { characterId: { in: $identities } }, orderBy: [{ updatedAt: desc }], take: $limit ) {
handle
updatedAt
characterId
@ -719,3 +719,31 @@ export async function getMiraBalance(address: string, contract: Contract) {
return result
}
export async function checkDomainServer(domain: string, handle: string) {
const dns = await (
await fetch(
`https://cloudflare-dns.com/dns-query?name=_xlog-challenge.${domain}&type=TXT`,
{
headers: {
accept: "application/dns-json",
},
},
)
).json()
const tenant = dns?.Answer?.[0]?.data.replace(/^"|"$/g, "")
if (!tenant || tenant !== handle) {
return false
} else {
return true
}
}
export async function checkDomain(domain: string, handle: string) {
const check = await (
await fetch(`/api/check-domain?domain=${domain}&handle=${handle}`)
).json()
return check.data
}

View File

@ -0,0 +1,18 @@
import { NextApiRequest, NextApiResponse } from "next"
import { checkDomainServer } from "~/models/site.model"
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
const { handle, domain } = req.query
if (!handle || !domain) {
res.status(400).json({ error: "Missing characterId or domain" })
return
}
res.status(200).json({
data: await checkDomainServer(domain as string, handle as string),
})
}

View File

@ -1,6 +1,7 @@
import { NextApiRequest, NextApiResponse } from "next"
import { OUR_DOMAIN } from "~/lib/env"
import { cacheGet } from "~/lib/redis.server"
import { checkDomainServer } from "~/models/site.model"
export default async function handler(
req: NextApiRequest,
@ -31,7 +32,10 @@ export default async function handler(
char?.metadata?.content?.attributes?.find(
(a: any) => a.trait_type === "xlog_custom_domain",
)?.value || ""
if (customDomain) {
if (
customDomain &&
(await checkDomainServer(customDomain, subdomain))
) {
return {
redirect: /^https?:\/\//.test(customDomain)
? customDomain

View File

@ -0,0 +1,48 @@
import { NextApiRequest, NextApiResponse } from "next"
import { getSiteLink } from "~/lib/helpers"
import { getDefaultSlug } from "~/lib/helpers"
import { checkDomainServer } from "~/models/site.model"
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
const { characterId, noteId } = req.query
if (!characterId) {
res.status(400).json({ error: "Missing characterId" })
return
}
const character = await (
await fetch(`https://indexer.crossbell.io/v1/characters/${characterId}`)
).json()
let domain = character?.metadata?.content?.attributes?.find(
(a: any) => a.trait_type === "xlog_custom_domain",
)?.value
if (domain && !(await checkDomainServer(domain, character.handle))) {
domain = undefined
}
let link = getSiteLink({
domain,
subdomain: character?.handle,
})
if (noteId) {
const note = await (
await fetch(
`https://indexer.crossbell.io/v1/characters/${characterId}/notes/${noteId}`,
)
).json()
const slug =
note?.metadata?.content?.attributes?.find(
(a: any) => a.trait_type === "xlog_slug",
)?.value ||
getDefaultSlug(note?.metadata?.content?.title, `${characterId}-${noteId}`)
link += `/${slug}`
}
res.redirect(link)
}

View File

@ -1,5 +1,5 @@
import { useRouter } from "next/router"
import { useEffect, useState } from "react"
import { useCallback, useEffect, useState } from "react"
import { useForm } from "react-hook-form"
import toast from "react-hot-toast"
import { DashboardLayout } from "~/components/dashboard/DashboardLayout"
@ -17,6 +17,7 @@ import { useTranslation } from "next-i18next"
import { getServerSideProps as getLayoutServerSideProps } from "~/components/dashboard/DashboardLayout.server"
import { GetServerSideProps } from "next"
import { serverSidePropsHandler } from "~/lib/server-side-props"
import { checkDomain } from "~/models/site.model"
export const getServerSideProps: GetServerSideProps = serverSidePropsHandler(
async (ctx) => {
@ -63,7 +64,10 @@ export default function SettingsDomainsPage() {
const [customDomain, setCustomDomain] = useState("")
form.register("custom_domain", {
onChange: (e) => setCustomDomain(e.target.value),
onChange: (e) => {
setCustomDomain(e.target.value)
toCheckDomain(e.target.value)
},
})
const customSubdomain = customDomain?.split(".").slice(0, -2).join(".") || ""
@ -73,7 +77,9 @@ export default function SettingsDomainsPage() {
if (updateSite.data?.code === 0) {
toast.success("Saved!")
router.replace(
`/dashboard/${updateSite.variables?.subdomain}/settings/domains`,
`/dashboard/${
updateSite.variables?.subdomain || updateSite.variables?.site
}/settings/domains`,
)
} else {
toast.error("Failed to update site" + ": " + updateSite.data.message)
@ -83,6 +89,30 @@ export default function SettingsDomainsPage() {
}
}, [updateSite.isSuccess])
const [domainCheckResult, setDomainCheckResult] = useState<{
isLoading: boolean
data?: boolean
}>({
isLoading: false,
data: true,
})
const toCheckDomain = useCallback(
(custom: string) => {
if (custom) {
setDomainCheckResult({
isLoading: true,
})
checkDomain(custom, subdomain).then((r) =>
setDomainCheckResult({
isLoading: false,
data: r,
}),
)
}
},
[subdomain],
)
const [hasSet, setHasSet] = useState(false)
useEffect(() => {
if (site.isSuccess && site.data && !hasSet) {
@ -90,8 +120,9 @@ export default function SettingsDomainsPage() {
form.setValue("subdomain", site.data.username || "")
form.setValue("custom_domain", site.data.custom_domain || "")
setCustomDomain(site.data.custom_domain || "")
toCheckDomain(site.data.custom_domain || "")
}
}, [form, site.data, site.isSuccess, customDomain, hasSet])
}, [form, site.data, site.isSuccess, customDomain, hasSet, toCheckDomain])
return (
<SettingsLayout title={"Site Settings"} type="site">
@ -143,14 +174,14 @@ export default function SettingsDomainsPage() {
{...form.register("custom_domain")}
/>
{customDomain && (
<div className="mt-2 text-xs">
<p className="mb-2">
<div className="mt-2 text-xs space-y-2">
<p>
{t(
"Set the following record on your DNS provider to active your custom domain",
)}
:
</p>
<table className="">
<table>
<tbody>
<tr className="border-b">
<th className="text-center p-3">Type</th>
@ -173,11 +204,33 @@ export default function SettingsDomainsPage() {
</tr>
</tbody>
</table>
<div className="text-sm">
{domainCheckResult.isLoading ? (
<span>Checking...</span>
) : domainCheckResult.data ? (
<span className="text-green-600">DNS check Passed.</span>
) : (
<span>
<span className="text-red-600">DNS check failed.</span>
<Button
className="ml-4 font-media"
variant="secondary"
onClick={() => toCheckDomain(customDomain)}
>
Recheck
</Button>
</span>
)}
</div>
</div>
)}
</div>
<div className="mt-5">
<Button type="submit" isLoading={updateSite.isLoading}>
<Button
type="submit"
isLoading={updateSite.isLoading || domainCheckResult.isLoading}
isDisabled={domainCheckResult?.data === false}
>
{t("Save")}
</Button>
</div>