# Comment API iskeleti Node.js + Express + Socket.io + Supabase + Redis tabanlı login/register API iskeleti. Güvenlik odaklı, host allowlist, rate limit, admin rol kilidi ve Redis oturum yönetimi içerir. Docker-compose ile Redis + API birlikte ayağa kalkar. ## Dizın yapısı ``` src/ app.js // Express app server.js // HTTP + Socket.io bootstrap config/ env.js // .env yükleme ve parse supabase.js // Supabase client redis.js // Redis client middleware/ authRequired.js adminRequired.js rateLimit.js hostOnly.js errorHandler.js routes/ auth.routes.js health.routes.js controllers/ auth.controller.js services/ auth.service.js session.service.js adminLock.service.js utils/ crypto.js // bcrypt + JWT yardımcıları response.js // standart JSON çıktı ``` ## Çalıştırma 1. Bağımlılıklar: ```bash npm install ``` > Not: `metascraper` bağımlılığı özel git repo’dan gelir (`git+https://gitea.wisecolt-panda.net/wisecolt/metascraper.git`). Erişim için uygun token/SSH anahtarı olan bir ortamda `npm install` çalıştırın. > Docker build için base imaja `git` eklenmiştir; özel repo erişimi için gerekirse `GIT_SSH_COMMAND` veya `GITHUB_TOKEN` benzeri auth ekleyin. 2. Ortam değişkenleri: `.env.example` dosyasını `.env` olarak kopyalayıp doldurun. 3. Geliştirme: ```bash npm run dev ``` 4. Üretim: ```bash npm start ``` ## Docker Yerelde (dev, hot-reload) Redis ile birlikte çalıştırmak için: ```bash docker compose -f docker-compose.dev.yml up --build # durdurmak için docker compose -f docker-compose.dev.yml down ``` - API: `http://localhost:${PORT}` - Redis: `redis://localhost:6379` (container içinde `redis://redis:6379`) ### Prod modunda Docker 1. `.env` içinde `NODE_ENV=production` ve mümkünse `COOKIE_SECURE=true`, `TRUST_PROXY=loopback` (veya proxy sayısı) olarak ayarlayın. 2. Çalıştırın: ```bash docker compose up --build -d ``` 3. Durdurun: ```bash docker compose down ``` ## .env örneği ``` NODE_ENV=development PORT=3000 TRUST_PROXY=loopback SUPABASE_URL=https://YOUR-SUPABASE-PROJECT.supabase.co SUPABASE_SERVICE_ROLE_KEY=YOUR_SUPABASE_SERVICE_ROLE_KEY REDIS_URL=redis://redis:6379 JWT_SECRET=supersecretjwt JWT_EXPIRES_IN=7d COOKIE_NAME=sid COOKIE_SECURE=false ADMIN_EMAIL=admin@example.com ADMIN_USERNAME=admin ADMIN_NAME=Admin User ADMIN_ROLE_LOCK=true HOST_ONLY_ALLOWLIST=127.0.0.1,::1 RATE_LIMIT_WINDOW_MS=900000 RATE_LIMIT_MAX=100 LOGIN_RATE_LIMIT_WINDOW_MS=60000 LOGIN_RATE_LIMIT_MAX=5 ``` ## Endpointler - `GET /health` — `{ ok: true, time }` - `POST /auth/register` — body `{ email, name, username, password }` - `POST /auth/login` — body `{ emailOrUsername, password }` - `POST /auth/logout` — cookie tabanlı JWT siler - `GET /auth/me` — aktif kullanıcıyı döndürür - `POST /auth/refresh` — yeni JWT üretir (iskelet) - `POST /meta/scrape` — body `{ url }`, destekli domainler: `netflix.com`, `primevideo.com` (yalnızca `role: user`) - Başarılı sorgu Supabase `media_data` tablosuna kaydedilir: `media_id, media_name, year, type(movie/tv), thumbnail_url, info, genre, media_provider (netflix/primevideo)` ### Postman örnekleri **Register** ``` POST {{baseUrl}}/auth/register Content-Type: application/json Body: { "email": "user@example.com", "name": "Test User", "username": "testuser", "password": "password123" } ``` **200**: ``` { "user": { "id": "...", "email": "user@example.com", "name": "Test User", "username": "testuser", "roleEffective": "user" } } ``` **Login** ``` POST {{baseUrl}}/auth/login Content-Type: application/json Body: { "emailOrUsername": "testuser", "password": "password123" } ``` **200**: ``` { "user": { "id": "...", "email": "user@example.com", "name": "Test User", "username": "testuser", "roleEffective": "user" } } ``` JWT otomatik olarak `Cookie: sid=` olarak set edilir. **Me** ``` GET {{baseUrl}}/auth/me Cookie: sid= ``` **200**: ``` { "user": { "id": "...", "email": "...", "username": "...", "roleEffective": "user" } } ``` **Logout** ``` POST {{baseUrl}}/auth/logout Cookie: sid= ``` **200**: ``` { "message": "Çıkış yapıldı" } ``` **Meta scrape (Netflix/Prime)** ``` POST {{baseUrl}}/meta/scrape Content-Type: application/json Cookie: sid= # roleEffective=user Body: { "url": "https://www.netflix.com/tr/title/81507921" } ``` Prime örneği: ``` { "url": "https://www.primevideo.com/-/tr/detail/0NHIN3TGAI9L7VZ45RS52RHUPL/ref=share_ios_movie" } ``` **200** (örnek): ``` { "provider": "netflix", "type": "movie", "media": { "url": "https://www.netflix.com/title/82123114", "id": "82123114", "name": "ONE SHOT with Ed Sheeran", "year": "2025", "seasons": null, "thumbnail": "...", "info": "...", "genre": "..." }, "saved": { "media_id": "82123114", "media_name": "ONE SHOT with Ed Sheeran", "year": "2025", "type": "movie", "thumbnail_url": "...", "info": "...", "genre": "...", "media_provider": "netflix", "created_at": "..." } } ``` Hata çıktıları tek tip: ``` { "error": { "code": "ERROR_CODE", "message": "Açıklama", "details": [...] } } ``` ## Supabase şeması (SQL) ```sql create extension if not exists "pgcrypto"; create table if not exists public.users ( id uuid primary key default gen_random_uuid(), email text unique not null, name text not null, username text unique not null, password_hash text not null, role text not null default 'user' check (role in ('user','admin')), created_at timestamp with time zone default now() ); create table if not exists public.media_data ( media_id varchar, media_name text, year numeric, type text, thumbnail_url varchar, info text, genre text, media_provider text, created_at timestamp with time zone default now() ); ``` > Admin rol kilidi: Sadece `.env` içindeki `ADMIN_EMAIL` ve `ADMIN_USERNAME` ile birebir eşleşen kullanıcı API tarafından `admin` kabul edilir. Supabase üzerinde rol elle değiştirilse bile etkisizdir. ## Güvenlik notları - `hostOnly` middleware ve CORS sadece allowlist’teki host/ip erişimine izin verir. - Global rate limit + login özel rate limit aktiftir. - JWT httpOnly cookie olarak set edilir; Redis içinde `session:{jti}` anahtarıyla oturum metadata tutulur. ## Socket.io - Handshake sırasında cookie’deki JWT doğrulanır, Redis oturum kontrol edilir. - Başarılı bağlantıda `welcome` eventi döner. - Örnek ping/pong: - Client: `socket.emit('ping')` - Server cevabı: `pong` içinde zaman damgası. ## Docker notları - `docker-compose.yml` içinde `api` servisi `redis` servisine bağımlıdır. - Geliştirme için kod klasörü container içine mount edilerek canlı yenileme (`npm run dev`) sağlanır. - Sağlık kontrolü: container içinde `GET /health` çağrısı ile yapılır. ## Yapılandırılabilir noktalar - Rate limit penceresi ve max değerleri `.env` ile ayarlanır. - `HOST_ONLY_ALLOWLIST` ile izin verilen host/ip listesi yönetilir. - `ADMIN_ROLE_LOCK` kapatılmadıkça tek admin `.env`’deki kullanıcıdır. ## TODO / Genişletme fikirleri - Refresh token için ayrı bir anahtar ve whitelist mekanizması. - Audit logları (morgan loglarını dosyaya yazma). - Supabase RPC veya policy’lerle ek güvenlik. - Testler (Jest/Supertest) ve CI pipeline’ı.