update_workspace now takes an image media id (or null to remove the picture). updateWorkspace writes image_media_id through resolveWorkspacePicture, which only accepts the caller's own image and deletes the picture it replaced, so a bot can never show someone else's file or drop a picture it is still using.
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import AppError from "./app-error";
|
||||
import resolveWorkspacePicture from "./resolve-workspace-picture";
|
||||
|
||||
const image = (id: number) => ({ id, media_type: "image" });
|
||||
|
||||
describe("resolveWorkspacePicture", () => {
|
||||
test("leaves the picture alone when nothing is asked for", () => {
|
||||
expect(resolveWorkspacePicture({ current: 4 })).toBeUndefined();
|
||||
});
|
||||
|
||||
test("sets a new image of the caller's", () => {
|
||||
expect(
|
||||
resolveWorkspacePicture({ requested: 9, current: 4, media: image(9) }),
|
||||
).toBe(9);
|
||||
});
|
||||
|
||||
test("clears the picture with null", () => {
|
||||
expect(
|
||||
resolveWorkspacePicture({ requested: null, current: 4 }),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
test("naming the picture it already has changes nothing, so it is never deleted", () => {
|
||||
expect(
|
||||
resolveWorkspacePicture({ requested: 4, current: 4 }),
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
resolveWorkspacePicture({ requested: "4", current: 4, media: image(4) }),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
test("refuses media that isn't the caller's", () => {
|
||||
expect(() =>
|
||||
resolveWorkspacePicture({ requested: 9, current: null, media: null }),
|
||||
).toThrow(AppError);
|
||||
});
|
||||
|
||||
test("refuses anything that isn't an image", () => {
|
||||
expect(() =>
|
||||
resolveWorkspacePicture({
|
||||
requested: 9,
|
||||
current: null,
|
||||
media: { id: 9, media_type: "document" },
|
||||
}),
|
||||
).toThrow(`A workspace picture has to be an image.`);
|
||||
});
|
||||
|
||||
test("refuses an id that names a different row than the one looked up", () => {
|
||||
expect(() =>
|
||||
resolveWorkspacePicture({
|
||||
requested: 9,
|
||||
current: null,
|
||||
media: image(10),
|
||||
}),
|
||||
).toThrow(AppError);
|
||||
});
|
||||
|
||||
test("refuses an id that isn't a positive integer", () => {
|
||||
for (const requested of [0, -3, 1.5, "abc"]) {
|
||||
expect(() =>
|
||||
resolveWorkspacePicture({ requested, current: null }),
|
||||
).toThrow(AppError);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import AppError from "@/src/utils/app-error";
|
||||
|
||||
type Params = {
|
||||
/**
|
||||
* The `workspace_media_id` the caller asked for: a new picture, or
|
||||
* null to remove the one it has. Undefined leaves it alone.
|
||||
*/
|
||||
requested?: number | string | null;
|
||||
/**
|
||||
* The picture the workspace has now.
|
||||
*/
|
||||
current?: number | string | null;
|
||||
/**
|
||||
* The media row the requested id names, when it was found for the
|
||||
* caller (`grabOwnPictureMedia`); required to set a new picture.
|
||||
*/
|
||||
media?: { id?: number | string | null; media_type?: string | null } | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* What `updateWorkspace` should write into `workspaces.image_media_id`:
|
||||
* undefined to leave it as it is, null to clear it, or the new media id.
|
||||
*
|
||||
* A new picture has to be the caller's own image: the resolver refuses an
|
||||
* id that isn't an image, and the media lookup refuses one that isn't
|
||||
* theirs. Handing back the picture it already has changes nothing, so
|
||||
* saying "use this picture" twice never deletes it.
|
||||
*/
|
||||
export default function resolveWorkspacePicture({ requested, current, media }: Params) {
|
||||
if (requested === undefined) return undefined;
|
||||
|
||||
if (requested === null) return null;
|
||||
|
||||
const id = Number(requested);
|
||||
|
||||
if (!Number.isInteger(id) || id <= 0) {
|
||||
throw new AppError(`Pick an image for the workspace.`);
|
||||
}
|
||||
|
||||
if (current != null && Number(current) == id) return undefined;
|
||||
|
||||
if (!media || Number(media.id) != id) {
|
||||
throw new AppError(`That picture isn't one of yours. Upload it again.`);
|
||||
}
|
||||
|
||||
if (media.media_type != "image") {
|
||||
throw new AppError(`A workspace picture has to be an image.`);
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
Reference in New Issue
Block a user