twitch-clone/backend/src/module/auth/deactivate/deactivate.service.ts

122 lines
3.8 KiB
TypeScript

import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { verify } from 'argon2';
import { PrismaService } from '@/src/core/prisma/prisma.service';
import { RedisService } from '@/src/core/redis/redis.service';
import { DeactivateAccountInput } from '@/src/module/auth/deactivate/inputs/deactivate-account.input';
import { MailService } from '@/src/module/libs/mail/mail.service';
import { TelegramService } from '@/src/module/libs/telegram/telegram.service';
import { generateToken } from '@/src/shared/util/generate-token.util';
import { getSessionMetadata } from '@/src/shared/util/session-metadata.util';
import { destroySession } from '@/src/shared/util/session.util';
import { TokenType, User } from '@prisma/generated';
import { ProcessEnv } from '../../../shared/types/env';
import type { Request } from 'express';
@Injectable()
export class DeactivateService {
constructor(
private readonly prismaService: PrismaService,
private readonly redisService: RedisService,
private readonly configService: ConfigService<ProcessEnv>,
private readonly mailService: MailService,
private readonly telegramService: TelegramService,
) {
}
public async deactivate(req: Request, input: DeactivateAccountInput, user: User, userAgent: string) {
const { email, password, pin } = input;
if (email !== user.email) {
throw new BadRequestException('Неверная почта');
}
const isValidPassword = await verify(user.password, password);
if (!isValidPassword) {
throw new BadRequestException('Неверный пароль');
}
if (!pin) {
await this.sendDeactivationToken(req, user, userAgent);
return { message: 'Требуется ввести код подтверждения' };
}
await this.validateDeactivateToken(req, pin);
return { user };
}
public async sendDeactivationToken(req: Request, user: User, userAgent: string) {
const deactivateToken = await generateToken(
this.prismaService,
user,
TokenType.DEACTIVATE_ACCOUNT,
false,
);
const metadata = getSessionMetadata(req, userAgent);
await this.mailService.sendDeactivateToken(user.email, deactivateToken.token, metadata);
if (deactivateToken.user?.notificationSettings?.telegramNotifications && deactivateToken.user.telegramId) {
await this.telegramService.sendDeactivateToken(deactivateToken.user.telegramId, deactivateToken.token, metadata);
}
return true;
}
private async validateDeactivateToken(req: Request, token: string) {
const existingToken = await this.prismaService.token.findUnique({
where: { token, type: TokenType.DEACTIVATE_ACCOUNT },
});
if (!existingToken?.userId) {
throw new NotFoundException('Токен не найден');
}
const hasExpired = new Date(existingToken.expiresIn) < new Date();
if (hasExpired) {
throw new BadRequestException('Токен истек');
}
const user = await this.prismaService.user.update({
where: { id: existingToken.userId },
data: {
isDeactivated: true,
deactivatedAt: new Date(),
},
});
await this.prismaService.token.delete({
where: {
id: existingToken.id,
type: TokenType.DEACTIVATE_ACCOUNT,
},
});
await this.clearSessions(user.id);
return destroySession(req, this.configService);
}
private async clearSessions(userId: string) {
const keys = await this.redisService.keys('*');
for (const key of keys) {
const sessionData = await this.redisService.get(key);
if (sessionData) {
const session = JSON.parse(sessionData);
if (session.userId === userId) {
await this.redisService.del(key);
}
}
}
}
}