126 lines
7.5 KiB
Markdown
126 lines
7.5 KiB
Markdown
# Task 01: Persistent OAuth-state & Session Store Report
|
||
|
||
**Date:** 2026-08-21
|
||
**Base Commit SHA:** `b2888950cdd2d948c15acf0004a0cdc91eeb70e6`
|
||
**Status:** IMPLEMENTED / PASS
|
||
**Assigned Agent:** Antigravity (Implementation Orchestrator)
|
||
|
||
---
|
||
|
||
## 1. Executive Summary
|
||
|
||
Устранена проблема хранения OAuth-состояний и пользовательских сессий исключительно в памяти одного процесса Node.js (`MemoryOAuthTransactionStore` и `MemorySessionStore`), из-за которой в multi-instance / serverless среде или при перезапуске сервера происходили сбои аутентификации VK ID и сброс активных сессий пользователей.
|
||
|
||
Реализованы персистентные хранилища на базе PostgreSQL / Prisma:
|
||
1. В `prisma/schema.prisma` добавлены модели `OAuthTransaction` (с полями `state`, `codeVerifier`, `redirectTarget`, `createdAt`, `expiresAt`) и `Session` (с полями `sessionId`, `userId`, `user`, `createdAt`, `expiresAt`), а также индексы по `expiresAt` и `userId`.
|
||
2. Создана SQL-миграция `prisma/migrations/20260821120000_persistent_auth_stores/migration.sql`.
|
||
3. В `src/lib/auth/oauth-state.ts` реализован `PrismaOAuthTransactionStore` с атомарной транзакционной операцией `consumeTransaction` (`$transaction` find + delete), предотвращающей race conditions и гарантирующей single-use семантику OAuth state.
|
||
4. В `src/lib/auth/session.ts` реализован `PrismaSessionStore` со строгой валидацией TTL и каскадным удалением сессий при удалении пользователя.
|
||
5. Настроены фабрики `createOAuthTransactionStore()` и `createSessionStore()`: при `STORAGE_DRIVER=memory` или `NODE_ENV=test` используются in-memory реализации, в остальных случаях — Prisma-драйверы.
|
||
6. Снят фатальный запрет на запуск с `MULTI_INSTANCE=true` для Prisma-хранилищ.
|
||
|
||
---
|
||
|
||
## 2. Modified Files
|
||
|
||
| File | Type | Description |
|
||
|------|------|-------------|
|
||
| `prisma/schema.prisma` | DB Schema | Добавлены модели `OAuthTransaction` и `Session`, добавлена связь `sessions` в модель `User`. |
|
||
| `prisma/migrations/20260821120000_persistent_auth_stores/migration.sql` | Migration | SQL-миграция создания таблиц и индексов для `OAuthTransaction` и `Session`. |
|
||
| `src/lib/auth/oauth-state.ts` | Auth | Реализован `PrismaOAuthTransactionStore`, селектор `createOAuthTransactionStore`, функции установки стора. |
|
||
| `src/lib/auth/session.ts` | Auth | Реализован `PrismaSessionStore`, селектор `createSessionStore`, функции установки стора. |
|
||
| `tests/persistent-auth-stores.test.ts` | Tests (NEW) | Набор тестов на single-use конкурентность, multi-instance обмен, TTL, жизненный цикл и селекторы драйверов (11 тестов). |
|
||
|
||
---
|
||
|
||
## 3. Database Migration & Execution Order
|
||
|
||
### SQL Migration Content:
|
||
```sql
|
||
-- CreateTable
|
||
CREATE TABLE "OAuthTransaction" (
|
||
"id" TEXT NOT NULL,
|
||
"state" TEXT NOT NULL,
|
||
"codeVerifier" TEXT NOT NULL,
|
||
"redirectTarget" TEXT,
|
||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||
|
||
CONSTRAINT "OAuthTransaction_pkey" PRIMARY KEY ("id")
|
||
);
|
||
|
||
-- CreateTable
|
||
CREATE TABLE "Session" (
|
||
"id" TEXT NOT NULL,
|
||
"sessionId" TEXT NOT NULL,
|
||
"userId" TEXT NOT NULL,
|
||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||
|
||
CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
|
||
);
|
||
|
||
-- CreateIndex
|
||
CREATE UNIQUE INDEX "OAuthTransaction_state_key" ON "OAuthTransaction"("state");
|
||
|
||
-- CreateIndex
|
||
CREATE INDEX "OAuthTransaction_expiresAt_idx" ON "OAuthTransaction"("expiresAt");
|
||
|
||
-- CreateIndex
|
||
CREATE UNIQUE INDEX "Session_sessionId_key" ON "Session"("sessionId");
|
||
|
||
-- CreateIndex
|
||
CREATE INDEX "Session_expiresAt_idx" ON "Session"("expiresAt");
|
||
|
||
-- CreateIndex
|
||
CREATE INDEX "Session_userId_idx" ON "Session"("userId");
|
||
|
||
-- AddForeignKey
|
||
ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||
```
|
||
|
||
### Порядок применения на существующей БД:
|
||
1. Выполнить `npx prisma migrate deploy` или применить приведенный SQL-скрипт в PostgreSQL.
|
||
2. Никаких изменений существующих данных `User`, `Giveaway`, `Participant` не требуется (обратно-совместимо).
|
||
|
||
---
|
||
|
||
## 4. Verification Evidence & Test Gate
|
||
|
||
Фактически выполненные команды:
|
||
|
||
```text
|
||
npx prisma generate -> EXIT 0 (Prisma Client v5.22.0 generated with OAuthTransaction and Session models)
|
||
npx tsc --noEmit -> EXIT 0 (Clean TypeScript check, 0 errors)
|
||
npm test -> EXIT 0 (50 test suites, 295 passed, 0 failed)
|
||
npm run lint -> EXIT 0 (Next.js ESLint passed clean)
|
||
npm run build -> EXIT 0 (Next.js production build compiled successfully)
|
||
```
|
||
|
||
### Summary of New Tests (`tests/persistent-auth-stores.test.ts`):
|
||
- `concurrent consumeTransaction calls on same state yield exactly 1 success and N-1 UnauthorizedErrors` → **PASS**
|
||
- `state created by instance A can be consumed by instance B sharing underlying state` → **PASS**
|
||
- `expired OAuth state is rejected with UnauthorizedError` → **PASS**
|
||
- `invalidateTransaction removes state explicitly` → **PASS**
|
||
- `expired Session is rejected and returns null from getSession` → **PASS**
|
||
- `createSession, getSession and destroySession work correctly` → **PASS**
|
||
- `session survives re-creation of store instance when sharing storage` → **PASS**
|
||
- `Memory stores throw fatal error when MULTI_INSTANCE=true` → **PASS**
|
||
- `Prisma stores do NOT throw when MULTI_INSTANCE=true` → **PASS**
|
||
- `createOAuthTransactionStore selects Memory in test/memory mode, Prisma in production mode` → **PASS**
|
||
- `createSessionStore selects Memory in test/memory mode, Prisma in production mode` → **PASS**
|
||
|
||
---
|
||
|
||
## 5. Core Invariants & Security
|
||
|
||
- **Randomizer / Audit Proof Invariants:** `HMAC_SHA256_FY_V1`, `DeterministicHmacStream`, `executeDeterministicDrawV1`, `verifyDrawResult` сохранены без изменений.
|
||
- **PKCE / State Invariants:** S256 code challenge, криптостойкие случайные токены (CSPRNG) сохранены.
|
||
- **Single-Use Invariant:** Гарантируется как в памяти, так и в базе данных через атомарную транзакцию `$transaction`.
|
||
|
||
---
|
||
|
||
## 6. UNVERIFIED Assertions & Tech Debt
|
||
|
||
1. **UNVERIFIED: Live PostgreSQL CI execution for Prisma auth stores:**
|
||
- В текущем тестовом окружении автоматизированные тесты Vitest выполняются с `STORAGE_DRIVER=memory` и `NODE_ENV=test`. Хотя `PrismaOAuthTransactionStore` и `PrismaSessionStore` скомпилированы и проверены, сквозной прогон с живой базой PostgreSQL в Vitest требует отдельного интеграционного сьюта.
|