import { BadRequestException, ForbiddenException, Injectable, PreconditionFailedException, } from '@nestjs/common'; import { LoginRequest } from '../interfaces/auth.interface'; import { JWTService } from './jwt.service'; import { ILike, Repository } from 'typeorm'; import { UserEntity } from '../database/entities/user.entity'; import { compare } from 'bcrypt'; import { instanceToPlain } from 'class-transformer'; import { InjectRepository } from '@nestjs/typeorm'; import { OTPService } from './otp.service'; import { BanService } from './ban.service'; import { UserInfo } from '@freeblox/shared'; @Injectable() export class AuthService { constructor( private readonly jwtService: JWTService, private readonly otpService: OTPService, private readonly banService: BanService, @InjectRepository(UserEntity) private readonly userRepository: Repository, ) {} /** * Login by username/email and password * @param body Username/email and password * @returns JWT token */ async login(body: LoginRequest) { if (!body.email || !body.password) { throw new BadRequestException('Invalid username or password'); } // Prevent wildcards const userInput = body.email?.replace(/%/g, ''); const userEntity = await this.userRepository.findOne({ where: [ { username: ILike(userInput), activated: true, }, { email: ILike(userInput), activated: true, }, ], }); // User not found if (!userEntity) { throw new BadRequestException('Invalid username or password'); } // Compare passwords const passwordMatch = await compare(body.password, userEntity.password); if (!passwordMatch) { throw new BadRequestException('Invalid username or password'); } // Check TOTP const userOTPToken = await this.otpService.getUserTOTP(userEntity); if (userOTPToken) { if (!body.totpToken) { throw new PreconditionFailedException('TOTP Token required'); } const validate = this.otpService.validateTOTP( userOTPToken.token, body.totpToken, ); if (!validate) { throw new ForbiddenException('Invalid TOTP Token'); } } const bans = await this.banService.getActiveBansForUser(userEntity); const banned = !!bans.length; // Issue token const issuedToken = await this.jwtService.sign({ sub: userEntity.id, username: userEntity.username, display_name: userEntity.displayName, language: userEntity.language, banned: banned, privileges: banned ? [] : ['freeblox'], }); // Set login time to now await this.userRepository.update( { id: userEntity.id }, { loginAt: new Date() }, ); return issuedToken; } /** * Validate user token * @param token JWT Token * @returns User entity */ async getUserFromToken(token: string) { const tokenInfo = await this.jwtService.verify(token); const user = await this.userRepository.findOneByOrFail({ id: tokenInfo.sub, activated: true, }); return instanceToPlain(user); } /** * Get user bans * @param tokeninfo */ async getUserBans(userInfo: UserInfo) { const user = await this.userRepository.findOneByOrFail({ id: userInfo.sub, activated: true, }); const bans = await this.banService.getAllBansForUser(user); return instanceToPlain(bans); } }