Доступ для акаунтів CRM: чужое личное по access_grants (projects=all, level read/full), custom через shared=all; режим «лише перегляд» (403 на изменения, access в /api/me и /api/tree); кэш доступа к workspace — 1 мин

This commit is contained in:
EugeneTes
2026-10-16 15:20:28 +00:00
parent c474bb3607
commit e0e4a18476
10 changed files with 235 additions and 50 deletions

View File

@@ -16,9 +16,9 @@
| `Authorization` | `Bearer <access_token Supabase из CRM>` |
| `X-Wiki-Workspace` | `shared` (по умолчанию), `personal:<uid>` или id пользовательского workspace CRM (например, `wsmv5l7d425re160`, как отдаёт `crm workspaces list`) |
Сервер проверяет токен: `exp`, `aud = "authenticated"` и `GET <SUPABASE_URL>/auth/v1/user` с publishable-ключом (результат кэшируется на 5 минут по SHA-256 токена). Workspace из запроса не доверяется: `shared` доступен всем, `personal:<uid>` только владельцу `uid`, id пользовательского workspace проверяется запросом `GET /rest/v1/workspaces?id=eq.<id>` с токеном пользователя (RLS Supabase: строку видят только владелец и участники). Id в таблице `workspaces` CRM текстовые: `shared`, `personal:<uid>` и для пользовательских `ws` + base36 (`createWorkspace` в CRM). Принимается любой id из `[A-Za-z0-9_-]{1,64}` (в том числе uuid), другие символы отклоняются ещё до запроса к Supabase. Ответы: `401` — токен не принят или истёк, `403` — нет доступа к workspace.
Сервер проверяет токен: `exp`, `aud = "authenticated"` и `GET <SUPABASE_URL>/auth/v1/user` с publishable-ключом (результат кэшируется на 5 минут по SHA-256 токена). Workspace из запроса не доверяется: `shared` доступен всем; `personal:<uid>` — владельцу `uid`, а другим — по выдаче доступа CRM («Доступ для акаунтів», таблица `access_grants`, строка `<owner>:<grantee>`): `GET /rest/v1/access_grants?owner_uid=eq.<uid>&grantee_uid=eq.<я>` с токеном пользователя, личную вики открывает `projects = all` («Мої особисті проєкти: Усі»), права — `level` (`read` — лишь просмотр, `full` — полные); «Вибрані» проекты и один лишь план вики не открывают. Id пользовательского workspace проверяется запросом `GET /rest/v1/workspaces?id=eq.<id>&select=id,owner_uid,members` с токеном пользователя (RLS Supabase): владелец и участники — полные права; строка видна, но я не владелец и не участник — значит, её открыли через `access_grants.shared = all` владельцем или участником, права по `level`. Результат проверки workspace кэшируется на 1 минуту, поэтому выданный или отозванный в CRM доступ вступает в силу в пределах минуты. В workspace «лишь просмотр» любой запрос, кроме `GET`/`HEAD`, получает `403`; `/api/me` и `/api/tree` возвращают `access: "read" | "full"`, интерфейс в режиме `read` прячет кнопки изменения. Id в таблице `workspaces` CRM текстовые: `shared`, `personal:<uid>` и для пользовательских `ws` + base36 (`createWorkspace` в CRM). Принимается любой id из `[A-Za-z0-9_-]{1,64}` (в том числе uuid), другие символы отклоняются ещё до запроса к Supabase. Ответы: `401` — токен не принят или истёк, `403` — нет доступа к workspace.
Пока CRM не передаёт workspace (задача Олега `mv6p5b92slw1an`), фронтенд работает в `shared`. Когда в `crm:auth` появится `workspace: { id, kind, title }`, он подхватится автоматически: `kind: "shared"` → `shared`, `kind: "personal"` → `personal:<uid>`, иначе `id`. Сообщение о смене workspace тоже обрабатывается (`{ type: "crm:workspace", workspace: {…} }`), если Олег выберет другое имя — поправить в `frontend/src/crm.ts`.
Пока CRM не передаёт workspace (задача Олега `mv6p5b92slw1an`), фронтенд работает в `shared`. Когда в `crm:auth` появится `workspace: { id, kind, title }`, он подхватится автоматически: `kind: "shared"` → `shared`, `kind: "personal"` → `personal:<uid>` (свой), иначе `id` (в том числе `personal:<uid владельца>` с `kind: "granted"` — чужое личное, открытое мне). Сообщение о смене workspace тоже обрабатывается (`{ type: "crm:workspace", workspace: {…} }`), если Олег выберет другое имя — поправить в `frontend/src/crm.ts`.
## Ссылки на страницы в CRM
@@ -39,8 +39,8 @@
| `GET` | `/health` | `{ status, dbCreatedAt }` — без авторизации; `dbCreatedAt` не меняется, пока жив том с базой |
| `GET` | `/api/config` | `{ crmOrigins: [...] }` — без авторизации |
| `POST` | `/api/verify` | `{ access_token }` → `{ ok, id, email, name, exp }` — без авторизации |
| `GET` | `/api/me` | текущий пользователь и workspace |
| `GET` | `/api/tree` | `{ workspace, nodes: [Node] }` — все папки и страницы workspace без текста |
| `GET` | `/api/me` | текущий пользователь, workspace и `access` (`read` / `full`) |
| `GET` | `/api/tree` | `{ workspace, access, nodes: [Node] }` — все папки и страницы workspace без текста |
| `GET` | `/api/nodes/{id}` | папка или страница (Page) |
| `GET` | `/api/pages/{id}` | страница (Page), `404` для папки |
| `GET` | `/api/pages/{id}/markdown` | текст страницы, `text/markdown` |