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);
if (!note?.isViewableByUser(user)) {
if (!(note && (await note?.isViewableByUser(user)))) {
return context.json({ error: "Record not found" }, 404);
}

View file

@ -80,9 +80,9 @@ export default apiRoute((app) =>
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);
}
@ -91,7 +91,7 @@ export default apiRoute((app) =>
max_id ? lt(Users.id, max_id) : undefined,
since_id ? gte(Users.id, since_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,
context.req.url,

View file

@ -215,7 +215,7 @@ export default apiRoute((app) => {
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);
}
@ -228,7 +228,7 @@ export default apiRoute((app) => {
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);
}
@ -254,7 +254,7 @@ export default apiRoute((app) => {
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);
}

View file

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

View file

@ -79,9 +79,9 @@ export default apiRoute((app) =>
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);
}
@ -90,7 +90,7 @@ export default apiRoute((app) =>
max_id ? lt(Users.id, max_id) : undefined,
since_id ? gte(Users.id, since_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,
context.req.url,

View file

@ -75,18 +75,18 @@ export default apiRoute((app) =>
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(
{
id: status.id,
id: note.id,
// TODO: Give real source for spoilerText
spoiler_text: status.data.spoilerText,
text: status.data.contentSource,
spoiler_text: note.data.spoilerText,
text: note.data.contentSource,
} satisfies ApiStatusSource,
200,
);

View file

@ -72,7 +72,7 @@ export default apiRoute((app) =>
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);
}

View file

@ -79,18 +79,15 @@ export default apiRoute((app) =>
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)
if (!foundStatus?.isViewableByUser(user)) {
if (!(note && (await note?.isViewableByUser(user)))) {
return context.json({ error: "Record not found" }, 404);
}
const existingReblog = await Note.fromSql(
and(
eq(Notes.authorId, user.id),
eq(Notes.reblogId, foundStatus.data.id),
),
and(eq(Notes.authorId, user.id), eq(Notes.reblogId, note.data.id)),
undefined,
user?.id,
);

View file

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

View file

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