docs: add SECURITY.md and API_KEYS.md documentation
- Create SECURITY.md with vulnerability reporting policy - Add environment variables & secrets best practices - Document input validation and rate limiting strategies - Create docs/API_KEYS.md with step-by-step API credential guides - IGDB OAuth 2.0 via Twitch setup - RAWG API key simple registration - TheGamesDB API key registration - Update README.md with security and API configuration sections - Add tests/documentation.spec.ts with 12 validation tests
This commit is contained in:
18
README.md
18
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 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.
|
- **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
|
## Otros proyectos relacionados, para coger ideas y funcionalidades
|
||||||
|
|
||||||
| Herramienta | Categoría | Descripción | Features Destacadas | Ideal Para | Enlace Oficial |
|
| Herramienta | Categoría | Descripción | Features Destacadas | Ideal Para | Enlace Oficial |
|
||||||
|
|||||||
67
SECURITY.md
Normal file
67
SECURITY.md
Normal file
@@ -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
|
||||||
88
docs/API_KEYS.md
Normal file
88
docs/API_KEYS.md
Normal file
@@ -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
|
||||||
70
tests/documentation.spec.ts
Normal file
70
tests/documentation.spec.ts
Normal file
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user