added conection to db and login with google

This commit is contained in:
2025-10-17 09:06:53 -06:00
parent f68aacb21a
commit 935a8384a7
13 changed files with 1289 additions and 83 deletions
+911 -81
View File
File diff suppressed because it is too large Load Diff
+12 -1
View File
@@ -21,10 +21,19 @@
},
"dependencies": {
"@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.0.1",
"@nestjs/jwt": "^11.0.1",
"@nestjs/mapped-types": "*",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/typeorm": "^11.0.0",
"mysql2": "^3.15.2",
"passport": "^0.7.0",
"passport-google-oauth20": "^2.0.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1"
"rxjs": "^7.8.1",
"typeorm": "^0.3.27"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
@@ -35,6 +44,8 @@
"@types/express": "^5.0.0",
"@types/jest": "^30.0.0",
"@types/node": "^22.10.7",
"@types/passport": "^1.0.17",
"@types/passport-google-oauth20": "^2.0.16",
"@types/supertest": "^6.0.2",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
+25 -1
View File
@@ -1,9 +1,33 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { PersonaModule } from './persona/persona.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({
imports: [],
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (config: ConfigService) => ({
type: 'mysql', // ejemplo, ajusta a tu BD
host: config.get<string>('DB_HOST'),
port: config.get<number>('DB_PORT'),
username: config.get<string>('DB_USER'),
password: config.get<string>('DB_PASS'),
database: config.get<string>('DB_NAME'),
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true,
}),
}),
PersonaModule,
],
controllers: [AppController],
providers: [AppService],
})
+1
View File
@@ -0,0 +1 @@
export class CreatePersonaDto {}
+4
View File
@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreatePersonaDto } from './create-persona.dto';
export class UpdatePersonaDto extends PartialType(CreatePersonaDto) {}
+55
View File
@@ -0,0 +1,55 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity({ name: 'persona' })
export class Persona {
@PrimaryGeneratedColumn()
idPersona: number;
@Column({ length: 50 })
nombre: string;
@Column({ length: 50 })
apellidoP: string;
@Column({ length: 50 })
apellidoM: string;
@Column({ length: 150 })
password: string;
@Column({ length: 150, nullable: true })
passwordTem?: string;
@Column({ type: 'datetime', nullable: true })
fechaPasswordTem?: Date;
@Column({ length: 100, unique: true })
correo: string;
@Column({ length: 100, nullable: true })
carreraAds?: string;
@Column({ type: 'float', default: 0 })
cantidadCuenta: number;
@Column({ length: 150, unique: true })
usuario: string;
@Column({ length: 50, nullable: true })
numeroIdentificar?: string;
@Column({ length: 50 })
tipoUsuario: string;
@Column({ type: 'boolean', default: false })
correoEnviado: boolean;
@Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP' })
fechaRegistro: Date;
@Column({ type: 'boolean', default: false })
cambioPasswordReq: boolean;
@Column({ type: 'boolean', default: false })
primerLogin: boolean;
}
+16
View File
@@ -0,0 +1,16 @@
import {
Injectable,
ExecutionContext,
UnauthorizedException,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class GoogleAuthGuard extends AuthGuard('google') {
handleRequest(err, user, info, context: ExecutionContext) {
if (err || !user) {
throw err || new UnauthorizedException(); // deja que el controller maneje el redirect
}
return user;
}
}
+55
View File
@@ -0,0 +1,55 @@
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
import { ConfigService } from '@nestjs/config';
import { PersonaService } from './persona.service';
@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor(
private readonly personaService: PersonaService,
private readonly config: ConfigService,
) {
const clientID = config.get<string>('GOOGLE_CLIENT_ID');
const clientSecret = config.get<string>('GOOGLE_CLIENT_SECRET');
const callbackURL = config.get<string>('GOOGLE_CALLBACK_URL');
if (!clientID || !clientSecret || !callbackURL) {
throw new Error(
'Las variables de entorno de Google OAuth no están definidas',
);
}
super({
clientID,
clientSecret,
callbackURL,
scope: ['email', 'profile'],
passReqToCallback: true,
});
}
async validate(
req: any,
accessToken: string,
refreshToken: string,
profile: any,
done: VerifyCallback,
): Promise<any> {
try {
const email = profile.emails?.[0]?.value;
const name = profile.name?.givenName || '';
const apellidoP = profile.name?.familyName || '';
const user = await this.personaService.validateOrCreateGoogleUser({
email,
nombre: name,
apellidoP,
});
done(null, user);
} catch (error) {
done(error, false);
}
}
}
+20
View File
@@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { PersonaController } from './persona.controller';
import { PersonaService } from './persona.service';
describe('PersonaController', () => {
let controller: PersonaController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [PersonaController],
providers: [PersonaService],
}).compile();
controller = module.get<PersonaController>(PersonaController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});
+64
View File
@@ -0,0 +1,64 @@
import { Controller, Get, UseGuards, Req, Res } from '@nestjs/common';
import type { Response } from 'express';
import { PersonaService } from './persona.service';
import { GoogleAuthGuard } from './google-auth.guard';
@Controller('persona')
export class PersonaController {
constructor(private readonly personaService: PersonaService) {}
@Get()
getAll() {
return this.personaService.findAll();
}
// Redirige a Google
@Get('google')
@UseGuards(GoogleAuthGuard)
async googleLogin() {
// NestJS + Passport se encarga de redirigir a Google
}
// Callback después de autenticación en Google
@Get('google/callback')
@UseGuards(GoogleAuthGuard)
async googleCallback(@Req() req, @Res() res: Response) {
const googleUser = req.user;
if (!googleUser) {
return res.redirect(`${process.env.FRONTEND_URL}?error=oauth_failed`);
}
// Buscar en la base de datos si ya existe
let persona = await this.personaService.findByEmail(googleUser.email);
if (!persona) {
// Si no existe, crear nueva persona con los datos de Google
persona = await this.personaService.create({
nombre: googleUser.nombre || '',
apellidoP: googleUser.apellidoP || '',
apellidoM: '', // Google no da este dato
password: '', // vacío porque es login OAuth
passwordTem: '',
fechaPasswordTem: new Date(),
correo: googleUser.email,
carreraAds: '',
cantidadCuenta: 0,
usuario: '',
numeroIdentificar: '',
tipoUsuario: '',
correoEnviado: false,
fechaRegistro: new Date(),
cambioPasswordReq: true,
primerLogin: false,
});
}
// Genera JWT con los datos de la persona
const jwt = await this.personaService.generateJwt(persona);
return res.redirect(
`${process.env.FRONTEND_URL}/oauth-callback?token=${jwt.access_token}`,
);
}
}
+43
View File
@@ -0,0 +1,43 @@
import { Module } from '@nestjs/common';
import { PersonaService } from './persona.service';
import { PersonaController } from './persona.controller';
import { GoogleStrategy } from './google.strategy';
import { PassportModule } from '@nestjs/passport';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Persona } from './entities/persona.entity';
import { JwtModule } from '@nestjs/jwt';
@Module({
imports: [
TypeOrmModule.forFeature([Persona]),
PassportModule,
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (cs: ConfigService) => {
const secret = cs.get<string>('JWT_SECRET');
if (!secret) {
throw new Error('JWT_SECRET no está definido');
}
// Se obtiene el valor de JWT_EXPIRES_IN en segundos
const expiresInStr = cs.get<string>('JWT_EXPIRES_IN') || '3600';
const expiresIn = parseInt(expiresInStr, 10); // convertir a número
if (isNaN(expiresIn)) {
throw new Error(
'JWT_EXPIRES_IN debe ser un número válido en segundos',
);
}
return {
secret,
signOptions: { expiresIn }, // aquí ya es un número
};
},
}),
],
controllers: [PersonaController],
providers: [PersonaService, GoogleStrategy],
})
export class PersonaModule {}
+18
View File
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { PersonaService } from './persona.service';
describe('PersonaService', () => {
let service: PersonaService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [PersonaService],
}).compile();
service = module.get<PersonaService>(PersonaService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
+65
View File
@@ -0,0 +1,65 @@
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Persona } from './entities/persona.entity';
@Injectable()
export class PersonaService {
constructor(
@InjectRepository(Persona)
private readonly personaRepository: Repository<Persona>,
private readonly jwtService: JwtService,
) {}
async findAll() {
return this.personaRepository.find();
}
async findByEmail(email: string): Promise<Persona | null> {
return this.personaRepository.findOne({ where: { correo: email } });
}
async create(data: Partial<Persona>): Promise<Persona> {
const persona = this.personaRepository.create(data);
return this.personaRepository.save(persona);
}
async generateJwt(user: Persona) {
const payload = { sub: user.idPersona, correo: user.correo };
return {
access_token: this.jwtService.sign(payload),
};
}
// Opción: validateOrCreateGoogleUser para usar en GoogleStrategy
async validateOrCreateGoogleUser(data: {
email: string;
nombre: string;
apellidoP: string;
}) {
let persona = await this.findByEmail(data.email);
if (!persona) {
persona = await this.create({
nombre: data.nombre || '',
apellidoP: data.apellidoP || '',
apellidoM: '',
password: '',
passwordTem: '',
fechaPasswordTem: new Date(),
correo: data.email,
carreraAds: '',
cantidadCuenta: 0,
usuario: '',
numeroIdentificar: '',
tipoUsuario: '',
correoEnviado: false,
fechaRegistro: new Date(),
cambioPasswordReq: true,
primerLogin: false,
});
}
return persona;
}
}