mirror of
https://github.com/HamzaSha1/zod-backend.git
synced 2025-08-26 06:09:41 +00:00
feat: add loggers to all services
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { BadRequestException, Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { BadRequestException, Injectable, Logger, UnauthorizedException } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
@ -7,10 +7,9 @@ import { CacheService } from '~/common/modules/cache/services';
|
||||
import { OtpScope, OtpType } from '~/common/modules/otp/enums';
|
||||
import { OtpService } from '~/common/modules/otp/services';
|
||||
import { JuniorTokenService } from '~/junior/services';
|
||||
import { User } from '../../user/entities';
|
||||
|
||||
import { DeviceService, UserService } from '~/user/services';
|
||||
import { PASSCODE_REGEX, PASSWORD_REGEX } from '../constants';
|
||||
import { User } from '../../user/entities';
|
||||
import { PASSCODE_REGEX } from '../constants';
|
||||
import {
|
||||
CreateUnverifiedUserRequestDto,
|
||||
DisableBiometricRequestDto,
|
||||
@ -20,9 +19,9 @@ import {
|
||||
SendForgetPasswordOtpRequestDto,
|
||||
SetEmailRequestDto,
|
||||
setJuniorPasswordRequestDto,
|
||||
VerifyUserRequestDto,
|
||||
} from '../dtos/request';
|
||||
import { VerifyUserRequestDto } from '../dtos/request/verify-user.request.dto';
|
||||
import { GrantType, Roles } from '../enums';
|
||||
import { GrantType } from '../enums';
|
||||
import { IJwtPayload, ILoginResponse } from '../interfaces';
|
||||
import { removePadding, verifySignature } from '../utils';
|
||||
|
||||
@ -30,6 +29,7 @@ const ONE_THOUSAND = 1000;
|
||||
const SALT_ROUNDS = 10;
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
private readonly logger = new Logger(AuthService.name);
|
||||
constructor(
|
||||
private readonly otpService: OtpService,
|
||||
private readonly jwtService: JwtService,
|
||||
@ -40,6 +40,7 @@ export class AuthService {
|
||||
private readonly cacheService: CacheService,
|
||||
) {}
|
||||
async sendRegisterOtp({ phoneNumber, countryCode }: CreateUnverifiedUserRequestDto) {
|
||||
this.logger.log(`Sending OTP to ${countryCode + phoneNumber}`);
|
||||
const user = await this.userService.findOrCreateUser({ phoneNumber, countryCode });
|
||||
|
||||
return this.otpService.generateAndSendOtp({
|
||||
@ -51,9 +52,13 @@ export class AuthService {
|
||||
}
|
||||
|
||||
async verifyUser(verifyUserDto: VerifyUserRequestDto): Promise<[ILoginResponse, User]> {
|
||||
this.logger.log(`Verifying user with phone number ${verifyUserDto.countryCode + verifyUserDto.phoneNumber}`);
|
||||
const user = await this.userService.findUserOrThrow({ phoneNumber: verifyUserDto.phoneNumber });
|
||||
|
||||
if (user.isPasswordSet) {
|
||||
this.logger.error(
|
||||
`User with phone number ${verifyUserDto.countryCode + verifyUserDto.phoneNumber} already verified`,
|
||||
);
|
||||
throw new BadRequestException('USERS.PHONE_ALREADY_VERIFIED');
|
||||
}
|
||||
|
||||
@ -65,26 +70,34 @@ export class AuthService {
|
||||
});
|
||||
|
||||
if (!isOtpValid) {
|
||||
this.logger.error(
|
||||
`Invalid OTP for user with phone number ${verifyUserDto.countryCode + verifyUserDto.phoneNumber}`,
|
||||
);
|
||||
throw new BadRequestException('USERS.INVALID_OTP');
|
||||
}
|
||||
|
||||
const updatedUser = await this.userService.verifyUserAndCreateCustomer(user);
|
||||
|
||||
const tokens = await this.generateAuthToken(updatedUser);
|
||||
|
||||
this.logger.log(
|
||||
`User with phone number ${verifyUserDto.countryCode + verifyUserDto.phoneNumber} verified successfully`,
|
||||
);
|
||||
return [tokens, updatedUser];
|
||||
}
|
||||
|
||||
async setEmail(userId: string, { email }: SetEmailRequestDto) {
|
||||
this.logger.log(`Setting email for user with id ${userId}`);
|
||||
const user = await this.userService.findUserOrThrow({ id: userId });
|
||||
|
||||
if (user.email) {
|
||||
this.logger.error(`Email already set for user with id ${userId}`);
|
||||
throw new BadRequestException('USERS.EMAIL_ALREADY_SET');
|
||||
}
|
||||
|
||||
const existingUser = await this.userService.findUser({ email });
|
||||
|
||||
if (existingUser) {
|
||||
this.logger.error(`Email ${email} already taken`);
|
||||
throw new BadRequestException('USERS.EMAIL_ALREADY_TAKEN');
|
||||
}
|
||||
|
||||
@ -92,21 +105,26 @@ export class AuthService {
|
||||
}
|
||||
|
||||
async setPasscode(userId: string, passcode: string) {
|
||||
this.logger.log(`Setting passcode for user with id ${userId}`);
|
||||
const user = await this.userService.findUserOrThrow({ id: userId });
|
||||
|
||||
if (user.password) {
|
||||
this.logger.error(`Passcode already set for user with id ${userId}`);
|
||||
throw new BadRequestException('USERS.PASSCODE_ALREADY_SET');
|
||||
}
|
||||
const salt = bcrypt.genSaltSync(SALT_ROUNDS);
|
||||
const hashedPasscode = bcrypt.hashSync(passcode, salt);
|
||||
|
||||
await this.userService.setPasscode(userId, hashedPasscode, salt);
|
||||
this.logger.log(`Passcode set successfully for user with id ${userId}`);
|
||||
}
|
||||
|
||||
async enableBiometric(userId: string, { deviceId, publicKey }: EnableBiometricRequestDto) {
|
||||
this.logger.log(`Enabling biometric for user with id ${userId}`);
|
||||
const device = await this.deviceService.findUserDeviceById(deviceId, userId);
|
||||
|
||||
if (!device) {
|
||||
this.logger.log(`Device not found, creating new device for user with id ${userId}`);
|
||||
return this.deviceService.createDevice({
|
||||
deviceId,
|
||||
userId,
|
||||
@ -115,6 +133,7 @@ export class AuthService {
|
||||
}
|
||||
|
||||
if (device.publicKey) {
|
||||
this.logger.error(`Biometric already enabled for user with id ${userId}`);
|
||||
throw new BadRequestException('AUTH.BIOMETRIC_ALREADY_ENABLED');
|
||||
}
|
||||
|
||||
@ -125,10 +144,12 @@ export class AuthService {
|
||||
const device = await this.deviceService.findUserDeviceById(deviceId, userId);
|
||||
|
||||
if (!device) {
|
||||
this.logger.error(`Device not found for user with id ${userId} and device id ${deviceId}`);
|
||||
throw new BadRequestException('AUTH.DEVICE_NOT_FOUND');
|
||||
}
|
||||
|
||||
if (!device.publicKey) {
|
||||
this.logger.error(`Biometric already disabled for user with id ${userId}`);
|
||||
throw new BadRequestException('AUTH.BIOMETRIC_ALREADY_DISABLED');
|
||||
}
|
||||
|
||||
@ -136,9 +157,11 @@ export class AuthService {
|
||||
}
|
||||
|
||||
async sendForgetPasswordOtp({ email }: SendForgetPasswordOtpRequestDto) {
|
||||
this.logger.log(`Sending forget password OTP to ${email}`);
|
||||
const user = await this.userService.findUserOrThrow({ email });
|
||||
|
||||
if (!user.isProfileCompleted) {
|
||||
this.logger.error(`Profile not completed for user with email ${email}`);
|
||||
throw new BadRequestException('USERS.PROFILE_NOT_COMPLETED');
|
||||
}
|
||||
|
||||
@ -151,8 +174,10 @@ export class AuthService {
|
||||
}
|
||||
|
||||
async verifyForgetPasswordOtp({ email, otp, password, confirmPassword }: ForgetPasswordRequestDto) {
|
||||
this.logger.log(`Verifying forget password OTP for ${email}`);
|
||||
const user = await this.userService.findUserOrThrow({ email });
|
||||
if (!user.isProfileCompleted) {
|
||||
this.logger.error(`Profile not completed for user with email ${email}`);
|
||||
throw new BadRequestException('USERS.PROFILE_NOT_COMPLETED');
|
||||
}
|
||||
const isOtpValid = await this.otpService.verifyOtp({
|
||||
@ -163,6 +188,7 @@ export class AuthService {
|
||||
});
|
||||
|
||||
if (!isOtpValid) {
|
||||
this.logger.error(`Invalid OTP for user with email ${email}`);
|
||||
throw new BadRequestException('USERS.INVALID_OTP');
|
||||
}
|
||||
|
||||
@ -171,81 +197,102 @@ export class AuthService {
|
||||
const hashedPassword = bcrypt.hashSync(password, user.salt);
|
||||
|
||||
await this.userService.setPasscode(user.id, hashedPassword, user.salt);
|
||||
this.logger.log(`Passcode updated successfully for user with email ${email}`);
|
||||
}
|
||||
|
||||
async login(loginDto: LoginRequestDto, deviceId: string): Promise<[ILoginResponse, User]> {
|
||||
this.logger.log(`Logging in user with email ${loginDto.email}`);
|
||||
const user = await this.userService.findUser({ email: loginDto.email });
|
||||
let tokens;
|
||||
|
||||
if (!user) {
|
||||
this.logger.error(`User with email ${loginDto.email} not found`);
|
||||
throw new UnauthorizedException('AUTH.INVALID_CREDENTIALS');
|
||||
}
|
||||
|
||||
if (loginDto.grantType === GrantType.PASSWORD) {
|
||||
this.logger.log(`Logging in user with email ${loginDto.email} using password`);
|
||||
tokens = await this.loginWithPassword(loginDto, user);
|
||||
} else {
|
||||
this.logger.log(`Logging in user with email ${loginDto.email} using biometric`);
|
||||
tokens = await this.loginWithBiometric(loginDto, user, deviceId);
|
||||
}
|
||||
|
||||
this.deviceService.updateDevice(deviceId, {
|
||||
await this.deviceService.updateDevice(deviceId, {
|
||||
lastAccessOn: new Date(),
|
||||
fcmToken: loginDto.fcmToken,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
this.logger.log(`User with email ${loginDto.email} logged in successfully`);
|
||||
|
||||
return [tokens, user];
|
||||
}
|
||||
|
||||
async setJuniorPasscode(body: setJuniorPasswordRequestDto) {
|
||||
this.logger.log(`Setting passcode for junior with qrToken ${body.qrToken}`);
|
||||
const juniorId = await this.juniorTokenService.validateToken(body.qrToken);
|
||||
const salt = bcrypt.genSaltSync(SALT_ROUNDS);
|
||||
const hashedPasscode = bcrypt.hashSync(body.passcode, salt);
|
||||
await this.userService.setPasscode(juniorId, hashedPasscode, salt);
|
||||
await this.juniorTokenService.invalidateToken(body.qrToken);
|
||||
this.logger.log(`Passcode set successfully for junior with id ${juniorId}`);
|
||||
}
|
||||
|
||||
async refreshToken(refreshToken: string): Promise<[ILoginResponse, User]> {
|
||||
this.logger.log('Refreshing token');
|
||||
try {
|
||||
const isValid = await this.jwtService.verifyAsync<IJwtPayload>(refreshToken, {
|
||||
secret: this.configService.getOrThrow('JWT_REFRESH_TOKEN_SECRET'),
|
||||
});
|
||||
|
||||
this.logger.log(`Refreshing token for user with id ${isValid.sub}`);
|
||||
|
||||
const user = await this.userService.findUserOrThrow({ id: isValid.sub });
|
||||
|
||||
const tokens = await this.generateAuthToken(user);
|
||||
|
||||
this.logger.log(`Token refreshed successfully for user with id ${isValid.sub}`);
|
||||
|
||||
return [tokens, user];
|
||||
} catch (error) {
|
||||
this.logger.error('Invalid refresh token');
|
||||
throw new BadRequestException('AUTH.INVALID_REFRESH_TOKEN');
|
||||
}
|
||||
}
|
||||
|
||||
logout(req: Request) {
|
||||
this.logger.log('Logging out');
|
||||
const accessToken = req.headers.authorization?.split(' ')[1] as string;
|
||||
const expiryInTtl = this.jwtService.decode(accessToken).exp - Date.now() / ONE_THOUSAND;
|
||||
return this.cacheService.set(accessToken, 'LOGOUT', expiryInTtl);
|
||||
}
|
||||
|
||||
private async loginWithPassword(loginDto: LoginRequestDto, user: User): Promise<ILoginResponse> {
|
||||
this.logger.log(`validating password for user with email ${loginDto.email}`);
|
||||
const isPasswordValid = bcrypt.compareSync(loginDto.password, user.password);
|
||||
|
||||
if (!isPasswordValid) {
|
||||
this.logger.error(`Invalid password for user with email ${loginDto.email}`);
|
||||
throw new UnauthorizedException('AUTH.INVALID_CREDENTIALS');
|
||||
}
|
||||
|
||||
const tokens = await this.generateAuthToken(user);
|
||||
|
||||
this.logger.log(`Password validated successfully for user with email ${loginDto.email}`);
|
||||
return tokens;
|
||||
}
|
||||
|
||||
private async loginWithBiometric(loginDto: LoginRequestDto, user: User, deviceId: string): Promise<ILoginResponse> {
|
||||
this.logger.log(`validating biometric for user with email ${loginDto.email}`);
|
||||
const device = await this.deviceService.findUserDeviceById(deviceId, user.id);
|
||||
|
||||
if (!device) {
|
||||
this.logger.error(`Device not found for user with email ${loginDto.email} and device id ${deviceId}`);
|
||||
throw new UnauthorizedException('AUTH.DEVICE_NOT_FOUND');
|
||||
}
|
||||
|
||||
if (!device.publicKey) {
|
||||
this.logger.error(`Biometric not enabled for user with email ${loginDto.email}`);
|
||||
throw new UnauthorizedException('AUTH.BIOMETRIC_NOT_ENABLED');
|
||||
}
|
||||
|
||||
@ -258,15 +305,17 @@ export class AuthService {
|
||||
);
|
||||
|
||||
if (!isValidToken) {
|
||||
this.logger.error(`Invalid biometric for user with email ${loginDto.email}`);
|
||||
throw new UnauthorizedException('AUTH.INVALID_BIOMETRIC');
|
||||
}
|
||||
|
||||
const tokens = await this.generateAuthToken(user);
|
||||
|
||||
this.logger.log(`Biometric validated successfully for user with email ${loginDto.email}`);
|
||||
return tokens;
|
||||
}
|
||||
|
||||
private async generateAuthToken(user: User) {
|
||||
this.logger.log(`Generating auth token for user with id ${user.id}`);
|
||||
const [accessToken, refreshToken] = await Promise.all([
|
||||
this.jwtService.sign(
|
||||
{ sub: user.id, roles: user.roles },
|
||||
@ -284,22 +333,20 @@ export class AuthService {
|
||||
),
|
||||
]);
|
||||
|
||||
this.logger.log(`Auth token generated successfully for user with id ${user.id}`);
|
||||
return { accessToken, refreshToken, expiresAt: new Date(this.jwtService.decode(accessToken).exp * ONE_THOUSAND) };
|
||||
}
|
||||
|
||||
private validatePassword(password: string, confirmPassword: string, user: User) {
|
||||
this.logger.log(`Validating password for user with id ${user.id}`);
|
||||
if (password !== confirmPassword) {
|
||||
this.logger.error(`Password mismatch for user with id ${user.id}`);
|
||||
throw new BadRequestException('AUTH.PASSWORD_MISMATCH');
|
||||
}
|
||||
|
||||
const roles = user.roles;
|
||||
|
||||
if (roles.includes(Roles.GUARDIAN) && !PASSCODE_REGEX.test(password)) {
|
||||
if (!PASSCODE_REGEX.test(password)) {
|
||||
this.logger.error(`Invalid password for user with id ${user.id}`);
|
||||
throw new BadRequestException('AUTH.INVALID_PASSCODE');
|
||||
}
|
||||
|
||||
if (roles.includes(Roles.JUNIOR) && !PASSWORD_REGEX.test(password)) {
|
||||
throw new BadRequestException('AUTH.INVALID_PASSWORD');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user