add jwt guard and another EP to return information
This commit is contained in:
Generated
+32
@@ -18,9 +18,11 @@
|
||||
"@nestjs/platform-express": "^11.0.1",
|
||||
"@nestjs/typeorm": "^11.0.0",
|
||||
"bcrypt": "^6.0.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"mysql2": "^3.15.2",
|
||||
"passport": "^0.7.0",
|
||||
"passport-google-oauth20": "^2.0.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1",
|
||||
"typeorm": "^0.3.27"
|
||||
@@ -37,6 +39,7 @@
|
||||
"@types/node": "^22.10.7",
|
||||
"@types/passport": "^1.0.17",
|
||||
"@types/passport-google-oauth20": "^2.0.16",
|
||||
"@types/passport-jwt": "^4.0.1",
|
||||
"@types/supertest": "^6.0.2",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
@@ -2857,6 +2860,16 @@
|
||||
"@types/passport-oauth2": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/passport-jwt": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-4.0.1.tgz",
|
||||
"integrity": "sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/jsonwebtoken": "*",
|
||||
"@types/passport-strategy": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/passport-oauth2": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/passport-oauth2/-/passport-oauth2-1.8.0.tgz",
|
||||
@@ -2868,6 +2881,16 @@
|
||||
"@types/passport": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/passport-strategy": {
|
||||
"version": "0.2.38",
|
||||
"resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.38.tgz",
|
||||
"integrity": "sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/express": "*",
|
||||
"@types/passport": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/qs": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
|
||||
@@ -7973,6 +7996,15 @@
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/passport-jwt": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz",
|
||||
"integrity": "sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==",
|
||||
"dependencies": {
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"passport-strategy": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/passport-oauth2": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.8.0.tgz",
|
||||
|
||||
@@ -29,9 +29,11 @@
|
||||
"@nestjs/platform-express": "^11.0.1",
|
||||
"@nestjs/typeorm": "^11.0.0",
|
||||
"bcrypt": "^6.0.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"mysql2": "^3.15.2",
|
||||
"passport": "^0.7.0",
|
||||
"passport-google-oauth20": "^2.0.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1",
|
||||
"typeorm": "^0.3.27"
|
||||
@@ -48,6 +50,7 @@
|
||||
"@types/node": "^22.10.7",
|
||||
"@types/passport": "^1.0.17",
|
||||
"@types/passport-google-oauth20": "^2.0.16",
|
||||
"@types/passport-jwt": "^4.0.1",
|
||||
"@types/supertest": "^6.0.2",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import {
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
ForbiddenException,
|
||||
} from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
|
||||
@Injectable()
|
||||
export class JwtAuthGuard extends AuthGuard('jwt') {
|
||||
handleRequest(err: any, user: any, info: any, context: ExecutionContext) {
|
||||
if (err || !user) {
|
||||
throw new ForbiddenException('Acceso denegado: token inválido o ausente');
|
||||
}
|
||||
return user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Injectable, ForbiddenException } from '@nestjs/common';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
export interface JwtPayload {
|
||||
id: string;
|
||||
usuario: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||
constructor(private configService: ConfigService) {
|
||||
super({
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
ignoreExpiration: false,
|
||||
secretOrKey: configService.get<string>('JWT_SECRET')!,
|
||||
});
|
||||
}
|
||||
|
||||
async validate(payload: JwtPayload) {
|
||||
if (!payload || !payload.id || !payload.usuario) {
|
||||
throw new ForbiddenException('Token inválido o corrupto');
|
||||
}
|
||||
return { id: payload.id, usuario: payload.usuario };
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ 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';
|
||||
import { JwtAuthGuard } from './jwt.guard';
|
||||
|
||||
@Controller('persona')
|
||||
export class PersonaController {
|
||||
@@ -12,14 +13,10 @@ export class PersonaController {
|
||||
return this.personaService.findAll();
|
||||
}
|
||||
|
||||
// Redirige a Google
|
||||
@Get('google')
|
||||
@UseGuards(GoogleAuthGuard)
|
||||
async googleLogin() {
|
||||
// NestJS + Passport se encarga de redirigir a Google
|
||||
}
|
||||
async googleLogin() {}
|
||||
|
||||
// Callback después de autenticación en Google
|
||||
@Get('google/callback')
|
||||
@UseGuards(GoogleAuthGuard)
|
||||
async googleCallback(@Req() req, @Res() res: Response) {
|
||||
@@ -32,11 +29,23 @@ export class PersonaController {
|
||||
// Generar JWT con los datos de la persona
|
||||
const jwt = await this.personaService.generateJwt(persona);
|
||||
|
||||
return res.status(200).json({
|
||||
return res.redirect(
|
||||
`${process.env.FRONTEND_URL}/oauth-callback?token=${jwt.access_token}`,
|
||||
);
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('me')
|
||||
async getMe(@Req() req) {
|
||||
const idPersona = req.user.idPersona;
|
||||
|
||||
const persona = await this.personaService.findById(idPersona);
|
||||
|
||||
return {
|
||||
error: false,
|
||||
msj: `Bienvenido ${persona.nombre} ${persona.apellidoP} ${persona.apellidoM}`,
|
||||
data: {
|
||||
token: jwt.access_token,
|
||||
token: req.headers.authorization.split(' ')[1], // token que envió el cliente
|
||||
userId: persona.idPersona,
|
||||
nombre: persona.nombre,
|
||||
apellidoP: persona.apellidoP,
|
||||
@@ -48,6 +57,6 @@ export class PersonaController {
|
||||
primerLogin: persona.primerLogin,
|
||||
cambioPassword: persona.cambioPasswordReq,
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,31 @@
|
||||
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';
|
||||
import { GoogleStrategy } from './google.strategy';
|
||||
import { JwtStrategy } from './jwt.strategy';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Persona]),
|
||||
PassportModule,
|
||||
PassportModule.register({ defaultStrategy: 'jwt' }),
|
||||
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',
|
||||
);
|
||||
}
|
||||
|
||||
useFactory: async (configService: ConfigService) => {
|
||||
return {
|
||||
secret,
|
||||
signOptions: { expiresIn }, // aquí ya es un número
|
||||
global: true,
|
||||
secret: configService.get<string>('JWT_SECRET'),
|
||||
signOptions: { expiresIn: '1h' },
|
||||
};
|
||||
},
|
||||
}),
|
||||
],
|
||||
controllers: [PersonaController],
|
||||
controllers: [PersonaController, JwtStrategy],
|
||||
providers: [PersonaService, GoogleStrategy],
|
||||
})
|
||||
export class PersonaModule {}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { Repository } from 'typeorm';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
@@ -31,6 +31,15 @@ export class PersonaService {
|
||||
return this.personaRepository.save(persona);
|
||||
}
|
||||
|
||||
async findById(idPersona): Promise<Persona> {
|
||||
const user = await this.personaRepository.findOne({ where: { idPersona } });
|
||||
|
||||
if (!user) {
|
||||
throw new NotFoundException('user not found');
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
async generateJwt(user: Persona) {
|
||||
const payload = {
|
||||
userType: 'usuario',
|
||||
@@ -42,7 +51,6 @@ export class PersonaService {
|
||||
};
|
||||
}
|
||||
|
||||
// Opción: validateOrCreateGoogleUser para usar en GoogleStrategy
|
||||
async validateOrCreateGoogleUser(data: { email: string; apellido: string }) {
|
||||
let persona = await this.findByEmail(data.email);
|
||||
|
||||
@@ -52,7 +60,6 @@ export class PersonaService {
|
||||
const apellidoM = apellidoArray[1] || '';
|
||||
const nombre = apellidoArray.slice(2).join(' ') || '';
|
||||
|
||||
// Número identificador del correo
|
||||
const numeroIdentificar = data.email.split('@')[0];
|
||||
|
||||
const password = await passwordRand(12);
|
||||
@@ -79,4 +86,9 @@ export class PersonaService {
|
||||
|
||||
return persona;
|
||||
}
|
||||
|
||||
saldo(idPersona) {
|
||||
this.personaRepository.findOne({ where: { idPersona } });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user