New projects created: - michangarrito (marketplace mobile) - template-saas (SaaS template) - clinica-dental (dental ERP) - clinica-veterinaria (veterinary ERP) Architecture updates: - Move catalog from core/ to shared/ - Add MCP servers structure and templates - Add git management scripts - Update SUBREPOSITORIOS.md with 15 new repos - Update .gitignore for new projects Repository infrastructure: - 4 main repositories - 11 subrepositorios - Gitea remotes configured 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
97 lines
2.8 KiB
TypeScript
97 lines
2.8 KiB
TypeScript
/**
|
|
* JWT STRATEGY - REFERENCE IMPLEMENTATION
|
|
*
|
|
* @description Estrategia de autenticación JWT para NestJS/Passport.
|
|
* Valida tokens JWT y extrae payload del usuario.
|
|
*
|
|
* @usage Copiar y adaptar según necesidades del proyecto.
|
|
* @origin gamilit/apps/backend/src/modules/auth/strategies/jwt.strategy.ts
|
|
*/
|
|
|
|
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
|
import { PassportStrategy } from '@nestjs/passport';
|
|
import { ExtractJwt, Strategy } from 'passport-jwt';
|
|
import { ConfigService } from '@nestjs/config';
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
import { Repository } from 'typeorm';
|
|
|
|
// Adaptar import según proyecto
|
|
// import { User } from '../entities';
|
|
|
|
/**
|
|
* Payload del JWT
|
|
*
|
|
* @property sub - ID del usuario (auth.users.id)
|
|
* @property email - Email del usuario
|
|
* @property role - Rol del usuario
|
|
* @property iat - Issued at (timestamp)
|
|
* @property exp - Expiration (timestamp)
|
|
*/
|
|
interface JwtPayload {
|
|
sub: string;
|
|
email: string;
|
|
role: string;
|
|
iat: number;
|
|
exp: number;
|
|
}
|
|
|
|
/**
|
|
* Usuario validado que se inyecta en req.user
|
|
*/
|
|
interface ValidatedUser {
|
|
id: string;
|
|
email: string;
|
|
role: string;
|
|
}
|
|
|
|
@Injectable()
|
|
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
|
constructor(
|
|
private readonly configService: ConfigService,
|
|
@InjectRepository(User, 'auth')
|
|
private readonly userRepository: Repository<User>,
|
|
) {
|
|
super({
|
|
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
|
ignoreExpiration: false,
|
|
secretOrKey: configService.get<string>('JWT_SECRET'),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Validar payload del JWT
|
|
*
|
|
* @description Este método es llamado por Passport después de verificar
|
|
* la firma y expiración del token. El valor retornado se asigna a req.user.
|
|
*
|
|
* @param payload - Payload decodificado del JWT
|
|
* @returns Usuario validado para inyectar en request
|
|
* @throws UnauthorizedException si el usuario no existe o está inactivo
|
|
*/
|
|
async validate(payload: JwtPayload): Promise<ValidatedUser> {
|
|
// Opción 1: Solo validar que el payload es válido (más rápido)
|
|
// return { id: payload.sub, email: payload.email, role: payload.role };
|
|
|
|
// Opción 2: Verificar que el usuario existe en BD (más seguro)
|
|
const user = await this.userRepository.findOne({
|
|
where: { id: payload.sub },
|
|
select: ['id', 'email', 'role'],
|
|
});
|
|
|
|
if (!user) {
|
|
throw new UnauthorizedException('Usuario no encontrado');
|
|
}
|
|
|
|
// Opción 3: También verificar status (si aplica)
|
|
// if (user.status !== 'active') {
|
|
// throw new UnauthorizedException('Usuario inactivo');
|
|
// }
|
|
|
|
return {
|
|
id: user.id,
|
|
email: user.email,
|
|
role: user.role,
|
|
};
|
|
}
|
|
}
|