- 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
129 lines
4.3 KiB
TypeScript
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
|
|
});
|
|
}
|
|
}
|