UNPKG

backend-mcp

Version:

Generador automático de backends con Node.js, Express, Prisma y módulos configurables. Servidor MCP compatible con npx para agentes IA. Soporta PostgreSQL, MySQL, MongoDB y SQLite.

390 lines (323 loc) 12.2 kB
// modules/auth/init.js const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); class AuthModuleInitializer { constructor(config = {}) { this.config = { projectPath: config.projectPath || process.cwd(), features: config.features || ['login', 'register', 'refresh-tokens'], roles: config.roles || ['admin', 'manager', 'employee'], jwtSecret: config.jwtSecret || this.generateJwtSecret(), ...config }; this.metadata = { module: 'auth', version: '1.0.0', generatedFiles: [], endpoints: [], dependencies: ['database', 'email'], environment: [] }; } async initialize() { console.log('🔐 Inicializando módulo de autenticación...'); try { // 1. Configurar JWT await this.setupJwtConfiguration(); // 2. Crear tablas de autenticación await this.createAuthTables(); // 3. Generar endpoints de autenticación await this.generateAuthEndpoints(); // 4. Configurar middleware await this.configureMiddleware(); // 5. Configurar guards de roles await this.setupRoleGuards(); // 6. Retornar metadata return this.returnAuthMetadata(); } catch (error) { console.error('❌ Error inicializando módulo auth:', error.message); throw error; } } generateJwtSecret() { return crypto.randomBytes(64).toString('hex'); } async setupJwtConfiguration() { console.log('⚙️ Configurando JWT...'); // Asegurar que el directorio del proyecto existe if (!fs.existsSync(this.config.projectPath)) { fs.mkdirSync(this.config.projectPath, { recursive: true }); } const envPath = path.join(this.config.projectPath, '.env'); const envExamplePath = path.join(this.config.projectPath, '.env.example'); const jwtConfig = [ `# JWT Configuration`, `JWT_SECRET=${this.config.jwtSecret}`, `JWT_EXPIRES_IN=15m`, `JWT_REFRESH_EXPIRES_IN=30d`, `JWT_ISSUER=backend-mcp`, `JWT_AUDIENCE=api-users`, `BCRYPT_ROUNDS=12`, '' ].join('\n'); // Agregar a .env si no existe if (!fs.existsSync(envPath)) { fs.writeFileSync(envPath, jwtConfig); } else { const envContent = fs.readFileSync(envPath, 'utf8'); if (!envContent.includes('JWT_SECRET')) { fs.appendFileSync(envPath, '\n' + jwtConfig); } } // Agregar a .env.example const exampleConfig = jwtConfig.replace(this.config.jwtSecret, 'your-super-secret-jwt-key-here'); if (!fs.existsSync(envExamplePath)) { fs.writeFileSync(envExamplePath, exampleConfig); } else { const exampleContent = fs.readFileSync(envExamplePath, 'utf8'); if (!exampleContent.includes('JWT_SECRET')) { fs.appendFileSync(envExamplePath, '\n' + exampleConfig); } } this.metadata.environment.push( 'JWT_SECRET', 'JWT_EXPIRES_IN', 'JWT_REFRESH_EXPIRES_IN', 'JWT_ISSUER', 'JWT_AUDIENCE', 'BCRYPT_ROUNDS' ); } async createAuthTables() { console.log('🗄️ Creando tablas de autenticación...'); const prismaSchemaPath = path.join(this.config.projectPath, 'prisma', 'schema.prisma'); const authSchema = ` // Auth Module Tables model User { id String @id @default(cuid()) email String @unique password String firstName String? lastName String? role Role @default(EMPLOYEE) isEmailVerified Boolean @default(false) isActive Boolean @default(true) lastLoginAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations refreshTokens RefreshToken[] passwordResets PasswordReset[] emailVerifications EmailVerification[] auditLogs AuditLog[] @@map("users") } model RefreshToken { id String @id @default(cuid()) token String @unique userId String expiresAt DateTime isRevoked Boolean @default(false) createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@map("refresh_tokens") } model PasswordReset { id String @id @default(cuid()) email String token String @unique expiresAt DateTime isUsed Boolean @default(false) createdAt DateTime @default(now()) user User @relation(fields: [email], references: [email], onDelete: Cascade) @@map("password_resets") } model EmailVerification { id String @id @default(cuid()) email String token String @unique expiresAt DateTime isUsed Boolean @default(false) createdAt DateTime @default(now()) user User @relation(fields: [email], references: [email], onDelete: Cascade) @@map("email_verifications") } enum Role { ADMIN MANAGER EMPLOYEE } `; if (fs.existsSync(prismaSchemaPath)) { const schemaContent = fs.readFileSync(prismaSchemaPath, 'utf8'); if (!schemaContent.includes('model User')) { fs.appendFileSync(prismaSchemaPath, authSchema); } } else { // Crear schema básico si no existe const basicSchema = `generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } ${authSchema}`; const prismaDir = path.dirname(prismaSchemaPath); if (!fs.existsSync(prismaDir)) { fs.mkdirSync(prismaDir, { recursive: true }); } fs.writeFileSync(prismaSchemaPath, basicSchema); } } async generateAuthEndpoints() { console.log('🛣️ Generando endpoints de autenticación...'); const endpoints = [ { method: 'POST', path: '/auth/login', description: 'Iniciar sesión' }, { method: 'POST', path: '/auth/register', description: 'Registrar usuario' }, { method: 'POST', path: '/auth/refresh', description: 'Renovar tokens' }, { method: 'POST', path: '/auth/logout', description: 'Cerrar sesión' }, { method: 'GET', path: '/auth/profile', description: 'Obtener perfil' } ]; if (this.config.features.includes('email-verification')) { endpoints.push( { method: 'POST', path: '/auth/verify-email', description: 'Verificar email' }, { method: 'POST', path: '/auth/resend-verification', description: 'Reenviar verificación' } ); } if (this.config.features.includes('password-reset')) { endpoints.push( { method: 'POST', path: '/auth/forgot-password', description: 'Solicitar reset' }, { method: 'POST', path: '/auth/reset-password', description: 'Restablecer contraseña' } ); } this.metadata.endpoints = endpoints; } async configureMiddleware() { console.log('🔧 Configurando middleware de autenticación...'); const middlewarePath = path.join(this.config.projectPath, 'src', 'middleware'); const controllersPath = path.join(this.config.projectPath, 'src', 'controllers'); const routesPath = path.join(this.config.projectPath, 'src', 'routes'); // Crear directorios [middlewarePath, controllersPath, routesPath].forEach(dir => { if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } }); // Generar archivos desde plantillas await this.generateFromTemplate('auth.middleware.js.hbs', 'src/middleware/auth.js'); await this.generateFromTemplate('auth.controller.js.hbs', 'src/controllers/auth.js'); await this.generateFromTemplate('auth.routes.js.hbs', 'src/routes/auth.js'); this.metadata.generatedFiles.push( 'src/middleware/auth.js', 'src/controllers/auth.js', 'src/routes/auth.js' ); } async generateFromTemplate(templateName, outputPath) { const Handlebars = require('handlebars'); const templatePath = path.join(__dirname, 'templates', templateName); const outputFullPath = path.join(this.config.projectPath, outputPath); console.log(`🔍 Generando desde plantilla: ${templateName} -> ${outputPath}`); console.log(`📁 Ruta completa de salida: ${outputFullPath}`); if (!fs.existsSync(templatePath)) { console.warn(`⚠️ Plantilla no encontrada: ${templatePath}`); return; } // Asegurar que el directorio de destino existe const outputDir = path.dirname(outputFullPath); console.log(`📂 Verificando directorio: ${outputDir}`); try { if (!fs.existsSync(outputDir)) { console.log(`📁 Creando directorio: ${outputDir}`); fs.mkdirSync(outputDir, { recursive: true }); } // Verificar nuevamente que el directorio existe if (!fs.existsSync(outputDir)) { throw new Error(`No se pudo crear el directorio: ${outputDir}`); } console.log(`✅ Directorio confirmado: ${outputDir}`); const templateContent = fs.readFileSync(templatePath, 'utf8'); const template = Handlebars.compile(templateContent); const generatedContent = template({ features: this.config.features, roles: this.config.roles, projectName: this.config.projectName || 'backend-app' }); console.log(`💾 Escribiendo archivo: ${outputFullPath}`); fs.writeFileSync(outputFullPath, generatedContent); console.log(`✅ Generado: ${outputPath}`); } catch (error) { console.error(`❌ Error en generateFromTemplate para ${outputPath}:`, error.message); console.error(`📍 Directorio objetivo: ${outputDir}`); console.error(`📍 Archivo objetivo: ${outputFullPath}`); throw error; } } async setupRoleGuards() { console.log('🛡️ Configurando guards de roles...'); const rolesConfig = { admin: ['*'], manager: ['read:*', 'write:own', 'manage:team'], employee: ['read:own', 'write:own'] }; // Crear archivo de configuración de roles const configPath = path.join(this.config.projectPath, 'src', 'config', 'roles.config.ts'); const configDir = path.dirname(configPath); if (!fs.existsSync(configDir)) { fs.mkdirSync(configDir, { recursive: true }); } const rolesConfigContent = `export const ROLES_CONFIG = ${JSON.stringify(rolesConfig, null, 2)};\n\nexport type Role = keyof typeof ROLES_CONFIG;\nexport type Permission = string;`; fs.writeFileSync(configPath, rolesConfigContent); this.metadata.generatedFiles.push('src/config/roles.config.ts'); } returnAuthMetadata() { console.log('✅ Módulo de autenticación inicializado correctamente'); return { ...this.metadata, config: { features: this.config.features, roles: this.config.roles, jwtConfigured: true, tablesCreated: true }, instructions: { nextSteps: [ 'Ejecutar: npx prisma generate', 'Ejecutar: npx prisma db push', 'Configurar variables de entorno', 'Implementar templates de autenticación' ], security: [ 'Verificar que JWT_SECRET tenga al menos 32 caracteres', 'Configurar CORS apropiadamente', 'Implementar rate limiting', 'Usar HTTPS en producción' ] } }; } } // Función principal para uso con agentes IA async function initializeAuthModule(config = {}) { const initializer = new AuthModuleInitializer(config); return await initializer.initialize(); } // Exportar para uso en otros módulos module.exports = { AuthModuleInitializer, initializeAuthModule }; // Si se ejecuta directamente if (require.main === module) { const config = { projectPath: process.argv[2] || process.cwd(), features: process.argv[3] ? process.argv[3].split(',') : undefined }; initializeAuthModule(config) .then(result => { console.log('\n📊 Resultado de inicialización:'); console.log(JSON.stringify(result, null, 2)); }) .catch(error => { console.error('❌ Error:', error.message); process.exit(1); }); }