From b9ba7a80dbf1b4bc70b6a099e6a36d3124543d9f Mon Sep 17 00:00:00 2001 From: josedario87 Date: Sat, 11 Oct 2025 19:18:52 -0600 Subject: [PATCH] Clean up codebase and prepare as template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove unused files and code: - Delete test/debug endpoints (test.get.ts, public.get.ts, user.get.ts, debug-config backup) - Remove unused OAuth wrapper (oauth-authentik.ts) - Clean up debug console.log statements - Simplify code comments Fix TypeScript errors: - Add @types/node dependency - Create index.d.ts with User interface extension - Fix UButton color props (red→error, gray→neutral) - Add type assertions in protected.get.ts Update documentation: - Enhance README.md as template documentation - Update SETUP.md with correct API routes (/api/auth/* instead of /auth/*) - Add NUXT_OAUTH_AUTHENTIK_SERVER_URL_INTERNAL documentation - Update endpoint documentation This commit prepares the repository to be used as a template for future Nuxt 4 + Authentik OAuth projects. --- README.md | 100 +++++++++++++- SETUP.md | 28 ++-- nuxt4-app/app/components/UserMenu.vue | 2 +- nuxt4-app/app/pages/index.vue | 2 +- nuxt4-app/index.d.ts | 18 +++ nuxt4-app/package-lock.json | 20 +++ nuxt4-app/package.json | 3 + nuxt4-app/server/api/auth/authentik.get.ts | 14 +- nuxt4-app/server/api/auth/logout.get.ts | 4 +- nuxt4-app/server/api/auth/test.get.ts | 7 - nuxt4-app/server/api/debug-config.get.ts.bak | 31 ----- nuxt4-app/server/api/protected.get.ts | 11 +- nuxt4-app/server/api/public.get.ts | 13 -- nuxt4-app/server/api/user.get.ts | 15 --- nuxt4-app/server/utils/oauth-authentik.ts | 131 ------------------- 15 files changed, 158 insertions(+), 241 deletions(-) create mode 100644 nuxt4-app/index.d.ts delete mode 100644 nuxt4-app/server/api/auth/test.get.ts delete mode 100644 nuxt4-app/server/api/debug-config.get.ts.bak delete mode 100644 nuxt4-app/server/api/public.get.ts delete mode 100644 nuxt4-app/server/api/user.get.ts delete mode 100644 nuxt4-app/server/utils/oauth-authentik.ts diff --git a/README.md b/README.md index cd522b4..ad26eae 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,99 @@ -# Seguidor de Lotes +# Nuxt 4 + Authentik OAuth + PWA Template -Proyecto reiniciado desde cero. +Template de aplicación Nuxt 4 con autenticación OAuth/OIDC usando Authentik y funcionalidad PWA (Progressive Web App). + +## 🚀 Características + +- ✅ **Nuxt 4** - Framework Vue.js de última generación +- ✅ **Autenticación OAuth/OIDC** - Integración con Authentik +- ✅ **PWA** - Funciona offline con Service Workers +- ✅ **Docker** - Despliegue con Docker Compose +- ✅ **Traefik** - Configurado para reverse proxy +- ✅ **Gitea Actions** - CI/CD automático incluido +- ✅ **TypeScript** - Tipado estático +- ✅ **Nuxt UI** - Componentes UI modernos + +## 📋 Requisitos Previos + +- Docker y Docker Compose +- Authentik corriendo (en la misma red Docker) +- Traefik como reverse proxy +- Gitea (opcional, para CI/CD) + +## 🛠️ Instalación Rápida + +1. **Clona el repositorio** + ```bash + git clone https://tu-repo.git + cd seguidor-lotes + ``` + +2. **Configura variables de entorno** (ver `SETUP.md` para detalles) + +3. **Despliega** + ```bash + docker-compose up -d + ``` + +## 📖 Documentación Completa + +Ver [SETUP.md](./SETUP.md) para: +- Configuración detallada de Authentik +- Variables de entorno requeridas +- Configuración de Gitea Actions +- Troubleshooting +- Personalización + +## 🏗️ Estructura del Proyecto + +``` +├── .gitea/ +│ └── workflows/ # CI/CD workflows +├── nuxt4-app/ +│ ├── app/ +│ │ ├── components/ # Componentes Vue +│ │ ├── middleware/ # Middleware de autenticación +│ │ └── pages/ # Páginas de la aplicación +│ ├── server/ +│ │ └── api/ # API endpoints +│ │ ├── auth/ # Rutas de autenticación OAuth +│ │ └── protected.get.ts # Ejemplo de API protegida +│ ├── public/ # Assets estáticos y PWA icons +│ ├── nuxt.config.ts # Configuración de Nuxt +│ └── Dockerfile # Dockerfile para producción +├── docker-compose.yml # Configuración de Docker Compose +└── SETUP.md # Guía de configuración detallada +``` + +## 🔐 Autenticación + +El template implementa OAuth 2.0 / OpenID Connect con: +- Login con Authentik +- Protección de rutas con middleware +- Protección de APIs con `requireUserSession` +- Información de usuario (email, nombre, grupos) +- Logout completo + +## 🎨 Personalización + +1. **Cambiar nombre de la app**: Edita `nuxt4-app/nuxt.config.ts` +2. **Añadir páginas protegidas**: Usa `definePageMeta({ middleware: 'auth' })` +3. **Crear APIs protegidas**: Usa `requireUserSession(event)` en tus handlers +4. **Customizar UI**: Modifica componentes en `app/components/` + +## 🐛 Troubleshooting + +Problemas comunes y soluciones en [SETUP.md - Sección 10](./SETUP.md#10-troubleshooting) + +## 📝 Licencia + +MIT + +## 🤝 Contribuir + +Este es un template base. Siéntete libre de: +1. Fork el proyecto +2. Crear una rama para tu feature +3. Commit tus cambios +4. Push a la rama +5. Abrir un Pull Request diff --git a/SETUP.md b/SETUP.md index 9603fa8..d6c233f 100644 --- a/SETUP.md +++ b/SETUP.md @@ -34,7 +34,7 @@ - **Client type**: Confidential - **Client ID**: (se genera automáticamente, guárdalo) - **Client Secret**: (se genera automáticamente, guárdalo) - - **Redirect URIs**: `https://app.tudominio.com/auth/authentik` + - **Redirect URIs**: `https://app.tudominio.com/api/auth/authentik` - **Scopes**: openid, profile, email - **Subject mode**: Based on the User's UUID - **Include claims in id_token**: ✅ Activado @@ -66,7 +66,8 @@ APP_DOMAIN=app.tudominio.com NUXT_OAUTH_AUTHENTIK_CLIENT_ID=tu-client-id-aqui NUXT_OAUTH_AUTHENTIK_CLIENT_SECRET=tu-client-secret-aqui NUXT_OAUTH_AUTHENTIK_SERVER_URL=https://auth.tudominio.com -NUXT_OAUTH_AUTHENTIK_REDIRECT_URL=https://app.tudominio.com/auth/authentik +NUXT_OAUTH_AUTHENTIK_REDIRECT_URL=https://app.tudominio.com/api/auth/authentik +NUXT_OAUTH_AUTHENTIK_SERVER_URL_INTERNAL=http://nombre-servicio-authentik:9000 # URL pública de la app NUXT_PUBLIC_APP_URL=https://app.tudominio.com @@ -158,14 +159,8 @@ docker-compose ps ### 7.2 Verificar APIs ```bash -# API Pública (sin auth) -curl https://app.tudominio.com/api/public - -# API Protegida (requiere auth - debería retornar 401) +# API Protegida (requiere auth - debería retornar 401 sin sesión) curl https://app.tudominio.com/api/protected - -# Info de usuario (requiere auth) -curl https://app.tudominio.com/api/user ``` ### 7.3 Verificar PWA @@ -183,19 +178,18 @@ curl https://app.tudominio.com/api/user ### Páginas - `/` - Página de inicio (pública) -- `/login` - Página de login (solo para usuarios no autenticados) +- `/login` - Página de login - `/dashboard` - Dashboard (requiere autenticación) +- `/profile` - Perfil (requiere autenticación) ### API Endpoints -- `GET /api/public` - Endpoint público -- `GET /api/protected` - Endpoint protegido (requiere auth) -- `GET /api/user` - Información del usuario autenticado +- `GET /api/protected` - Endpoint protegido (requiere autenticación) ### Auth Endpoints -- `GET /auth/authentik` - Inicia OAuth flow con Authentik -- `GET /auth/logout` - Cierra sesión +- `GET /api/auth/authentik` - Inicia OAuth flow con Authentik y maneja callback +- `GET /api/auth/logout` - Cierra sesión ## 9. Desarrollo Local @@ -218,8 +212,10 @@ npm run dev ``` **Nota**: Para desarrollo local, necesitas: -- Configurar Authentik con redirect URI: `http://localhost:3000/auth/authentik` +- Configurar Authentik con redirect URI: `http://localhost:3000/api/auth/authentik` - O usar túnel como ngrok para https +- Usar la URL pública de Authentik para `NUXT_OAUTH_AUTHENTIK_SERVER_URL` +- No necesitas `NUXT_OAUTH_AUTHENTIK_SERVER_URL_INTERNAL` en desarrollo local ## 10. Troubleshooting diff --git a/nuxt4-app/app/components/UserMenu.vue b/nuxt4-app/app/components/UserMenu.vue index ccb3f45..24970db 100644 --- a/nuxt4-app/app/components/UserMenu.vue +++ b/nuxt4-app/app/components/UserMenu.vue @@ -26,7 +26,7 @@ const logout = async () => { { Ir al Dashboard - + Ver Perfil diff --git a/nuxt4-app/index.d.ts b/nuxt4-app/index.d.ts new file mode 100644 index 0000000..5ebe403 --- /dev/null +++ b/nuxt4-app/index.d.ts @@ -0,0 +1,18 @@ +// Global type declarations +declare module '#auth-utils' { + interface User { + id?: string + email?: string + name?: string + username?: string + picture?: string + groups?: string[] + } + + interface UserSession { + user: User + loggedInAt: number + } +} + +export {} diff --git a/nuxt4-app/package-lock.json b/nuxt4-app/package-lock.json index ed01f16..c8d2b5e 100644 --- a/nuxt4-app/package-lock.json +++ b/nuxt4-app/package-lock.json @@ -14,6 +14,9 @@ "typescript": "^5.9.3", "vue": "^3.5.22", "vue-router": "^4.5.1" + }, + "devDependencies": { + "@types/node": "^24.7.2" } }, "node_modules/@adonisjs/hash": { @@ -5205,6 +5208,16 @@ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, + "node_modules/@types/node": { + "version": "24.7.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.2.tgz", + "integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.14.0" + } + }, "node_modules/@types/parse-path": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/parse-path/-/parse-path-7.0.3.tgz", @@ -13680,6 +13693,13 @@ "node": ">=20.18.1" } }, + "node_modules/undici-types": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", + "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/unenv": { "version": "2.0.0-rc.21", "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.21.tgz", diff --git a/nuxt4-app/package.json b/nuxt4-app/package.json index 196ca8e..7f7496b 100644 --- a/nuxt4-app/package.json +++ b/nuxt4-app/package.json @@ -17,5 +17,8 @@ "typescript": "^5.9.3", "vue": "^3.5.22", "vue-router": "^4.5.1" + }, + "devDependencies": { + "@types/node": "^24.7.2" } } diff --git a/nuxt4-app/server/api/auth/authentik.get.ts b/nuxt4-app/server/api/auth/authentik.get.ts index a874afa..5fff5cf 100644 --- a/nuxt4-app/server/api/auth/authentik.get.ts +++ b/nuxt4-app/server/api/auth/authentik.get.ts @@ -3,15 +3,12 @@ import { withQuery } from 'ufo' /** * OAuth Authentik Login Handler - * Ruta: /api/auth/authentik - * - * Este endpoint inicia el flujo OAuth con Authentik + * Handles OAuth flow: initial redirect and callback */ export default defineEventHandler(async (event) => { const runtimeConfig = useRuntimeConfig(event) const query = getQuery(event) - // Configuración de Authentik const config = { clientId: runtimeConfig.oauth.authentik.clientId, clientSecret: runtimeConfig.oauth.authentik.clientSecret, @@ -21,13 +18,6 @@ export default defineEventHandler(async (event) => { scope: ['openid', 'profile', 'email'], } - console.log('OAuth Authentik - Iniciando flujo:', { - serverUrl: config.serverUrl, - serverUrlInternal: config.serverUrlInternal, - redirectURL: config.redirectURL, - hasCode: !!query.code - }) - // Handle OAuth callback if (query.code) { try { @@ -70,7 +60,6 @@ export default defineEventHandler(async (event) => { loggedInAt: Date.now() }) - // Redirigir al dashboard después del login return sendRedirect(event, '/') } catch (error: any) { console.error('Authentik OAuth error:', error) @@ -89,6 +78,5 @@ export default defineEventHandler(async (event) => { } ) - console.log('Redirecting to:', authorizationUrl) return sendRedirect(event, authorizationUrl) }) diff --git a/nuxt4-app/server/api/auth/logout.get.ts b/nuxt4-app/server/api/auth/logout.get.ts index 8cd74a4..de3d321 100644 --- a/nuxt4-app/server/api/auth/logout.get.ts +++ b/nuxt4-app/server/api/auth/logout.get.ts @@ -1,8 +1,6 @@ /** * Logout Handler - * Ruta: /api/auth/logout - * - * Limpia la sesión del usuario y redirige a la página de inicio + * Clears user session and redirects to home */ export default defineEventHandler(async (event) => { await clearUserSession(event) diff --git a/nuxt4-app/server/api/auth/test.get.ts b/nuxt4-app/server/api/auth/test.get.ts deleted file mode 100644 index 3bcc3b9..0000000 --- a/nuxt4-app/server/api/auth/test.get.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Test endpoint - * Ruta: /api/auth/test - */ -export default defineEventHandler(() => { - return { message: 'Auth subfolder works!' } -}) diff --git a/nuxt4-app/server/api/debug-config.get.ts.bak b/nuxt4-app/server/api/debug-config.get.ts.bak deleted file mode 100644 index 942cfd7..0000000 --- a/nuxt4-app/server/api/debug-config.get.ts.bak +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Debug Config Endpoint - * Ruta: /api/debug-config - * - * TEMPORAL: Para verificar que las variables de entorno se estén cargando - * BORRAR EN PRODUCCIÓN - */ -export default defineEventHandler((event) => { - const runtimeConfig = useRuntimeConfig(event) - - return { - oauth: { - authentik: { - clientId: runtimeConfig.oauth.authentik.clientId ? 'SET (hidden)' : 'MISSING', - clientSecret: runtimeConfig.oauth.authentik.clientSecret ? 'SET (hidden)' : 'MISSING', - serverUrl: runtimeConfig.oauth.authentik.serverUrl || 'MISSING', - redirectURL: runtimeConfig.oauth.authentik.redirectURL || 'MISSING', - } - }, - public: { - appUrl: runtimeConfig.public.appUrl || 'MISSING' - }, - env: { - NODE_ENV: process.env.NODE_ENV, - // Verificar directamente las env vars - NUXT_OAUTH_AUTHENTIK_SERVER_URL: process.env.NUXT_OAUTH_AUTHENTIK_SERVER_URL || 'MISSING', - NUXT_OAUTH_AUTHENTIK_REDIRECT_URL: process.env.NUXT_OAUTH_AUTHENTIK_REDIRECT_URL || 'MISSING', - NUXT_PUBLIC_APP_URL: process.env.NUXT_PUBLIC_APP_URL || 'MISSING', - } - } -}) diff --git a/nuxt4-app/server/api/protected.get.ts b/nuxt4-app/server/api/protected.get.ts index d58d09c..d6ccf0e 100644 --- a/nuxt4-app/server/api/protected.get.ts +++ b/nuxt4-app/server/api/protected.get.ts @@ -1,21 +1,16 @@ /** * Protected API Endpoint - * Ruta: /api/protected - * - * Endpoint protegido que requiere autenticación - * Retorna datos sensibles solo para usuarios autenticados + * Requires authentication - returns user-specific data */ export default defineEventHandler(async (event) => { - // Verificar autenticación const session = await requireUserSession(event) return { message: 'Datos protegidos del usuario', - user: session.user.username, + user: (session.user as any).username, data: { - // Aquí puedes añadir datos específicos del usuario lotes: [], - permissions: session.user.groups || [] + permissions: (session.user as any).groups || [] }, timestamp: new Date().toISOString() } diff --git a/nuxt4-app/server/api/public.get.ts b/nuxt4-app/server/api/public.get.ts deleted file mode 100644 index d27e90f..0000000 --- a/nuxt4-app/server/api/public.get.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Public API Endpoint - * Ruta: /api/public - * - * Endpoint público que no requiere autenticación - * Útil para datos que deben estar disponibles offline - */ -export default defineEventHandler(async (event) => { - return { - message: 'Este endpoint es público y funciona offline', - timestamp: new Date().toISOString() - } -}) diff --git a/nuxt4-app/server/api/user.get.ts b/nuxt4-app/server/api/user.get.ts deleted file mode 100644 index b783659..0000000 --- a/nuxt4-app/server/api/user.get.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Get Current User API - * Ruta: /api/user - * - * Endpoint protegido que devuelve la información del usuario autenticado - */ -export default defineEventHandler(async (event) => { - // Obtener sesión del usuario (requiere autenticación) - const session = await requireUserSession(event) - - return { - user: session.user, - loggedInAt: session.loggedInAt - } -}) diff --git a/nuxt4-app/server/utils/oauth-authentik.ts b/nuxt4-app/server/utils/oauth-authentik.ts deleted file mode 100644 index b5fc0da..0000000 --- a/nuxt4-app/server/utils/oauth-authentik.ts +++ /dev/null @@ -1,131 +0,0 @@ -/** - * Custom OAuth Provider for Authentik - * - * Este archivo extiende nuxt-auth-utils para soportar Authentik como provider OAuth - */ -import type { H3Event } from 'h3' -import { eventHandler, getQuery, sendRedirect } from 'h3' -import { withQuery } from 'ufo' -import { defu } from 'defu' -import { useRuntimeConfig } from '#imports' - -export interface OAuthAuthentikConfig { - /** - * Authentik OAuth Client ID - * @default process.env.NUXT_OAUTH_AUTHENTIK_CLIENT_ID - */ - clientId?: string - /** - * Authentik OAuth Client Secret - * @default process.env.NUXT_OAUTH_AUTHENTIK_CLIENT_SECRET - */ - clientSecret?: string - /** - * Authentik Server URL - * @default process.env.NUXT_OAUTH_AUTHENTIK_SERVER_URL - */ - serverUrl?: string - /** - * Redirect URL - * @default process.env.NUXT_OAUTH_AUTHENTIK_REDIRECT_URL - */ - redirectURL?: string - /** - * Require email from user, adds the ['email'] scope if not present - * @default false - */ - emailRequired?: boolean - /** - * Authentik OAuth Scope - * @default ['openid', 'profile', 'email'] - */ - scope?: string[] -} - -export function oauthAuthentikEventHandler({ - config, - onSuccess, - onError, -}: { - config?: OAuthAuthentikConfig - onSuccess: (event: H3Event, result: { user: any; tokens: any }) => Promise | void - onError?: (event: H3Event, error: any) => Promise | void -}) { - return eventHandler(async (event: H3Event) => { - const runtimeConfig = useRuntimeConfig(event) - - // Debug: Log configuration - console.log('OAuth Authentik Config:', { - clientId: runtimeConfig.oauth.authentik.clientId ? '***' : 'MISSING', - clientSecret: runtimeConfig.oauth.authentik.clientSecret ? '***' : 'MISSING', - serverUrl: runtimeConfig.oauth.authentik.serverUrl, - redirectURL: runtimeConfig.oauth.authentik.redirectURL, - }) - - // Merge config with defaults - const authentikConfig = defu(config, { - clientId: runtimeConfig.oauth.authentik.clientId, - clientSecret: runtimeConfig.oauth.authentik.clientSecret, - serverUrl: runtimeConfig.oauth.authentik.serverUrl, - redirectURL: runtimeConfig.oauth.authentik.redirectURL, - scope: ['openid', 'profile', 'email'], - emailRequired: false - }) as Required - - const query = getQuery(event) - - // Handle callback - if (query.code) { - try { - // Exchange code for tokens - const tokenUrl = `${authentikConfig.serverUrl}/application/o/token/` - const tokenResponse = await $fetch(tokenUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: new URLSearchParams({ - grant_type: 'authorization_code', - client_id: authentikConfig.clientId, - client_secret: authentikConfig.clientSecret, - code: query.code as string, - redirect_uri: authentikConfig.redirectURL, - }), - }) - - const tokens = tokenResponse as any - - // Get user info - const userInfoUrl = `${authentikConfig.serverUrl}/application/o/userinfo/` - const user = await $fetch(userInfoUrl, { - headers: { - Authorization: `Bearer ${tokens.access_token}`, - }, - }) - - return onSuccess(event, { user, tokens }) - } catch (error: any) { - if (onError) return onError(event, error) - throw error - } - } - - // Initial redirect to Authentik - const authorizationUrl = withQuery( - `${authentikConfig.serverUrl}/application/o/authorize/`, - { - client_id: authentikConfig.clientId, - redirect_uri: authentikConfig.redirectURL, - response_type: 'code', - scope: authentikConfig.scope.join(' '), - } - ) - - return sendRedirect(event, authorizationUrl) - }) -} - -// Export for use in defineOAuthAuthentikEventHandler -export const oauth = { - authentikEventHandler: oauthAuthentikEventHandler -}