From 248af8f8d16467f040accb0658d4eb57067b60f2 Mon Sep 17 00:00:00 2001 From: josedario87 Date: Thu, 16 Oct 2025 17:14:49 -0600 Subject: [PATCH] Initial commit: Add Nuxt app with Authentik integration and profile editing - Add Nuxt 4 application with Authentik Proxy Outpost integration - Add EditProfileButton component for editing user profile via Authentik API - Add API endpoints for Authentik user management (GET/PATCH) - Configure Gitea Actions workflow for automated deployment - Add monitoring hook for Gitea Actions - Configure environment variables and Docker Compose setup --- .claude/hooks/monitor-gitea-action.sh | 2 +- .env.example | 16 ++ CONFIGURACION_GITEA.md | 56 +++++++ nuxt4/app/app.vue | 1 + .../app/components/auth/EditProfileButton.vue | 156 ++++++++++++++++++ nuxt4/app/server/api/authentik/user.get.ts | 57 +++++++ nuxt4/app/server/api/authentik/user.patch.ts | 74 +++++++++ nuxt4/nuxt.config.ts | 5 + 8 files changed, 366 insertions(+), 1 deletion(-) create mode 100644 CONFIGURACION_GITEA.md create mode 100644 nuxt4/app/components/auth/EditProfileButton.vue create mode 100644 nuxt4/app/server/api/authentik/user.get.ts create mode 100644 nuxt4/app/server/api/authentik/user.patch.ts diff --git a/.claude/hooks/monitor-gitea-action.sh b/.claude/hooks/monitor-gitea-action.sh index 7781def..abb5bbb 100755 --- a/.claude/hooks/monitor-gitea-action.sh +++ b/.claude/hooks/monitor-gitea-action.sh @@ -8,7 +8,7 @@ set -euo pipefail # Configuración GITEA_URL="https://gitea.nucleoriofrio.com" OWNER="nucleo000" -REPO="plantillaNuxtAuthentikProxy" +REPO="perfil" # Intentar cargar el token desde el entorno o desde ~/.bashrc GITEA_TOKEN="${GITEA_TOKEN:-}" diff --git a/.env.example b/.env.example index 3f3b307..091d454 100644 --- a/.env.example +++ b/.env.example @@ -54,3 +54,19 @@ REGISTRY_PASSWORD=mi-password-secreto # - X-authentik-name: nombre completo # - X-authentik-groups: grupos del usuario (separados por |) # - X-authentik-uid: ID único del usuario + +# URL pública de Authentik (para redirecciones de login/logout) +NUXT_PUBLIC_AUTHENTIK_URL=https://authentik.nucleoriofrio.com + +# =========================================== +# AUTHENTIK API (para edición de perfil) +# =========================================== +# Token de API de Authentik (SECRETO) +# Para crear un token: +# 1. Ve a Authentik Admin → Directory → Tokens & App passwords +# 2. Crea un nuevo token con el usuario que tendrá permisos para editar usuarios +# 3. Guarda el token de forma segura +NUXT_AUTHENTIK_API_TOKEN=tu-token-de-api-aqui + +# URL de la API de Authentik (usualmente la misma que NUXT_PUBLIC_AUTHENTIK_URL) +NUXT_AUTHENTIK_API_URL=https://authentik.nucleoriofrio.com diff --git a/CONFIGURACION_GITEA.md b/CONFIGURACION_GITEA.md new file mode 100644 index 0000000..b279a6b --- /dev/null +++ b/CONFIGURACION_GITEA.md @@ -0,0 +1,56 @@ +# Configuración de Variables en Gitea + +Para completar la configuración del repositorio, necesitas agregar las siguientes variables en Gitea: + +## Cómo configurar + +1. Ve a: https://gitea.nucleoriofrio.com/nucleo000/perfil/settings/actions/variables +2. Para cada variable, haz click en "Add variable" +3. Ingresa el nombre y valor exactamente como se indica abajo + +## Variables a configurar + +### REGISTRY_URL +- **Nombre**: `REGISTRY_URL` +- **Valor**: `gitea.nucleoriofrio.com` +- **Descripción**: URL del registro Docker + +### APP_NAME +- **Nombre**: `APP_NAME` +- **Valor**: `perfil` +- **Descripción**: Nombre de la aplicación + +### APP_DOMAIN +- **Nombre**: `APP_DOMAIN` +- **Valor**: `inicio.nucleoriofrio.com` +- **Descripción**: Dominio donde se desplegará la aplicación + +### NUXT_PUBLIC_APP_URL +- **Nombre**: `NUXT_PUBLIC_APP_URL` +- **Valor**: `https://inicio.nucleoriofrio.com` +- **Descripción**: URL pública de la aplicación + +### NUXT_PUBLIC_AUTHENTIK_URL +- **Nombre**: `NUXT_PUBLIC_AUTHENTIK_URL` +- **Valor**: `https://authentik.nucleoriofrio.com` +- **Descripción**: URL pública de Authentik + +### NUXT_AUTHENTIK_API_URL +- **Nombre**: `NUXT_AUTHENTIK_API_URL` +- **Valor**: `https://authentik.nucleoriofrio.com` +- **Descripción**: URL de la API de Authentik + +--- + +## Secrets ya configurados ✅ + +Los siguientes secrets ya fueron configurados automáticamente: + +- ✅ `REGISTRY_USERNAME` +- ✅ `REGISTRY_PASSWORD` +- ✅ `NUXT_AUTHENTIK_API_TOKEN` + +## Verificación + +Una vez configuradas todas las variables, puedes verificarlas en: +https://gitea.nucleoriofrio.com/nucleo000/perfil/settings/actions/variables diff --git a/nuxt4/app/app.vue b/nuxt4/app/app.vue index e37e66a..b9922f5 100644 --- a/nuxt4/app/app.vue +++ b/nuxt4/app/app.vue @@ -26,6 +26,7 @@

Acciones de Sesión

+ diff --git a/nuxt4/app/components/auth/EditProfileButton.vue b/nuxt4/app/components/auth/EditProfileButton.vue new file mode 100644 index 0000000..fb2c200 --- /dev/null +++ b/nuxt4/app/components/auth/EditProfileButton.vue @@ -0,0 +1,156 @@ + + + diff --git a/nuxt4/app/server/api/authentik/user.get.ts b/nuxt4/app/server/api/authentik/user.get.ts new file mode 100644 index 0000000..0e5ec69 --- /dev/null +++ b/nuxt4/app/server/api/authentik/user.get.ts @@ -0,0 +1,57 @@ +/** + * Obtiene la información del usuario actual desde Authentik + */ +export default defineEventHandler(async (event) => { + const config = useRuntimeConfig() + const headers = getRequestHeaders(event) + + // Obtener el username desde los headers de Authentik + const username = headers['x-authentik-username'] + + if (!username) { + throw createError({ + statusCode: 401, + message: 'Usuario no autenticado' + }) + } + + // Obtener la URL y token de Authentik desde variables de entorno + const authentikUrl = config.authentikApiUrl || config.public.authentikUrl + const authentikToken = config.authentikApiToken + + if (!authentikToken) { + throw createError({ + statusCode: 500, + message: 'Token de Authentik no configurado' + }) + } + + try { + // Consultar la API de Authentik para obtener información detallada del usuario + const response = await $fetch(`${authentikUrl}/api/v3/core/users/?username=${username}`, { + headers: { + 'Authorization': `Bearer ${authentikToken}`, + 'Content-Type': 'application/json' + } + }) + + // La API devuelve un array de resultados + const users = response as any + + if (!users.results || users.results.length === 0) { + throw createError({ + statusCode: 404, + message: 'Usuario no encontrado' + }) + } + + // Devolver el primer resultado (debería ser único por username) + return users.results[0] + } catch (error: any) { + console.error('Error al obtener usuario de Authentik:', error) + throw createError({ + statusCode: error.statusCode || 500, + message: error.message || 'Error al obtener información del usuario' + }) + } +}) diff --git a/nuxt4/app/server/api/authentik/user.patch.ts b/nuxt4/app/server/api/authentik/user.patch.ts new file mode 100644 index 0000000..dd4b627 --- /dev/null +++ b/nuxt4/app/server/api/authentik/user.patch.ts @@ -0,0 +1,74 @@ +/** + * Actualiza la información del usuario en Authentik + */ +export default defineEventHandler(async (event) => { + const config = useRuntimeConfig() + const headers = getRequestHeaders(event) + + // Obtener el username desde los headers de Authentik + const username = headers['x-authentik-username'] + + if (!username) { + throw createError({ + statusCode: 401, + message: 'Usuario no autenticado' + }) + } + + // Obtener la URL y token de Authentik desde variables de entorno + const authentikUrl = config.authentikApiUrl || config.public.authentikUrl + const authentikToken = config.authentikApiToken + + if (!authentikToken) { + throw createError({ + statusCode: 500, + message: 'Token de Authentik no configurado' + }) + } + + // Leer el body de la petición + const body = await readBody(event) + + try { + // Primero, obtener el ID del usuario + const usersResponse = await $fetch(`${authentikUrl}/api/v3/core/users/?username=${username}`, { + headers: { + 'Authorization': `Bearer ${authentikToken}`, + 'Content-Type': 'application/json' + } + }) + + const users = usersResponse as any + + if (!users.results || users.results.length === 0) { + throw createError({ + statusCode: 404, + message: 'Usuario no encontrado' + }) + } + + const userId = users.results[0].pk + + // Actualizar el usuario + const updateResponse = await $fetch(`${authentikUrl}/api/v3/core/users/${userId}/`, { + method: 'PATCH', + headers: { + 'Authorization': `Bearer ${authentikToken}`, + 'Content-Type': 'application/json' + }, + body: { + name: body.name, + // Otros campos que se puedan actualizar + ...(body.email && { email: body.email }) + } + }) + + return updateResponse + } catch (error: any) { + console.error('Error al actualizar usuario en Authentik:', error) + throw createError({ + statusCode: error.statusCode || 500, + message: error.message || 'Error al actualizar información del usuario' + }) + } +}) diff --git a/nuxt4/nuxt.config.ts b/nuxt4/nuxt.config.ts index a35fc79..c20a2ce 100644 --- a/nuxt4/nuxt.config.ts +++ b/nuxt4/nuxt.config.ts @@ -15,6 +15,11 @@ export default defineNuxtConfig({ css: ['~/assets/css/main.css'], runtimeConfig: { + // Variables privadas del servidor (no expuestas al cliente) + authentikApiToken: process.env.NUXT_AUTHENTIK_API_TOKEN || '', + authentikApiUrl: process.env.NUXT_AUTHENTIK_API_URL || 'https://authentik.nucleoriofrio.com', + + // Variables públicas (expuestas al cliente) public: { authentikUrl: process.env.NUXT_PUBLIC_AUTHENTIK_URL || 'https://authentik.nucleoriofrio.com' }