diff --git a/README.md b/README.md index 1855d41..c9e3597 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,24 @@ Quasar es una aplicación web para al gestión de una biblioteca personal de vid - **Búsqueda Avanzada**: Filtros para encontrar juegos rápidamente según diferentes criterios. - **Búsqueda de Metadatos**: Integración con APIs externas para obtener información adicional sobre los juegos. +## Seguridad + +Para información sobre políticas de seguridad, vulnerabilidades y prácticas recomendadas, consulta [SECURITY.md](SECURITY.md). + +## Configuración de APIs + +Quasar se integra con múltiples servicios de metadatos: IGDB, RAWG y TheGamesDB. Para obtener credenciales y configurar estas APIs, consulta [docs/API_KEYS.md](docs/API_KEYS.md). + +### Variables de Entorno + +La aplicación requiere variables de entorno sensibles (como claves de API y credenciales de base de datos). +Usa `.env.local` o `.env.{NODE_ENV}.local` para desarrollo. **Nunca** hagas commit de archivos `.env` al repositorio: + +```bash +cp .env.example .env.local +# Edita .env.local con tus credenciales +``` + ## Otros proyectos relacionados, para coger ideas y funcionalidades | Herramienta | Categoría | Descripción | Features Destacadas | Ideal Para | Enlace Oficial | diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..274b4bf --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,67 @@ +# Security Policy + +## Reporting Security Vulnerabilities + +If you discover a security vulnerability in Quasar, please email security@quasar.local with: + +- Description of the vulnerability +- Steps to reproduce +- Potential impact +- Suggested fix (if any) + +We'll acknowledge your report within 48 hours and work on a fix. + +## Environment Variables & Secrets + +**IMPORTANT:** Never commit `.env` files to the repository. + +### Sensitive Variables + +- `IGDB_CLIENT_ID`, `IGDB_CLIENT_SECRET` — Twitch OAuth credentials +- `RAWG_API_KEY` — RAWG API key (rate limited) +- `THEGAMESDB_API_KEY` — TheGamesDB key +- `DATABASE_URL` — SQLite file path (contains password if using remote DB) + +### Best Practices + +1. Use `.env.local` or `.env.{NODE_ENV}.local` for local development +2. Never log or print secrets +3. Use GitHub/Gitea Secrets for CI/CD pipelines +4. Rotate keys regularly +5. Use separate keys for development, staging, production + +## Input Validation & Sanitization + +All user inputs are validated using **Zod** schemas: + +- Backend: `src/validators/*.ts` define strict schemas +- Frontend: React Hook Form + Zod validation +- Game titles, ROM file paths, and user uploads are sanitized + +## Rate Limiting + +API calls to metadata services are rate-limited: + +- IGDB: 4 requests/second +- RAWG: 20 requests/second (free tier) +- TheGamesDB: 1 request/second + +## Database Security + +SQLite is used for MVP. For production: + +- Consider PostgreSQL or MySQL +- Enable encrypted connections (SSL/TLS) +- Use connection pooling (current: Prisma with pool settings) +- Regular backups + +## CORS & CSP + +Configure appropriate CORS headers in backend: + +- Frontend origin: `http://localhost:3000` (dev), `https://yourdomain.com` (prod) +- Content Security Policy headers recommended for production + +## Changelog + +- v1.0.0 (2026-02-12): Initial security guidelines diff --git a/docs/API_KEYS.md b/docs/API_KEYS.md new file mode 100644 index 0000000..abf3a0f --- /dev/null +++ b/docs/API_KEYS.md @@ -0,0 +1,88 @@ +# Obtaining API Keys + +This guide explains how to get credentials for each metadata service. + +## IGDB (Internet Game Database) + +IGDB uses **OAuth 2.0 via Twitch**. Steps: + +1. Go to [Twitch Developer Console](https://dev.twitch.tv/console/apps) +2. Sign in with your Twitch account (create one if needed) +3. Click "Create Application" + - Name: "Quasar" (or your app name) + - Category: Select relevant category + - Accept terms, click Create +4. You'll see: + - **Client ID** — Copy this + - Click "New Secret" to generate **Client Secret** — Copy this +5. Go to Settings → OAuth Redirect URLs + - Add: `http://localhost:3000/oauth/callback` (development) + - For production: `https://yourdomain.com/oauth/callback` +6. In your `.env` file: + ``` + IGDB_CLIENT_ID=your_client_id + IGDB_CLIENT_SECRET=your_client_secret + ``` +7. Start Quasar, it will use IGDB automatically + +**Rate Limit:** 4 requests/second + +## RAWG (Rawg.io) + +RAWG has a simpler **API Key** approach: + +1. Go to [RAWG Settings](https://rawg.io/settings/account) +2. Sign up if needed, then login +3. Find "API Key" section +4. Click "Create new key" (if needed) or copy existing key +5. In your `.env` file: + ``` + RAWG_API_KEY=your_api_key_here + ``` +6. Start Quasar + +**Rate Limit:** 20 requests/second (free tier) + +**Note:** RAWG requires attribution in UI (include "Powered by RAWG" somewhere visible) + +## TheGamesDB (thegamesdb.net) + +TheGamesDB uses a simple **API Key**: + +1. Go to [TheGamesDB API](https://thegamesdb.net/api) +2. Find "API Key" section (free registration required) +3. Register or login +4. Copy your API key +5. In your `.env` file: + ``` + THEGAMESDB_API_KEY=your_api_key_here + ``` +6. Start Quasar + +**Rate Limit:** 1 request/second (free tier) + +## Testing Without Real Keys + +For development/testing: + +- Leave API keys as `your_*_here` in `.env.local` +- Quasar will gracefully degrade and show limited metadata +- Frontend will still work with manual game entry + +## Production Deployment + +For production: + +1. Generate new keys on each service (don't reuse dev keys) +2. Store keys in **Gitea Secrets** (for CI/CD) +3. Or use environment variables on your hosting provider +4. Rotate keys every 3 months +5. Monitor rate limits in service dashboards + +## Troubleshooting + +**"IGDB_CLIENT_ID not found"** → Check `.env` file exists and has correct format + +**"429 Too Many Requests"** → Rate limit exceeded, wait and retry + +**"Invalid API Key"** → Copy key exactly (no spaces), verify it's active on service website diff --git a/tests/documentation.spec.ts b/tests/documentation.spec.ts new file mode 100644 index 0000000..01872fc --- /dev/null +++ b/tests/documentation.spec.ts @@ -0,0 +1,70 @@ +import { describe, it, expect } from 'vitest'; +import { readFileSync } from 'fs'; +import { existsSync } from 'fs'; + +describe('Documentation - Security and API Keys', () => { + // SECURITY.md tests + it('SECURITY.md exists and contains "Reporting Security Vulnerabilities"', () => { + expect(existsSync('./SECURITY.md')).toBe(true); + const content = readFileSync('./SECURITY.md', 'utf-8'); + expect(content).toContain('Reporting Security Vulnerabilities'); + }); + + it('SECURITY.md contains "Environment Variables & Secrets" section', () => { + const content = readFileSync('./SECURITY.md', 'utf-8'); + expect(content).toContain('Environment Variables & Secrets'); + }); + + it('SECURITY.md contains "Input Validation & Sanitization" section', () => { + const content = readFileSync('./SECURITY.md', 'utf-8'); + expect(content).toContain('Input Validation & Sanitization'); + }); + + it('SECURITY.md contains "Rate Limiting" section', () => { + const content = readFileSync('./SECURITY.md', 'utf-8'); + expect(content).toContain('Rate Limiting'); + }); + + it('SECURITY.md contains "Database Security" section', () => { + const content = readFileSync('./SECURITY.md', 'utf-8'); + expect(content).toContain('Database Security'); + }); + + // docs/API_KEYS.md tests + it('docs/API_KEYS.md exists and contains "IGDB" section', () => { + expect(existsSync('./docs/API_KEYS.md')).toBe(true); + const content = readFileSync('./docs/API_KEYS.md', 'utf-8'); + expect(content).toContain('IGDB'); + }); + + it('docs/API_KEYS.md contains "RAWG" section', () => { + const content = readFileSync('./docs/API_KEYS.md', 'utf-8'); + expect(content).toContain('RAWG'); + }); + + it('docs/API_KEYS.md contains "TheGamesDB" section', () => { + const content = readFileSync('./docs/API_KEYS.md', 'utf-8'); + expect(content).toContain('TheGamesDB'); + }); + + it('docs/API_KEYS.md contains step-by-step instructions', () => { + const content = readFileSync('./docs/API_KEYS.md', 'utf-8'); + expect(content).toMatch(/steps?|step-by-step|guide/i); + }); + + // README.md tests + it('README.md contains link to SECURITY.md', () => { + const content = readFileSync('./README.md', 'utf-8'); + expect(content).toMatch(/SECURITY\.md|security/i); + }); + + it('README.md contains link to docs/API_KEYS.md', () => { + const content = readFileSync('./README.md', 'utf-8'); + expect(content).toMatch(/API_KEYS\.md|api.*key|obtaining.*key/i); + }); + + it('README.md mentions .env.example template', () => { + const content = readFileSync('./README.md', 'utf-8'); + expect(content).toMatch(/\.env|environment.*variable/i); + }); +});