4 Commits

Author SHA1 Message Date
Your Name 0d0077cbcb smtp new account 2025-05-02 16:02:59 -06:00
Your Name c4666718a4 log 2025-05-02 15:41:20 -06:00
Your Name 11fe86a111 name stmp 2025-05-02 15:35:20 -06:00
Your Name 59a5ff8c35 smtp rellay 2025-05-02 15:10:08 -06:00
24 changed files with 4115 additions and 6804 deletions
+3729 -5824
View File
File diff suppressed because it is too large Load Diff
+24 -30
View File
@@ -20,58 +20,52 @@
"test:e2e": "jest --config ./test/jest-e2e.json" "test:e2e": "jest --config ./test/jest-e2e.json"
}, },
"dependencies": { "dependencies": {
"@nestjs/bull": "^11.0.2",
"@nestjs/common": "^11.0.1", "@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.1", "@nestjs/config": "^4.0.1",
"@nestjs/core": "^11.0.1", "@nestjs/core": "^11.0.1",
"@nestjs/jwt": "^11.0.2",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.0.1", "@nestjs/platform-express": "^11.0.1",
"@nestjs/swagger": "^11.4.5", "@nestjs/swagger": "^11.0.5",
"@nestjs/typeorm": "^11.0.3", "@nestjs/typeorm": "^11.0.0",
"bull": "^4.16.5", "class-validator": "^0.14.1",
"class-transformer": "^0.5.1", "csv-parser": "^3.2.0",
"class-validator": "^0.14.4", "dotenv": "^16.4.7",
"csv-parser": "^3.2.1",
"dotenv": "^17.4.2",
"exceljs": "^4.4.0", "exceljs": "^4.4.0",
"multer": "^1.4.5-lts.2", "fs": "^0.0.1-security",
"mysql2": "^3.23.1", "multer": "^1.4.5-lts.1",
"nodemailer": "^9.0.3", "mysql2": "^3.13.0",
"passport": "^0.7.0", "nodemailer": "^6.10.0",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",
"typeorm": "^1.1.0" "typeorm": "^0.3.21"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.2.0", "@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.39.5", "@eslint/js": "^9.18.0",
"@nestjs/cli": "^11.0.0", "@nestjs/cli": "^11.0.0",
"@nestjs/schematics": "^11.0.0", "@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.1.28", "@nestjs/testing": "^11.0.1",
"@swc/cli": "^0.8.1", "@swc/cli": "^0.6.0",
"@swc/core": "^1.15.46", "@swc/core": "^1.10.7",
"@types/bull": "^3.15.9", "@types/express": "^5.0.0",
"@types/express": "^5.0.6",
"@types/jest": "^29.5.14", "@types/jest": "^29.5.14",
"@types/multer": "^1.4.13", "@types/multer": "^1.4.12",
"@types/node": "^22.20.1", "@types/node": "^22.10.7",
"@types/supertest": "^6.0.2", "@types/supertest": "^6.0.2",
"@types/swagger-ui-express": "^4.1.8", "@types/swagger-ui-express": "^4.1.8",
"eslint": "^9.18.0", "eslint": "^9.18.0",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.5.6", "eslint-plugin-prettier": "^5.2.2",
"globals": "^15.14.0", "globals": "^15.14.0",
"jest": "^29.7.0", "jest": "^29.7.0",
"prettier": "^3.9.6", "prettier": "^3.4.2",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"supertest": "^7.2.2", "supertest": "^7.0.0",
"ts-jest": "^29.4.12", "ts-jest": "^29.2.5",
"ts-loader": "^9.6.2", "ts-loader": "^9.5.2",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0", "tsconfig-paths": "^4.2.0",
"typescript": "^5.7.3", "typescript": "^5.7.3",
"typescript-eslint": "^8.65.0" "typescript-eslint": "^8.20.0"
}, },
"jest": { "jest": {
"moduleFileExtensions": [ "moduleFileExtensions": [
BIN
View File
Binary file not shown.
+7 -16
View File
@@ -2,40 +2,31 @@ import { Module } from '@nestjs/common';
import { AppController } from './app.controller'; import { AppController } from './app.controller';
import { AppService } from './app.service'; import { AppService } from './app.service';
import { MailModule } from './mail/mail.module'; import { MailModule } from './mail/mail.module';
import { ExcelModule } from './excel/excel.module' import {ExcelModule} from './excel/excel.module'
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule } from '@nestjs/config'; import { ConfigModule } from '@nestjs/config';
import { SistemaModule } from './sistema/sistema.module'; import { SistemaModule } from './sistema/sistema.module';
import { JwtModule } from '@nestjs/jwt';
@Module({ @Module({
imports: [ imports: [
ConfigModule.forRoot( ConfigModule.forRoot(),
{ isGlobal: true }
),
JwtModule.register({
secret: process.env.JWT_SECRET,
}),
TypeOrmModule.forRoot({ TypeOrmModule.forRoot({
type: 'mysql', type: 'mysql',
host: process.env.db_host, host: process.env.db_host,
username: process.env.db_username, username: process.env.db_username,
database: process.env.db_database, database: process.env.db_database,
password: process.env.db_password, password: process.env.db_password,
port: Number(process.env.db_port), port: Number(process.env.db_port),
synchronize: true, synchronize: true,
dropSchema: false, // elimina la base de datos dropSchema: true, // elimina la base de datos
// logging: true, // Habilita los logs para depuración // logging: true, // Habilita los logs para depuración
autoLoadEntities: true, // Carga automáticamente las entidades autoLoadEntities: true, // Carga automáticamente las entidades
}), }),
MailModule, MailModule,
ExcelModule, ExcelModule,
SistemaModule, SistemaModule,
@@ -45,4 +36,4 @@ import { JwtModule } from '@nestjs/jwt';
controllers: [AppController], controllers: [AppController],
providers: [AppService], providers: [AppService],
}) })
export class AppModule { } export class AppModule {}
-19
View File
@@ -1,19 +0,0 @@
// auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { AuthService } from './auth.service';
@Module({
imports: [
ConfigModule, // Ya es global, pero lo puedes dejar
JwtModule.register({
secret: process.env.JWT_SECRET
})
],
providers: [AuthService],
exports: [AuthService, JwtModule],
})
export class AuthModule { }
-26
View File
@@ -1,26 +0,0 @@
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthService {
constructor(private jwtService: JwtService) {}
generarToken(payload: any) {
console.log('JWT_SECRET:', process.env.JWT_SECRET);
return this.jwtService.sign(payload, {
secret: process.env.JWT_SECRET,
});
}
async verificarToken(token: string) {
try {
const result = await this.jwtService.verify(token, {
secret: process.env.JWT_SECRET,
});
console.log('Resultado de verificación:', result);
return result;
} catch (error) {
throw new Error('Token inválido o expirado');
}
}
}
-35
View File
@@ -1,35 +0,0 @@
import * as crypto from 'crypto';
export function encrypt(text: string) {
const secret = process.env.KEY_SECRET;
if (!secret) throw new Error('Falta la variable de entorno KEY_SECRET');
const key = crypto.scryptSync(secret, 'salt', 32);
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
return {
iv: iv.toString('hex'),
encryptedData: encrypted,
};
}
export function decrypt(encryptedData: string, ivHex: string) {
const secret = process.env.KEY_SECRET;
if (!process.env.KEY_SECRET) {
throw new Error('Falta la variable de entorno KEY_SECRET');
}
if (!secret) throw new Error('Falta la variable de entorno KEY_SECRET');
const key = crypto.scryptSync(secret, 'salt', 32);
const iv = Buffer.from(ivHex, 'hex');
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
let decrypted = decipher.update(encryptedData, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
+9 -9
View File
@@ -7,14 +7,14 @@ import { ApiReadEmails } from './excel.decorators';
@ApiTags('Excel') // Agrupa los endpoints en Swagger @ApiTags('Excel') // Agrupa los endpoints en Swagger
@Controller('excel') @Controller('excel')
export class ExcelController { export class ExcelController {
constructor(private readonly excelService: ExcelService) { } constructor(private readonly excelService: ExcelService) {}
// @Post('read-emails') @Post('read-emails')
// @UseInterceptors(FileInterceptor('file')) // Permite subir archivos @UseInterceptors(FileInterceptor('file')) // Permite subir archivos
// @ApiConsumes('multipart/form-data') // Indica que se usa un formulario con archivos @ApiConsumes('multipart/form-data') // Indica que se usa un formulario con archivos
// @ApiReadEmails() @ApiReadEmails()
// async getEmails(@UploadedFile() file: Express.Multer.File) { async getEmails(@UploadedFile() file: Express.Multer.File) {
// const emails = await this.excelService.readEmailsFromExcel(file); const emails = await this.excelService.readEmailsFromExcel(file);
// return { emails }; return { emails };
// } }
} }
+27 -27
View File
@@ -4,42 +4,42 @@ import { Injectable } from '@nestjs/common';
@Injectable() @Injectable()
export class ExcelService { export class ExcelService {
// async readEmailsFromExcel(file: Express.Multer.File) { async readEmailsFromExcel(file: Express.Multer.File) {
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.load(file.buffer); // 🔹 `await` para leer el archivo correctamente
const worksheet = workbook.getWorksheet(1); // Obtiene la primera hoja
if (!worksheet) {
throw new Error(' No se encontró ninguna hoja en el archivo.');
}
const jsonData: any[] = [];
// const workbook = new ExcelJS.Workbook(); // 🔹 Iterar sobre las filas y extraer datos
// await workbook.xlsx.load(file.buffer); // 🔹 `await` para leer el archivo correctamente worksheet.eachRow((row, rowNumber) => {
if (rowNumber === 1) return; // Ignorar encabezado
// const worksheet = workbook.getWorksheet(1); // Obtiene la primera hoja const rowData = {
// if (!worksheet) { Correo: row.getCell(1).value, // Ajusta el índice según tu archivo
// throw new Error(' No se encontró ninguna hoja en el archivo.'); };
// }
// const jsonData: any[] = []; jsonData.push(rowData);
});
// // 🔹 Iterar sobre las filas y extraer datos console.log(` Datos extraídos:`, jsonData);
// worksheet.eachRow((row, rowNumber) => {
// if (rowNumber === 1) return; // Ignorar encabezado
// const rowData = { // 🔹 Extraer correos de la columna "Correo"
// Correo: row.getCell(1).value, // Ajusta el índice según tu archivo const emails = jsonData
// }; .map(row => row.Correo)
.filter(email => typeof email === 'string');
// jsonData.push(rowData); console.log(` Correos extraídos:`, emails);
// });
// console.log(` Datos extraídos:`, jsonData); return emails;
}
// // 🔹 Extraer correos de la columna "Correo"
// const emails = jsonData
// .map(row => row.Correo)
// .filter(email => typeof email === 'string');
// console.log(` Correos extraídos:`, emails);
// return emails;
// }
} }
-29
View File
@@ -1,29 +0,0 @@
import { IsString, IsEmail, IsDate, IsOptional } from 'class-validator';
import { Type } from 'class-transformer';
export class SendCorreoDto {
@IsEmail()
to: string;
@IsString()
@IsOptional()
subject: string;
@IsString()
@IsOptional()
text: string;
@IsDate()
@Type(() => Date)
fecha_recibido: Date;
@IsString()
@IsOptional()
html: string;
@IsOptional()
adjuntos: any;
}
+58 -61
View File
@@ -2,101 +2,98 @@ import {
Controller, Controller,
Post, Post,
Body, Body,
UploadedFile,
Headers, Headers,
UnauthorizedException, UnauthorizedException,
} from '@nestjs/common'; } from '@nestjs/common';
import { MailService } from './mail.service'; import { MailService } from './mail.service';
import { ExcelService } from 'src/excel/excel.service';
import { ApiTags } from '@nestjs/swagger'; import { ApiTags } from '@nestjs/swagger';
import { ApiSendMail } from './mail.decorators'; import { ApiSendMail, ApiSendMailExcel} from './mail.decorators';
import { SistemaService } from 'src/sistema/sistema.service'; import { Repository } from 'typeorm';
import { SendCorreoDto } from 'src/mail/dto/send-email.dto'; import { Sistema } from 'src/typeorm/votacionesPrueba.entity';
import { AuthService } from 'src/auth/auth.service'; import { InjectRepository } from '@nestjs/typeorm';
@ApiTags('Mail') // Grupo de endpoints @ApiTags('Mail') // Grupo de endpoints
@Controller('mail') @Controller('mail')
export class MailController { export class MailController {
constructor( constructor(
private readonly sistemaService: SistemaService,
private readonly mailService: MailService, private readonly mailService: MailService,
// private readonly excelService: ExcelService, private readonly excelService: ExcelService,
private readonly authService: AuthService, @InjectRepository(Sistema) private readonly sistemaRepository: Repository<Sistema>,
) {} ) {}
// Endpoint para enviar correos a través de una solicitud POST // Endpoint para enviar correos a través de una solicitud POST
@Post('send') @Post('send')
@ApiSendMail() @ApiSendMail()
async sendMail( async sendMail(
@Headers() headers: { token: string }, @Headers() headers:{token:string},
@Body() body: SendCorreoDto, @Body() body: { to: string; subject: string; text: string; fecha_recibido: Date, html:string, adjuntos:any }) {
) {
if (!headers.token) {
throw new UnauthorizedException('Header "token" es requerido');
}
const result = await this.mailService // esta parte hay que rehacerla, se tiene que obtener la contraseña del sistema en base a la clave del sistema.
.sendMail(body, headers.token) // debe de existir un modulo para crear contraseñas para las apis y administrar esas contraseñas
.then((value) => {
console.log('ya acabe el correo', value);
})
.catch((err) => {
console.log(err);
});
// sistem
/* if (!headers.token) {
throw new UnauthorizedException('Header "Password" es requerido');
} */
/* const sistem = await this.sistemaRepository.findOne({ where: { token: headers.token } });
if (!sistem) {
throw new UnauthorizedException('Password incorrecto');
} */
console.log("estos son los archivos adjuntos en el controller",body);
const { to, subject, text, html, fecha_recibido, adjuntos } = body;
const result = await this.mailService.sendMail(to, subject, text,html, fecha_recibido,1,adjuntos); // sistem
return result; return result;
}
@Post('masivo')
async enviarMasivo(
@Body() data: SendCorreoDto[],
@Headers() headers: { token: string },
) {
return await this.mailService.sendBulkMail(data, headers.token);
}
/*
}
@Post('send-excel') @Post('send-excel')
@UseInterceptors(FileInterceptor('file'))
@ApiSendMailExcel() @ApiSendMailExcel()
async sendMailExcel( async sendMailExcel(@Headers() headers:{password:string},@Body() file: Express.Multer.File) {
@Headers() headers: { token: string },
@UploadedFile() file: Express.Multer.File if (!headers.password) {
) { throw new UnauthorizedException('Header "Password" es requerido');
if (!headers.token) {
throw new UnauthorizedException('Header "token" es requerido');
} }
let payload; const sistem = await this.sistemaRepository.findOne({ where: { token: headers.password } });
try {
payload = await this.jwtService.verify(headers.token);
} catch {
throw new UnauthorizedException('Token inválido');
}
const sistem = await this.sistemaService.findByNombre(payload.nombre);
if (!sistem) { if (!sistem) {
throw new UnauthorizedException('Sistema no encontrado'); throw new UnauthorizedException('Password incorrecto');
} }
if (!file) { if (!file) {
throw new Error('Archivo Excel no encontrado'); throw new Error('Archivo Excel no encontrado');
} }
const emails = await this.excelService.readEmailsFromExcel(file); const emails=this.excelService.readEmailsFromExcel(file);
const fechaActual: Date = new Date(); let fechaActual: Date = new Date();
for (const email of emails) { for (const email of await emails) {
try { try {
await this.mailService.sendMail(email, 'subject', 'text', 'html', fechaActual, sistem); await this.mailService.sendMail(email, "subject", "text","html", fechaActual,sistem);
console.log(`Enviado a: ${email}`); console.log(`📧 Enviado a: ${email}`);
} catch (error) { } catch (error) {
console.error(`Error enviando a ${email}:`, error.message); console.error(`Error enviando a ${email}:`, error.message);
} }
} }
return { message: 'Correos enviados (si no hubo errores)' };
} }
*/
} }
+70 -45
View File
@@ -1,63 +1,88 @@
import { applyDecorators } from '@nestjs/common'; import { applyDecorators } from '@nestjs/common';
import { ApiOperation, ApiBody, ApiHeader } from '@nestjs/swagger'; import { ApiBody, ApiConsumes, ApiHeader, ApiOperation, ApiResponse } from '@nestjs/swagger';
export const ApiSendMail = () => { export const ApiSendMail = () => {
return applyDecorators( return applyDecorators(
ApiOperation({ ApiOperation({
summary: 'Enviar un correo electrónico desde el sistema autenticado', summary: 'Enviar un correo manualmente',
description: `Este endpoint permite a un sistema autenticado enviar un correo electrónico utilizando su configuración registrada. description: `Este endpoint permite enviar un correo individualmente. Se requiere proporcionar:
Debe incluirse un token JWT válido en los encabezados (headers). - **to**: Dirección de correo del destinatario.
El sistema debe proporcionar el destinatario, asunto, contenido del mensaje y credenciales de su correo.`, - **subject**: Asunto del correo.
}), - **text**: Cuerpo del mensaje.
- **fecha_recibido**: Fecha en que se registró la solicitud.
- **sistema**: contraseña del sistema.
- **html**: se puede mandar html
- **adjuntos**:mandar qr
(Se requiere una contraseña de aplicación
No caducan automáticamente.
Pero se invalidan si:
Cambias la contraseña de tu cuenta.
Desactivas la verificación en dos pasos.
Revocas el acceso desde https://myaccount.google.com/apppasswords.
Google detecta actividad sospechosa o problemas de seguridad.))`,
}),
ApiHeader({ ApiHeader({
name: 'token', name: 'password',
description: 'Token JWT del sistema para autenticación', description: 'Contraseña del sistema para autenticación',
required: true, required: true,
example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...', example: 'mi-password-segura'
}), }),
ApiBody({ ApiBody({
description: 'Datos del correo a enviar', description: 'Datos del correo a enviar',
schema: { schema: {
type: 'object', type: 'object',
properties: { properties: {
to: { to: { type: 'string', example: 'usuario@example.com' },
type: 'string', subject: { type: 'string', example: 'Asunto del correo' },
format: 'email', text: { type: 'string', example: 'Cuerpo del mensaje' },
example: 'destinatario@example.com', fecha_recibido: { type: 'string', format: 'date-time', example: '2025-03-10T12:00:00Z' }
},
subject: {
type: 'string',
example: 'Informe Semanal',
},
text: {
type: 'string',
example: 'Aquí está el informe solicitado...',
},
html: {
type: 'string',
example: '<p>Este es un <b>mensaje</b> en HTML</p>',
},
fecha_recibido: {
type: 'string',
format: 'date-time',
example: '2025-05-01T15:00:00Z',
},
adjuntos: {
type: 'array',
description: 'Archivos adjuntos en base64',
items: {
type: 'object',
properties: {
filename: { type: 'string', example: 'reporte.pdf' },
content: { type: 'string', example: 'base64string...' },
encoding: { type: 'string', example: 'base64' },
cid: { type: 'string', example: 'file123' }
},
},
}
}, },
required: ['to', 'fecha_recibido'],
}, },
}) })
); );
}; };
export const ApiSendMailExcel = () => {
return applyDecorators(
ApiOperation({
summary: 'Enviar correos desde un archivo Excel',
description: `Este endpoint permite enviar correos a partir de una lista contenida en un archivo Excel.
El archivo debe contener una columna con direcciones de correo válidas.`,
}),
ApiConsumes('multipart/form-data'),
ApiBody({
description: 'Sube un archivo Excel con correos',
schema: {
type: 'object',
properties: {
file: {
type: 'string',
format: 'binary',
},
},
},
}),
ApiResponse({
status: 200,
description: 'Correos enviados exitosamente',
schema: {
example: {
message: 'Correos enviados correctamente',
emails: ['usuario1@example.com', 'usuario2@example.com'],
},
},
}),
ApiResponse({
status: 400,
description: 'Error al procesar el archivo',
schema: {
example: {
statusCode: 400,
message: 'El archivo no es un Excel válido',
error: 'Bad Request',
},
},
}),
);
};
+2 -4
View File
@@ -7,13 +7,11 @@ import {
Status, Status,
Correo, Correo,
Sistema, Sistema,
}from 'src/typeorm/entidades' }from 'src/typeorm/votacionesPrueba.entity'
import { SistemaModule } from 'src/sistema/sistema.module'; import { SistemaModule } from 'src/sistema/sistema.module';
import { JwtService } from '@nestjs/jwt';
import { AuthService } from 'src/auth/auth.service';
@Module({ @Module({
providers: [MailService,JwtService,AuthService], providers: [MailService],
imports: [ imports: [
ExcelModule, ExcelModule,
SistemaModule, SistemaModule,
+1 -1
View File
@@ -2,7 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing';
import { MailService } from './mail.service'; import { MailService } from './mail.service';
import { getRepositoryToken } from '@nestjs/typeorm'; import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { Correo, Sistema, Status } from '../typeorm/entidades'; // Ajusta si es necesario import { Correo, Sistema, Status } from '../typeorm/votacionesPrueba.entity'; // Ajusta si es necesario
describe('MailService', () => { describe('MailService', () => {
let service: MailService; let service: MailService;
+118 -268
View File
@@ -1,51 +1,68 @@
import { Injectable, UnauthorizedException } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import * as nodemailer from 'nodemailer'; import * as nodemailer from 'nodemailer';
import * as dotenv from 'dotenv';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { Correo, Sistema, Status } from '../typeorm/entidades'; import { Correo, Status } from '../typeorm/votacionesPrueba.entity';
import { SistemaService } from 'src/sistema/sistema.service';
import { SendCorreoDto } from 'src/mail/dto/send-email.dto'; dotenv.config();
import { AuthService } from 'src/auth/auth.service';
import { decrypt } from '../crypto/crypto.util';
@Injectable() @Injectable()
export class MailService { export class MailService {
private transporter;
constructor( constructor(
private readonly sistemaService: SistemaService, @InjectRepository(Correo) private readonly correoRepository: Repository<Correo>,
private readonly authService: AuthService, @InjectRepository(Status) private readonly statusRepository: Repository<Status>,
@InjectRepository(Correo)
private readonly correoRepository: Repository<Correo>, ) {
@InjectRepository(Status)
private readonly statusRepository: Repository<Status>,
@InjectRepository(Sistema)
private readonly sistemaRepositosistemaRepositoryry: Repository<Sistema>, /* this.transporter = nodemailer.createTransport({
) { host: 'smtp-relay.gmail.com',
/* this.transporter = nodemailer.createTransport({ port: 587,
host: 'smtp-relay.gmail.com', secure: false,
port: 587, requireTLS: true,
secure: false, auth: {
requireTLS: true, user: process.env.USER_GMAIL,
auth: { pass: process.env.PASS_GMAIL,
user: process.env.USER_GMAIL, },
pass: process.env.PASS_GMAIL, }); */
},
}); */ this.transporter = nodemailer.createTransport({
/* host: 'smtp.gmail.com', // o smtprelay.gmail.com (ver punto 2)
this.transporter = nodemailer.createTransport({ port: 587,
host: 'smtp.gmail.com', // o smtprelay.gmail.com (ver punto 2) secure: false,
port: 587, requireTLS: true, // TLS STARTTLS
secure: false, auth: { user: process.env.USER_GMAIL, pass: process.env.PASS_GMAIL },
requireTLS: true, // TLS STARTTLS pool: true, // <‑‑ activa reuse
auth: { user: process.env.USER_GMAIL, pass: process.env.PASS_GMAIL }, maxConnections: 1, // una sola conexión viva
pool: true, // <‑‑ activa reuse maxMessages: 100, // reabrir después de 100 envíos
maxConnections: 1, // una sola conexión viva rateDelta: 2000, // ventana 1 s
maxMessages: 100, // reabrir después de 100 envíos rateLimit: 1 // máx. 5 mensajes/seg
rateDelta: 2000, // ventana 1 s });
rateLimit: 1 // máx. 5 mensajes/seg
});
/* this.transporter = nodemailer.createTransport({
host: 'smtp-relay.gmail.com', // o smtprelay.gmail.com (ver punto 2)
port: 587,
secure: false,
requireTLS: true, // TLS STARTTLS
//auth: { user: process.env.USER_GMAIL, pass: process.env.PASS_GMAIL },
name:"acatlan.unam.mx",
pool: true, // <‑‑ activa reuse
maxConnections: 1, // una sola conexión viva
maxMessages: 100, // reabrir después de 100 envíos
rateDelta: 2000, // ventana 1 s
rateLimit: 1 // máx. 5 mensajes/seg
}); */
*/
/* /*
this.transporter = nodemailer.createTransport({ this.transporter = nodemailer.createTransport({
host: 'localhost', host: 'localhost',
@@ -55,252 +72,85 @@ export class MailService {
*/ */
} }
async findAllBySistema(TOKEN: string, nombreSistema: string) { // Función para generar el reporte de correos enviados y fallidos
if (!TOKEN) {
throw new UnauthorizedException('Header "Password" es requerido');
}
const sistem = await this.sistemaService.acceso(TOKEN);
if (!sistem) {
throw new UnauthorizedException('Password incorrecto');
}
const result = await this.correoRepository.find({
where: {
id_sistema: { nombre_sistema: nombreSistema },
},
relations: {
id_sistema: true,
id_status: true,
},
});
return result;
}
async sendMail(sendEmail: SendCorreoDto, token: string) {
let payload;
payload = await this.authService.verificarToken(token);
if (!payload || !payload.nombre) {
throw new UnauthorizedException('Token inválido o incompleto');
}
const sistem = await this.sistemaService.findByNombre(payload.nombre);
if (!sistem) {
throw new UnauthorizedException('sistema No encontrado');
}
const decryptedPassword = decrypt(
sistem.email_password_encrypted,
sistem.email_password_iv,
);
const decryptedToken = decrypt(
sistem.refreshToken_encrypted,
sistem.refreshToken_iv,
);
console.log('sistem', {
type: 'OAuth2',
user: sistem.email,
clientId: sistem.clientId,
clientSecret: decryptedPassword,
refreshToken: decryptedToken,
//accessToken: sistem.accessToken, // opcional
});
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
type: 'OAuth2',
user: sistem.email,
clientId: sistem.clientId,
clientSecret: decryptedPassword,
refreshToken: decryptedToken,
//accessToken: sistem.accessToken, // opcional
},
pool: true,
maxConnections: 1,
maxMessages: 100,
rateDelta: 2000,
rateLimit: 1,
});
async sendMail(to: string, subject: string, text: string, html: string, fecha_recibido: Date, sistema, adjuntos?: any[] ) {
console.log("estos son los archivos adjuntos en el service",adjuntos);
const mailOptions = { const mailOptions = {
from: sistem.email, from: process.env.USER_GMAIL,
to: sendEmail.to, to,
subject: sendEmail.subject, subject,
text: sendEmail.text, text,
html: sendEmail.html, html,
// OJO: nodemailer usa 'attachments', no 'adjuntos'
attachments: (adjuntos || []).map((adj) => {
// Asegúrate de no incluir 'data:image/png;base64,' en 'content'
let base64Clean = adj.content;
// Si viene con prefijo "data:image...", lo quitamos
if (base64Clean.startsWith('data:image/')) {
base64Clean = base64Clean.split('base64,')[1];
}
return {
filename: adj.filename || 'qr.png',
content: Buffer.from(base64Clean, adj.encoding || 'base64'),
cid: adj.cid || 'qrCode', // el mismo que uses en <img src="cid:..."/>
contentType: 'image/png' // recomendable para que sepa que es PNG
};
})
// OJO: nodemailer usa 'attachments', no 'adjuntos'
attachments: (sendEmail.adjuntos || []).map((adj) => {
// Asegúrate de no incluir 'data:image/png;base64,' en 'content'
let base64Clean = adj.content;
// Si viene con prefijo "data:image...", lo quitamos
if (base64Clean.startsWith('data:image/')) {
base64Clean = base64Clean.split('base64,')[1];
}
return {
filename: adj.filename || 'qr.png',
content: Buffer.from(base64Clean, adj.encoding || 'base64'),
cid: adj.cid || 'qrCode', // el mismo que uses en <img src="cid:..."/>
contentType: 'image/png', // recomendable para que sepa que es PNG
};
}),
}; };
let resMail = await transporter.sendMail(mailOptions);
if (!resMail) throw new Error('');
const statusTexto = resMail.accepted.length > 0 ? 'Enviado' : 'Fallido'; console.log(mailOptions);
let resMail = await this.transporter.sendMail(mailOptions);
console.log("este es el resultado del envio",resMail);
//console.log("este es el resultado del envio",resMail.accepted.length);
const statusTexto = resMail.accepted.length > 0 ? "Enviado" : "Fallido";
/*
let status = await this.statusRepository.findOne({ where: { status: statusTexto } });
let status = await this.statusRepository.findOne({
where: { status: statusTexto },
});
if (!status) { if (!status) {
status = this.statusRepository.create({ status: statusTexto }); status = this.statusRepository.create({ status: statusTexto });
await this.statusRepository.save(status); await this.statusRepository.save(status);
} }
const info = this.correoRepository.create({ const info= this.correoRepository.create({
id_status: status, id_status: status,
id_sistema: sistem, id_sistema: sistema,
fecha_recibido: sendEmail.fecha_recibido, fecha_recibido: fecha_recibido,
fecha_enviado: new Date(), fecha_enviado: new Date(),
destinatario: sendEmail.to, destinatario: to,
remitente: sistem.email, remitente: process.env.USER_GMAIL,
});
if (!resMail) { }); */
throw new Error('fallo');
if(!resMail){
throw new Error("fallo")
} }
this.correoRepository.save(info); //this.correoRepository.save(info);
return statusTexto;
return (statusTexto);
} }
private async saveEmailStatus(
dto: SendCorreoDto,
sistem: any,
statusText: string,
) {
try {
// 1. Buscar el estado en la base de datos (Enviado o Fallido)
let status = await this.statusRepository.findOne({
where: { status: statusText },
});
// 2. Si el estado no existe en el catálogo, lo creamos para evitar errores de FK
if (!status) {
status = this.statusRepository.create({ status: statusText });
await this.statusRepository.save(status);
}
// 3. Crear el registro histórico del correo
const info = this.correoRepository.create({
id_status: status,
id_sistema: sistem,
fecha_recibido: dto.fecha_recibido || new Date(), // Fecha en que se solicitó
fecha_enviado: new Date(), // Fecha real de salida
destinatario: dto.to,
remitente: sistem.email,
});
// 4. Guardar el registro de forma asíncrona
return await this.correoRepository.save(info);
} catch (error) {
// Solo hacemos log del error para no detener el proceso masivo si falla el log en DB
console.error(
`Error crítico al guardar status en DB para ${dto.to}:`,
error.message,
);
}
}
async sendBulkMail(emailsData: SendCorreoDto[], token: string) {
// 1. Validación de acceso y obtención de credenciales (una sola vez)
const payload = await this.authService.verificarToken(token);
if (!payload || !payload.nombre)
throw new UnauthorizedException('Token inválido');
const sistem = await this.sistemaService.findByNombre(payload.nombre);
if (!sistem) throw new UnauthorizedException('Sistema no encontrado');
const decryptedPassword = decrypt(
sistem.email_password_encrypted,
sistem.email_password_iv,
);
const decryptedToken = decrypt(
sistem.refreshToken_encrypted,
sistem.refreshToken_iv,
);
// 2. Crear el transporter único con Pool
const transporter = nodemailer.createTransport({
service: 'gmail',
pool: true,
maxConnections: 5, // Procesará hasta 5 conexiones SMTP simultáneas
auth: {
type: 'OAuth2',
user: sistem.email,
clientId: sistem.clientId,
clientSecret: decryptedPassword,
refreshToken: decryptedToken,
},
});
const CHUNK_SIZE = 50; // Tamaño de cada lote
const results = { successful: 0, failed: 0 };
// 3. Proceso por Chunks (Lotes)
for (let i = 0; i < emailsData.length; i += CHUNK_SIZE) {
const chunk = emailsData.slice(i, i + CHUNK_SIZE);
// Procesamos el lote actual en paralelo para ganar velocidad
await Promise.all(
chunk.map(async (emailDto) => {
try {
const mailOptions = {
from: sistem.email,
to: emailDto.to,
subject: emailDto.subject,
html: emailDto.html,
attachments: (emailDto.adjuntos || []).map((adj) => ({
filename: adj.filename || 'adjunto.png',
content: Buffer.from(
adj.content.split('base64,')[1] || adj.content,
'base64',
),
cid: adj.cid,
contentType: adj.contentType || 'image/png',
})),
};
await transporter.sendMail(mailOptions);
await this.saveEmailStatus(emailDto, sistem, 'Enviado');
results.successful++;
} catch (error) {
console.error(
`Error en envío masivo a ${emailDto.to}:`,
error.message,
);
await this.saveEmailStatus(emailDto, sistem, 'Fallido');
results.failed++;
}
}),
);
console.log(
`Lote completado: ${i + chunk.length} de ${emailsData.length}`,
);
}
transporter.close();
return results;
}
} }
+3 -6
View File
@@ -1,12 +1,11 @@
import { NestFactory } from '@nestjs/core'; import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module'; import { AppModule } from './app.module';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { json } from 'express';
async function bootstrap() { async function bootstrap() {
const app = await NestFactory.create(AppModule); const app = await NestFactory.create(AppModule);
app.use(json({ limit: '50mb' }));
app.enableCors(); app.enableCors();
@@ -20,11 +19,9 @@ async function bootstrap() {
.build(); .build();
const document = SwaggerModule.createDocument(app, config); const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('documentation', app, document); SwaggerModule.setup('documentation', app, document);
await app.listen(process.env.PORT ?? 3000); await app.listen(process.env.PORT ?? 3000);
console.log( console.log(`Aplicación corriendo en: http://localhost:${process.env.PORT ?? 3000}`);
`Aplicación corriendo en: http://localhost:${process.env.PORT ?? 3000}`,
);
} }
bootstrap(); bootstrap();
-5
View File
@@ -1,5 +0,0 @@
// comprobar-sistema.dto.ts
export class ComprobarSistemaDto {
password: string;
}
+4 -25
View File
@@ -1,4 +1,4 @@
import { IsString, IsNotEmpty } from 'class-validator'; import { IsString, IsOptional, IsBoolean, IsNotEmpty } from 'class-validator';
export class CreateSistemaDto { export class CreateSistemaDto {
@IsNotEmpty() @IsNotEmpty()
@@ -9,29 +9,8 @@ export class CreateSistemaDto {
@IsString() @IsString()
ip:string; ip:string;
//Password del sistema @IsNotEmpty()
@IsNotEmpty() @IsString()
@IsString() password: string;
password: string;
@IsNotEmpty()
@IsString()
email: string;
//clientSecret
@IsNotEmpty()
@IsString()
email_password:string
@IsNotEmpty()
@IsString()
clientId:string
//TU_REFRESH_TOKEN
@IsNotEmpty()
@IsString()
refreshToken:string;
} }
-34
View File
@@ -1,34 +0,0 @@
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
export class UpdateSistemaDto {
@IsOptional()
@IsString()
Nombre?: string;
@IsOptional()
@IsString()
ip?: string;
//Password del sistema
@IsNotEmpty()
@IsString()
token: string;
@IsOptional()
@IsString()
email?: string;
//clientSecret
@IsOptional()
@IsString()
email_password?: string;
@IsOptional()
@IsString()
clientId?: string;
//TU_REFRESH_TOKEN
@IsOptional()
@IsString()
refreshToken?: string;
}
+6 -42
View File
@@ -1,58 +1,22 @@
import { import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
Controller,
Get,
Post,
Body,
Patch,
Param,
Query,
} from '@nestjs/common';
import { SistemaService } from './sistema.service'; import { SistemaService } from './sistema.service';
import { CreateSistemaDto } from './dto/create-sistema.dto'; import { CreateSistemaDto } from './dto/create-sistema.dto';
import { ComprobarSistemaDto } from './dto/comprobar-sistema'; import { ApiCreateSistema, ApiGenerarToken } from './sistema.decorators';
import {
ApiAccesoSistema,
ApiCreateSistema,
ApiGenerarToken,
ApiUpdateSistema,
} from './sistema.decorators';
import { UpdateSistemaDto } from './dto/sistema-update';
@Controller('sistema') @Controller('sistema')
export class SistemaController { export class SistemaController {
constructor(private readonly sistemaService: SistemaService) {} constructor(private readonly sistemaService: SistemaService) {}
@Get()
findAll() {
return this.sistemaService.findAll();
}
@Post() @Post()
@ApiCreateSistema() @ApiCreateSistema()
create(@Body() createSistemaDto: CreateSistemaDto) { create(@Body() createSistemaDto: CreateSistemaDto, password:string) {
return this.sistemaService.create(createSistemaDto); return this.sistemaService.create(createSistemaDto);
} }
@Patch('config')
@ApiUpdateSistema()
update2(@Body() updateSistemaDto: UpdateSistemaDto) {
return this.sistemaService.update(updateSistemaDto);
}
@Patch(':nombre_sistema') @Patch(':nombre_sistema')
@ApiGenerarToken() @ApiGenerarToken()
update( update(@Param('nombre_sistema') nombre_sistema: string, @Body() password: string) {
@Param('nombre_sistema') nombre_sistema: string, return this.sistemaService.Generar_Token(nombre_sistema, password);
@Body() comprobarSistemaDto: ComprobarSistemaDto,
) {
return this.sistemaService.Generar_Token(
nombre_sistema,
comprobarSistemaDto.password,
);
}
@ApiAccesoSistema()
@Get('acceso')
async acceso(@Query('token') token: string) {
return this.sistemaService.acceso(token);
} }
} }
+18 -127
View File
@@ -1,115 +1,28 @@
import { applyDecorators } from '@nestjs/common'; import { applyDecorators } from '@nestjs/common';
import { ApiBody, ApiOperation, ApiParam, ApiQuery } from '@nestjs/swagger'; import { ApiBody, ApiOperation, ApiParam } from '@nestjs/swagger';
export const ApiUpdateSistema = () => {
return applyDecorators(
ApiOperation({
summary: 'Actualizar configuración del sistema',
description: `
Este endpoint permite **actualizar parcialmente** la configuración de un sistema existente.
🔐 **Requiere token del sistema** para autorizar la operación.
Se pueden actualizar uno o más de los siguientes campos:
- Nombre del sistema
- IP
- Credenciales de correo
- Credenciales OAuth (clientId, refreshToken)
⚠️ Solo se actualizarán los campos enviados en el cuerpo.
`,
}),
ApiBody({
description: 'Datos a actualizar del sistema',
schema: {
type: 'object',
properties: {
Nombre: {
type: 'string',
example: 'Sistema de Notificaciones',
description: 'Nuevo nombre del sistema',
},
ip: {
type: 'string',
example: '192.168.0.20',
description: 'Nueva dirección IP autorizada',
},
token: {
type: 'string',
example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
description: 'Token JWT del sistema (obligatorio)',
},
email: {
type: 'string',
format: 'email',
example: 'sistema@gmail.com',
description: 'Correo del sistema para envío de emails',
},
email_password: {
type: 'string',
example: 'app-password-123',
description: 'Contraseña o secreto del correo',
},
clientId: {
type: 'string',
example: '1234567890-abc.apps.googleusercontent.com',
description: 'Client ID de Google OAuth',
},
refreshToken: {
type: 'string',
example: '1//0gxxxxxxxxxxxxxxxx',
description: 'Refresh Token OAuth del correo',
},
},
required: ['token'],
},
}),
);
};
export const ApiCreateSistema = () => { export const ApiCreateSistema = () => {
return applyDecorators( return applyDecorators(
ApiOperation({ ApiOperation({
summary: 'Registrar un nuevo sistema', summary: 'Registrar un nuevo sistema',
description: `Este endpoint permite registrar un sistema que podrá autenticarse y utilizar servicios protegidos. description: `Este endpoint permite dar de alta un sistema para poder utilizar funcionalidades autenticadas mediante contraseña.
Debe incluirse la contraseña maestra del administrador para autorizar la creación. Se debe proporcionar:
Además, se deben proporcionar las credenciales del correo que el sistema usará para enviar emails.`, - **Nombre**: Nombre único del sistema.
- **ip**: Dirección IP desde la cual se harán peticiones.
- **password**: Contraseña segura que el sistema usará para autenticarse.`,
}), }),
ApiBody({ ApiBody({
description: 'Datos del sistema a registrar', description: 'Datos del sistema a registrar',
schema: { schema: {
type: 'object', type: 'object',
properties: { properties: {
Nombre: { Nombre: { type: 'string', example: 'Sistema de Reportes' },
type: 'string', ip: { type: 'string', example: '192.168.1.100' },
example: 'Sistema de Notificaciones', password: { type: 'string', example: 'clave-segura-123' },
description: 'Nombre único del sistema a registrar',
},
ip: {
type: 'string',
example: '192.168.0.10',
description: 'Dirección IP desde donde se harán las peticiones',
},
email: {
type: 'string',
format: 'email',
example: 'sistema@gmail.com',
description: 'Correo electrónico del sistema para enviar emails',
},
email_password: {
type: 'string',
example: 'contraseña-de-aplicacion',
description: 'Contraseña de aplicación del correo proporcionado',
},
password: {
type: 'string',
example: 'clave-admin-secreta',
description: 'Contraseña maestra para autorizar el registro',
},
}, },
required: ['Nombre', 'ip', 'email', 'email_password', 'password'], required: ['Nombre', 'ip', 'password'],
}, },
}), })
); );
}; };
@@ -117,43 +30,21 @@ export const ApiGenerarToken = () => {
return applyDecorators( return applyDecorators(
ApiOperation({ ApiOperation({
summary: 'Generar token JWT para un sistema', summary: 'Generar token JWT para un sistema',
description: `Este endpoint permite generar un token de autenticación JWT para un sistema previamente registrado. description: `Este endpoint permite generar un token de acceso válido para el sistema registrado.
Se debe enviar el nombre del sistema como parámetro de ruta y su contraseña de registro en el cuerpo.`, Se debe enviar el nombre del sistema como parámetro de ruta y la contraseña como cuerpo.`,
}), }),
ApiParam({ ApiParam({
name: 'nombre_sistema', name: 'nombre_sistema',
required: true, required: true,
description: 'Nombre exacto del sistema registrado', description: 'Nombre exacto del sistema registrado',
example: 'Sistema de Notificaciones', example: 'Sistema de Reportes',
}), }),
ApiBody({ ApiBody({
description: 'Contraseña del sistema para verificar identidad', description: 'Contraseña del sistema',
schema: { schema: {
type: 'object', type: 'string',
properties: { example: 'clave-segura-123',
password: {
type: 'string',
example: 'clave-admin-secreta',
description: 'Contraseña de autenticación del sistema',
},
},
required: ['password'],
}, },
}), })
);
};
export const ApiAccesoSistema = () => {
return applyDecorators(
ApiOperation({
summary: 'Verificar acceso del sistema mediante token',
description: `Este endpoint permite verificar si un token JWT enviado por un sistema es válido y tiene acceso.`,
}),
ApiQuery({
name: 'token',
required: true,
description: 'Token JWT del sistema',
example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
}),
); );
}; };
+3 -5
View File
@@ -2,17 +2,15 @@ import { Module } from '@nestjs/common';
import { SistemaService } from './sistema.service'; import { SistemaService } from './sistema.service';
import { SistemaController } from './sistema.controller'; import { SistemaController } from './sistema.controller';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { Sistema } from 'src/typeorm/entidades'; import { Sistema } from 'src/typeorm/votacionesPrueba.entity';
import { AuthModule } from 'src/auth/auth.module';
@Module({ @Module({
controllers: [SistemaController], controllers: [SistemaController],
providers: [SistemaService], providers: [SistemaService],
imports:[ imports:[
TypeOrmModule.forFeature([ TypeOrmModule.forFeature([
Sistema]), Sistema])
AuthModule
], ],
exports:[SistemaModule,SistemaService] exports:[SistemaModule]
}) })
export class SistemaModule {} export class SistemaModule {}
+30 -141
View File
@@ -1,164 +1,53 @@
import { import { Injectable, NotFoundException, UnauthorizedException } from '@nestjs/common';
Injectable,
NotFoundException,
UnauthorizedException,
} from '@nestjs/common';
import { CreateSistemaDto } from './dto/create-sistema.dto'; import { CreateSistemaDto } from './dto/create-sistema.dto';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Sistema } from 'src/typeorm/entidades'; import { Sistema } from 'src/typeorm/votacionesPrueba.entity';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { AuthService } from 'src/auth/auth.service';
import { encrypt } from 'src/crypto/crypto.util'; import { randomBytes } from 'crypto';
import { UpdateSistemaDto } from './dto/sistema-update';
@Injectable() @Injectable()
export class SistemaService { export class SistemaService {
constructor( constructor(
@InjectRepository(Sistema) @InjectRepository(Sistema) private readonly sistemaRepository: Repository<Sistema>
private readonly sistemaRepository: Repository<Sistema>,
private readonly authService: AuthService,
) {}
async findAll() { ){}
const sistemas = await this.sistemaRepository.find();
if (!sistemas || sistemas.length === 0) { generarPassword(longitud = 12): string {
throw new NotFoundException('No hay sistemas registrados'); return randomBytes(longitud)
} .toString('base64')
return sistemas; .slice(0, longitud)
.replace(/[+/=]/g, () => String.fromCharCode(33 + Math.floor(Math.random() * 94))); // reemplaza caracteres raros
} }
async Generar_Token(nombreSistema: string, adminPassword: string) { async Generar_Token(nombreSistema:string,adminPasword:string) {
if (adminPassword !== process.env.ADMIN_PASSWORD) { if(adminPasword!==process.env.ADMIN_PASSWORD){
throw new UnauthorizedException('Password erróneo'); throw new UnauthorizedException('Password erroneo');
} }
const sistema = await this.sistemaRepository.findOneBy({nombre_sistema:nombreSistema})
const sistema = await this.sistemaRepository.findOneBy({ if(!sistema){
nombre_sistema: nombreSistema, throw new NotFoundException('Sistema no encontrado')
});
if (!sistema) {
throw new NotFoundException('Sistema no encontrado');
} }
sistema.token=this.generarPassword()
const token = this.authService.generarToken({ this.sistemaRepository.save(sistema)
sistemaId: sistema.id_sistema, return sistema.token;
nombre: sistema.nombre_sistema,
ip: sistema.ip,
});
return token;
}
async acceso(token: string) {
const decoded = await this.authService.verificarToken(token);
if (!decoded) {
throw new UnauthorizedException('Token inválido');
}
const sistema = await this.sistemaRepository.findOneBy({
id_sistema: decoded.sistemaId,
});
if (!sistema) {
throw new UnauthorizedException('Sistema no encontrado');
}
return true;
} }
async create(createSistemaDto: CreateSistemaDto) { async create(createSistemaDto: CreateSistemaDto) {
if (createSistemaDto.password !== process.env.ADMIN_PASSWORD) { if(createSistemaDto.password!==process.env.ADMIN_PASSWORD){
throw new UnauthorizedException('Password erróneo'); throw new UnauthorizedException('Password erroneo');
} }
const { encryptedData: encryptedData_token, iv: encryptedData_iv } = const sistem = this.sistemaRepository.create({
encrypt(createSistemaDto.refreshToken); nombre_sistema:createSistemaDto.Nombre,
ip:createSistemaDto.ip,
const { encryptedData: encrypted_password, iv: iv_password } = encrypt(
createSistemaDto.email_password,
);
const sistema = this.sistemaRepository.create({
nombre_sistema: createSistemaDto.Nombre,
ip: createSistemaDto.ip,
email: createSistemaDto.email,
email_password_encrypted: encrypted_password,
email_password_iv: iv_password,
clientId: createSistemaDto.clientId,
refreshToken_encrypted: encryptedData_token,
refreshToken_iv: encryptedData_iv,
}); });
await this.sistemaRepository.save(sistema); await this.sistemaRepository.save(sistem)
return this.Generar_Token( return this.Generar_Token(createSistemaDto.Nombre,createSistemaDto.password);
createSistemaDto.Nombre,
createSistemaDto.password,
);
} }
async update(updateSistemaDto: UpdateSistemaDto) {
const decoded = await this.authService.verificarToken(
updateSistemaDto.token,
);
console.log('aqui');
if (!decoded) {
throw new UnauthorizedException('Token inválido');
}
let sistem = await this.sistemaRepository.findOneBy({
id_sistema: decoded.sistemaId,
});
if (!sistem) {
throw new UnauthorizedException('Sistema no encontrado');
}
let encryptedData_token: string | undefined;
let encryptedData_iv: string | undefined;
let encrypted_password: string | undefined;
let iv_password: string | undefined;
if (updateSistemaDto.refreshToken) {
({ encryptedData: encryptedData_token, iv: encryptedData_iv } = encrypt(
updateSistemaDto.refreshToken,
));
sistem.refreshToken_encrypted = encryptedData_token;
sistem.refreshToken_iv = encryptedData_iv;
}
if (updateSistemaDto.email_password) {
({ encryptedData: encrypted_password, iv: iv_password } = encrypt(
updateSistemaDto.email_password,
));
sistem.email_password_encrypted = encrypted_password;
sistem.email_password_iv = iv_password;
}
if (updateSistemaDto.Nombre) {
sistem.nombre_sistema = updateSistemaDto.Nombre;
}
if (updateSistemaDto.ip) {
sistem.ip = updateSistemaDto.ip;
}
if (updateSistemaDto.email) {
sistem.email = updateSistemaDto.email;
}
if (updateSistemaDto.clientId) {
sistem.clientId = updateSistemaDto.clientId;
}
await this.sistemaRepository.save(sistem);
return 'se guardo correctamente';
}
async findByNombre(sistema: string) {
const sistem = this.sistemaRepository.findOne({
where: { nombre_sistema: sistema },
});
if (!sistem) {
throw new UnauthorizedException('Password erróneo');
}
return sistem;
}
} }
@@ -6,33 +6,14 @@ export class Sistema {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id_sistema: number; id_sistema: number;
@Column({ type: 'varchar', length: 50, nullable: false }) @Column({ type: 'varchar', length: 255, nullable: true })
token: string;
@Column({ type: 'varchar', length: 50 })
nombre_sistema: string; nombre_sistema: string;
@Column({ type: 'varchar', length: 50,nullable: false }) @Column({ type: 'varchar', length: 50 })
ip:string; ip:string
@Column({nullable: false})
email: string;
//clientSecret encriptada
@Column({nullable: false})
email_password_encrypted: string;
@Column({nullable: false})
email_password_iv: string;
@Column({nullable: false})
clientId:string
//TU_REFRESH_TOKEN
@Column({nullable: false})
refreshToken_encrypted:string;
@Column({nullable: false})
refreshToken_iv: string;
@OneToMany(() => Correo, (correo) => correo.id_sistema) @OneToMany(() => Correo, (correo) => correo.id_sistema)
correos: Correo[]; correos: Correo[];