fix(avatars): every signed-in account sees other users' pictures
System Health shows every user's name and avatarAt to everyone, but the picture route GET /api/users/:id/avatar sat under the staff-only /users prefix, so a developer got 403 EADMIN and saw only initials. authGuard now lets any signed-in account make that one read (EVERYONE_READS); every other /users path stays staff-only, and the test checks both.
This commit is contained in:
@@ -4,8 +4,9 @@
|
||||
* server/lib/avatars.js): an account sets, reads and removes its own picture;
|
||||
* only PNG/JPEG/GIF/WebP by magic bytes (an SVG is refused), at most 1 MB,
|
||||
* served with `nosniff` and a `sandbox` CSP; `avatarAt` reaches `/api/auth/me`;
|
||||
* another account never sees it through `/api/me`; staff read it at
|
||||
* `/api/users/:id/avatar` (a developer is refused) and a workspace carries
|
||||
* another account never sees it through `/api/me`; every account reads it at
|
||||
* `/api/users/:id/avatar` (a developer too — only the picture, the rest of
|
||||
* `/users` stays staff-only) and a workspace carries
|
||||
* `owner_avatar_at` for the sidebar's owner groups. A picture from a link
|
||||
* (`POST /api/me/avatar/url`, server/lib/avatar-url.js) is stored, never
|
||||
* downloaded, and the avatar routes redirect to it; https only, never a
|
||||
@@ -196,7 +197,7 @@ it("a picture from a link: https only, never a private or local address", async
|
||||
}
|
||||
});
|
||||
|
||||
it("staff read another account's avatar; workspaces carry owner_avatar_at", async () => {
|
||||
it("every account reads another's avatar; workspaces carry owner_avatar_at", async () => {
|
||||
const ava = await login("ava", "ava-pass-1");
|
||||
const ann = await login("ann", "ann-pass-1");
|
||||
const bob = await login("bob", "bob-pass-1");
|
||||
@@ -210,8 +211,12 @@ it("staff read another account's avatar; workspaces carry owner_avatar_at", asyn
|
||||
assert.deepEqual(Buffer.from(await got.arrayBuffer()), PNG);
|
||||
assert.equal(
|
||||
(await fetch(`${BASE}/api/users/${id}/avatar`, { headers: { cookie: bob } })).status,
|
||||
403
|
||||
200
|
||||
);
|
||||
// Only the picture: the rest of /users stays staff-only for a developer.
|
||||
for (const path of [`/api/users/${id}`, `/api/users/${id}/details`, "/api/users"]) {
|
||||
assert.equal((await fetch(BASE + path, { headers: { cookie: bob } })).status, 403, path);
|
||||
}
|
||||
const lanes = (await (await fetch(BASE + "/api/lanes", { headers: { cookie: ann } })).json())
|
||||
.lanes;
|
||||
assert.ok(lanes.find((l) => l.cwd === "/w/ava").owner_avatar_at);
|
||||
|
||||
Reference in New Issue
Block a user