fix(api): 🔒 Correctly check for note ownership when editing

This commit is contained in:
Jesse Wierzbinski 2024-11-19 17:26:14 +01:00
parent 653cf712ea
commit 9682cd0f99
No known key found for this signature in database
10 changed files with 40 additions and 36 deletions

View file

@ -73,7 +73,7 @@ export default apiRoute((app) =>
const note = await Note.fromId(id, user?.id); const note = await Note.fromId(id, user?.id);
if (!note?.isViewableByUser(user)) { if (!(note && (await note?.isViewableByUser(user)))) {
return context.json({ error: "Record not found" }, 404); return context.json({ error: "Record not found" }, 404);
} }

View file

@ -80,9 +80,9 @@ export default apiRoute((app) =>
return context.json({ error: "Unauthorized" }, 401); return context.json({ error: "Unauthorized" }, 401);
} }
const status = await Note.fromId(id, user?.id); const note = await Note.fromId(id, user?.id);
if (!status?.isViewableByUser(user)) { if (!(note && (await note?.isViewableByUser(user)))) {
return context.json({ error: "Record not found" }, 404); return context.json({ error: "Record not found" }, 404);
} }
@ -91,7 +91,7 @@ export default apiRoute((app) =>
max_id ? lt(Users.id, max_id) : undefined, max_id ? lt(Users.id, max_id) : undefined,
since_id ? gte(Users.id, since_id) : undefined, since_id ? gte(Users.id, since_id) : undefined,
min_id ? gt(Users.id, min_id) : undefined, min_id ? gt(Users.id, min_id) : undefined,
sql`EXISTS (SELECT 1 FROM "Likes" WHERE "Likes"."likedId" = ${status.id} AND "Likes"."likerId" = ${Users.id})`, sql`EXISTS (SELECT 1 FROM "Likes" WHERE "Likes"."likedId" = ${note.id} AND "Likes"."likerId" = ${Users.id})`,
), ),
limit, limit,
context.req.url, context.req.url,

View file

@ -215,7 +215,7 @@ export default apiRoute((app) => {
const note = await Note.fromId(id, user?.id); const note = await Note.fromId(id, user?.id);
if (!note?.isViewableByUser(user)) { if (!(note && (await note?.isViewableByUser(user)))) {
return context.json({ error: "Record not found" }, 404); return context.json({ error: "Record not found" }, 404);
} }
@ -228,7 +228,7 @@ export default apiRoute((app) => {
const note = await Note.fromId(id, user?.id); const note = await Note.fromId(id, user?.id);
if (!note?.isViewableByUser(user)) { if (!(note && (await note?.isViewableByUser(user)))) {
return context.json({ error: "Record not found" }, 404); return context.json({ error: "Record not found" }, 404);
} }
@ -254,7 +254,7 @@ export default apiRoute((app) => {
const note = await Note.fromId(id, user?.id); const note = await Note.fromId(id, user?.id);
if (!note?.isViewableByUser(user)) { if (!(note && (await note?.isViewableByUser(user)))) {
return context.json({ error: "Record not found" }, 404); return context.json({ error: "Record not found" }, 404);
} }

View file

@ -104,17 +104,14 @@ export default apiRoute((app) =>
return context.json({ error: "Unauthorized" }, 401); return context.json({ error: "Unauthorized" }, 401);
} }
const foundStatus = await Note.fromId(id, user.id); const note = await Note.fromId(id, user.id);
if (!foundStatus?.isViewableByUser(user)) { if (!(note && (await note?.isViewableByUser(user)))) {
return context.json({ error: "Record not found" }, 404); return context.json({ error: "Record not found" }, 404);
} }
const existingReblog = await Note.fromSql( const existingReblog = await Note.fromSql(
and( and(eq(Notes.authorId, user.id), eq(Notes.reblogId, note.data.id)),
eq(Notes.authorId, user.id),
eq(Notes.reblogId, foundStatus.data.id),
),
); );
if (existingReblog) { if (existingReblog) {
@ -123,7 +120,7 @@ export default apiRoute((app) =>
const newReblog = await Note.insert({ const newReblog = await Note.insert({
authorId: user.id, authorId: user.id,
reblogId: foundStatus.data.id, reblogId: note.data.id,
visibility, visibility,
sensitive: false, sensitive: false,
updatedAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
@ -140,10 +137,10 @@ export default apiRoute((app) =>
return context.json({ error: "Failed to reblog" }, 500); return context.json({ error: "Failed to reblog" }, 500);
} }
if (foundStatus.author.isLocal() && user.isLocal()) { if (note.author.isLocal() && user.isLocal()) {
await Notification.insert({ await Notification.insert({
accountId: user.id, accountId: user.id,
notifiedId: foundStatus.author.id, notifiedId: note.author.id,
type: "reblog", type: "reblog",
noteId: newReblog.data.reblogId, noteId: newReblog.data.reblogId,
}); });

View file

@ -79,9 +79,9 @@ export default apiRoute((app) =>
return context.json({ error: "Unauthorized" }, 401); return context.json({ error: "Unauthorized" }, 401);
} }
const status = await Note.fromId(id, user.id); const note = await Note.fromId(id, user.id);
if (!status?.isViewableByUser(user)) { if (!(note && (await note?.isViewableByUser(user)))) {
return context.json({ error: "Record not found" }, 404); return context.json({ error: "Record not found" }, 404);
} }
@ -90,7 +90,7 @@ export default apiRoute((app) =>
max_id ? lt(Users.id, max_id) : undefined, max_id ? lt(Users.id, max_id) : undefined,
since_id ? gte(Users.id, since_id) : undefined, since_id ? gte(Users.id, since_id) : undefined,
min_id ? gt(Users.id, min_id) : undefined, min_id ? gt(Users.id, min_id) : undefined,
sql`EXISTS (SELECT 1 FROM "Notes" WHERE "Notes"."reblogId" = ${status.id} AND "Notes"."authorId" = ${Users.id})`, sql`EXISTS (SELECT 1 FROM "Notes" WHERE "Notes"."reblogId" = ${note.id} AND "Notes"."authorId" = ${Users.id})`,
), ),
limit, limit,
context.req.url, context.req.url,

View file

@ -75,18 +75,18 @@ export default apiRoute((app) =>
return context.json({ error: "Unauthorized" }, 401); return context.json({ error: "Unauthorized" }, 401);
} }
const status = await Note.fromId(id, user.id); const note = await Note.fromId(id, user.id);
if (!status?.isViewableByUser(user)) { if (!(note && (await note?.isViewableByUser(user)))) {
return context.json({ error: "Record not found" }, 404); return context.json({ error: "Record not found" }, 404);
} }
return context.json( return context.json(
{ {
id: status.id, id: note.id,
// TODO: Give real source for spoilerText // TODO: Give real source for spoilerText
spoiler_text: status.data.spoilerText, spoiler_text: note.data.spoilerText,
text: status.data.contentSource, text: note.data.contentSource,
} satisfies ApiStatusSource, } satisfies ApiStatusSource,
200, 200,
); );

View file

@ -72,7 +72,7 @@ export default apiRoute((app) =>
const note = await Note.fromId(id, user.id); const note = await Note.fromId(id, user.id);
if (!note?.isViewableByUser(user)) { if (!(note && (await note?.isViewableByUser(user)))) {
return context.json({ error: "Record not found" }, 404); return context.json({ error: "Record not found" }, 404);
} }

View file

@ -79,18 +79,15 @@ export default apiRoute((app) =>
return context.json({ error: "Unauthorized" }, 401); return context.json({ error: "Unauthorized" }, 401);
} }
const foundStatus = await Note.fromId(id, user.id); const note = await Note.fromId(id, user.id);
// Check if user is authorized to view this status (if it's private) // Check if user is authorized to view this status (if it's private)
if (!foundStatus?.isViewableByUser(user)) { if (!(note && (await note?.isViewableByUser(user)))) {
return context.json({ error: "Record not found" }, 404); return context.json({ error: "Record not found" }, 404);
} }
const existingReblog = await Note.fromSql( const existingReblog = await Note.fromSql(
and( and(eq(Notes.authorId, user.id), eq(Notes.reblogId, note.data.id)),
eq(Notes.authorId, user.id),
eq(Notes.reblogId, foundStatus.data.id),
),
undefined, undefined,
user?.id, user?.id,
); );

View file

@ -81,7 +81,7 @@ export default apiRoute((app) =>
foundAuthor = foundObject ? foundObject.author : null; foundAuthor = foundObject ? foundObject.author : null;
if (foundObject) { if (foundObject) {
if (!foundObject.isViewableByUser(null)) { if (!(await foundObject.isViewableByUser(null))) {
return context.json({ error: "Object not found" }, 404); return context.json({ error: "Object not found" }, 404);
} }
} else { } else {

View file

@ -1099,8 +1099,13 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
} }
// Filter for posts that are viewable by the user // Filter for posts that are viewable by the user
const viewableAncestors = ancestors.filter((ancestor) => const viewableAncestors = await Promise.all(
ancestor.isViewableByUser(fetcher), ancestors.map(async (ancestor) => {
const isViewable = await ancestor.isViewableByUser(fetcher);
return isViewable ? ancestor : null;
}),
).then((filteredAncestors) =>
filteredAncestors.filter((n) => n !== null),
); );
// Reverse the order so that the oldest posts are first // Reverse the order so that the oldest posts are first
@ -1133,8 +1138,13 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
// Filter for posts that are viewable by the user // Filter for posts that are viewable by the user
const viewableDescendants = descendants.filter((descendant) => const viewableDescendants = await Promise.all(
descendant.isViewableByUser(fetcher), descendants.map(async (descendant) => {
const isViewable = await descendant.isViewableByUser(fetcher);
return isViewable ? descendant : null;
}),
).then((filteredDescendants) =>
filteredDescendants.filter((n) => n !== null),
); );
return viewableDescendants; return viewableDescendants;