feat: auth/stars API, collections and publish routes, star/author migrations
This commit is contained in:
parent
43cf0e277d
commit
65820d4ef9
43 changed files with 2008 additions and 408 deletions
|
|
@ -1,10 +1,10 @@
|
|||
# Профили, аккаунты и авторизация — дизайн Indexium
|
||||
# Профили, аккаунты и авторизация - дизайн Indexium
|
||||
|
||||
> Цель: максимально лёгкая, но крутая система профилей без паролей, где GitHub — источник правды.
|
||||
> Цель: максимально лёгкая, но крутая система профилей без паролей, где GitHub - источник правды.
|
||||
|
||||
---
|
||||
|
||||
## 1. TL;DR — рекомендуем для MVP
|
||||
## 1. TL;DR - рекомендуем для MVP
|
||||
|
||||
**Авторизация: только GitHub OAuth / GitHub App.** Никаких паролей, email+пароль, Google и т.д. на старте.
|
||||
|
||||
|
|
@ -15,10 +15,10 @@
|
|||
| 1 клик, нет форм регистрации | Отсекаем тех у кого нет GitHub (но они и моды не публикуют) |
|
||||
| Доказуемое владение репозиторием (`GET /repos` с токеном) | Зависимость от GitHub OAuth (но у нас и так всё на GitHub) |
|
||||
| Аватар, ник, био подтягиваются автоматически | Нет anon-публикаций (и это хорошо для open source) |
|
||||
| Один токен — и публикация, и вебхуки, и профиль | Если GitHub лежит — логин не работает (редкость) |
|
||||
| Один токен - и публикация, и вебхуки, и профиль | Если GitHub лежит - логин не работает (редкость) |
|
||||
| Нет хранения паролей, нет утечек | |
|
||||
|
||||
> **Вывод:** для каталога где `1 мод = 1 GitHub репо` — GitHub-only это не ограничение, а фича. Пользователи-читатели (игроки) могут смотреть каталог **без логина вообще**. Логин нужен только авторам.
|
||||
> **Вывод:** для каталога где `1 мод = 1 GitHub репо` - GitHub-only это не ограничение, а фича. Пользователи-читатели (игроки) могут смотреть каталог **без логина вообще**. Логин нужен только авторам.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -26,10 +26,10 @@
|
|||
|
||||
### Роли
|
||||
|
||||
- **Reader (anonymous)** — ищет, качает по прямым ссылкам, смотрит профили. Без аккаунта.
|
||||
- **Author** — залогинен через GitHub, импортировал хотя бы один репо. Может публиковать релизы (через `git push` + webhook, без кнопки "загрузить jar").
|
||||
- **Contributor** — указан в `mod_authors` с `role=contributor`, не обязательно owner репо. Получает бейдж на карточке мода.
|
||||
- **Moderator / Admin** — ручная выдача, может ставить `verified` / `suspicious`, банить.
|
||||
- **Reader (anonymous)** - ищет, качает по прямым ссылкам, смотрит профили. Без аккаунта.
|
||||
- **Author** - залогинен через GitHub, импортировал хотя бы один репо. Может публиковать релизы (через `git push` + webhook, без кнопки "загрузить jar").
|
||||
- **Contributor** - указан в `mod_authors` с `role=contributor`, не обязательно owner репо. Получает бейдж на карточке мода.
|
||||
- **Moderator / Admin** - ручная выдача, может ставить `verified` / `suspicious`, банить.
|
||||
|
||||
### Что храним (минимум GDPR)
|
||||
|
||||
|
|
@ -56,12 +56,12 @@ CREATE TABLE mod_authors (
|
|||
);
|
||||
```
|
||||
|
||||
Никаких email в открытом виде (берём только для JWT, не показываем), никаких паролей. `github_id` — неизменяемый PK, `login` может смениться — обновляем по webhook `user.renamed` или при следующем логине.
|
||||
Никаких email в открытом виде (берём только для JWT, не показываем), никаких паролей. `github_id` - неизменяемый PK, `login` может смениться - обновляем по webhook `user.renamed` или при следующем логине.
|
||||
|
||||
### Сессии
|
||||
|
||||
- **JWT (httpOnly cookie)**: `sub: github_id`, `login`, `exp: 7d`. Подпись `HS256` с `JWT_SECRET` или `RS256` если хотим ротацию.
|
||||
- **Не храним сессии в Redis на MVP** — stateless JWT достаточно. Позже — refresh token в `author_sessions`.
|
||||
- **Не храним сессии в Redis на MVP** - stateless JWT достаточно. Позже - refresh token в `author_sessions`.
|
||||
- **CSRF**: `SameSite=Lax` + `Origin` check для `POST /mods/import`.
|
||||
|
||||
---
|
||||
|
|
@ -78,7 +78,7 @@ CREATE TABLE mod_authors (
|
|||
→ 302 /me или /?welcomed=1
|
||||
```
|
||||
|
||||
**Для публикации модов нужен `repo` scope** только если хотим ставить webhook автоматически. На MVP можно `read:user` + `public_repo` (только публичные). Токен GitHub не храним долго — меняем на JWT и забываем (или храним encrypted `github_access_token` для будущих API вызовов, с возможностью revoke).
|
||||
**Для публикации модов нужен `repo` scope** только если хотим ставить webhook автоматически. На MVP можно `read:user` + `public_repo` (только публичные). Токен GitHub не храним долго - меняем на JWT и забываем (или храним encrypted `github_access_token` для будущих API вызовов, с возможностью revoke).
|
||||
|
||||
**GitHub App (альтернатива OAuth):**
|
||||
- Плюс: `5k–12.5k RPH`, управление webhooks через App, `installation_id` per org.
|
||||
|
|
@ -87,26 +87,26 @@ CREATE TABLE mod_authors (
|
|||
|
||||
---
|
||||
|
||||
## 4. Профили — как сделать круто и по open source
|
||||
## 4. Профили - как сделать круто и по open source
|
||||
|
||||
### URL структура
|
||||
|
||||
- `/u/:login` — профиль пользователя (зеркало GitHub, но с модами)
|
||||
- `/org/:login` — профиль организации (если `type: Organization`)
|
||||
- `/mod/:slug` — карточка мода (показывает авторов с ролями)
|
||||
- `/u/:login` - профиль пользователя (зеркало GitHub, но с модами)
|
||||
- `/org/:login` - профиль организации (если `type: Organization`)
|
||||
- `/mod/:slug` - карточка мода (показывает авторов с ролями)
|
||||
|
||||
Все профили **публичны и кэшируются** (ISR в SvelteKit).
|
||||
|
||||
### Что показываем на `/u/:login`
|
||||
|
||||
```
|
||||
[avatar] flashy (@flashy) — "Minecraft modder"
|
||||
[avatar] flashy (@flashy) - "Minecraft modder"
|
||||
bio | 📍 Berlin | 🔗 flashy.dev | Joined 2024
|
||||
|
||||
Stats: 12 mods · 48 releases · 12k downloads (aggregated) · 342 stars (from GH)
|
||||
|
||||
Mods:
|
||||
[sodium-extra] 1.20.1 fabric — ★ 42 — MIT
|
||||
[sodium-extra] 1.20.1 fabric - ★ 42 - MIT
|
||||
[lithium-fork] ...
|
||||
|
||||
Contributions: контрибьютил в 5 чужих модов (через mod_authors)
|
||||
|
|
@ -124,22 +124,22 @@ Links: GitHub → github.com/flashy | Indexium RSS → /u/flashy/feed.xml
|
|||
|
||||
### Крутые идеи (backlog, но заложим)
|
||||
|
||||
- **Profile README** — рендерим `https://github.com/:login/:login/blob/main/README.md` если есть (как GitHub profile README).
|
||||
- **Profile README** - рендерим `https://github.com/:login/:login/blob/main/README.md` если есть (как GitHub profile README).
|
||||
- **Achievements:** `First Mod`, `10k Downloads`, `GPL Defender` (все моды GPL).
|
||||
- **Follow:** подписка на автора (email / webhook) — `POST /u/:login/follow` → уведомляем о новых релизах (через `author_follows` таблицу).
|
||||
- **Follow:** подписка на автора (email / webhook) - `POST /u/:login/follow` → уведомляем о новых релизах (через `author_follows` таблицу).
|
||||
- **Organizations:** группируем моды по `owner` (из `mods.owner`), страница `/org/:owner` агрегирует всех авторов организации.
|
||||
- **Sponsors:** кнопка `Sponsor` → ссылка на `github.com/sponsors/:login` если у автора включён Sponsors.
|
||||
|
||||
---
|
||||
|
||||
## 5. Альтернативы — когда добавлять второй провайдер
|
||||
## 5. Альтернативы - когда добавлять второй провайдер
|
||||
|
||||
| Провайдер | Когда добавлять | Как |
|
||||
|---|---|---|
|
||||
| **Discord OAuth** | Если заведём Discord сервер и хотим связать роли | `GET /auth/discord` → линк к `authors.discord_id`, не как замена GitHub, а как `linked_accounts` |
|
||||
| **Google / Email magic link** | Если появятся читатели-комментаторы без GitHub | Только для `Reader` роли, без права публикации. Публикация всё равно требует GitHub линк (`GET /link/github`) |
|
||||
| **Passkeys / WebAuthn** | Если хотим passwordless для модераторов | Избыточно на MVP |
|
||||
| **Gitea / Codeberg / GitLab** | Если хотим тру-децентрализацию | Добавляем `provider: github|gitlab|codeberg` в `authors`, но каждый — отдельный OAuth. На MVP — только GitHub |
|
||||
| **Gitea / Codeberg / GitLab** | Если хотим тру-децентрализацию | Добавляем `provider: github|gitlab|codeberg` в `authors`, но каждый - отдельный OAuth. На MVP - только GitHub |
|
||||
|
||||
**Архитектура на будущее (не делаем сейчас, но не блокируем):**
|
||||
|
||||
|
|
@ -153,66 +153,66 @@ CREATE TABLE linked_accounts (
|
|||
-- Публикация мода всё равно требует linked GitHub с доступом к репо
|
||||
```
|
||||
|
||||
**Рекомендация:** MVP — **только GitHub**. Второй провайдер — Discord линк **после** первых 500 пользователей, если попросят.
|
||||
**Рекомендация:** MVP - **только GitHub**. Второй провайдер - Discord линк **после** первых 500 пользователей, если попросят.
|
||||
|
||||
---
|
||||
|
||||
## 6. Безопасность и приватность
|
||||
|
||||
- Никаких паролей — нечего утекать.
|
||||
- Никаких паролей - нечего утекать.
|
||||
- `access_token` GitHub храним только в памяти/JWT, не в БД (или encrypted at rest).
|
||||
- Rate limit на `/auth/*` — 10 req/min per IP.
|
||||
- Rate limit на `/auth/*` - 10 req/min per IP.
|
||||
- Удаление аккаунта: `DELETE /me` → удаляем `authors` + `mod_authors`, но `mods` остаются ( orphan → показываем `by @deleted` ), т.к. код уже open source и на GitHub.
|
||||
- GDPR: `GET /me/export` → JSON со всеми данными, `DELETE` — право на забвение (кроме публичных модов).
|
||||
- GDPR: `GET /me/export` → JSON со всеми данными, `DELETE` - право на забвение (кроме публичных модов).
|
||||
|
||||
---
|
||||
|
||||
## 7. Расширенная авторизация — твои идеи (оценка)
|
||||
## 7. Расширенная авторизация - твои идеи (оценка)
|
||||
|
||||
### 7.1 API Keys / PAT — **да, делаем в Phase 1**
|
||||
### 7.1 API Keys / PAT - **да, делаем в Phase 1**
|
||||
Генерация в `/settings/tokens` с кастомными скоупами `read:mods`, `write:mods`, `webhooks:manage`.
|
||||
- Хранение: `personal_access_tokens (id, github_id, token_hash, scopes[], expires_at)` — храним только `SHA256(token)` как у GitHub.
|
||||
- Хранение: `personal_access_tokens (id, github_id, token_hash, scopes[], expires_at)` - храним только `SHA256(token)` как у GitHub.
|
||||
- Зачем: CI/CD (`github actions: indexium publish --token $INDEXIUM_TOKEN`), лаунчеры без браузера.
|
||||
- Риск: утечка → лимит скоупов + `expires_at` 30/90 дней + `last_used_at` + revoke.
|
||||
- **Вердикт:** берём в MVP — 1 таблица + 2 эндпоинта, без OAuth сервера.
|
||||
- **Вердикт:** берём в MVP - 1 таблица + 2 эндпоинта, без OAuth сервера.
|
||||
|
||||
### 7.2 OAuth2 Provider / Device Code Flow (RFC 8628) — **круто, но Phase 2**
|
||||
### 7.2 OAuth2 Provider / Device Code Flow (RFC 8628) - **круто, но Phase 2**
|
||||
Ты предлагаешь сделать Indexium IdP для лаунчеров: лаунчер показывает `ABCD-1234` → юзер на `indexium.example.com/activate` подтверждает.
|
||||
- Плюс: идеален для Prism на Linux/TV/без браузера, как у GitHub CLI (`gh auth login --web`).
|
||||
- Минус: нужно реализовать полноценный Authorization Server (`/oauth/authorize`, `/oauth/token`, `/oauth/device/code`, `/oauth/device/verify`) + consent screen + refresh tokens. Это +2-3 недели.
|
||||
- Альтернатива на MVP: **PAT** — лаунчер просит вставить токен вручную (как `gh` с PAT). UX хуже, но без IdP.
|
||||
- Альтернатива на MVP: **PAT** - лаунчер просит вставить токен вручную (как `gh` с PAT). UX хуже, но без IdP.
|
||||
- **Вердикт:** проектируем сейчас (закладываем `oauth_clients`, `device_codes`), реализуем после PAT когда попросят лаунчеры.
|
||||
|
||||
### 7.3 Discord линк — **да, но как linked_account, не как логин**
|
||||
### 7.3 Discord линк - **да, но как linked_account, не как логин**
|
||||
- Флоу: `GET /auth/discord` → `linked_accounts (github_id, provider='discord', provider_id)` → бот выдаёт `Verified Modder` на сервере Indexium, шлёт DM о релизах.
|
||||
- Не делаем Discord как замену GitHub — публикация всё равно требует GitHub. Это синк ролей, не вход.
|
||||
- Не делаем Discord как замену GitHub - публикация всё равно требует GitHub. Это синк ролей, не вход.
|
||||
- **Вердикт:** делаем после MVP, когда заведём Discord сервер.
|
||||
|
||||
---
|
||||
|
||||
## 8. Фичи профиля — разбор твоих идей
|
||||
## 8. Фичи профиля - разбор твоих идей
|
||||
|
||||
### 8.1 Для разработчиков (оценка)
|
||||
|
||||
| Идея | Оценка | Комментарий |
|
||||
|---|---|---|
|
||||
| **Дашборд аналитики** (скачивания по версиям/лоадерам/OS, краш-логи) | **Phase 2** | Скачивания считаем агрегатом `downloads_daily` (без IP), OS — из `User-Agent` лаунчера если пришлёт. Краш-логи — отдельный `POST /telemetry/crash` с анонимизацией, опционально. |
|
||||
| **Дашборд аналитики** (скачивания по версиям/лоадерам/OS, краш-логи) | **Phase 2** | Скачивания считаем агрегатом `downloads_daily` (без IP), OS - из `User-Agent` лаунчера если пришлёт. Краш-логи - отдельный `POST /telemetry/crash` с анонимизацией, опционально. |
|
||||
| **Организации/команды** (Team CoFH) | **MVP-лайт** | Уже есть `mod_authors` + `mods.owner` (org). Делаем `/org/:login` как агрегатор, `role=maintainer` для команды. Без отдельного `teams` на старте. |
|
||||
| **Спонсорство** (GitHub Sponsors, Patreon, Ko-fi) | **MVP** | Поле `authors.sponsors: JSONB { github, patreon, kofi, bmc }` + кнопки в шапке профиля/мода. Парсим из GitHub `sponsors` API или ручной ввод. |
|
||||
| **Verified + PGP/GPG подпись** | **MVP / Phase 2** | `verified` уже в `mods` — ставим если репо через GitHub App и `license` ok. PGP — показываем `gpg_keys` из GitHub API (`GET /users/:login/gpg_keys`), проверка `.asc` рядом с `.jar` — Phase 2. |
|
||||
| **Verified + PGP/GPG подпись** | **MVP / Phase 2** | `verified` уже в `mods` - ставим если репо через GitHub App и `license` ok. PGP - показываем `gpg_keys` из GitHub API (`GET /users/:login/gpg_keys`), проверка `.asc` рядом с `.jar` - Phase 2. |
|
||||
|
||||
### 8.2 Для игроков
|
||||
|
||||
| Идея | Оценка |
|
||||
|---|---|
|
||||
| **Коллекции / Модпаки** (`My OptiFine Alternatives`) с экспортом в Prism/CurseForge | **Phase 2, хит** | `collections (id, author_id, slug, title, mods[] JSONB, visibility)` + `collection_stars`. Экспорт — `GET /collections/:slug/export?format=prism|packwiz`. Виральная фича. |
|
||||
| **Star / Follow + подписки** (уведомления о релизе под `1.20.1+fabric`) | **MVP-лайт** | `stars (github_id, mod_id)`, `follows (github_id, author_id)` + фильтр `notify_game_version/loader`. Уведомления — сначала in-app + Discord DM, email позже. |
|
||||
| **Коллекции / Модпаки** (`My OptiFine Alternatives`) с экспортом в Prism/CurseForge | **Phase 2, хит** | `collections (id, author_id, slug, title, mods[] JSONB, visibility)` + `collection_stars`. Экспорт - `GET /collections/:slug/export?format=prism|packwiz`. Виральная фича. |
|
||||
| **Star / Follow + подписки** (уведомления о релизе под `1.20.1+fabric`) | **MVP-лайт** | `stars (github_id, mod_id)`, `follows (github_id, author_id)` + фильтр `notify_game_version/loader`. Уведомления - сначала in-app + Discord DM, email позже. |
|
||||
| **Activity Feed** | **Phase 2** | Лента из `webhook_deliveries` + `collections` + `stars` по подпискам. |
|
||||
|
||||
### 8.3 Геймификация и виджет
|
||||
|
||||
- **Бейджи:** `Early Adopter` (id <1000), `Bug Hunter` (репорты), `Top Contributor` (N релизов/мес), `Open Source Veteran` (GitHub age >5 лет через `created_at` из API). Храним `badges (github_id, badge_id)` — выдаём воркером раз в день. Показываем на `/u/:login`.
|
||||
- **Showcase Widget SVG:** `GET /v1/badges/:slug/downloads.svg` и `GET /v1/badges/:slug/version.svg` — генерируем SVG на лету (как `shields.io`), кэш 1h, без JS. Пример: `` — **делаем в MVP**, это маркетинг.
|
||||
- **Бейджи:** `Early Adopter` (id <1000), `Bug Hunter` (репорты), `Top Contributor` (N релизов/мес), `Open Source Veteran` (GitHub age >5 лет через `created_at` из API). Храним `badges (github_id, badge_id)` - выдаём воркером раз в день. Показываем на `/u/:login`.
|
||||
- **Showcase Widget SVG:** `GET /v1/badges/:slug/downloads.svg` и `GET /v1/badges/:slug/version.svg` - генерируем SVG на лету (как `shields.io`), кэш 1h, без JS. Пример: `` - **делаем в MVP**, это маркетинг.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -226,7 +226,7 @@ CREATE TABLE linked_accounts (
|
|||
|
||||
## 10. Что решить сейчас
|
||||
|
||||
1. Подтверди: **PAT в MVP — да?** (я заложил, это быстро).
|
||||
2. Device Flow — **проектируем сейчас, код позже** — ок?
|
||||
3. Коллекции — делать сразу после MVP или откладываем до 500 юзеров?
|
||||
1. Подтверди: **PAT в MVP - да?** (я заложил, это быстро).
|
||||
2. Device Flow - **проектируем сейчас, код позже** - ок?
|
||||
3. Коллекции - делать сразу после MVP или откладываем до 500 юзеров?
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue