saveinmed/backoffice/src/auth/auth.service.ts
Tiago Yamamoto b41f1f6a52 feat(phase2): implement Trust, Safety & Financials
- Backend: Add Financial logic (Ledger, Withdrawals) and KYC Endpoints
- Backoffice: Update Prisma Schema and add KYC Review Module
- Frontend: Add Company Profile, Wallet View, and Reviews/Ratings UI
- Frontend: Enhance Admin Companies Page for KYC Review Queue
2025-12-27 01:56:32 -03:00

129 lines
4.3 KiB
TypeScript

import { BadRequestException, Injectable, UnauthorizedException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import { FastifyReply } from 'fastify';
import * as bcrypt from 'bcrypt';
import { PrismaService } from '../prisma/prisma.service';
import { UsersService } from '../users/users.service';
import { CreateUserDto } from '../users/dto/create-user.dto';
import { JwtPayload } from './types/jwt-payload.type';
import { LoginDto } from './dto/login.dto';
const REFRESH_COOKIE = 'refresh_token';
@Injectable()
export class AuthService {
constructor(
private readonly prisma: PrismaService,
private readonly jwtService: JwtService,
private readonly configService: ConfigService,
private readonly usersService: UsersService,
) { }
async register(dto: CreateUserDto, reply: FastifyReply) {
const existingUser = await this.usersService.findByEmail(dto.email);
if (existingUser) {
throw new BadRequestException('Email already registered');
}
const hashedPassword = await bcrypt.hash(dto.password, 10);
const user = await this.usersService.createWithCompany(dto, hashedPassword);
const tokens = await this.issueTokens(user);
await this.updateRefreshToken(user.id, tokens.refreshToken);
this.attachRefreshTokenCookie(reply, tokens.refreshToken);
return { accessToken: tokens.accessToken };
}
async login(dto: LoginDto, reply: FastifyReply) {
const user = await this.usersService.findByEmailWithCompany(dto.email);
if (!user) {
throw new UnauthorizedException('Invalid credentials');
}
const passwordMatches = await bcrypt.compare(dto.password, user.password);
if (!passwordMatches) {
throw new UnauthorizedException('Invalid credentials');
}
const tokens = await this.issueTokens(user);
await this.updateRefreshToken(user.id, tokens.refreshToken);
this.attachRefreshTokenCookie(reply, tokens.refreshToken);
return { accessToken: tokens.accessToken };
}
async refreshTokens(userId: string, refreshToken: string, reply: FastifyReply) {
const user = await this.prisma.user.findUnique({
where: { id: userId },
include: { company: true },
});
if (!user || !user.refreshToken) {
throw new UnauthorizedException('Refresh token not found');
}
const refreshMatches = await bcrypt.compare(refreshToken, user.refreshToken);
if (!refreshMatches) {
throw new UnauthorizedException('Refresh token invalid');
}
const tokens = await this.issueTokens(user);
await this.updateRefreshToken(user.id, tokens.refreshToken);
this.attachRefreshTokenCookie(reply, tokens.refreshToken);
return { accessToken: tokens.accessToken };
}
async logout(userId: string, reply: FastifyReply) {
await this.prisma.user.update({
where: { id: userId },
data: { refreshToken: null },
});
reply.clearCookie(REFRESH_COOKIE, { path: '/' });
return { success: true };
}
private async issueTokens(user: any) {
const payload: JwtPayload = {
sub: user.id,
email: user.email,
companyId: user.companyId,
companyStatus: user.company?.status,
role: user.role,
};
const [accessToken, refreshToken] = await Promise.all([
this.jwtService.signAsync(payload, {
secret: this.configService.get<string>('JWT_ACCESS_SECRET') || 'access-secret',
expiresIn: this.configService.get<string>('JWT_ACCESS_EXPIRES', '15m'),
}),
this.jwtService.signAsync(payload, {
secret: this.configService.get<string>('JWT_REFRESH_SECRET') || 'refresh-secret',
expiresIn: this.configService.get<string>('JWT_REFRESH_EXPIRES', '7d'),
}),
]);
return { accessToken, refreshToken };
}
private async updateRefreshToken(userId: string, refreshToken: string) {
const hashedRefresh = await bcrypt.hash(refreshToken, 10);
await this.prisma.user.update({
where: { id: userId },
data: { refreshToken: hashedRefresh },
});
}
attachRefreshTokenCookie(reply: FastifyReply, token: string) {
reply.setCookie(REFRESH_COOKIE, token, {
httpOnly: true,
sameSite: 'lax',
secure: this.configService.get<string>('NODE_ENV') === 'production',
path: '/',
maxAge: 60 * 60 * 24 * 7, // 7 days
});
}
}