mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-08-26 06:29:38 +00:00

* fix: commission device API * task: add create booking API * add get All api for dashboard & mobile * add Find APIs for bookings * implement sending email updates on update bookable space * move email interfaces to separate files
358 lines
11 KiB
TypeScript
358 lines
11 KiB
TypeScript
import { RoleType } from '@app/common/constants/role.type.enum';
|
|
import { differenceInSeconds } from '@app/common/helper/differenceInSeconds';
|
|
import {
|
|
BadRequestException,
|
|
ForbiddenException,
|
|
Injectable,
|
|
} from '@nestjs/common';
|
|
import { ConfigService } from '@nestjs/config';
|
|
import * as argon2 from 'argon2';
|
|
import { RoleService } from 'src/role/services';
|
|
import { LessThan, MoreThan } from 'typeorm';
|
|
import { AuthService } from '../../../libs/common/src/auth/services/auth.service';
|
|
import { OtpType } from '../../../libs/common/src/constants/otp-type.enum';
|
|
import { HelperHashService } from '../../../libs/common/src/helper/services';
|
|
import { UserSessionRepository } from '../../../libs/common/src/modules/session/repositories/session.repository';
|
|
import { UserEntity } from '../../../libs/common/src/modules/user/entities/user.entity';
|
|
import { UserRepository } from '../../../libs/common/src/modules/user/repositories';
|
|
import { UserOtpRepository } from '../../../libs/common/src/modules/user/repositories/user.repository';
|
|
import { EmailService } from '../../../libs/common/src/util/email/email.service';
|
|
import { ForgetPasswordDto, UserOtpDto, VerifyOtpDto } from '../dtos';
|
|
import { UserSignUpDto } from '../dtos/user-auth.dto';
|
|
import { UserLoginDto } from '../dtos/user-login.dto';
|
|
|
|
@Injectable()
|
|
export class UserAuthService {
|
|
constructor(
|
|
private readonly userRepository: UserRepository,
|
|
private readonly sessionRepository: UserSessionRepository,
|
|
private readonly otpRepository: UserOtpRepository,
|
|
private readonly helperHashService: HelperHashService,
|
|
private readonly authService: AuthService,
|
|
private readonly emailService: EmailService,
|
|
private readonly roleService: RoleService,
|
|
private readonly configService: ConfigService,
|
|
) {}
|
|
|
|
async signUp(
|
|
userSignUpDto: UserSignUpDto,
|
|
clientUuid?: string,
|
|
): Promise<UserEntity> {
|
|
const findUser = await this.findUser(userSignUpDto.email);
|
|
|
|
if (findUser) {
|
|
if (!findUser.isActive) {
|
|
throw new BadRequestException('User is not active');
|
|
}
|
|
throw new BadRequestException('User already registered with given email');
|
|
}
|
|
const salt = this.helperHashService.randomSalt(10); // Hash the password using bcrypt
|
|
const hashedPassword = await this.helperHashService.bcrypt(
|
|
userSignUpDto.password,
|
|
salt,
|
|
);
|
|
|
|
try {
|
|
const { regionUuid, hasAcceptedAppAgreement, ...rest } = userSignUpDto;
|
|
if (!hasAcceptedAppAgreement) {
|
|
throw new BadRequestException('Please accept the terms and conditions');
|
|
}
|
|
const spaceMemberRole = await this.roleService.findRoleByType(
|
|
RoleType.SPACE_MEMBER,
|
|
);
|
|
const user = await this.userRepository.save({
|
|
...rest,
|
|
appAgreementAcceptedAt: new Date(),
|
|
hasAcceptedAppAgreement,
|
|
password: hashedPassword,
|
|
roleType: { uuid: spaceMemberRole.uuid },
|
|
client: { uuid: clientUuid },
|
|
region: regionUuid
|
|
? {
|
|
uuid: regionUuid,
|
|
}
|
|
: {
|
|
regionName: 'United Arab Emirates',
|
|
},
|
|
});
|
|
|
|
return user;
|
|
} catch (error) {
|
|
throw new BadRequestException(error.message || 'Failed to register user');
|
|
}
|
|
}
|
|
|
|
async findUser(email: string) {
|
|
return await this.userRepository.findOne({
|
|
where: {
|
|
email,
|
|
},
|
|
});
|
|
}
|
|
|
|
async forgetPassword(forgetPasswordDto: ForgetPasswordDto) {
|
|
const findUser = await this.findUser(forgetPasswordDto.email);
|
|
if (!findUser) {
|
|
throw new BadRequestException('User not found');
|
|
}
|
|
const salt = this.helperHashService.randomSalt(10);
|
|
const password = this.helperHashService.bcrypt(
|
|
forgetPasswordDto.password,
|
|
salt,
|
|
);
|
|
return await this.userRepository.update(
|
|
{ uuid: findUser.uuid },
|
|
{ password },
|
|
);
|
|
}
|
|
|
|
async userLogin(data: UserLoginDto) {
|
|
try {
|
|
let user: Omit<UserEntity, 'password'>;
|
|
if (data.googleCode) {
|
|
const googleUserData = await this.authService.login({
|
|
googleCode: data.googleCode,
|
|
});
|
|
const userExists = await this.userRepository.exists({
|
|
where: {
|
|
email: googleUserData['email'],
|
|
},
|
|
});
|
|
user = await this.userRepository.findOne({
|
|
where: {
|
|
email: googleUserData['email'],
|
|
},
|
|
});
|
|
if (!userExists) {
|
|
await this.signUp({
|
|
email: googleUserData['email'],
|
|
firstName: googleUserData['given_name'],
|
|
lastName: googleUserData['family_name'],
|
|
password: googleUserData['email'],
|
|
hasAcceptedAppAgreement: true,
|
|
});
|
|
}
|
|
data.email = googleUserData['email'];
|
|
data.password = googleUserData['password'];
|
|
}
|
|
if (!data.googleCode) {
|
|
user = await this.authService.validateUser(
|
|
data.email,
|
|
data.password,
|
|
data.regionUuid,
|
|
data.platform,
|
|
);
|
|
}
|
|
const session = await Promise.all([
|
|
await this.sessionRepository.update(
|
|
{ userId: user?.['id'] },
|
|
{
|
|
isLoggedOut: true,
|
|
},
|
|
),
|
|
await this.authService.createSession({
|
|
userId: user.uuid,
|
|
loginTime: new Date(),
|
|
isLoggedOut: false,
|
|
}),
|
|
]);
|
|
|
|
const res = await this.authService.login({
|
|
email: user.email,
|
|
userId: user.uuid,
|
|
uuid: user.uuid,
|
|
role: user.roleType,
|
|
hasAcceptedWebAgreement: user.hasAcceptedWebAgreement,
|
|
hasAcceptedAppAgreement: user.hasAcceptedAppAgreement,
|
|
project: user.project,
|
|
sessionId: session[1].uuid,
|
|
bookingPoints: user.bookingPoints,
|
|
});
|
|
return res;
|
|
} catch (error) {
|
|
throw new BadRequestException(error.message || 'Invalid credentials');
|
|
}
|
|
}
|
|
|
|
async findOneById(id: string): Promise<UserEntity> {
|
|
return await this.userRepository.findOne({ where: { uuid: id } });
|
|
}
|
|
|
|
async generateOTP(data: UserOtpDto): Promise<{
|
|
otpCode: string;
|
|
cooldown: number;
|
|
}> {
|
|
try {
|
|
const otpLimiter = new Date();
|
|
otpLimiter.setDate(
|
|
otpLimiter.getDate() - this.configService.get<number>('OTP_LIMITER'),
|
|
);
|
|
const user = await this.userRepository.findOne({
|
|
where: {
|
|
region: data.regionUuid ? { uuid: data.regionUuid } : undefined,
|
|
email: data.email,
|
|
isUserVerified: data.type === OtpType.PASSWORD ? true : undefined,
|
|
},
|
|
});
|
|
if (!user) {
|
|
throw new BadRequestException('User not found');
|
|
}
|
|
await this.otpRepository.softDelete({
|
|
email: data.email,
|
|
type: data.type,
|
|
});
|
|
await this.otpRepository.delete({
|
|
email: data.email,
|
|
type: data.type,
|
|
createdAt: LessThan(otpLimiter),
|
|
});
|
|
const countOfOtp = await this.otpRepository.count({
|
|
withDeleted: true,
|
|
where: {
|
|
email: data.email,
|
|
type: data.type,
|
|
createdAt: MoreThan(otpLimiter),
|
|
},
|
|
});
|
|
const lastOtp = await this.otpRepository.findOne({
|
|
where: { email: data.email, type: data.type },
|
|
order: { createdAt: 'DESC' },
|
|
withDeleted: true,
|
|
});
|
|
let cooldown = 30 * Math.pow(2, countOfOtp - 1);
|
|
if (lastOtp) {
|
|
const now = new Date();
|
|
const timeSinceLastOtp = differenceInSeconds(now, lastOtp.createdAt);
|
|
|
|
if (timeSinceLastOtp < cooldown) {
|
|
throw new BadRequestException({
|
|
message: `Please wait ${cooldown - timeSinceLastOtp} more seconds before requesting a new OTP.`,
|
|
data: {
|
|
cooldown: cooldown - timeSinceLastOtp,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
const otpCode = Math.floor(100000 + Math.random() * 900000).toString();
|
|
const expiryTime = new Date();
|
|
expiryTime.setMinutes(expiryTime.getMinutes() + 10);
|
|
await this.otpRepository.save({
|
|
email: data.email,
|
|
otpCode,
|
|
expiryTime,
|
|
type: data.type,
|
|
});
|
|
const countOfOtpToReturn = await this.otpRepository.count({
|
|
withDeleted: true,
|
|
where: {
|
|
email: data.email,
|
|
type: data.type,
|
|
createdAt: MoreThan(otpLimiter),
|
|
},
|
|
});
|
|
cooldown = 30 * Math.pow(2, countOfOtpToReturn - 1);
|
|
|
|
const [otp1, otp2, otp3, otp4, otp5, otp6] = otpCode.split('');
|
|
|
|
await this.emailService.sendOtpEmailWithTemplate(data.email, {
|
|
name: user.firstName,
|
|
otp1,
|
|
otp2,
|
|
otp3,
|
|
otp4,
|
|
otp5,
|
|
otp6,
|
|
});
|
|
|
|
return { otpCode, cooldown };
|
|
} catch (error) {
|
|
if (error instanceof BadRequestException) {
|
|
throw error;
|
|
}
|
|
console.error('OTP generation error:', error);
|
|
throw new BadRequestException(
|
|
'An unexpected error occurred while generating the OTP.',
|
|
);
|
|
}
|
|
}
|
|
|
|
async verifyOTP(
|
|
data: VerifyOtpDto,
|
|
fromNewPassword: boolean = false,
|
|
): Promise<boolean> {
|
|
const otp = await this.otpRepository.findOne({
|
|
where: { email: data.email, type: data.type, otpCode: data.otpCode },
|
|
});
|
|
|
|
if (!otp) {
|
|
const user = await this.userRepository.findOne({
|
|
where: {
|
|
email: data.email,
|
|
},
|
|
});
|
|
if (!user) {
|
|
throw new BadRequestException('this email is not registered');
|
|
}
|
|
throw new BadRequestException('You entered wrong otp');
|
|
}
|
|
|
|
if (otp.otpCode !== data.otpCode) {
|
|
throw new BadRequestException('You entered wrong otp');
|
|
}
|
|
|
|
if (otp.expiryTime < new Date()) {
|
|
await this.otpRepository.delete(otp.uuid);
|
|
throw new BadRequestException('OTP expired');
|
|
}
|
|
if (fromNewPassword) {
|
|
await this.otpRepository.delete(otp.uuid);
|
|
}
|
|
if (data.type == OtpType.VERIFICATION) {
|
|
await this.userRepository.update(
|
|
{ email: data.email },
|
|
{ isUserVerified: true },
|
|
);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
async userList(): Promise<UserEntity[]> {
|
|
return await this.userRepository.find({
|
|
where: { isActive: true },
|
|
select: {
|
|
firstName: true,
|
|
lastName: true,
|
|
email: true,
|
|
isActive: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
async refreshToken(
|
|
userId: string,
|
|
refreshToken: string,
|
|
type: string,
|
|
sessionId: string,
|
|
) {
|
|
const user = await this.userRepository.findOne({ where: { uuid: userId } });
|
|
if (!user || !user.refreshToken)
|
|
throw new ForbiddenException('Access Denied');
|
|
const refreshTokenMatches = await argon2.verify(
|
|
user.refreshToken,
|
|
refreshToken,
|
|
);
|
|
if (!refreshTokenMatches) throw new ForbiddenException('Access Denied');
|
|
const tokens = await this.authService.getTokens({
|
|
email: user.email,
|
|
userId: user.uuid,
|
|
uuid: user.uuid,
|
|
type,
|
|
bookingPoints: user.bookingPoints,
|
|
sessionId,
|
|
});
|
|
await this.authService.updateRefreshToken(user.uuid, tokens.refreshToken);
|
|
return tokens;
|
|
}
|
|
}
|