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
JavaScript
// 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
email String
password String
firstName String?
lastName String?
role Role
isEmailVerified Boolean
isActive Boolean
lastLoginAt DateTime?
createdAt DateTime
updatedAt DateTime
// Relations
refreshTokens RefreshToken[]
passwordResets PasswordReset[]
emailVerifications EmailVerification[]
auditLogs AuditLog[]
@
}
model RefreshToken {
id String
token String
userId String
expiresAt DateTime
isRevoked Boolean
createdAt DateTime
user User
@
}
model PasswordReset {
id String
email String
token String
expiresAt DateTime
isUsed Boolean
createdAt DateTime
user User
@
}
model EmailVerification {
id String
email String
token String
expiresAt DateTime
isUsed Boolean
createdAt DateTime
user User
@
}
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);
});
}