Skip to content

Commit

Permalink
offload link checks to qstash, fix /exists 404
Browse files Browse the repository at this point in the history
  • Loading branch information
steven-tey committed Dec 25, 2023
1 parent c66087d commit 7776e6b
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 57 deletions.
42 changes: 42 additions & 0 deletions apps/web/app/api/cron/verify/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { receiver } from "@/lib/cron";
import prisma from "@/lib/prisma";
import { GOOGLE_FAVICON_URL, getApexDomain, log } from "@dub/utils";

export async function POST(req: Request) {
const body = await req.json();
if (process.env.VERCEL === "1") {
const isValid = await receiver.verify({
signature: req.headers.get("Upstash-Signature") || "",
body: JSON.stringify(body),
});
if (!isValid) {
return new Response("Unauthorized", { status: 401 });
}
}

const { linkId } = body;

const link = await prisma.link.findUnique({
where: {
id: linkId,
},
});

if (!link) {
return new Response("Link not found", { status: 200 });
}

const invalidFavicon = await fetch(
`${GOOGLE_FAVICON_URL}${getApexDomain(link.url)}`,
).then((res) => !res.ok);

if (invalidFavicon) {
await log({
message: `Suspicious link detected: ${link.domain}/${link.key}${link.url}`,
type: "links",
mention: true,
});
}

return new Response("OK", { status: 200 });
}
38 changes: 20 additions & 18 deletions apps/web/app/api/links/[linkId]/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { withAuth } from "@/lib/auth";
import { deleteLink, editLink, processLink } from "@/lib/api/links";
import { NextResponse } from "next/server";
import { GOOGLE_FAVICON_URL, getApexDomain, log } from "@dub/utils";
import {
APP_DOMAIN_WITH_NGROK,
GOOGLE_FAVICON_URL,
getApexDomain,
log,
} from "@dub/utils";
import { qstash } from "@/lib/cron";

// GET /api/links/[linkId] – get a link
export const GET = withAuth(async ({ headers, link }) => {
Expand Down Expand Up @@ -52,36 +58,32 @@ export const PUT = withAuth(
return new Response(error, { status, headers });
}

const [response, invalidFavicon] = await Promise.allSettled([
const [response, _] = await Promise.allSettled([
editLink({
// link is guaranteed to exist because if not we will return 404
domain: link!.domain,
key: link!.key,
updatedLink: processedLink,
}),
...(!project && processedLink.url
...(link &&
processedLink.domain === "dub.sh" &&
processedLink.url !== link.url
? [
fetch(
`${GOOGLE_FAVICON_URL}${getApexDomain(processedLink.url)}`,
).then((res) => !res.ok),
qstash.publishJSON({
url: `${APP_DOMAIN_WITH_NGROK}/api/cron/verify`,
body: {
linkId: link.id,
},
}),
]
: []),
// @ts-ignore
]).then((results) => results.map((result) => result.value));

if (response === null) {
return new Response("Key already exists.", { status: 409, headers });
}

if (!project && invalidFavicon) {
await log({
message: `*${session.user.email}* edited a link (${
processedLink.domain
}/${processedLink.key}) to the ${processedLink.url} ${
invalidFavicon ? " but it has an invalid favicon :thinking_face:" : ""
}`,
type: "links",
mention: true,
return new Response("Duplicate key: This short link already exists.", {
status: 409,
headers,
});
}

Expand Down
23 changes: 13 additions & 10 deletions apps/web/app/api/links/exists/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ import { checkIfKeyExists } from "@/lib/api/links";
import { NextResponse } from "next/server";

// GET /api//links/exists – check if a link exists
export const GET = withAuth(async ({ headers, searchParams }) => {
const { domain, key } = searchParams;
if (!domain || !key) {
return new Response("Missing domain or key", { status: 400 });
}
const response = await checkIfKeyExists(domain, key);
return NextResponse.json(response, {
headers,
});
});
export const GET = withAuth(
async ({ headers, searchParams }) => {
const { domain, key } = searchParams;
if (!domain || !key) {
return new Response("Missing domain or key", { status: 400 });
}
const response = await checkIfKeyExists(domain, key);
return NextResponse.json(response, {
headers,
});
},
{ skipLinkChecks: true },
);
31 changes: 10 additions & 21 deletions apps/web/app/api/links/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { addLink, getLinksForProject, processLink } from "@/lib/api/links";
import { withAuth } from "@/lib/auth";
import { qstash } from "@/lib/cron";
import { ratelimit } from "@/lib/upstash";
import {
APP_DOMAIN_WITH_NGROK,
DUB_PROJECT_ID,
GOOGLE_FAVICON_URL,
LOCALHOST_IP,
Expand Down Expand Up @@ -71,34 +73,21 @@ export const POST = withAuth(
}
}

const [response, invalidFavicon] = await Promise.allSettled([
addLink(link),
...(!project
? [
fetch(`${GOOGLE_FAVICON_URL}${getApexDomain(link.url)}`).then(
(res) => !res.ok,
),
]
: []),
// @ts-ignore
]).then((results) => results.map((result) => result.value));
const response = await addLink(link);

if (response === null) {
return new Response("Duplicate key: this short link already exists.", {
return new Response("Duplicate key: This short link already exists.", {
status: 409,
headers,
});
}

if (!project && invalidFavicon) {
await log({
message: `*${
session?.user?.email || "Anonymous User"
}* created a new link (${link.domain}/${link.key}) for ${link.url} ${
invalidFavicon ? " but it has an invalid favicon :thinking_face:" : ""
}`,
type: "links",
mention: true,
if (link.domain === "dub.sh") {
await qstash.publishJSON({
url: `${APP_DOMAIN_WITH_NGROK}/api/cron/verify`,
body: {
linkId: response.id,
},
});
}

Expand Down
4 changes: 3 additions & 1 deletion apps/web/lib/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,14 @@ export const withAuth =
needNotExceededUsage, // if the action needs the user to not have exceeded their usage
allowAnonymous, // special case for /api/links (POST /api/links) – allow no session
allowSelf, // special case for removing yourself from a project
skipLinkChecks, // special case for /api/links/exists – skip link checks
}: {
requiredPlan?: Array<PlanProps>;
requiredRole?: Array<"owner" | "member">;
needNotExceededUsage?: boolean;
allowAnonymous?: boolean;
allowSelf?: boolean;
skipLinkChecks?: boolean;
} = {},
) =>
async (
Expand Down Expand Up @@ -337,7 +339,7 @@ export const withAuth =
}

// link checks (if linkId or domain and key are provided)
if (linkId || (domain && key && key !== "_root")) {
if ((linkId || (domain && key && key !== "_root")) && !skipLinkChecks) {
// if link doesn't exist
if (!link) {
return new Response("Link not found.", {
Expand Down
11 changes: 4 additions & 7 deletions apps/web/ui/modals/add-edit-link-modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,9 @@ function AddEditLinkModal({
).then(async (res) => {
if (res.status === 200) {
const exists = await res.json();
setKeyError(exists ? "Key already exists" : null);
setKeyError(
exists ? "Duplicate key: This short link already exists." : null,
);
}
});
}
Expand Down Expand Up @@ -340,11 +342,6 @@ function AddEditLinkModal({
body: JSON.stringify(rest),
}).then(async (res) => {
if (res.status === 200) {
// track link creation event
endpoint.method === "POST" &&
va.track("Created Link", {
type: slug ? "Custom Domain" : "Default Domain",
});
await mutate(
(key) =>
typeof key === "string" && key.startsWith("/api/links"),
Expand All @@ -353,7 +350,7 @@ function AddEditLinkModal({
);
// for welcome page, redirect to links page after adding a link
if (pathname === "/welcome") {
await router.push("/links");
router.push("/links");
setShowAddEditLinkModal(false);
}
// copy shortlink to clipboard when adding a new link
Expand Down

0 comments on commit 7776e6b

Please sign in to comment.