feat: avatar by link, loaded by the browser; Files nests workspaces under owners' homes
- POST /api/me/avatar/url stores the https link (users.avatar_link) instead of downloading it; GET /api/me/avatar and /api/users/:id/avatar answer 302 to it (no referrer). Link rules: https, no userinfo, <= 2048 chars, no localhost or private/reserved IP literal. Upload and link replace each other. - Settings -> Account first loads the link as an embedded image and refuses one this browser cannot show: a picture behind a login (Mattermost's /api/v4/users/<id>/image) gets no cookie cross-site and 401s. - Files sidebar: each workspace under its owner's home, chevrons remembered per browser (agenthub.files.homesOpen); ownerless ones stay flat.
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
* - GET/PATCH /api/me/account, POST /api/me/password, POST /api/me/sign-out-others
|
||||
* (Auth) — Settings → Account: the account's own profile, password and logins
|
||||
* - GET/PUT/DELETE /api/me/avatar (Auth) — the account's own picture
|
||||
* - POST /api/me/avatar/url (Auth) — the same picture downloaded from a link
|
||||
* - POST /api/me/avatar/url (Auth) — the picture as a link, redirected to
|
||||
* - GET/PATCH /api/users/{id} (Auth) — the Users page's Edit user form
|
||||
* - GET /api/users/{id}/avatar (Auth) — one account's picture (admin/analyst)
|
||||
* - GET /api/users/tmux, POST /api/users/{id}/tmux-restart, POST /api/users/tmux-restart-idle
|
||||
@@ -309,9 +309,10 @@ const paths = {
|
||||
get: {
|
||||
tags: ["Auth"],
|
||||
operationId: "getMyAvatar",
|
||||
summary: "The account's own picture (nosniff, sandbox CSP)",
|
||||
summary: "The account's own picture (nosniff, sandbox CSP), or a 302 to its avatar link",
|
||||
responses: {
|
||||
200: { description: "The image (PNG, JPEG, GIF or WebP)" },
|
||||
302: { description: "Redirect to the account's avatar link" },
|
||||
401: err("EAUTH"),
|
||||
404: err("ENOAVATAR"),
|
||||
},
|
||||
@@ -500,7 +501,7 @@ const paths = {
|
||||
tags: ["Auth"],
|
||||
operationId: "setMyAvatarFromUrl",
|
||||
summary:
|
||||
"Set the account's picture from an https link the server downloads (public addresses only, at most 3 redirects, PNG/JPEG/GIF/WebP, at most 1 MB). Login cookie only, audited",
|
||||
"Set the account's picture to an https link, stored and never downloaded — the avatar GETs answer 302 to it (no userinfo, no localhost/private IP literal, at most 2048 chars). Login cookie only, audited",
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
@@ -515,11 +516,9 @@ const paths = {
|
||||
},
|
||||
responses: {
|
||||
200: { description: "Set", content: accountBody },
|
||||
400: err("EBADURL / EPRIVATEURL / EBADIMAGE"),
|
||||
400: err("EBADURL / EPRIVATEURL"),
|
||||
401: err("EAUTH — no login cookie (a pane token is refused)"),
|
||||
403: err("ELOCALMODE"),
|
||||
413: err("ETOOBIG"),
|
||||
502: err("EFETCH — the link could not be downloaded"),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -531,6 +530,7 @@ const paths = {
|
||||
parameters: [{ name: "id", in: "path", required: true, schema: { type: "integer" } }],
|
||||
responses: {
|
||||
200: { description: "The image (PNG, JPEG, GIF or WebP)" },
|
||||
302: { description: "Redirect to that account's avatar link" },
|
||||
403: err("EADMIN — a developer"),
|
||||
404: err("ENOAVATAR"),
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user