From 1895e29cf219ae236fff024b3f6067d8e2188a08 Mon Sep 17 00:00:00 2001 From: evenegas Date: Wed, 9 Apr 2025 11:10:28 -0600 Subject: [PATCH] =?UTF-8?q?Se=20agreg=C3=B3=20m=C3=B3dulo=20de=20sistemas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env_example | 1 + package-lock.json | 33 ++++++++++++++++ package.json | 1 + src/app.module.ts | 2 + src/mail/mail.controller.ts | 12 +++--- src/mail/mail.decorators.ts | 14 ++++++- src/mail/mail.module.ts | 2 + src/sistema/dto/create-sistema.dto.ts | 16 ++++++++ src/sistema/sistema.controller.ts | 22 +++++++++++ src/sistema/sistema.decorators.ts | 50 ++++++++++++++++++++++++ src/sistema/sistema.module.ts | 16 ++++++++ src/sistema/sistema.service.ts | 53 ++++++++++++++++++++++++++ src/typeorm/votacionesPrueba.entity.ts | 8 +++- 13 files changed, 220 insertions(+), 10 deletions(-) create mode 100644 src/sistema/dto/create-sistema.dto.ts create mode 100644 src/sistema/sistema.controller.ts create mode 100644 src/sistema/sistema.decorators.ts create mode 100644 src/sistema/sistema.module.ts create mode 100644 src/sistema/sistema.service.ts diff --git a/.env_example b/.env_example index a56b453..fe11ec3 100644 --- a/.env_example +++ b/.env_example @@ -1,6 +1,7 @@ # Gmail USER_GMAIL= PASS_GMAIL= +ADMIN_PASSWORD=12345 # MariaDB database settings db_type=mysql diff --git a/package-lock.json b/package-lock.json index 160f13f..f140b7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@nestjs/platform-express": "^11.0.1", "@nestjs/swagger": "^11.0.5", "@nestjs/typeorm": "^11.0.0", + "class-validator": "^0.14.1", "csv-parser": "^3.2.0", "dotenv": "^16.4.7", "exceljs": "^4.4.0", @@ -3169,6 +3170,12 @@ "@types/serve-static": "*" } }, + "node_modules/@types/validator": { + "version": "13.12.3", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.3.tgz", + "integrity": "sha512-2ipwZ2NydGQJImne+FhNdhgRM37e9lCev99KnqkbFHd94Xn/mErARWI1RSLem1QA19ch5kOhzIZd7e8CA2FI8g==", + "license": "MIT" + }, "node_modules/@types/yargs": { "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", @@ -4643,6 +4650,17 @@ "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", "dev": true }, + "node_modules/class-validator": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz", + "integrity": "sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==", + "license": "MIT", + "dependencies": { + "@types/validator": "^13.11.8", + "libphonenumber-js": "^1.10.53", + "validator": "^13.9.0" + } + }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -7929,6 +7947,12 @@ "node": ">= 0.8.0" } }, + "node_modules/libphonenumber-js": { + "version": "1.12.6", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.6.tgz", + "integrity": "sha512-PJiS4ETaUfCOFLpmtKzAbqZQjCCKVu2OhTV4SVNNE7c2nu/dACvtCqj4L0i/KWNnIgRv7yrILvBj5Lonv5Ncxw==", + "license": "MIT" + }, "node_modules/lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", @@ -11099,6 +11123,15 @@ "node": ">=10.12.0" } }, + "node_modules/validator": { + "version": "13.15.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.0.tgz", + "integrity": "sha512-36B2ryl4+oL5QxZ3AzD0t5SsMNGvTtQHpjgFO5tbNxfXbMFkY822ktCDe1MnlqV3301QQI9SLHDNJokDI+Z9pA==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 66c35fa..4d57a19 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@nestjs/platform-express": "^11.0.1", "@nestjs/swagger": "^11.0.5", "@nestjs/typeorm": "^11.0.0", + "class-validator": "^0.14.1", "csv-parser": "^3.2.0", "dotenv": "^16.4.7", "exceljs": "^4.4.0", diff --git a/src/app.module.ts b/src/app.module.ts index 5b3a3a7..c956c8b 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -5,6 +5,7 @@ import { MailModule } from './mail/mail.module'; import {ExcelModule} from './excel/excel.module' import { TypeOrmModule } from '@nestjs/typeorm'; import { ConfigModule } from '@nestjs/config'; +import { SistemaModule } from './sistema/sistema.module'; @@ -28,6 +29,7 @@ import { ConfigModule } from '@nestjs/config'; MailModule, ExcelModule, + SistemaModule, ], diff --git a/src/mail/mail.controller.ts b/src/mail/mail.controller.ts index f7e7c11..2cf3598 100644 --- a/src/mail/mail.controller.ts +++ b/src/mail/mail.controller.ts @@ -30,24 +30,24 @@ export class MailController { @Post('send') @ApiSendMail() async sendMail( - @Headers() headers:{password:string}, + @Headers() headers:{token:string}, @Body() body: { to: string; subject: string; text: string; fecha_recibido: Date, html:string, adjuntos:any }) { -/* + // esta parte hay que rehacerla, se tiene que obtener la contraseña del sistema en base a la clave del sistema. // debe de existir un modulo para crear contraseñas para las apis y administrar esas contraseñas - if (!headers.password) { + if (!headers.token) { throw new UnauthorizedException('Header "Password" es requerido'); } - const sistem = await this.sistemaRepository.findOne({ where: { password: headers.password } }); + const sistem = await this.sistemaRepository.findOne({ where: { token: headers.token } }); if (!sistem) { throw new UnauthorizedException('Password incorrecto'); - } */ + } @@ -73,7 +73,7 @@ export class MailController { throw new UnauthorizedException('Header "Password" es requerido'); } - const sistem = await this.sistemaRepository.findOne({ where: { password: headers.password } }); + const sistem = await this.sistemaRepository.findOne({ where: { token: headers.password } }); if (!sistem) { throw new UnauthorizedException('Password incorrecto'); diff --git a/src/mail/mail.decorators.ts b/src/mail/mail.decorators.ts index 2389d17..5f3f55f 100644 --- a/src/mail/mail.decorators.ts +++ b/src/mail/mail.decorators.ts @@ -10,8 +10,18 @@ export const ApiSendMail = () => { - **subject**: Asunto del correo. - **text**: Cuerpo del mensaje. - **fecha_recibido**: Fecha en que se registró la solicitud. -- **sistema**: contraseña del sistema.`, - }), +- **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({ name: 'password', description: 'Contraseña del sistema para autenticación', diff --git a/src/mail/mail.module.ts b/src/mail/mail.module.ts index 8208010..154c7b5 100644 --- a/src/mail/mail.module.ts +++ b/src/mail/mail.module.ts @@ -8,11 +8,13 @@ import { Correo, Sistema, }from 'src/typeorm/votacionesPrueba.entity' +import { SistemaModule } from 'src/sistema/sistema.module'; @Module({ providers: [MailService], imports: [ ExcelModule, + SistemaModule, TypeOrmModule.forFeature([ Status, Correo, diff --git a/src/sistema/dto/create-sistema.dto.ts b/src/sistema/dto/create-sistema.dto.ts new file mode 100644 index 0000000..5ce14e6 --- /dev/null +++ b/src/sistema/dto/create-sistema.dto.ts @@ -0,0 +1,16 @@ +import { IsString, IsOptional, IsBoolean, IsNotEmpty } from 'class-validator'; + +export class CreateSistemaDto { + @IsNotEmpty() + @IsString() + Nombre:string; + + @IsNotEmpty() + @IsString() + ip:string; + + @IsNotEmpty() + @IsString() + password: string; + +} diff --git a/src/sistema/sistema.controller.ts b/src/sistema/sistema.controller.ts new file mode 100644 index 0000000..70c1d06 --- /dev/null +++ b/src/sistema/sistema.controller.ts @@ -0,0 +1,22 @@ +import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; +import { SistemaService } from './sistema.service'; +import { CreateSistemaDto } from './dto/create-sistema.dto'; +import { ApiCreateSistema, ApiGenerarToken } from './sistema.decorators'; + + +@Controller('sistema') +export class SistemaController { + constructor(private readonly sistemaService: SistemaService) {} + + @Post() + @ApiCreateSistema() + create(@Body() createSistemaDto: CreateSistemaDto, password:string) { + return this.sistemaService.create(createSistemaDto); + } + + @Patch(':nombre_sistema') + @ApiGenerarToken() + update(@Param('nombre_sistema') nombre_sistema: string, @Body() password: string) { + return this.sistemaService.Generar_Token(nombre_sistema, password); + } +} diff --git a/src/sistema/sistema.decorators.ts b/src/sistema/sistema.decorators.ts new file mode 100644 index 0000000..b0eaab5 --- /dev/null +++ b/src/sistema/sistema.decorators.ts @@ -0,0 +1,50 @@ +import { applyDecorators } from '@nestjs/common'; +import { ApiBody, ApiOperation, ApiParam } from '@nestjs/swagger'; + +export const ApiCreateSistema = () => { + return applyDecorators( + ApiOperation({ + summary: 'Registrar un nuevo sistema', + description: `Este endpoint permite dar de alta un sistema para poder utilizar funcionalidades autenticadas mediante contraseña. +Se debe proporcionar: +- **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({ + description: 'Datos del sistema a registrar', + schema: { + type: 'object', + properties: { + Nombre: { type: 'string', example: 'Sistema de Reportes' }, + ip: { type: 'string', example: '192.168.1.100' }, + password: { type: 'string', example: 'clave-segura-123' }, + }, + required: ['Nombre', 'ip', 'password'], + }, + }) + ); +}; + +export const ApiGenerarToken = () => { + return applyDecorators( + ApiOperation({ + summary: 'Generar token JWT para un sistema', + 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 la contraseña como cuerpo.`, + }), + ApiParam({ + name: 'nombre_sistema', + required: true, + description: 'Nombre exacto del sistema registrado', + example: 'Sistema de Reportes', + }), + ApiBody({ + description: 'Contraseña del sistema', + schema: { + type: 'string', + example: 'clave-segura-123', + }, + }) + ); +}; diff --git a/src/sistema/sistema.module.ts b/src/sistema/sistema.module.ts new file mode 100644 index 0000000..e6c1ec3 --- /dev/null +++ b/src/sistema/sistema.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { SistemaService } from './sistema.service'; +import { SistemaController } from './sistema.controller'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Sistema } from 'src/typeorm/votacionesPrueba.entity'; + +@Module({ + controllers: [SistemaController], + providers: [SistemaService], + imports:[ + TypeOrmModule.forFeature([ + Sistema]) + ], + exports:[SistemaModule] +}) +export class SistemaModule {} diff --git a/src/sistema/sistema.service.ts b/src/sistema/sistema.service.ts new file mode 100644 index 0000000..5c1fd51 --- /dev/null +++ b/src/sistema/sistema.service.ts @@ -0,0 +1,53 @@ +import { Injectable, NotFoundException, UnauthorizedException } from '@nestjs/common'; +import { CreateSistemaDto } from './dto/create-sistema.dto'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Sistema } from 'src/typeorm/votacionesPrueba.entity'; +import { Repository } from 'typeorm'; + +import { randomBytes } from 'crypto'; + +@Injectable() +export class SistemaService { + constructor( + @InjectRepository(Sistema) private readonly sistemaRepository: Repository + + ){} + + generarPassword(longitud = 12): string { + return randomBytes(longitud) + .toString('base64') + .slice(0, longitud) + .replace(/[+/=]/g, () => String.fromCharCode(33 + Math.floor(Math.random() * 94))); // reemplaza caracteres raros + } + + + async Generar_Token(nombreSistema:string,adminPasword:string) { + if(adminPasword!==process.env.ADMIN_PASSWORD){ + throw new UnauthorizedException('Password erroneo'); + } + const sistema = await this.sistemaRepository.findOneBy({nombre_sistema:nombreSistema}) + if(!sistema){ + throw new NotFoundException('Sistema no encontrado') + } + sistema.token=this.generarPassword() + + this.sistemaRepository.save(sistema) + return sistema.token; + } + + async create(createSistemaDto: CreateSistemaDto) { + if(createSistemaDto.password!==process.env.ADMIN_PASSWORD){ + throw new UnauthorizedException('Password erroneo'); + } + + const sistem = this.sistemaRepository.create({ + nombre_sistema:createSistemaDto.Nombre, + ip:createSistemaDto.ip, + }); + await this.sistemaRepository.save(sistem) + + return this.Generar_Token(createSistemaDto.Nombre,createSistemaDto.password); + } + + +} diff --git a/src/typeorm/votacionesPrueba.entity.ts b/src/typeorm/votacionesPrueba.entity.ts index 71bd67d..605a3ff 100644 --- a/src/typeorm/votacionesPrueba.entity.ts +++ b/src/typeorm/votacionesPrueba.entity.ts @@ -1,3 +1,4 @@ +import { isEmpty } from 'class-validator'; import { Entity, PrimaryGeneratedColumn, Column, OneToMany, ManyToOne, JoinColumn } from 'typeorm'; @Entity() @@ -5,12 +6,15 @@ export class Sistema { @PrimaryGeneratedColumn() id_sistema: number; - @Column({ type: 'varchar', length: 50 }) - password: string; + @Column({ type: 'varchar', length: 255, nullable: true }) + token: string; @Column({ type: 'varchar', length: 50 }) nombre_sistema: string; + @Column({ type: 'varchar', length: 50 }) + ip:string + @OneToMany(() => Correo, (correo) => correo.id_sistema) correos: Correo[]; }