# Plantilla Nuxt + Authentik Proxy

Plantilla base para aplicaciones Nuxt 4 con autenticación mediante Authentik Proxy Outpost.

## Estructura del Proyecto

```
.
├── nuxt4/              # Aplicación Nuxt 4
├── docker-compose.yml  # Configuración de despliegue
└── .gitea/            # Gitea Actions CI/CD
```

## Características

- ✅ Nuxt 4
- ✅ Autenticación con Authentik Proxy Outpost
- ✅ Docker y Docker Compose
- ✅ CI/CD con Gitea Actions
- ✅ Traefik para proxy reverso y SSL
- ✅ Claude Code hooks para monitoreo de Actions
- ✅ Sin configuración OAuth necesaria (todo manejado por el proxy)

## Desarrollo Local

```bash
cd nuxt4
npm install
npm run dev
```

## Claude Code Hooks

Este proyecto incluye hooks de Claude Code para monitorear automáticamente las Gitea Actions.

### Configuración Rápida:

1. **Crear token de Gitea:**
   - Ve a tu instancia de Gitea → Settings → Applications
   - Genera un token con permisos `repo`

2. **Configurar token:**
   ```bash
   echo "export GITEA_TOKEN='tu_token_aqui'" >> ~/.bashrc
   source ~/.bashrc
   ```

3. **Listo!** Cuando Claude Code haga `git push`, automáticamente:
   - Monitoreará la Gitea Action
   - Te notificará cuando termine con información detallada
   - Te mostrará un link directo a los logs

📖 Ver documentación completa en [`.claude/hooks/README.md`](.claude/hooks/README.md)

## Arquitectura de Componentes

### Componentes de Autenticación

La aplicación utiliza componentes Vue modulares para manejar la autenticación:

#### 1. **UserAvatar** (`app/components/auth/UserAvatar.vue`)
- Muestra el avatar del usuario (generado por UI Avatars)
- Información básica: nombre/username, email y UID
- Se renderiza solo cuando el usuario está autenticado

#### 2. **UserMetadata** (`app/components/auth/UserMetadata.vue`)
- Card detallada con todos los metadatos del usuario
- Muestra: username, email, nombre completo, UID y grupos
- Incluye badges para visualizar los grupos del usuario

#### 3. **Botones de Acción** (componentes individuales)
Cada botón es un componente independiente con su propia lógica:

- **SessionStatusButton**: Verifica el estado de la sesión contra Authentik
  - Hace una petición a `/api/auth/status`
  - Muestra notificaciones toast con el resultado
  - Maneja casos offline y errores de conexión

- **ProfileButton**: Redirige al perfil de usuario en Authentik
  - Abre el panel de usuario de Authentik en nueva pestaña
  - URL: `{authentikUrl}/if/user/`

- **LogoutButton**: Cierra sesión en Authentik
  - Invalida la sesión en todas las aplicaciones
  - Redirige a: `{authentikUrl}/flows/-/default/invalidation/`

- **LoginButton**: Fuerza re-autenticación
  - Recarga la página para activar el flujo de login de Authentik

### Composable `useAuthentik()`

Composable centralizado para manejar autenticación (`app/composables/useAuthentik.ts`):

**Funcionalidades:**
- Lee headers de Authentik en el servidor (SSR)
- Almacena información del usuario en `useState` (compartido entre cliente/servidor)
- Proporciona funciones reactivas: `user`, `isAuthenticated`
- Métodos: `logout()`, `goToProfile()`, `checkSessionStatus()`

**Headers leídos del Proxy:**
```typescript
X-authentik-username  // Nombre de usuario
X-authentik-email     // Email
X-authentik-name      // Nombre completo
X-authentik-groups    // Grupos (separados por |)
X-authentik-uid       // ID único del usuario
```

## Despliegue

El proyecto incluye Gitea Actions que automáticamente:
1. Construye la imagen Docker
2. La sube al registro
3. Despliega usando docker-compose

### Variables Requeridas en Gitea

Para configurar el despliegue automático, ve a tu repositorio en Gitea:
- **Secrets**: `Settings > Actions > Secrets`
- **Variables**: `Settings > Actions > Variables`

**Secrets (valores sensibles):**
- `REGISTRY_USERNAME` - Usuario del registro Docker
- `REGISTRY_PASSWORD` - Contraseña del registro Docker

**Variables (valores públicos):**
- `REGISTRY_URL` - URL del registro Docker (ej: `gitea.nucleoriofrio.com`)
  - El owner del repositorio se agrega automáticamente
  - Imagen final: `REGISTRY_URL/owner/APP_NAME:latest`
- `APP_NAME` - Nombre de la aplicación (ej: `mi-app`) - usado para container, imagen y Traefik
- `APP_DOMAIN` - Dominio de la aplicación (ej: `miapp.ejemplo.com`)
- `NUXT_PUBLIC_APP_URL` - URL pública de la app (ej: `https://miapp.ejemplo.com`)

📄 Ver ejemplo completo en [`.env.example`](.env.example)

## Configuración de Traefik y Authentik

### 🔐 Cómo Funciona Authentik Proxy Outpost

Authentik Proxy Outpost actúa como un **Forward Auth** middleware que intercepta todas las peticiones antes de que lleguen a tu aplicación:

```
Usuario → Traefik → Authentik Forward Auth → Aplicación Nuxt
                         ↓ (si no auth)
                    Redirect a Login
```

**Flujo de autenticación:**

1. **Usuario hace petición** → Traefik recibe la petición a tu dominio
2. **Traefik consulta Authentik** → Envía la petición al middleware de Forward Auth
3. **Authentik valida sesión:**
   - ✅ **Con sesión válida**: Authentik agrega headers con info del usuario y envía la petición a tu app
   - ❌ **Sin sesión**: Authentik redirige al usuario al flujo de login
4. **Tu app recibe la petición** → Con headers de usuario ya inyectados (SSR)

**Ventajas:**
- ✅ No necesitas implementar OAuth en tu app
- ✅ La autenticación se maneja completamente fuera de tu código
- ✅ Los headers llegan automáticamente en cada petición SSR
- ✅ Funciona con cualquier framework (Nuxt, Next.js, PHP, etc.)

### 🚦 Configuración de Reglas de Traefik

El `docker-compose.yml` configura **dos routers** en Traefik para balancear seguridad y funcionalidad PWA:

#### Router 1: Rutas Públicas (Sin Autenticación) - Prioridad 100

```yaml
# Rutas que NO requieren autenticación
- PathPrefix(`/manifest.webmanifest`)  # Manifest PWA
- PathPrefix(`/sw.js`)                 # Service Worker
- PathPrefix(`/workbox-`)              # Workbox (PWA)
- PathPrefix(`/icon-`)                 # Iconos de la app
- PathPrefix(`/apple-touch-icon`)      # Icono iOS
- PathPrefix(`/favicon.ico`)           # Favicon
- PathPrefix(`/robots.txt`)            # SEO
- PathPrefix(`/offline.html`)          # Página offline PWA
- PathPrefix(`/api/_nuxt_icon/`)       # API de iconos de Nuxt
```

**Middlewares aplicados:**
- `${APP_NAME}-headers`: Headers personalizados (X-Forwarded-Proto)
- `${APP_NAME}-cors`: Configuración CORS para recursos públicos

**¿Por qué sin autenticación?**
- Los Service Workers necesitan acceso sin auth para funcionar offline
- Los manifests PWA deben ser públicos para instalación
- Prioridad 100 asegura que estas rutas se evalúen primero

#### Router 2: Aplicación Protegida (Con Autenticación) - Prioridad 10

```yaml
# Todas las demás rutas (Host match)
Host(`${APP_DOMAIN}`)
```

**Middlewares aplicados:**
- `authentik-forward-auth@file`: Forward Auth de Authentik
- `${APP_NAME}-headers`: Headers personalizados

**Características:**
- Prioridad menor (10) → se evalúa después de las rutas públicas
- Cualquier ruta no pública pasa por autenticación
- Authentik inyecta headers con información del usuario

### 📋 Configuración de Authentik Proxy Outpost

**Requisitos previos:**
- Traefik corriendo con las redes `traefik-network` y `principal`
- Authentik instalado y funcionando

**Pasos de configuración:**

#### 1. Crear el Middleware en Traefik

Crea o edita el archivo de configuración dinámica de Traefik (ej: `dynamic/middlewares.yml`):

```yaml
http:
  middlewares:
    authentik-forward-auth:
      forwardAuth:
        address: http://authentik-server:9000/outpost.goauthentik.io/auth/traefik
        trustForwardHeader: true
        authResponseHeaders:
          - X-authentik-username
          - X-authentik-groups
          - X-authentik-email
          - X-authentik-name
          - X-authentik-uid
```

**Importante:** Cambia `authentik-server` por el nombre de tu contenedor/servicio de Authentik.

#### 2. Configurar Authentik

**a) Crear un Proxy Provider:**
- Ve a **Admin > Applications > Providers**
- Click en **Create**
- Tipo: **Proxy Provider**
- Configuración:
  - **Name**: `Mi App - Forward Auth`
  - **Authorization flow**: Selecciona tu flujo (ej: `default-provider-authorization-implicit-consent`)
  - **Type**: `Forward auth (single application)`
  - **External host**: `https://miapp.ejemplo.com` (tu dominio)

**b) Crear una Application:**
- Ve a **Admin > Applications > Applications**
- Click en **Create**
- Configuración:
  - **Name**: `Mi App`
  - **Slug**: `mi-app`
  - **Provider**: Selecciona el provider creado arriba

**c) Vincular al Outpost:**
- Ve a **Admin > Outposts > Outposts**
- Edita tu Outpost (o crea uno nuevo si no existe)
- En **Applications**: Agrega la aplicación creada

#### 3. Configurar las Redes en Docker Compose

Tu aplicación necesita estar en ambas redes:

```yaml
networks:
  principal:           # Red de tu infraestructura
    external: true
  traefik-network:     # Red donde corre Traefik
    external: true
```

Asegúrate de que estas redes existan:

```bash
docker network create principal
docker network create traefik-network
```

### 🔍 Headers Disponibles en tu Aplicación

Una vez configurado, Authentik inyecta estos headers en cada petición:

| Header | Descripción | Ejemplo |
|--------|-------------|---------|
| `X-authentik-username` | Nombre de usuario | `nucleo000` |
| `X-authentik-email` | Email del usuario | `user@example.com` |
| `X-authentik-name` | Nombre completo | `John Doe` |
| `X-authentik-groups` | Grupos (separados por `\|`) | `admins\|users` |
| `X-authentik-uid` | ID único del usuario | `703cae063c59...` |

**En Nuxt**, estos headers se leen automáticamente en SSR mediante `useRequestHeaders()`.

### 🐛 Troubleshooting

**Problema: "Cannot find name 'process'"**
- ✅ Usa `import.meta.server` en lugar de `process.server` (Nuxt 4)

**Problema: Redirect loop infinito**
- Verifica que el middleware `authentik-forward-auth@file` esté correctamente configurado
- Revisa que la aplicación esté asociada al Outpost en Authentik
- Comprueba que el External host coincida con tu dominio

**Problema: Headers no llegan a la aplicación**
- Verifica que `authResponseHeaders` incluya todos los headers necesarios
- Asegúrate de que estés leyendo headers en SSR (`import.meta.server`)

**Problema: PWA no funciona offline**
- Verifica que las rutas públicas tengan prioridad 100
- Confirma que `/sw.js`, `/manifest.webmanifest` y `/offline.html` estén en el router público

## Licencia

MIT
