Skip to content

Commit

Permalink
Fix UUID validation and handle server errors for invalid UUID inputs
Browse files Browse the repository at this point in the history
Fix #65
  • Loading branch information
dahlia committed Dec 18, 2024
1 parent 261868b commit 9b4796f
Show file tree
Hide file tree
Showing 25 changed files with 280 additions and 139 deletions.
5 changes: 5 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ To be released.

- The profile page now shows a user's cover image if they have one.

- Fixed a bug where a server error occurred when an invalid UUID was input via
URL or form data. [[#65]]

[#65]: https://github.com/dahlia/hollo/issues/65


Version 0.3.2
-------------
Expand Down
38 changes: 25 additions & 13 deletions src/api/v1/accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import {
} from "../../schema";
import { disk, getAssetUrl } from "../../storage";
import { extractCustomEmojis, formatText } from "../../text";
import { type Uuid, isUuid, uuid } from "../../uuid";
import { timelineQuerySchema } from "./timelines";

const app = new Hono<{ Variables: Variables }>();
Expand Down Expand Up @@ -265,7 +266,7 @@ app.get(
422,
);
}
const ids = c.req.queries("id[]") ?? [];
const ids = (c.req.queries("id[]") ?? []).filter(isUuid);
const accountList =
ids.length > 0
? await db.query.accounts.findMany({
Expand Down Expand Up @@ -448,7 +449,7 @@ app.get(
422,
);
}
const ids: string[] = c.req.queries("id[]") ?? [];
const ids: Uuid[] = (c.req.queries("id[]") ?? []).filter(isUuid);
const result: {
id: string;
accounts: ReturnType<typeof serializeAccount>[];
Expand Down Expand Up @@ -488,6 +489,7 @@ app.get(

app.get("/:id", async (c) => {
const id = c.req.param("id");
if (!isUuid(id)) return c.json({ error: "Record not found" }, 404);
const account = await db.query.accounts.findFirst({
where: eq(accounts.id, id),
with: { owner: true, successor: true },
Expand Down Expand Up @@ -518,14 +520,15 @@ app.get(
),
),
async (c) => {
const id = c.req.param("id");
if (!isUuid(id)) return c.json({ error: "Record not found" }, 404);
const tokenOwner = c.get("token").accountOwner;
if (tokenOwner == null) {
return c.json(
{ error: "This method requires an authenticated user" },
422,
);
}
const id = c.req.param("id");
const account = await db.query.accounts.findFirst({
where: eq(accounts.id, id),
with: {
Expand Down Expand Up @@ -687,14 +690,15 @@ app.post(
tokenRequired,
scopeRequired(["write:follows"]),
async (c) => {
const id = c.req.param("id");
if (!isUuid(id)) return c.json({ error: "Record not found" }, 404);
const owner = c.get("token").accountOwner;
if (owner == null) {
return c.json(
{ error: "This method requires an authenticated user" },
422,
);
}
const id = c.req.param("id");
const following = await db.query.accounts.findFirst({
where: eq(accounts.id, id),
with: { owner: true },
Expand Down Expand Up @@ -740,14 +744,15 @@ app.post(
tokenRequired,
scopeRequired(["write:follows"]),
async (c) => {
const id = c.req.param("id");
if (!isUuid(id)) return c.json({ error: "Record not found" }, 404);
const owner = c.get("token").accountOwner;
if (owner == null) {
return c.json(
{ error: "This method requires an authenticated user" },
422,
);
}
const id = c.req.param("id");
const following = await db.query.accounts.findFirst({
where: eq(accounts.id, id),
with: { owner: true },
Expand Down Expand Up @@ -782,6 +787,7 @@ app.post(

app.get("/:id/followers", async (c) => {
const accountId = c.req.param("id");
if (!isUuid(accountId)) return c.json({ error: "Record not found" }, 404);
const followers = await db.query.follows.findMany({
where: and(eq(follows.followingId, accountId), isNotNull(follows.approved)),
orderBy: desc(follows.approved),
Expand All @@ -801,6 +807,7 @@ app.get("/:id/followers", async (c) => {

app.get("/:id/following", async (c) => {
const accountId = c.req.param("id");
if (!isUuid(accountId)) return c.json({ error: "Record not found" }, 404);
const followers = await db.query.follows.findMany({
where: and(eq(follows.followerId, accountId), isNotNull(follows.approved)),
orderBy: desc(follows.approved),
Expand All @@ -823,6 +830,8 @@ app.get(
tokenRequired,
scopeRequired(["read:lists"]),
async (c) => {
const accountId = c.req.param("id");
if (!isUuid(accountId)) return c.json({ error: "Record not found" }, 404);
const owner = c.get("token").accountOwner;
if (owner == null) {
return c.json(
Expand All @@ -838,7 +847,7 @@ app.get(
db
.select({ id: listMembers.listId })
.from(listMembers)
.where(eq(listMembers.accountId, c.req.param("id"))),
.where(eq(listMembers.accountId, accountId)),
),
),
});
Expand All @@ -853,8 +862,8 @@ app.get(
zValidator(
"query",
z.object({
max_id: z.string().uuid().optional(),
since_id: z.string().uuid().optional(),
max_id: uuid.optional(),
since_id: uuid.optional(),
limit: z
.string()
.default("40")
Expand Down Expand Up @@ -911,15 +920,15 @@ app.post(
}),
),
async (c) => {
const id = c.req.param("id");
if (!isUuid(id)) return c.json({ error: "Record not found" }, 404);
const owner = c.get("token").accountOwner;

if (owner == null) {
return c.json(
{ error: "This method requires an authenticated user" },
422,
);
}
const id = c.req.param("id");
const { notifications, duration } = c.req.valid("json");
const account = await db.query.accounts.findFirst({
where: eq(accounts.id, id),
Expand Down Expand Up @@ -983,14 +992,15 @@ app.post(
tokenRequired,
scopeRequired(["write:mutes"]),
async (c) => {
const id = c.req.param("id");
if (!isUuid(id)) return c.json({ error: "Record not found" }, 404);
const owner = c.get("token").accountOwner;
if (owner == null) {
return c.json(
{ error: "This method requires an authenticated user" },
422,
);
}
const id = c.req.param("id");
await db
.delete(mutes)
.where(and(eq(mutes.accountId, owner.id), eq(mutes.mutedAccountId, id)));
Expand Down Expand Up @@ -1024,14 +1034,15 @@ app.post(
tokenRequired,
scopeRequired(["read:blocks"]),
async (c) => {
const id = c.req.param("id");
if (!isUuid(id)) return c.json({ error: "Record not found" }, 404);
const owner = c.get("token").accountOwner;
if (owner == null) {
return c.json(
{ error: "This method requires an authenticated user" },
422,
);
}
const id = c.req.param("id");
const acct = await db.query.accounts.findFirst({
where: eq(accounts.id, id),
with: { owner: true },
Expand Down Expand Up @@ -1069,14 +1080,15 @@ app.post(
tokenRequired,
scopeRequired(["read:blocks"]),
async (c) => {
const id = c.req.param("id");
if (!isUuid(id)) return c.json({ error: "Record not found" }, 404);
const owner = c.get("token").accountOwner;
if (owner == null) {
return c.json(
{ error: "This method requires an authenticated user" },
422,
);
}
const id = c.req.param("id");
const acct = await db.query.accounts.findFirst({
where: eq(accounts.id, id),
with: { owner: true },
Expand Down
10 changes: 7 additions & 3 deletions src/api/v1/featured_tags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import {
import type { PgDatabase } from "drizzle-orm/pg-core";
import type { PostgresJsQueryResultHKT } from "drizzle-orm/postgres-js";
import { Hono } from "hono";
import { uuidv7 } from "uuidv7-js";
import { z } from "zod";
import db from "../../db";
import { serializeFeaturedTag } from "../../entities/tag";
import { type Variables, scopeRequired, tokenRequired } from "../../oauth";
import type * as schema from "../../schema";
import { featuredTags, posts } from "../../schema";
import { type Uuid, isUuid, uuidv7 } from "../../uuid";

const app = new Hono<{ Variables: Variables }>();

Expand Down Expand Up @@ -65,6 +65,10 @@ app.delete(
tokenRequired,
scopeRequired(["write:accounts"]),
async (c) => {
const featuredTagId = c.req.param("id");
if (!isUuid(featuredTagId)) {
return c.json({ error: "Record not found" }, 404);
}
const owner = c.get("token").accountOwner;
if (owner == null) {
return c.json({ error: "The access token is invalid." }, 401);
Expand All @@ -74,7 +78,7 @@ app.delete(
.where(
and(
eq(featuredTags.accountOwnerId, owner.id),
eq(featuredTags.id, c.req.param("id")),
eq(featuredTags.id, featuredTagId),
),
)
.returning();
Expand All @@ -89,7 +93,7 @@ async function getFeaturedTagStats(
typeof schema,
ExtractTablesWithRelations<typeof schema>
>,
ownerId: string,
ownerId: Uuid,
): Promise<Record<string, { posts: number; lastPublished: Date | null }>> {
const result = await db
.select({
Expand Down
7 changes: 5 additions & 2 deletions src/api/v1/follow_requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { federation } from "../../federation";
import { updateAccountStats } from "../../federation/account";
import { type Variables, scopeRequired, tokenRequired } from "../../oauth";
import { accounts, blocks, follows, mutes } from "../../schema";
import { isUuid } from "../../uuid";

const app = new Hono<{ Variables: Variables }>();

Expand Down Expand Up @@ -40,14 +41,15 @@ app.post(
tokenRequired,
scopeRequired(["write:follows"]),
async (c) => {
const followerId = c.req.param("account_id");
if (!isUuid(followerId)) return c.json({ error: "Record not found" }, 404);
const owner = c.get("token").accountOwner;
if (owner == null) {
return c.json(
{ error: "This method requires an authenticated user" },
422,
);
}
const followerId = c.req.param("account_id");
const follower = await db.query.accounts.findFirst({
where: eq(accounts.id, followerId),
with: { owner: true },
Expand Down Expand Up @@ -113,14 +115,15 @@ app.post(
tokenRequired,
scopeRequired(["write:follows"]),
async (c) => {
const followerId = c.req.param("account_id");
if (!isUuid(followerId)) return c.json({ error: "Record not found" }, 404);
const owner = c.get("token").accountOwner;
if (owner == null) {
return c.json(
{ error: "This method requires an authenticated user" },
422,
);
}
const followerId = c.req.param("account_id");
const follower = await db.query.accounts.findFirst({
where: eq(accounts.id, followerId),
with: { owner: true },
Expand Down
Loading

0 comments on commit 9b4796f

Please sign in to comment.