saveinmed/backoffice/src/auth/auth.service.ts
Tiago Yamamoto 42f72f5f43 docs: adiciona documentação completa do projeto SaveInMed
- Cria README.md na raiz com visão global e diagrama de arquitetura
- Adiciona/atualiza README.md em todos os componentes:
  - backend (API Go)
  - backoffice (NestJS)
  - marketplace (React/Vite)
  - saveinmed-bff (Python/FastAPI)
  - saveinmed-frontend (Next.js)
  - website (Fresh/Deno)
- Atualiza .gitignore em todos os componentes com regras abrangentes
- Cria .gitignore na raiz do projeto
- Renomeia pastas para melhor organização:
  - backend-go → backend
  - backend-nest → backoffice
  - marketplace-front → marketplace
- Documenta arquitetura, tecnologias, setup e fluxo de desenvolvimento
2025-12-17 17:07:30 -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: number, 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: number, 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: number, 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
});
}
}