254 lines
12 KiB
Markdown
254 lines
12 KiB
Markdown
# Email OTP Auth Implementation Plan
|
||
|
||
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||
|
||
**Goal:** Включить реальный вход в приложение по одноразовому пинкоду из письма через Supabase только для заранее заведенных пользователей с ролями `admin` и `logistician`.
|
||
|
||
**Architecture:** Фронтенд остается на текущем `AuthContext` и `OtpLoginForm`, но demo-подсказки перестают влиять на рабочий auth flow. Supabase Auth отвечает за отправку email OTP и создание session, а приложение после подтверждения кода читает профиль только из `public.users` и `roles`. Новые пользователи через форму не создаются: доступ есть только у тех email, которые заранее заведены в `auth.users` и синхронизированы с `public.users`.
|
||
|
||
**Tech Stack:** React 18, Vite, Supabase JS v2, Supabase Auth Email OTP, Supabase SQL, Vitest.
|
||
|
||
---
|
||
|
||
## File Structure
|
||
|
||
- Modify: `src/context/AuthContext.jsx` — отключить автосоздание пользователей, ужесточить рабочий OTP-flow, обработать отсутствие профиля.
|
||
- Modify: `src/context/AuthContext.test.js` — покрыть новую логику для рабочего режима и сохранить demo-фолбэк.
|
||
- Modify: `src/components/auth/OtpLoginForm.jsx` — убрать рабочую зависимость от выбора demo-роли и уточнить тексты для OTP-входа.
|
||
- Modify: `src/components/auth/OtpLoginForm.test.jsx` — скорректировать ожидания под новый рабочий сценарий.
|
||
- Modify: `src/pages/LoginPage.jsx` — оставить линейный сценарий `email -> код -> вход`, без влияния роли в production-режиме.
|
||
- Create: `docs/operations/supabase-email-otp-auth.md` — пошаговая настройка Supabase Auth, пользователей и ролей.
|
||
- Reference: `src/supabaseClient.js` — проверить, что frontend env уже подаются корректно.
|
||
- Reference: `supabase/schema.sql` — использовать существующие `roles`, `users`, `handle_new_user()` и не дублировать auth-механику.
|
||
|
||
## Chunk 1: Frontend Auth Flow Tightening
|
||
|
||
### Task 1: Зафиксировать ожидаемое поведение auth-flow тестами
|
||
|
||
**Files:**
|
||
- Modify: `src/context/AuthContext.test.js`
|
||
- Modify: `src/components/auth/OtpLoginForm.test.jsx`
|
||
|
||
- [ ] **Step 1: Add failing tests for production OTP constraints**
|
||
|
||
Добавить проверки на:
|
||
- в рабочем режиме email не подменяется demo-значением;
|
||
- форма не подсказывает выбор роли как источник прав;
|
||
- текст формы объясняет вход по email-коду;
|
||
- demo-режим остается рабочим fallback.
|
||
|
||
- [ ] **Step 2: Run targeted tests to verify they fail**
|
||
|
||
Run: `npm test -- src/context/AuthContext.test.js src/components/auth/OtpLoginForm.test.jsx`
|
||
Expected: FAIL on outdated demo-oriented behavior.
|
||
|
||
- [ ] **Step 3: Keep existing demo tests if still valid**
|
||
|
||
Если текущие тесты про `resolveDemoUser` и `resolveLoginEmail` остаются полезными, сохранить их и добавить рядом production-specific expectations вместо полной замены.
|
||
|
||
- [ ] **Step 4: Re-run targeted tests after each test update**
|
||
|
||
Run: `npm test -- src/context/AuthContext.test.js src/components/auth/OtpLoginForm.test.jsx`
|
||
Expected: FAIL only for not-yet-implemented UI/auth changes.
|
||
|
||
### Task 2: Отключить саморегистрацию через OTP-форму
|
||
|
||
**Files:**
|
||
- Modify: `src/context/AuthContext.jsx`
|
||
- Modify: `src/pages/LoginPage.jsx`
|
||
|
||
- [ ] **Step 1: Write the minimal production auth contract**
|
||
|
||
В `AuthContext` зафиксировать:
|
||
- `requestOtp()` вызывает `supabase.auth.signInWithOtp`
|
||
- `options.shouldCreateUser` больше не передается как `true`
|
||
- `pendingEmail` остается текущим entered email
|
||
- demo-mode behavior не ломается
|
||
|
||
- [ ] **Step 2: Make the implementation minimal**
|
||
|
||
Изменить рабочий режим так, чтобы:
|
||
- логин шел только для уже существующего email;
|
||
- новая учетная запись не создавалась автоматически;
|
||
- текст ошибки из Supabase пробрасывался наверх без маскировки.
|
||
|
||
- [ ] **Step 3: Verify the targeted auth tests**
|
||
|
||
Run: `npm test -- src/context/AuthContext.test.js`
|
||
Expected: PASS
|
||
|
||
- [ ] **Step 4: Sanity-check login screen code**
|
||
|
||
Убедиться, что `src/pages/LoginPage.jsx` не передает рабочую роль из UI в production-сценарий и не связывает доступ с выбором `roleHint`.
|
||
|
||
### Task 3: Привести OTP-форму к рабочему сценарию доступа по списку email
|
||
|
||
**Files:**
|
||
- Modify: `src/components/auth/OtpLoginForm.jsx`
|
||
- Modify: `src/components/auth/OtpLoginForm.test.jsx`
|
||
|
||
- [ ] **Step 1: Remove role selection from the production story**
|
||
|
||
Сохранить `roleHint` только как demo-only affordance, но не показывать пользователю, что роль задается при реальном входе.
|
||
|
||
- [ ] **Step 2: Update copy for real OTP flow**
|
||
|
||
Тексты формы должны говорить:
|
||
- email вводится пользователем;
|
||
- код приходит на почту;
|
||
- доступ определяется учетной записью в системе, а не выбором роли.
|
||
|
||
- [ ] **Step 3: Keep demo fallback explicit but isolated**
|
||
|
||
Если `isDemoMode === true`, оставить подсказку и поведение demo-режима, но визуально отделить его от реального сценария.
|
||
|
||
- [ ] **Step 4: Re-run form tests**
|
||
|
||
Run: `npm test -- src/components/auth/OtpLoginForm.test.jsx`
|
||
Expected: PASS
|
||
|
||
## Chunk 2: Profile Resolution And Access Guard
|
||
|
||
### Task 4: Обработать сценарий “auth user есть, профиля в public.users нет”
|
||
|
||
**Files:**
|
||
- Modify: `src/context/AuthContext.jsx`
|
||
- Modify: `src/context/AuthContext.test.js`
|
||
|
||
- [ ] **Step 1: Add a failing test for missing profile**
|
||
|
||
Проверить сценарий:
|
||
- Supabase session создана;
|
||
- запрос в `public.users` не нашел профиль или вернул ошибку;
|
||
- пользователь не считается успешно авторизованным в приложении без понятной реакции.
|
||
|
||
- [ ] **Step 2: Implement minimal guard**
|
||
|
||
В `onAuthStateChange`:
|
||
- если профиль не найден, не выдавать рабочий `user` в приложение;
|
||
- сохранять понятную ошибку или вынуждать повторный вход;
|
||
- не падать молча.
|
||
|
||
- [ ] **Step 3: Verify tests**
|
||
|
||
Run: `npm test -- src/context/AuthContext.test.js`
|
||
Expected: PASS
|
||
|
||
### Task 5: Уточнить контракт роли и профиля
|
||
|
||
**Files:**
|
||
- Modify: `src/context/AuthContext.jsx`
|
||
- Reference: `supabase/schema.sql`
|
||
|
||
- [ ] **Step 1: Verify role source**
|
||
|
||
Роль должна читаться только из `public.users -> roles(name)`.
|
||
|
||
- [ ] **Step 2: Keep fallback conservative**
|
||
|
||
Если роль не пришла, не повышать доступ по умолчанию; допустим fallback только в безопасный минимум (`manager`) либо ошибка доступа, в зависимости от того, что окажется проще и безопаснее после чтения кода.
|
||
|
||
- [ ] **Step 3: Re-run auth tests**
|
||
|
||
Run: `npm test -- src/context/AuthContext.test.js`
|
||
Expected: PASS
|
||
|
||
## Chunk 3: Supabase Setup And Data Seeding
|
||
|
||
### Task 6: Подготовить рабочую инструкцию по настройке Supabase Auth
|
||
|
||
**Files:**
|
||
- Create: `docs/operations/supabase-email-otp-auth.md`
|
||
|
||
- [ ] **Step 1: Document dashboard settings**
|
||
|
||
Описать:
|
||
- где включить email OTP;
|
||
- что проверить в email auth settings;
|
||
- что self-signup должен быть выключен, если используется whitelist-only access.
|
||
|
||
- [ ] **Step 2: Document required frontend env**
|
||
|
||
Зафиксировать:
|
||
- `VITE_SUPABASE_URL`
|
||
- `VITE_SUPABASE_ANON_KEY`
|
||
|
||
- [ ] **Step 3: Document function secrets separately**
|
||
|
||
Коротко пояснить:
|
||
- frontend env идут в `.env.local`;
|
||
- Edge Function secrets задаются через Supabase secrets/dashboard;
|
||
- для OTP-входа frontend не использует `SUPABASE_SERVICE_ROLE_KEY`.
|
||
|
||
### Task 7: Подготовить SQL/операционную часть для двух пользователей
|
||
|
||
**Files:**
|
||
- Create or extend: `docs/operations/supabase-email-otp-auth.md`
|
||
- Reference: `supabase/schema.sql`
|
||
|
||
- [ ] **Step 1: Describe manual user creation in Supabase Auth**
|
||
|
||
Нужно заранее создать:
|
||
- `skylanguage@yandex.ru`
|
||
- `mk7029953@yandex.ru`
|
||
|
||
- [ ] **Step 2: Document role binding in public.users**
|
||
|
||
Подготовить SQL-пример или пошаговое описание, как этим пользователям назначить:
|
||
- `skylanguage@yandex.ru` -> `admin`
|
||
- `mk7029953@yandex.ru` -> `logistician`
|
||
|
||
- [ ] **Step 3: Check trigger compatibility**
|
||
|
||
Убедиться по схеме, что `handle_new_user()`:
|
||
- не ломает вручную заведенных пользователей;
|
||
- корректно пишет профиль в `public.users`;
|
||
- использует `raw_user_meta_data ->> 'role'`, если оно есть.
|
||
|
||
- [ ] **Step 4: Add operator verification checklist**
|
||
|
||
Проверка после настройки:
|
||
- код приходит на обе почты;
|
||
- `admin` видит админский раздел;
|
||
- `logistician` видит логистический контур;
|
||
- неизвестный email не получает доступ.
|
||
|
||
## Chunk 4: End-To-End Verification
|
||
|
||
### Task 8: Полная проверка реализации
|
||
|
||
**Files:**
|
||
- Reference: `src/context/AuthContext.jsx`
|
||
- Reference: `src/components/auth/OtpLoginForm.jsx`
|
||
- Reference: `src/pages/LoginPage.jsx`
|
||
- Reference: `docs/operations/supabase-email-otp-auth.md`
|
||
|
||
- [ ] **Step 1: Run focused frontend tests**
|
||
|
||
Run: `npm test -- src/context/AuthContext.test.js src/components/auth/OtpLoginForm.test.jsx`
|
||
Expected: PASS
|
||
|
||
- [ ] **Step 2: Run full test suite**
|
||
|
||
Run: `npm test`
|
||
Expected: PASS
|
||
|
||
- [ ] **Step 3: Run linter**
|
||
|
||
Run: `npm run lint`
|
||
Expected: PASS
|
||
|
||
- [ ] **Step 4: Run production build**
|
||
|
||
Run: `npm run build`
|
||
Expected: PASS
|
||
|
||
- [ ] **Step 5: Manual Supabase validation**
|
||
|
||
Проверить руками:
|
||
- отправка кода на `skylanguage@yandex.ru`
|
||
- отправка кода на `mk7029953@yandex.ru`
|
||
- успешный вход по пинкоду
|
||
- корректная роль после входа
|
||
- отказ для email вне whitelist
|