web-service/apps/auth/src/services/auth.service.ts
2023-06-30 19:47:29 +03:00

130 lines
3.4 KiB
TypeScript

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<UserEntity>,
) {}
/**
* 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);
}
}